[Experience + Proposal] Universal local video surveillance in Gladys — go2rtc + Frigate (person/dog/horse detection... without cloud)

Hello everyone! :wave:

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 :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. :coffee:


:warning: 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. :man_facepalming:

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.

:bulb: 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. :racehorse:

:hammer_and_wrench: 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. :exploding_head:

:white_check_mark: 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. :muscle:

: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 peer warning on port 8800: the camera sometimes disconnects, go2rtc reconnects automatically. Non-blocking.
---

:rocket: 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. :heart:

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

  1. Container management of Frigate/go2rtc from the Gladys UI (installation, startup, restart) — on the same model as what already exists for Zigbee2MQTT.
  2. Smart automatic configuration: hardware detection (Intel iGPU present? → OpenVINO + VAAPI; otherwise CPU), calculation of shm_size according to the number of cameras, stable defaults — all of which can be overridden in expert mode.
  3. 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.
  4. 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).
  1. Health: status per camera, reconnection counter, Frigate stats reported.

Open questions for the community (and @pierre-gilles :slight_smile:)

  • 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.js web 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! :raised_hands:

See you soon, Terdious

Important addition, because the case of my Tapo C520WS perfectly illustrates that this approach isn’t just for crippled cameras without RTSP. :point_down:

The C520WS Case: RTSP OK… but detection unusable locally

