How Helen Gets Made A making-of, not the live show Watch her live

Frigate · Camera setup · the whole config

The whole Frigate config, annotated line by line

The config.yml that has run the cat cams since the move to the Intel iGPU on 2026-08-29: eleven cameras, one detector, three retention numbers.

Read camera setup first if a go2rtc restream is new to you; the notes below assume you know why there are two streams.

Helen, a dilute tortoiseshell calico, in frame on one of the seven cameras this config watches for cat and person
Every visit that becomes a Short starts as a row in this file: a name, two streams, 640×360 at 2 fps, cat and person.

Pulled from /mnt/d/frigate/config/config.yml with the camera logins and addresses replaced by placeholders; otherwise as it runs. Section notes follow the file. To add a camera to an existing Frigate, skip to the copy-this template. The machine underneath is the WSL2 setup files, and the detectors block at the top has a page of its own, the OpenVINO ZMQ detector.

/mnt/d/frigate/config/config.yml
mqtt:
  enabled: false

detectors:
  ov_gpu:
    type: zmq
    endpoint: tcp://172.17.0.1:5555
    request_timeout_ms: 1000

model:
  path: /openvino-model/ssdlite_mobilenet_v2.xml
  width: 300
  height: 300
  input_tensor: nhwc
  input_pixel_format: bgr
  labelmap_path: /openvino-model/coco_91cl_bkgr.txt

record:
  enabled: true
  continuous:
    days: 3
  alerts:
    retain:
      days: 21
  detections:
    retain:
      days: 21

snapshots:
  enabled: true
  retain:
    default: 21

objects:
  track:
    - person
    - car
    - cat
    - dog
  filters:
    cat:
      threshold: 0.6
      min_score: 0.4

motion:
  threshold: 20
  contour_area: 8
  improve_contrast: true

audio:
  enabled: false
  min_volume: 500
  listen:
    - meow
    - cat
    - caterwaul

go2rtc:
  streams:
    cat_water_bowl:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    cat_water_bowl_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    cat_food_bowl:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    cat_food_bowl_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    cat_treat_dispenser:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    cat_treat_dispenser_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    cat_scratching_pad:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    cat_scratching_pad_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    helen_food_face:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    helen_food_face_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    playroom_cam:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    playroom_cam_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    bed_cam:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    bed_cam_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
    bed_cam_2:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    bed_cam_2_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2

cameras:
  cat_water_bowl:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cat_water_bowl_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cat_water_bowl
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  cat_food_bowl:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cat_food_bowl_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cat_food_bowl
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  cat_treat_dispenser:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cat_treat_dispenser_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cat_treat_dispenser
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  cat_scratching_pad:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cat_scratching_pad_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cat_scratching_pad
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  helen_food_face:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/helen_food_face_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/helen_food_face
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  playroom_cam:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/playroom_cam_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/playroom_cam
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  bed_cam:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/bed_cam_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/bed_cam
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

  bed_cam_2:                      # audio trial camera; the whole camera is switched off
    enabled: false
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/bed_cam_2_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/bed_cam_2
          roles:
            - record
            - audio
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: true
    objects:
      track:
        - cat

  # ---- house cameras: never published, detect off, 21 days continuous ----

  front_door:
    ffmpeg:
      inputs:
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
          roles:
            - detect
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
          roles:
            - record
    detect:
      enabled: false
      width: 640
      height: 360
      fps: 3
    record:
      enabled: true
      continuous:
        days: 21

  back_yard:                      # non-Tapo camera: its own path scheme
    ffmpeg:
      inputs:
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>/live/ch1
          roles:
            - detect
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>/live/ch0
          roles:
            - record
    detect:
      enabled: false
      width: 640
      height: 360
      fps: 3
    record:
      enabled: true
      continuous:
        days: 21

  carport:
    ffmpeg:
      inputs:
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2
          roles:
            - detect
        - path: rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
          roles:
            - record
    detect:
      enabled: false
      width: 640
      height: 360
      fps: 3
    record:
      enabled: true
      continuous:
        days: 21

version: 0.17-0

mqtt

Off. Nothing on this box subscribes to Frigate's MQTT topics; the Shorts pipeline polls /api/events over HTTP every fifteen minutes and Home Assistant's job here is the treat relay, not Frigate. Turning MQTT off removes a broker from the list of things that can be down.

detectors

One detector, named ov_gpu, of type zmq. Frigate does not load a model at all; it sends each detect frame to tcp://172.17.0.1:5555 and reads the detections back. 172.17.0.1 is docker's default bridge gateway, which is the WSL host, where a small Python process holds the Intel UHD 630 through OpenVINO. That process, its systemd unit, and the protocol are on the detector page. request_timeout_ms: 1000 is roughly eighty times the steady-state round trip of 12.5 ms; it only fires if the server is gone.

model

