Hello everyone! ![]()
I’m here to share a complete experience report that turned into an integration project for Gladys. On the menu: how I managed to make a 100% local TP-Link camera work (which was intentionally crippled by TP-Link — no RTSP, no ONVIF), with AI object detection (person, dog, cat… and even horse
), all on a mini-PC that already runs 2 Gladys and a Home Assistant. And at the end, the development proposal I plan to launch.
Hold on, it’s a bit long, but everything is reproducible. ![]()
The Initial Problem
I have several Tapo cameras, including a C660 (4K, pan/tilt, solar/battery — but plugged into the mains at my place, the battery is just a backup).
I wanted the same thing as everyone here: control my cameras and receive detection alerts (person, animal…) locally, in Gladys / Node-RED via MQTT, without depending on TP-Link’s cloud.
First hurdle: on the entire battery/solar range (C425, C460, C660, C645D, D230…), TP-Link disables RTSP and ONVIF. This is acknowledged and documented by the manufacturer: « energy saving ». The fact that the camera is permanently powered doesn’t change anything, it’s the firmware that decides. ![]()
Verification that leaves no doubt:
$ nmap -Pn -p 443,554,2020,8800 10.6.0.222
PORT STATE SERVICE
443/tcp open https
554/tcp closed rtsp ← RTSP closed
2020/tcp closed onvif ← ONVIF closed
8800/tcp open ??? ← hmm... 👀
RTSP and ONVIF closed… but a mysterious port 8800 open.
Second hurdle, valid even for Tapo cameras with RTSP (my C520WS for example): the detection event reporting via ONVIF is broken on several models (known firmware bug). So even with RTSP, no reliable local alerts.
The Workaround: go2rtc Speaks « Tapo »
Port 8800 is the TP-Link proprietary protocol — the one used by the Tapo app. And it turns out that go2rtc (AlexxIT’s video Swiss army knife, embedded in Frigate AND in Home Assistant) knows how to speak it natively via a tapo:// source.
The complete architecture becomes:
Camera (tapo://, rtsp://, onvif://, ...) → go2rtc → Frigate (AI detection) → MQTT → Gladys / Node-RED
And the key point: it’s Frigate that does the object detection, not the camera firmware. So no need for ONVIF, no need for the cloud, and a detection better than TP-Link’s — the Tapo app tells me « animal » for everything, Frigate distinguishes person, dog, cat, horse (COCO labelmap, 91 classes). For my part, I need to precisely detect horses, so it changes everything. ![]()
The Pitfalls Encountered (to Save You Hours of Debugging)
It didn’t work on the first try, and each pitfall is worth documenting:
1. The password must be URL-encoded. The source is tapo://PASSWORD@IP with the password of your Tapo cloud account (no « camera account » on these models). If your password contains #, ^, %, @… you need to encode it (# → %23, etc.), otherwise go2rtc silently truncates the URL. Tip:
bash
python3 -c "import urllib.parse,getpass; print(urllib.parse.quote(getpass.getpass('password: '), safe=''))"
2. The main 4K stream = black screen. Known bug (SPS/PPS not propagated, see go2rtc issue #2202): the camera sends the video but go2rtc can’t parse it. The substream works perfectly:
tapo://ENCODED_PWD@10.6.0.222?channel=0&subtype=1
640×360, it’s ugly to the eye but it’s exactly what Frigate wants for detection (the models work at 300-640 px anyway). The 4K remains on the camera’s SD card for playback.
3. The rotten timestamps (THE pitfall). The tapo stream generates non-monotonic DTS → ffmpeg goes wild at 190% CPU, the Frigate watchdog kills it in a loop, no clips are recorded. The fix that solved everything:
yaml
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport tcp -use_wallclock_as_timestamps 1
The key is -use_wallclock_as_timestamps 1: ffmpeg ignores the source timestamps and regenerates them.
4. The Intel iGPU changes everything. My server is a modest Beelink U59 (Celeron N5105) that already hosts HA + 2 Gladys. As a CPU detector, it was on its knees (86% system). By enabling OpenVINO on the iGPU + VAAPI decoding:
| Metric | CPU | iGPU (OpenVINO + VAAPI) |
|---|---|---|
| Inference | 94.6 ms | 15.8 ms |
| Detector process | 191.8% | 8.5 % |
| System CPU | 86% | 22 % |
| Dropped frames | 7/s | 0 |
A Celeron at 22% CPU doing real-time object detection in addition to HA and 2 Gladys. ![]()
The Result
On the MQTT side, Frigate publishes everything we dream of:
frigate/c660/person → 1 / 0 (binary, perfect for Gladys)
frigate/c660/dog → 1 / 0
frigate/events → Rich JSON (label, score, box, trajectory, zones...)
frigate/reviews → Alerts with thumbnail
frigate/stats → Complete health (fps, inference, storage...)
Detection validated in real life (person at 0.93 score, dog recognized where Tapo says « animal »), clips recorded, snapshots, zero cloud. A camera « officially impossible to integrate » that works better locally than the « compatible » models. ![]()
:gear: Complete validated configurations (docker-compose + Frigate config) — click to expand
docker-compose.yml :
yaml
services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable
restart: unless-stopped
shm_size: "256mb"
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
volumes:
- ./config:/config
- ./storage:/media/frigate
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 1000000000
ports:
- "8971:8971" # UI (HTTPS since 0.17!)
- "8554:8554" # RTSP restream
- "1984:1984" # go2rtc
config/config.yml :
yaml
mqtt:
enabled: true
host: <your_mosquitto>
user: xxx
password: xxx
detectors:
ov:
type: openvino
device: GPU
model:
width: 300
height: 300
input_tensor: nhwc
input_pixel_format: bgr
path: /openvino-model/ssdlite_mobilenet_v2.xml
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
go2rtc:
streams:
c660:
- tapo://URL_ENCODED_PWD@10.6.0.222?channel=0&subtype=1
cameras:
c660:
ffmpeg:
hwaccel_args: preset-vaapi
inputs:
- path: rtsp://127.0.0.1:8554/c660
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport tcp -use_wallclock_as_timestamps 1
roles: [detect, record]
detect:
enabled: true
fps: 5
width: 640
height: 360
objects:
track: [person, dog, cat, horse]
record:
enabled: true
retain:
days: 7
mode: motion
snapshots:
enabled: true
retain:
default: 14
Operating notes:
- Frigate 0.17: the UI is in HTTPS on port 8971 (self-signed certificate), admin account generated on first startup (password in the logs).
shm_size: 128 MB is not enough, even for 1 camera.- Tapo cameras limit concurrent streams → always consume via go2rtc (which multiplexes into 1 connection), never directly.
- Small periodic
connection reset by peerwarning on port 8800: the camera sometimes disconnects, go2rtc reconnects automatically. Non-blocking.
And nowenant : Gladys integration proposal
All this works, but let’s be honest: between nmap, URL-encoding, ffmpeg input_args, and OpenVINO config, this is not accessible to the common mortal. And it’s exactly the kind of complexity that Gladys knows how to hide. ![]()
I therefore plan to develop a « Local Video Surveillance » integration (new service or extension of rtsp-camera, to be discussed) whose principle is not to reinvent the wheel:
- go2rtc = protocol abstraction layer (it speaks rtsp://, tapo://, onvif://, Ring, Nest, Dahua, USB… and is maintained by AlexxIT + a large community). One integration = all cameras.
- Frigate = object detection + recordings + MQTT.
- Gladys = orchestration, simple configuration, and feature exposure.
What the integration would do
- Container management of Frigate/go2rtc from the Gladys UI (installation, startup, restart) — on the same model as what already exists for Zigbee2MQTT.
- Smart automatic configuration: hardware detection (Intel iGPU present? → OpenVINO + VAAPI; otherwise CPU), calculation of
shm_sizeaccording to the number of cameras, stable defaults — all of which can be overridden in expert mode. - Simplest camera addition: name, type (RTSP / Tapo / ONVIF…), IP, password (automatically encoded!), choice of objects to detect (person, dog, cat, horse… multi-select). The integration generates the Frigate config and applies known fixes (Tapo substream, input_args timestamps…) without the user needing to know they exist.
- Gladys features created automatically per camera:
- the classic live camera;
- a detection binary per object type (
frigate/<cam>/person→ Gladys presence sensor → scenes!); - an « image camera » per type: the last captured image of each detection (probably a new image-only feature type, without live — to be discussed).
- Health: status per camera, reconnection counter, Frigate stats reported.
Open questions for the community (and @pierre-gilles
)
- New service or extension of
rtsp-camera? My intuition: new service (the scope is very different), but the existing one should remain the simple path for those who only want an RTSP stream. - New « image-only camera » feature type (display of the last image, no live player): does this seem like the right modeling for « last detection of type X »?
- Companion containers policy: Frigate includes its own go2rtc, so a single container is sufficient. OK to follow the Z2M pattern?
- Live view: integration of the
video-stream.jsweb component from go2rtc (WebRTC/MSE) with fallback snapshots for capricious streams. The question of proxying via the Gladys server needs to be explored (mixed content HTTPS/HTTP).
I will start development with a phase of analyzing the existing system, then an MVP (container + generic RTSP camera + MQTT detection binaries), then the advanced sources (tapo://, onvif://) and the live view. Small, split PRs, as usual.
All feedback is welcome: use cases, cameras you would like to see supported, opinions on the questions above… And if some people want to test the manual setup in the meantime, the complete configs are in the spoiler above — I answer questions! ![]()
See you soon, Terdious












