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

Bonjour à tous ! :wave:

Je viens partager un retour d’expérience complet qui s’est transformé en projet d’intégration pour Gladys. Au menu : comment j’ai fait fonctionner en 100 % local une caméra que TP-Link a volontairement bridée (pas de RTSP, pas d’ONVIF), avec de la détection d’objets IA (personne, chien, chat… et même cheval :horse:), le tout sur un mini-PC qui fait déjà tourner 2 Gladys et un Home Assistant. Et à la fin, la proposition de développement que je compte lancer.

Accrochez-vous, c’est un peu long, mais tout est reproductible. :coffee:


:warning: La problématique de départ

J’ai plusieurs caméras Tapo, dont une C660 (4K, pan/tilt, solaire/batterie — mais branchée sur secteur chez moi, la batterie n’est qu’un secours).

Je voulais la même chose que tout le monde ici : contrôler mes caméras et recevoir les alertes de détection (personne, animal…) en local, dans Gladys / Node-RED via MQTT, sans dépendre du cloud TP-Link.

Premier mur : sur toute la gamme batterie/solaire (C425, C460, C660, C645D, D230…), TP-Link désactive RTSP et ONVIF. C’est assumé et documenté par le constructeur : « économie d’énergie ». Le fait que la caméra soit alimentée en permanence sur secteur n’y change rien, c’est le firmware qui décide. :man_facepalming:

Vérification qui ne laisse aucun doute :

$ nmap -Pn -p 443,554,2020,8800 10.6.0.222

PORT     STATE  SERVICE
443/tcp  open   https
554/tcp  closed rtsp       ← RTSP fermé
2020/tcp closed onvif      ← ONVIF fermé
8800/tcp open   ???        ← tiens tiens... 👀

RTSP et ONVIF fermés… mais un mystérieux port 8800 ouvert.

Second mur, valable même pour les caméras Tapo avec RTSP (ma C520WS par exemple) : la remontée des événements de détection via ONVIF est cassée sur plusieurs modèles (bug firmware connu). Donc même en RTSP, pas d’alertes fiables en local.

:bulb: Le contournement : go2rtc parle le « Tapo »

Le port 8800, c’est le protocole propriétaire TP-Link — celui qu’utilise l’app Tapo. Et il se trouve que go2rtc (le couteau suisse vidéo d’AlexxIT, embarqué dans Frigate ET dans Home Assistant) sait le parler nativement via une source tapo://.

L’architecture complète devient :