Even with an external detector Frigate needs to know the model's input contract, because Frigate does the resize and layout before it sends the frame. This is the bundled SSDLite MobileNet v2 OpenVINO IR: 300×300, nhwc (batch, height, width, channels), BGR pixel order, and the 91-class COCO labelmap with a background entry so the class ids the detector returns map to the right words. The host process loads its own copy of the same files, so the ids agree.

record

The shape that makes three weeks affordable: continuous: 3 days keeps everything from every recording camera for three days, and alerts / detections at 21 days keep the segments where something tracked was in frame for three weeks. On the cat cameras that means three weeks of cat without three weeks of empty room. The house cameras override this per camera to 21 days continuous, because a front door is a different question. Frigate's own count of what that keeps (/api/recordings/storage, 2026-09-03) is about 420 GiB across the eleven cameras on a 3.7 TB drive; the retention page has the per-camera numbers and does the arithmetic for other camera counts.

snapshots

On, 21 days, matching the event retention. The Shorts pipeline pulls the best-scoring event's snapshot as the hero frame of a visit (the still that goes to the vision model alongside the clip), but it decides whether a visit is worth publishing from the event timings, not from the snapshot. The public site's "latest" stills come from a different endpoint entirely, so these are for review, not display.

objects

Globally: person, car, cat, dog. Each cat camera narrows that to cat and person; car and dog only matter if a house camera ever turns detection on. The cat filter is the one tuned number in the file: threshold: 0.6 is the score a tracked object must reach at some point to become an event, min_score: 0.4 is the floor below which a single frame's detection is ignored entirely. SSDLite is confident about a cat at a bowl and less confident about a cat's back walking away; 0.6 / 0.4 keeps both. If your cushions start becoming cats, raise min_score before threshold.

person stays on the cat cameras because the Shorts pipeline has a deny gate: a visit with a person in any frame is never published. That gate can only refuse what Frigate detected, so the cat cameras must look for people even though the show is never about people.

motion

threshold: 20 and contour_area: 8 are a little more sensitive than Frigate's defaults, because a cat drinking is a small, slow motion and the cameras are close. improve_contrast: true helps the dark-room cameras at night. Motion is the gate in front of the detector: a frame with no motion regions is never sent over ZMQ, which is why detection_fps in the stats is usually well below camera_fps.

audio

Configured, disabled. The Tapo streams carry PCMA/8000 audio, and Frigate can listen for meow, cat and caterwaul as audio events. We trialled it on bed_cam_2 (the one camera with an audio role in its inputs) and left it off globally; the trial is parked, not concluded. It stays in the file so it can be resumed by flipping one boolean.

go2rtc.streams

Sixteen entries for eight cat camera blocks (seven running, plus the switched-off bed_cam_2 trial): <name> pulls /stream1 (the full-resolution main stream), <name>_sub pulls /stream2 (640×360). go2rtc connects to each camera once and republishes on rtsp://127.0.0.1:8554/<name>. Frigate's own processes, the two 24/7 YouTube pushes, and anything else on the box all read from go2rtc, never from the camera. The three house cameras have no go2rtc entry; nothing but Frigate is meant to see them.

cameras — the cat-cam block

Seven cameras share one block, differing only in the name. Sub-stream for detect, main stream for record. Detect at 640×360 — the sub-stream's native size, so no scaling — at 2 fps. A cat at a bowl stays for many seconds; 2 fps loses nothing and keeps seven cameras' worth of detection at about 14 fps total, which the iGPU answers at about 20 % busy. audio: enabled: false per camera is explicit so the global block can be flipped without every camera following.

bed_cam_2 is the audio-trial camera, switched off at the camera level with enabled: false; its detect.enabled is still true, which is why /api/stats reports detection_enabled: true for it. It shows up in the stats with camera_fps 0 and pid: null, because Frigate never starts a process for a disabled camera. That is what a disabled camera looks like, as opposed to a broken one (back_yard below: process alive, camera_fps 0.0).

cameras — the house block

Front door, back yard, carport. Three differences from the cat block: the inputs point at the camera directly (no go2rtc entry to restream), detect.enabled: false so nothing is tracked and nothing becomes an event, and a per-camera record.continuous.days: 21 that overrides the global 3. They run at 3 fps instead of 2; with detection off the only cost is decode, and a door is the one place a third frame a second is worth having. back_yard is not a Tapo; its RTSP paths are /live/ch0 for the main and /live/ch1 for the sub, which is a reminder that every maker has its own scheme and Frigate's camera docs are where to look them up.

Copy this for the next camera

Replace <JOB> in five places, the address once, and paste the two fragments into the matching sections.

If the next camera is not bought yet: everything on this page was written against Tapo C120 and C210 units. The link that follows is an affiliate link to TP-Link's US store, meaning a purchase through it pays this site a small commission at no extra cost to you; it changes nothing above: Tapo C120, the one we bought last.

