We have two tabby cats at home, aki and kuro. In the previous post, I connected a Tapo C210 in the living room to Frigate, building video playback, a cat diary, and daily highlight reels. But the C210 is wall-mounted to capture the full living room — the feeding area happens to be in its blind spot.
I wanted a device that combines a camera with an automatic feeder to cover that angle. Seems simple enough: a pet feeder camera whose video stream I can pipe into Frigate.
After researching ten brands, I realized one thing: not a single product in this entire category exposes its video stream. It's not that a few models lack support — the whole category is structurally closed off.
Every Single One Is Cloud-Locked — This Isn't a Coincidence#
The first one I looked at was the Xiaomi Smart Pet Feeder 2 with Camera. Crowdfunded in March 2026 — 5MP sensor, 150° ultra-wide angle, 940nm infrared night vision, 5L hopper, and even a weighing function. Plus, in 's source code, the xiaomi source's device filter explicitly includes .feeder. — looked perfect.
But after reading through go2rtc's xiaomi module source, I found the problem: its video transport has only two paths — Xiaomi's own cloud relay (CS2) or ThroughTek Kalay (TUTK) P2P — both going through the cloud. Not "key exchange needs internet, video stays on LAN" — the video itself routes through the cloud. Block the WAN and the stream dies instantly.
Compare that with the C210 I was already using: Tapo's RTSP is purely local. Cut the router's internet connection, and recording and detection keep working fine. Xiaomi's cameras are architecturally a different animal.
IMPORTANT
I later found an important observation in the go2rtc community: go2rtc's xiaomi source compatibility list has reports from 50+ cameras, but zero pet feeders. The author himself has said: "If a camera isn't on the list, don't ask if it works. No one knows until you try it."
The official Taiwanese store only sells the standard version without a camera; the camera version is a China-market model (RMB 449 ≈ NT$2,000), requiring Taobao or a proxy buyer, with no warranty. Add Xiaomi's security track record — in 2020 someone saw a stranger's Xiaomi camera feed on their Google Nest Hub; in 2021 ThroughTek Kalay was disclosed with a CVSS 9.6 vulnerability affecting roughly 83 million devices — and I decided to pass.
PETKIT + LocalKit: The Cost of De-Clouding#
Next I found YumShare Dual Gen2 (P592). It doesn't support local streaming natively, but there's a third-party project on GitHub called LocalKit that claims to fully de-cloud PETKIT devices, including camera streaming.
I gave up after reading the deployment docs. Here's what it requires:
- DNS hijacking — redirect PETKIT's cloud domains to a local server, making the device think it's talking to the manufacturer
- Certificate trust modification — the device has cert pinning; you have to get in and modify the trust chain
- Local MQTT broker — run a container simulating PETKIT's cloud broker
- go2rtc on the device — each camera-equipped device runs its own go2rtc instance
- Firmware locked to version 895 — the install script only accepts a specific firmware version; you can't let PETKIT auto-update
And it's maintained by a single person. Tapo's RTSP is a vendor-supported feature. PETKIT's local streaming is a precarious foothold built on a vendor oversight — one OTA push could brick the whole setup.
The P592 retails for about NT$5,000 in Taiwan. Spending that on a feeder that requires firmware locking and DNS hijacking felt like the wrong direction.
The Structural Reason: Why No Vendor Wants to Open Up#
After researching the first few models, I started understanding why this entire category refuses to expose video streams.
Camera brands (Tapo, Reolink, EZVIZ, Imou) make money on hardware — opening RTSP is a selling point. Feeder brands rely on app stickiness and cloud subscriptions — cloud storage, AI pet recognition, remote viewing, all locked inside their proprietary apps. Opening the stream would push users toward third-party NVRs, and nobody would pay for subscriptions.
NOTE
Even camera manufacturers with an RTSP tradition drop it when they cross into the feeder category. Imou (Dahua's consumer line) makes the PF1 feeder camera, but I found zero evidence of RTSP support from three sources: iSpy's Imou device list has no PF1, HA's imou_life integration has zero related issues, and Frigate's community has zero reports. It's also unavailable in Taiwan.
How to Know Before You Buy: Cracking Open the APK for the Video SDK#
Since product listings never say "supports local RTSP," how do you determine before buying whether a feeder's video can be extracted?
The method is surprisingly simple: download the app's installation file, unzip it, and check which video SDK it uses.
An is just a ZIP file, and the native libraries (.so files) in its lib/ folder reveal which video transport SDK it bundles. No decompilation needed, no installation — just look at the filenames:
| Libraries Found | SDK Used | Can It Connect to Frigate? |
|---|---|---|
libtuya_*.so | Tuya | go2rtc has tuya://, but signaling goes through cloud |
libmiot*.so, libmiss*.so | Xiaomi (Mi Home) | go2rtc has xiaomi://, but video is forced through cloud |
libIOTCAPIs.so, libAVAPIs.so | ThroughTek Kalay (TUTK) | go2rtc has no tutk — must reverse-engineer yourself |
libagora-*.so | Agora | Commercial cloud RTC, no local path whatsoever |
Just "seeing TUTK libraries" isn't enough. The key is whether the app actually calls the SDK's LAN direct-connect functions — some apps bundle the TUTK SDK but only use cloud relay, never attempting LAN. This requires a deeper look at the dex bytecode calls, but it's sufficient for judging "definitely no chance" vs. "might have a chance."
Zero-Cost Pre-Purchase Screening#
I used this method to scan ten brands available in Taiwan (or through proxy purchasing):
展開
| Brand | App Package | Video SDK | App Calls LAN Direct-Connect | Verdict |
|---|---|---|---|---|
| FurSpark | com.ddpai.cpp_overseas | TUTK Kalay | Yes (12 call sites) | ✅ Has a chance |
| Dogness | — | TUTK Kalay | Yes (17 call sites) | ✅ Has a chance |
| CATLINK | — | TUTK Kalay | Yes (15 call sites) | Taiwan only sells camera-less models |
| Xiaomi Mi Home | — | TUTK + CS2 | Yes | Video forced through cloud |
| PETLIBRO | — | TUTK | No | Cloud relay only |
| Petwant | — | TUTK | No | Cloud relay only |
| PETKIT | — | Agora RTC | — | No local path on the app side |
| Petoneer | — | Agora RTC | — | Same as above |
| Hama Home (Germany) | — | Tuya | — | App is 100% Tuya under the hood |
| maoxga | — | Agora RTC | — | Camera definitely can't be extracted |
A few surprises:
- PETKIT uses Agora, not TUTK. I initially assumed it used Kalay like PETLIBRO, but it's completely different — this also explains why LocalKit has to get inside the device: there's simply no LAN path available from the app side.
- Germany's Hama has a branded app that's 100% Tuya underneath. You can't tell from the outside; crack it open and it's obvious. "You can't figure out if it's Tuya" is true on the surface, but "there's no way to check" is false.
- maoxga's app unexpectedly contains a Matter SDK, suggesting the feeding half might support Matter. But the camera portion is Agora — definitely no local path.
WARNING
The app calling LAN direct-connect functions doesn't mean the firmware supports it too. App calls only prove "the developer wrote this code" — whether the device has corresponding handling is something you can only know with the hardware in hand. But the reverse is reliable: if the app has zero LAN direct-connect calls, you can rule it out immediately.
The Final Choice: FurSpark#
After filtering, two candidates remained: Dogness F10 and FurSpark Big Eye.
| FurSpark Big Eye | Dogness F10 | PETKIT P592 | |
|---|---|---|---|
| Resolution | 2K | 1080P | 1080P |
| Field of View | Wide-angle (vendor doesn't publish numbers) | 165° | 140° |
| Hopper | 3.7L | 3.6L | Dual hopper |
| Storage | Built-in 64GB, 48h playback | — | — |
| Special Feature | Mechanical eyelid physically covers the lens | Battery backup | Cat face recognition |
| Video SDK | TUTK (with LAN direct-connect) | TUTK (with LAN direct-connect) | Agora (no local path) |
| Frigate Integration | Requires reverse engineering | Requires reverse engineering | Requires LocalKit modification |
| Lowest Price in Taiwan | NT$3,990 | NT$4,200 (momo sale) | ~NT$5,000 |
FurSpark had the best hardware at the lowest price, and the app's package name com.ddpai.cpp_overseas revealed something interesting: the developer is Shenzhen DDPai — a dashcam company. "American Furspark" is marketing packaging.
This was actually good news — DDPai's dashcams have been publicly torn down before, with precedent for a local HTTP interface. This meant there might be a second path besides TUTK P2P.
IMPORTANT
I couldn't guarantee this unit would work with Frigate. All the information pointed to "there's a chance," but nobody had done it before. The reason I decided to buy was simple: even if integration fails, it still works as a feeder with timed feeding and an app for viewing. No firmware modifications, all vendor features preserved — the worst case is just having a regular pet feeder.
Video Integration: A Private TCP Port#
September 13, 2026 — the feeder arrived. Set it up, fed the cats, connected to Wi-Fi, and started hunting for video.
No RTSP, but the Daemon Is Alive#
An nmap scan of the device's open ports showed port 554 running Hisilicon RTSP Streaming Media Server/1.0.0. But every path I tried — over a dozen — returned 400 or 500. No media source was mounted. The RTSP daemon was alive, but nothing was feeding it content.
NOTE
I made a rookie mistake during the first probe: the RTSP request used a relative URI instead of an absolute URI (rtsp://host:554/path), so it got a 400. After resending with the correct format, OPTIONS returned 200 with a correct CSeq, confirming the daemon was parsing requests. But DESCRIBE returned 500 for some paths (recognized but no media available) and 400 for others (unrecognized). No path produced an SDP on its own.
No ONVIF either. The device reported its model as DDPaiQ3, SoC as Rockchip RV1126 — the hostname was still the factory default RV1126_RV1109, unchanged.
The Private TCP 6200#
I turned to the other ports. TCP 6200 had something: upon connecting, it started spitting out binary data. Each packet had an 8-byte header. In hex:
0a 0b 0c 00 [4-byte big-endian length] → keyframe (I-frame)
0a 0b 0c 01 [4-byte big-endian length] → inter-frame (P-frame)
0a 0b 0c is a fixed magic sequence, the fourth byte distinguishes frame types, and the next four bytes encode the NAL unit length. Strip the header, feed the rest to ffmpeg — zero decoding errors.
Specs measured with ffprobe:
| Property | Value |
|---|---|
| Resolution | 1664 × 2224 (portrait) |
| Frame Rate | 25 fps |
| Codec | H.264 High profile, level 5.0 |
| Bitrate | ~2530 kbps (≈ 15 GB/day) |
| Audio | None (no audio track on this path) |
| Keyframe Interval | 4 seconds |
Portrait video. The official product page mentions "expanding the upper and lower visual range from the previous 16:9 aspect ratio" — the previous generation was 16:9, and the new model rotates the sensor 90° to extend vertical coverage. Dashcam parts repurposed.
WARNING
The stream specs can change. I verified from historical recordings with ffprobe: on the first day it was 576×768 / level 3.1, and the next day it jumped to 1664×2224 / level 5.0. Presumably the "video quality" setting in the app was changed (currently set to "Ultra HD"). Frigate's detect resolution must match, otherwise it will undersample or oversample, degrading detection quality.
Architecture: Device → go2rtc → Frigate#
go2rtc uses an exec:ffmpeg source to connect to the device's TCP 6200, where ffmpeg receives the raw H.264 byte stream and converts it to RTSP. Frigate pulls RTSP from go2rtc, handling both detection and recording. The browser gets the feed via WebRTC directly from go2rtc.
A few gotchas:
- Only one connection at a time. Opening a second TCP 6200 connection simultaneously causes camera_fps to spike above 100, NAL errors everywhere, and the entire stream breaks. The phone app may cause the same contention.
- go2rtc must bind all interfaces (
:8554). The ffmpeg producer internally publishes media tortsp://127.0.0.1:${port}— if loopback isn't listening, the self-loop fails. Security comes from firewall rules — only allowing Frigate's container subnet to pull RTSP, rejecting everything else. - The watermark doesn't need masking. The bottom-left corner shows "Forspark Big Eye" and a per-second timestamp, but at Frigate's motion detection resolution it shrinks to about 1 pixel of area — well below the
contour_areathreshold, automatically filtered out.
Opening the Feed Takes a Few Seconds#
The device's keyframe interval is fixed at 4 seconds, so a new connection has to wait for the next keyframe before decoding can start. Average wait is 2 seconds, worst case 4. The C210 is about 0.1 to 1.7 seconds. The app has no option to adjust the keyframe interval.
On the audio side, ffmpeg probing the input format also slows down the initial feed (go2rtc starts producers sequentially). Adding -f aac -probesize 32 -analyzeduration 0 reduced that from ~3.2 seconds to 0.2 seconds. Combined, opening the feed takes about 3 seconds.
Feeding Control: Cloud MQTT Bridged to Home Assistant#
Video goes local; feeding control goes through the cloud. Two completely separate paths.
The device's real-time status and control commands travel through the vendor's own broker. I reverse-engineered the connection method and command format from the app, then wrote a Python daemon (furspark_ha.py) that connects to the vendor's MQTT to read status on one side, and pushes entities to the local Home Assistant via MQTT discovery on the other.
IMPORTANT
The specific command formats and cloud endpoints for feeding control are proprietary to this device and involve the security of other users' devices — they won't be published here. Below I'll describe what was built, not how the commands work.
22 Entities, All Under One Device#
| Type | Items |
|---|---|
| sensor | Food level, feeding schedule, today's planned portions, next feeding time, desiccant remaining days, storage space remaining & usage, video quality, firmware version |
| binary_sensor | Online status, malfunction |
| switch | Camera on/off, indicator light, child lock, microphone, feeding reminder, feeding voice |
| number | Speaker volume, portions per button press, feeding amount |
| button | Feed now, feed 1 portion |
| text + button | Schedule editor (format 07:30x40, 18:00x2), apply schedule, enable schedule |
A few easy-to-get-wrong details:
- Food level is a tier, not a percentage. The device reports 0 (sufficient), 1 (low), 2 (empty) — not 0–100. I initially treated it as a percentage, resulting in "reads 0% when full."
- 1 portion ≈ 1 gram. Measured empirically. The manual says "varies with kibble density, not adjustable." Our two cats eat 40 grams in the morning, so I set 40 portions. The upper limit works up to at least 99.
- Only daemon mode (
--daemon) pushes controls. One-shot mode only pushes sensors — because switches and buttons need someone listening for commands to be useful.
The daemon is installed as a systemd service, starting on boot. It polls status every 10 minutes. Toggling switches in HA takes effect immediately — I tested the indicator light and camera on/off, with less than one second from command to device state change. The feed button wasn't tested (it actually dispenses food) — left for manual use.
Live Audio and Two-Way Talk#
The TCP 6200 stream has no audio track. But the device's recording files contain AAC audio, meaning the microphone works fine — audio just isn't on the 6200 path.
TUTK P2P Bridge#
Audio travels over ThroughTek Kalay (TUTK) P2P protocol. go2rtc's 41 source protocols don't include tutk, so I had to build the bridge myself.
The approach: extract the arm64 TUTK SDK libraries (libIOTCAPIs.so, libAVAPIs.so, etc.) from the app's APK, run them on the x86 host using , then use Python's ctypes to call SDK functions to establish a P2P session and receive audio.
This was the hardest part of the entire project. The TUTK SDK was compiled for Android (bionic libc), and running it on Linux (glibc) requires shimming several ABI incompatibilities:
展開
| Problem | Symptom | Fix |
|---|---|---|
pthread_mutex_t size differs | bionic 4 bytes, glibc arm64 48 bytes; TUTK FIFO embeds bionic-sized mutex → segfault | Shim intercepts pthread_mutex_*, uses object address as key, maintains real glibc mutexes alongside |
struct addrinfo field order differs | bionic swaps ai_canonname and ai_addr → wild pointer | Shim intercepts getaddrinfo, translates to bionic layout |
| init_array contains null entries | bionic linker skips them, glibc calls address 0 → segfault | Scan each library, zero out DT_INIT_ARRAYSZ for all-null init_arrays |
__errno symbol name differs | bionic uses __errno, glibc uses __errno_location | Shim redirects |
_ctype_ is a pointer, not an array | bionic's _ctype_ is const char*; providing an array is treated as a wild pointer | Shim provides the correct pointer version |
The bridge architecture is an on-demand TCP server. When go2rtc connects (someone is watching the feed), it establishes a TUTK connection, starts receiving audio, and outputs AAC-ADTS. When nobody is watching, it releases the session to avoid occupying the device's P2P connection slots.
Steady-state resource usage: CPU ~5% (one core), RAM 67MB, audio bandwidth ~65kbps. My earlier worry about "running qemu bridging 24/7 being resource-heavy" was wrong — that's the build-time spike, not steady-state.
Pitfalls Encountered#
Credentials expire. The connection password is derived from a cloud API and becomes invalid within a few days. When it expires, the DTLS handshake fails silently — the error code says "timeout" rather than "wrong password" — completely obscuring the real issue. Now handled with a crontab that regenerates every 6 hours.
Audio failure takes down video. go2rtc's multi-source muxing is all-or-nothing — if the audio source can't connect, the entire stream won't start, killing video along with it. The fix: make the bridge "always able to produce an audio track as long as there's a client" — if real audio drops for over 600ms, inject silence frames; retry TUTK connection failures with exponential backoff.
WebRTC requires Opus. If you only provide AAC audio, WebRTC can't carry it (AAC isn't supported), and the browser falls back to MSE, which stutters over WAN. Adding Opus transcoding let WebRTC carry audio, making the remote feed smooth.
Two-Way Talk#
The outgoing audio protocol was also reverse-engineered. The device accepts AAC-LC 16kHz mono ADTS audio (same as the incoming side). The method for identifying the correct codec was brute force: four candidate formats each paired with audio files containing different numbers of beeps (1/2/3/4), all sent at once, with someone standing by the feeder counting beeps — whichever group's count was heard, that's the format.
NOTE
During the talk testing, I found a bug that had been hiding for six days: Python's struct.pack used a signed int32 for a millisecond timestamp, and when the value exceeded 2^31, it threw struct.error — the outgoing audio crashed before sending a single frame. The previous four rounds of blind-testing different codec formats were all testing a code path that never actually executed. Lesson: confirming "whether anything was actually sent" matters more than "whether the format is correct."
Real-time talk (speaking directly from the browser microphone) didn't work out. go2rtc's exec: source has backchannel support, which should work in theory, but when the browser switched to talk mode, go2rtc never started the producer. Tried various audio formats and architectures — same result every time, root cause unknown. The current workaround is a button in HA that plays a pre-recorded chime — good enough for calling the cats to eat.
Cat Identification from the Feeder Camera#
The feeder's camera is mounted on top of the unit, with the stainless steel bowl at the very bottom of the frame. When a cat eats, its face points directly at the camera from about 40 cm away, filling roughly 37% of the frame — much larger than the 6% from the C210's 3-meter wall-mount view. In theory, telling aki and kuro apart should be much easier.
The Bowl Zone#
I defined a feeder_bowl zone in Frigate covering the bottom 5% of the frame. Only cats that stay in this zone for at least 10 seconds count as a "visit," filtering out cats just passing by.
Validation using historical events: events with bounding box bottom ≥ 0.95 — sampled 8, all 8 were eating. Between 0.88 and 0.95 — sampled 8, only 2 were eating. The 10-second threshold combined with zone restriction keeps the false positive rate very low.
Before adding the zone, the daemon counted "cat in the entire living room for ≥10 seconds" — the camera can see the whole room, and it was hitting 30+ events per day, most of which were cats on the dining table or cabinet, not eating at all. After adding the zone, it dropped to about 7.5 per day — much more reasonable.
Meal Merging#
The daemon pulls "cats detected in the feeder_bowl zone today" from Frigate's event API. Events from the same cat within 2 minutes of each other are merged into a single meal (Frigate often splits one feeding session into several events). HA has two sensors: today's visit count for aki and kuro respectively.
NOTE
The label is "visits," not "meals." The camera can only confirm how long a cat stayed in front of the bowl — it can't confirm the cat was actually eating. It might just be sitting there or grooming. I initially named it "meals today" and was corrected: that's presenting inference as fact.
The Full Setup#
| Item | Spec |
|---|---|
| Hardware Cost | NT$3,990 (FurSpark Big Eye, lowest price on quoview) |
| Monthly Fee | NT$0 |
| Video | 1664×2224 portrait, 25fps, local live stream (TCP 6200 → go2rtc → Frigate) |
| Recording | Continuous recording, 3 days (~15 GB/day, on the same drive as the C210's ~10 GB/day) |
| Feeding Control | HA switches, portion adjustment, schedule editing, one-tap feeding |
| Audio | Live audio (TUTK bridge → Opus/AAC → WebRTC), HA button plays chime |
| Cat ID | Bowl zone + 10-second threshold, daily visit count × 2 cats |
| Detection | Frigate object detection (cat / person), GPU usage 6% |
| Services | furspark-ha.service (feeding control), tutk-audio-bridge (audio), go2rtc (streaming) |
Compared to Furbo:
| Furbo 360° + Pet Nanny Subscription | This Setup | |
|---|---|---|
| Hardware | NT$5,399 (standalone, no contract) | NT$3,990 |
| Annual Fee | From NT$1,840 | NT$0 |
| Recording | AI clips, retained 3–7 days | Continuous recording, retention days configurable |
| Playback | Subscription only | Anytime |
| Two-Way Talk | Yes | Pre-recorded chime |
| Treat Tossing | Yes | Timed feeding (no treat tossing) |
| Pet Diary | Subscription feature | Built in the previous post |
| Individual ID | Subscription feature (multi-pet recognition) | Frigate custom classifier (bowl close-up naming rate ~89%) |
| Data Location | Furbo cloud | Local hard drive |
One last thing. The feeder's RTSP daemon is alive — it just has no media source mounted. The firmware contains two commands that look like "enable RTSP" (names found from publicly available dashcam firmware string tables). If I tried them, they might yield an RTSP stream with audio included. But that would change device state, risking disruption to the stable TCP 6200 live stream. I haven't tried. When the day comes that a firmware update breaks something and I'm forced to reconfigure anyway — that's when I'll give it a shot.