The C520WS is a wired camera, so in theory it’s the ideal situation: RTSP and ONVIF officially supported, image and live feed functional without any workaround (rtsp://user:pass@IP/stream1, standard camera account in the app).

Except that… event detection via ONVIF is broken at the firmware level on this model. :sob: The symptom is documented by several users: the ONVIF connection is cut ~10 seconds after the PullMessage request, while events do appear in the Tapo app (and models like the C110/C210 send them correctly). TP-Link has released firmwares supposedly fixing the issue (C520WS V1: 1.3.2, V2: 1.1.1), but feedback remains inconsistent.

Result: I have the video stream locally, but no reliable way to know locally THAT something is detected, or WHAT. Which is still the core need for home automation…

Why the go2rtc + Frigate architecture also solves this

This is exactly where the approach in the post makes perfect sense: since detection is done by Frigate (on the RTSP stream), the ONVIF bug in the camera becomes… completely irrelevant. We no longer need the firmware to send its events:

  • native RTSP stream → Frigate → detection person / dog / cat / horse (my real need for this camera, which monitors an area where horses pass — the Tapo app can only say “animal” :horse:);
  • rich events on MQTT (frigate/c520ws/person, frigate/events with score, position, trajectory…) → directly usable in Gladys;
  • and cherry on top: the pipeline is strictly identical to that of the C660. One architecture for the “impossible” camera AND the “compatible but buggy” camera. This is exactly what reinforces my idea of a single integration.

And PTZ control?

The C520WS is motorized, and eventually I also want to be able to move it from Gladys (follow an area, presets…). Good news: while ONVIF event is broken on this model, ONVIF PTZ works, itself. Two approaches I will evaluate:

  1. ONVIF PTZ via Frigate: Frigate can control the PTZ of ONVIF cameras (ONVIF block in the camera config, port 2020 for Tapo) — it would stay in the same tool;
  2. pytapo directly (the local Tapo API, port 443): more complete (presets, patrol, siren, privacy mode…), it’s the lib used by the HA Tapo Control integration.

This will be part of the integration reflection: detection goes through Frigate/MQTT in all cases, but control (PTZ, siren…) might deserve a dedicated channel depending on the brand. To be properly modeled on the Gladys features side (buttons/directions?).

Next step

I will soon start testing on the C520WS: integration into the same Frigate as the C660 (the Beelink has plenty of margin now :muscle:), real-world horse detection, then first PTZ tests. Full report here as soon as it’s done — with the numbers and potential pitfalls, as for the C660.

If others here have wired Tapo cameras (C210, C310, C320WS, C520WS…) and want to compare the ONVIF behavior of their firmware, I’m interested! :raised_hands:

I have a C610 on battery and it has always annoyed me not to have RTSP on this type of camera. If needed, I also have a C500 and a C210, so I could test without any problem.

Awesome!!

So far I’ve tested everything except Gladys, so now I’m launching Claude Fable on the dev (to try to use up my credits tonight ^^)!!

I’ll keep you posted when I have something to test!!

At home, I have a dedicated mini PC running Frigate, so I’ll definitely keep an eye on this.
Here’s what I’m wondering:

  • Many Frigate users like me use a Google Coral (even if this is less true recently), shouldn’t we take that into account?
  • I have Reolink cameras, and configuring them for Frigate has sometimes been laborious. If the integration with Gladys aims to be as user-friendly as possible, shouldn’t we make editing the YAML easier? Otherwise, we’ll lose people along the way ^^

That’s it for my first questions!

Thanks @guim31, those are exactly the two good questions — and they touch the heart of the design. I’ll respond point by point. :point_down:

1. Google Coral: yes, and it’s even a priority

Absolutely, and to be precise: Coral is better than what I’ve been running at home. My OpenVINO on iGPU runs at ~15.8 ms inference; a Coral is typically around 6-10 ms, completely offloading the CPU. It would be absurd not to support it.

On the Frigate side, it’s a simple detector choice:

yaml

# Coral USB
detectors:
  coral:
    type: edgetpu
    device: usb

# Coral M.2 / PCIe
detectors:
  coral:
    type: edgetpu
    device: pci

with the correct device mounting in the container (/dev/bus/usb for USB, /dev/apex_0 for PCIe).

What I plan for the « automatic hardware configuration » part is a detection cascade during installation:

  1. Coral detected (USB or PCIe) → edgetpu detector :white_check_mark:
  2. Otherwise, Intel iGPU / GPU available → openvino detector
  3. Otherwise → CPU detector + honest warning about the limits

…all of it manually overrideable of course (expert mode), because auto-detection doesn’t guess everything.

:bulb: Important nuance that goes in your direction: detection ≠ decoding. Coral does the inference, but video decoding remains on the CPU if there’s no hardware acceleration. Both are complementary: Coral (inference) + VAAPI/QSV/NVDEC (decoding) = optimal combo. The integration should propose both axes separately, not an exclusive choice.

And that’s where I need you :slight_smile: : you already have a Coral running. If you can share the type (USB? M.2?), your actual inference speed, and the part of docker-compose that mounts the device for you, that gives me a test base I don’t have on hand. That would be a real contribution to the project.

2. The YAML: my position (and I think we agree on the bottom line)

Here I’ll slightly rephrase your need, because I think the real goal isn’t « make YAML editing easier » but « not having to edit it ». :grin:

If Gladys just provides a prettier YAML editor than Frigate’s, we’re not adding anything: might as well use Frigate directly. Gladys’s value (and its philosophy) is to transform knowledge into configuration. The general public should never see YAML.

So we won’t support everything, but what we do support, we’ll do well. Besides that, it’s quite possible to set up an equivalent of GitHub issue auto-creation as done in Tuya. This should normally allow adding a knowledge base for easy configuration in Gladys.

But the goal is still to be able to do things manually in the YAML for advanced users. However, I don’t think we’ll talk about it directly in the interface. That will be up to @pierre-gilles to decide :wink:

Does that answer your questions? And if you’ve encountered other tricky cases with Frigate (Reolink config, Coral cases, performance pitfalls…), they’re welcome: every pitfall documented here is one the integration will avoid for everyone. That’s a bit the whole point of talking before coding. :slight_smile:

Thanks for your answers, it’s very clear!
After that I have to admit that even if I managed to get all this running, I still have trouble putting my knowledge and the right concepts in order.

I have a Coral USB, I’ll check the docker compose as soon as I’m on my computer.
Do you want my full config.yaml?

I understood your idea about the config and I completely share your vision of the implementation :+1:

Yes, please, both if possible, but don’t forget to clear your config of your personal information, especially :wink:

My docker compose:

  GNU nano 6.2                                                                                        docker-compose.yml                                                                                                 
services:
  frigate:
    container_name: frigate
    privileged: true
    restart: unless-stopped
    image: ghcr.io/blakeblackshear/frigate:0.17.0
    shm_size: "512mb"
    devices:
      - /dev/bus/usb:/dev/bus/usb
      - /dev/dri/renderD128:/dev/dri/renderD128
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./config:/config
      - ./storage:/media/frigate
      - type: tmpfs
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - "8971:8971"
      - "8554:8554"
      - "8555:8555"
      - "8555:8555/udp"
      - "1984:1984"
    environment:
      FRIGATE_RTSP_PASSWORD: "my-password"
      PLUS_API_KEY: "my-key"

My config.yaml:

auth:
  enabled: true

mqtt:
  host: 192.168.100.150
  port: 1883

ffmpeg:
  hwaccel_args: preset-vaapi

detect:
  enabled: true
  width: 1280
  height: 720
  fps: 5

record:
  enabled: true
  continuous:
    days: 2
  alerts:
    retain:
      days: 7
      mode: all
  detections:
    retain:
      days: 7
      mode: all

snapshots:
  enabled: true

detectors:
  coral:
    type: edgetpu
    device: usb

model:
  path: plus://ref
  width: 320
  height: 320
  model_type: yolo-generic
  input_tensor: nhwc
  input_pixel_format: rgb

semantic_search:
  enabled: true
  model_size: small 

face_recognition:
  enabled: true
  model_size: small
  min_area: 200

lpr:
  enabled: true

objects:
  track: [person, car, dog, cat]
  filters:
    person:
      threshold: 0.80
      min_area: 2500
    car:
      threshold: 0.85

go2rtc:
  streams:
    CAM1:
      - ffmpeg:http://192.168.100.201/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=admin&password=my-password#video=copy#audio=copy#audio=opus
    CAM1_SUB:
      - ffmpeg:http://192.168.100.201/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=admin&password=my-password
    CAM2:
      - ffmpeg:http://192.168.100.202/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=admin&password=my-password#video=copy#audio=copy#audio=opus
    CAM2_SUB:
      - ffmpeg:http://192.168.100.202/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=admin&password=my-password
    CAM3:
      - ffmpeg:http://192.168.100.203/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=admin&password=my-password#video=copy#audio=copy#audio=opus
    CAM3_SUB:
      - ffmpeg:http://192.168.100.203/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=admin&password=my-password

cameras:
  CAM1:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/CAM1
          roles: [record]
        - path: rtsp://127.0.0.1:8554/CAM1_SUB
          roles: [detect]
    face_recognition:
      enabled: true

  CAM2:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/CAM2
          roles: [record]
        - path: rtsp://127.0.0.1:8554/CAM2_SUB
          roles: [detect]
    lpr:
      enabled: true
      min_area: 100
    face_recognition:
      enabled: true

  CAM3: # Garage / Cat Flap
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/CAM3
          roles: [record]
        - path: rtsp://127.0.0.1:8554/CAM3_SUB
          roles: [detect]
    detect:
      enabled: true
    objects:
      track: [cat, person]
      filters:
        cat:
          min_area: 400
          threshold: 0.6

    zones:
      zone_chatiere:
        coordinates: 0.296,0.144,0.835,0.156,0.814,0.639,0.285,0.621
        loitering_time: 0
        objects: cat
        inertia: 3
 
version: 0.17-0

You have everything!

I’m in for the tests… but I don’t have any cameras :joy:

Actually, there is (among others) a camera I’d like to have: eufy Security Solar Wall Light Cam S120.
No RTSP or ONVIF.
If you think we can integrate it, I can get one for testing.

I’ll look for the literature then :sweat_smile: :wink:

First attempt:





For now, it’s running with 2 cameras on the Beelink CPU, in addition to 2 Gladys + 1 HA:
image

Now we’re moving to the GPU, which is not used at all by the current system!

Hello,

This feature is really nice :slight_smile:
Since I already have a Frigate installed on another machine, do you think it’s possible to use an external Frigate? :slight_smile:

Thanks

Hello @prohand,

I think that if all this is validated in form, it will indeed be quite possible to do like Zigbee2mqtt and be able to select an external Frigate!!

We will just add a Discovery tab, I think, everything should be able to rebuild itself. However, we will not be able to control the update of the devices and the container I think. Well, for now I see it like that ^^

Yes thanks.
I’ll be able to do some testing when it’s implemented :wink:

Just a little message to express myself… I can’t believe I’m only discovering Frigate now
It’s crazy the possibilities of this tool! And all this with a mini PC that’s already running some pretty serious stuff…
I’m amazed, thinking back to when I bought Netatmo at an outrageous price (negatively) and Frigate does the job 10 times better with cameras 5 to 6 times cheaper…
Precise detection, multi-zone definition by detection type, settings, speed calculation :scream:, facial recognition, license plate recognition…


I’m ashamed I didn’t see all these topics on Frigate for a whole year!!

We’ve made good progress, a test image is available here for those who would like to see: docker pull terdious/gladys:Frigate-test

Warning for Frigate instance owners, port management is normally fully handled. But you never know, I haven’t tested parallelization yet. So I advise either stopping the production instance during testing, or doing it on a separate machine.

Also, some cameras only support 1 access at a time, so in a first step, testing with the production Frigate instance turned off is preferable.

Google Coral is not yet implemented. But it should come very soon :face_with_peeking_eye: :wink:

The goal is to test the understanding and ease of setting up the system. See if the explanations are sufficient in the interface (outside the documentation which will be more in-depth if needed). And of course, for Tapo / Reolink owners, it works ^^

What is implemented:

  • Frigate integration,
  • container deployment
  • Adding a device
  • Selecting desired presence detection
  • Automating the use of CPU or GPU depending on what the system offers (the optional modifiable part will come with the integration of Google Coral
  • access to the Frigate web page
  • image and video upload
  • presence detection feedback (1 per type of detection selected)
  • Preparation for creating a GitHub issue for unsupported cameras

I’m trying to do a test tomorrow :clap:

Will this work for Action LSC cameras without RTSP and Imou cameras with RTSP?

This will be the opportunity to test all that ^^ Then for the LSC cameras, it’s Tuya, right? I’m currently developing on that side with @GBoulvin! But we’ll be able to test both, I hope!!