New cat camera
# 1. go2rtc: pull each stream from the camera once
go2rtc:
  streams:
    cat_<JOB>:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream1
    cat_<JOB>_sub:
      - rtsp://<CAMERA_USER>:<CAMERA_PASSWORD>@192.168.1.<N>:554/stream2

# 2. the camera: detect on the sub, record the main
cameras:
  cat_<JOB>:
    ffmpeg:
      inputs:
        - path: rtsp://127.0.0.1:8554/cat_<JOB>_sub
          roles:
            - detect
        - path: rtsp://127.0.0.1:8554/cat_<JOB>
          roles:
            - record
    detect:
      width: 640
      height: 360
      fps: 2
      enabled: true
    audio:
      enabled: false
    objects:
      track:
        - cat
        - person

What this config produces

/api/stats, captured 2026-09-03 00:17 UTC; trimmed only by deleting keys (cpu_usages, embeddings, processes, temperatures, latest_version, and per-camera capture_pid / ffmpeg_pid / audio_*)
$ curl -s http://127.0.0.1:5000/api/stats
{
  "cameras": {
    "cat_water_bowl":     {"camera_fps": 2.1, "process_fps": 2.1, "skipped_fps": 0.0, "detection_fps": 1.3, "detection_enabled": true, "pid": 1143},
    "playroom_cam":       {"camera_fps": 2.1, "process_fps": 2.1, "skipped_fps": 0.0, "detection_fps": 2.1, "detection_enabled": true, "pid": 1266},
    "cat_food_bowl":      {"camera_fps": 2.1, "process_fps": 2.1, "skipped_fps": 0.0, "detection_fps": 4.3, "detection_enabled": true, "pid": 1392},
    "cat_treat_dispenser":{"camera_fps": 2.1, "process_fps": 2.1, "skipped_fps": 0.0, "detection_fps": 2.5, "detection_enabled": true, "pid": 1515},
    "front_door":         {"camera_fps": 3.1, "process_fps": 3.1, "skipped_fps": 0.0, "detection_fps": 0.0, "detection_enabled": false, "pid": 1638},
    "back_yard":          {"camera_fps": 0.0, "process_fps": 0, "skipped_fps": 0.0, "detection_fps": 0, "detection_enabled": false, "pid": 1762},
    "carport":            {"camera_fps": 3.0, "process_fps": 3.0, "skipped_fps": 0.0, "detection_fps": 0.0, "detection_enabled": false, "pid": 1885},
    "cat_scratching_pad": {"camera_fps": 2.1, "process_fps": 2.1, "skipped_fps": 0.0, "detection_fps": 3.4, "detection_enabled": true, "pid": 2033},
    "helen_food_face":    {"camera_fps": 2.1, "process_fps": 2.0, "skipped_fps": 0.0, "detection_fps": 3.1, "detection_enabled": true, "pid": 2197},
    "bed_cam_2":          {"camera_fps": 0, "process_fps": 0, "skipped_fps": 0, "detection_fps": 0, "detection_enabled": true, "pid": null},
    "bed_cam":            {"camera_fps": 2.0, "process_fps": 2.0, "skipped_fps": 0.0, "detection_fps": 0.0, "detection_enabled": true, "pid": 2337}
  },
  "detectors": {"ov_gpu": {"inference_speed": 12.57, "detection_start": 0.0, "pid": 1073}},
  "camera_fps": 20.7, "process_fps": 20.6, "skipped_fps": 0.0, "detection_fps": 16.7,
  "service": {
    "uptime": 337595, "version": "0.17.2-3d4dd3a",
    "storage": {
      "/media/frigate/recordings": {"total": 3815430.0, "used": 905101.5, "free": 2910328.5, "mount_type": "9p"},
      "/media/frigate/clips": {"total": 3815430.0, "used": 905101.5, "free": 2910328.5, "mount_type": "9p"},
      "/tmp/cache": {"total": 953.7, "used": 83.2, "free": 870.4, "mount_type": "tmpfs"},
      "/dev/shm": {"total": 256.0, "used": 76.1, "free": 179.9, "mount_type": "tmpfs", "available": 206.0, "camera_frame_size": 9.2, "shm_frame_count": 22, "min_shm": 234}
    },
    "last_updated": 1788394640
  }
}

That capture is 3.9 days into the container's uptime: 20.7 camera-frames a second in, zero skipped, 16.7 detector calls a second at 12.57 ms each. detection_fps counts inference calls, not frames; a frame with several regions of interest costs several calls, which is how cat_food_bowl reads 4.3 on a 2.1 fps feed. back_yard at 0.0 with a live pid is a camera that was offline (it had been for days); bed_cam_2 at 0 with pid: null is the disabled one. service.storage is the filesystem's view of the whole D: drive through 9p, 905 GB used of 3.8 TB, which is the drive and not the recordings; the retention page has Frigate's own per-camera count.