Caméra (tapo://, rtsp://, onvif://, ...) → go2rtc → Frigate (détection IA) → MQTT → Gladys / Node-RED

Et le point clé : c’est Frigate qui fait la détection d’objets, pas le firmware de la caméra. Donc plus besoin d’ONVIF, plus besoin du cloud, et une détection meilleure que celle de TP-Link — l’app Tapo me dit « animal » pour tout, Frigate distingue person, dog, cat, horse (labelmap COCO, 91 classes). Pour ma part j’ai besoin de détecter précisément les chevaux, autant dire que ça change tout. :racehorse:

:hammer_and_wrench: Les pièges rencontrés (pour vous éviter des heures de debug)

Ça n’a pas marché du premier coup, et chaque piège vaut le coup d’être documenté :

1. Le mot de passe doit être URL-encodé. La source c’est tapo://MOT_DE_PASSE@IP avec le mot de passe de votre compte cloud Tapo (pas de « compte caméra » sur ces modèles). Si votre mdp contient #, ^, %, @… il faut l’encoder (#%23, etc.), sinon go2rtc tronque l’URL silencieusement. Astuce :

bash

python3 -c "import urllib.parse,getpass; print(urllib.parse.quote(getpass.getpass('mdp: '), safe=''))"

2. Le flux principal 4K = écran noir. Bug connu (SPS/PPS non propagés, cf. issue go2rtc #2202) : la caméra envoie bien la vidéo mais go2rtc n’arrive pas à la parser. Le substream fonctionne parfaitement :

tapo://MDP_ENCODE@10.6.0.222?channel=0&subtype=1

640×360, c’est moche à l’œil mais c’est exactement ce que veut Frigate pour la détection (les modèles travaillent en 300-640 px de toute façon). Le 4K reste sur la carte SD de la caméra pour la relecture.

3. Les timestamps pourris (LE piège). Le flux tapo génère des DTS non monotones → ffmpeg s’emballe à 190 % de CPU, le watchdog Frigate le tue en boucle, aucun clip ne s’enregistre. Le correctif qui a tout réparé :

yaml

input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport tcp -use_wallclock_as_timestamps 1

La clé c’est -use_wallclock_as_timestamps 1 : ffmpeg ignore les timestamps de la source et les régénère.

4. L’iGPU Intel change tout. Mon serveur est un modeste Beelink U59 (Celeron N5105) qui héberge déjà HA + 2 Gladys. En détecteur CPU, il était à genoux (86 % système). En activant OpenVINO sur l’iGPU + décodage VAAPI :

Métrique CPU iGPU (OpenVINO + VAAPI)
Inférence 94,6 ms 15,8 ms
Process détecteur 191,8 % 8,5 %
CPU système 86 % 22 %
Frames jetées 7/s 0

Un Celeron à 22 % de CPU qui fait de la détection d’objets temps réel en plus de HA et 2 Gladys. :exploding_head:

:white_check_mark: Le résultat

Côté MQTT, Frigate publie tout ce dont on rêve :

frigate/c660/person            → 1 / 0  (binaire, parfait pour Gladys)
frigate/c660/dog               → 1 / 0
frigate/events                 → JSON riche (label, score, box, trajectoire, zones...)
frigate/reviews                → alertes avec thumbnail
frigate/stats                  → santé complète (fps, inférence, stockage...)

Détection validée en réel (person à 0,93 de score, dog reconnu là où Tapo dit « animal »), clips enregistrés, snapshots, zéro cloud. Une caméra « officiellement impossible à intégrer » qui fonctionne mieux en local que les modèles « compatibles ». :muscle:

:gear: Configurations complètes validées (docker-compose + config Frigate) — cliquez pour déplier

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 depuis la 0.17 !)
      - "8554:8554"   # RTSP restream
      - "1984:1984"   # go2rtc

config/config.yml :

yaml

mqtt:
  enabled: true
  host: <votre_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://MDP_URL_ENCODE@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

Notes d’exploitation :

  • Frigate 0.17 : l’UI est en HTTPS sur le port 8971 (certificat auto-signé), compte admin généré au premier démarrage (mdp dans les logs).
  • shm_size : 128 Mo ne suffit pas, même pour 1 caméra.
  • Les caméras Tapo limitent les flux concurrents → toujours consommer via go2rtc (qui mutualise en 1 connexion), jamais en direct.
  • Petit warning connection reset by peer périodique sur le port 8800 : la caméra coupe de temps en temps, go2rtc reconnecte tout seul. Non bloquant.
---

:rocket: Et maintenant : proposition d’intégration Gladys

Tout ça fonctionne, mais soyons honnêtes : entre le nmap, l’URL-encoding, les input_args ffmpeg et la config OpenVINO, ce n’est pas accessible au commun des mortels. Et c’est exactement le genre de complexité que Gladys sait masquer. :heart:

Je compte donc développer une intégration « Vidéosurveillance locale » (nouveau service ou extension de rtsp-camera, à discuter) dont le principe est de ne PAS réinventer la roue :

  • go2rtc = couche d’abstraction protocolaire (il parle rtsp://, tapo://, onvif://, Ring, Nest, Dahua, USB… et c’est maintenu par AlexxIT + une grosse communauté). Une intégration = toutes les caméras.
  • Frigate = détection d’objets + enregistrements + MQTT.
  • Gladys = orchestration, configuration simple, et exposition des features.

Ce que ferait l’intégration

  1. Gestion des conteneurs Frigate/go2rtc depuis l’UI Gladys (installation, démarrage, restart) — sur le même modèle que ce qui existe déjà pour Zigbee2MQTT.
  2. Configuration intelligente automatique : détection du matériel (iGPU Intel présent ? → OpenVINO + VAAPI ; sinon CPU), calcul du shm_size selon le nombre de caméras, défauts stables — le tout surchargeable en mode expert.
  3. Ajout de caméra simplissime : nom, type (RTSP / Tapo / ONVIF…), IP, mot de passe (encodé automatiquement !), choix des objets à détecter (person, dog, cat, horse… multi-select). L’intégration génère la config Frigate et applique les correctifs connus (substream Tapo, input_args timestamps…) sans que l’utilisateur ait à savoir que ça existe.
  4. Features Gladys créées automatiquement par caméra :
  • la caméra live classique ;
  • un binaire de détection par type d’objet (frigate/<cam>/person → capteur présence Gladys → scènes !) ;
  • une « caméra image » par type : la dernière image capturée de chaque détection (probablement un nouveau type de feature image-only, sans live — à discuter).
  1. Santé : statut par caméra, compteur de reconnexions, stats Frigate remontées.

Questions ouvertes pour la communauté (et @pierre-gilles :slight_smile:)

  • Nouveau service ou extension de rtsp-camera ? Mon intuition : nouveau service (le périmètre est très différent), mais l’existant doit rester le chemin simple pour qui ne veut qu’un flux RTSP.
  • Nouveau type de feature « caméra image-only » (affichage de la dernière image, pas de player live) : ça vous semble la bonne modélisation pour « dernière détection de type X » ?
  • Politique conteneurs compagnons : Frigate embarque son propre go2rtc, donc un seul conteneur suffit. OK pour suivre le pattern Z2M ?
  • Live view : intégration du web component video-stream.js de go2rtc (WebRTC/MSE) avec fallback snapshots pour les flux capricieux. Question du proxy via le server Gladys à creuser (mixed content HTTPS/HTTP).

Je démarre le développement par une phase d’analyse de l’existant, puis un MVP (conteneur + caméra RTSP générique + binaires de détection MQTT), puis les sources avancées (tapo://, onvif://) et le live. PRs petites et découpées, comme d’habitude.

Tous les retours sont bienvenus : cas d’usage, caméras que vous aimeriez voir supportées, avis sur les questions ci-dessus… Et si certains veulent tester le setup manuel en attendant, les configs complètes sont dans le déplié ci-dessus — je réponds aux questions ! :raised_hands:

À très vite, 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: