Implementation Plan¶
Plan for building Supernotify RFC 0006 - Frigate Auto Notifier as the camera_autonotify custom component, following the shape and tooling of appliance_autonotify.
Approach¶
- Camera AutoNotify, Frigate first (renamed 2026-10-22 from Frigate AutoNotify). Later sources are anpr2mqtt, UniFi Protect, Viseron and doorbell cameras.
- Entry data: each entry records
source(now alwaysfrigate),source_entry_idfor that integration's own entry, andsource_namefor the camera's or zone's name as that integration gives it. - Dependencies: Frigate and MQTT are
after_dependencies, so it installs without them. - Still Frigate-specific:
frigate.py, the discovery indiscovery.py, and the review tracker. Split them into a source interface when the second source comes, shaped by what it needs, rather than guessing now. - Same model as Appliance AutoNotify: a config entry per camera, discovered from the camera's existing Frigate device and linked to it, so Camera AutoNotify shows as one more integration on that device. A single
discoveryentry holds the auto-discover switch. A camera added in Frigate shows up as discovered, beside Frigate's own discovery of it - Zones, optionally. Frigate gives each zone a device as well. A second switch on the
discoveryentry, off by default, discovers zones too. A zone entry notifies for reviews on any camera whose zones include it - One Frigate server is the target. Each camera entry records the
frigateconfig entry it came from, so a second server isn't ruled out, but it gets no special handling or UI - Needs Supernotify to be useful. Without it, a plain
notify.send_messagegoes to the chosen notify entities, or to every mobile app, as in appliances. That means no images, no actions and no fix, and the setup screens say so - No YAML and no templating for users. The fields are the Frigate blueprint's filters, as plain selectors, plus the same notification tuning as appliances: targets and deliveries. Anything Supernotify already does, such as priority mapping, recipients, snoozing, the archive and scenarios, is left to Supernotify
- RFC 0003a is set aside. Only what the Frigate use cases need gets built, in this component, and each workaround is listed in the table below, to judge later whether it's worth moving into Supernotify
Phase 0 - Scaffold¶
Copy from appliance_autonotify and rename:
pyproject.toml,mise.toml,.pre-commit-config.yaml,.python-version,refresh-deps.sh,hacs.json,.github/workflows,properdocs.yml,docgen/,LICENSE,CHANGELOG.md,README.mdskeletoncustom_components/camera_autonotify/:manifest.json-dependencies: ["mqtt", "frigate"], since only Frigate cameras are ever set up, andafter_dependencies: ["supernotify"],integration_type: service,iot_class: local_push, version0.1.0compat.py(the voluptuous/probatio shim andLINKED_DEVICES), copied as issupernotify_api.py(supernotify_available,deliveries_by_transport), copied as isconst.py,translations/en.json,brand/tests/conftest.pyusingpytest-homeassistant-custom-component, coverage set to fail under 90%
Phase 1 - Spikes, run against a real HA with ha-repl¶
Use the test instance, which will have Frigate installed alongside Supernotify. The real instance can be used through ha-repl too where needed, for example for real review payloads and real camera devices, but only to read it. ha-repl stays available in later phases for checking behaviour.
- Camera devices and entities: what a Frigate camera's device looks like, including its identifiers and which
frigateentry it belongs to, and how to find itscamera.*entity and the camera name Frigate uses, from the entity registry rather than by guessing entity IDs - Frigate config: where the integration keeps the Frigate config, to read each camera's zones and tracked labels,
mqtt.topic_prefix, the client ID and whether the notification proxy is on. Fall back to the integration's API client if it isn't held anywhere usable - Proxy URL paths: the actual paths for snapshot, thumbnail, clip, HLS
master.m3u8and the review page, with and without a client ID, for both standalone Frigate and the HA app - Review payloads: capture real
frigate/reviewsmessages (new,update,end, alert and detection) as test fixtures - Apple video: which of
clip.mp4andmaster.m3u8actually play from an iOS notification, through Supernotify'sclip_urlas it stands today. This decides how much of the fix is needed
Findings (2026-10-22)¶
From Frigate integration 5.15.6 with Frigate 0.18.2, on the test server and the real one.
- Devices: each Frigate device has the identifier
("frigate", "{entry_id}:{slug}"), withviaset to the server device("frigate", entry_id). - Cameras have exactly one
camera.*entity, whose unique ID is{entry_id}:camera:{name}. Read the camera name from that unique ID. - Zones get devices of the same shape, but have no camera entity, only occupancy and count sensors. Tell them apart by whether a camera entity is present. A zone is matched back to its cameras through the Frigate config.
- Name case: zone names keep their case in the config and in review payloads (
Barn_Door), but are slugified in device identifiers (barn_door), so compare slugs. - Name clash: a zone named the same as a camera would get the same identifier, an edge case in the Frigate integration.
- Frigate config:
hass.data["frigate"][entry_id]hasclient,config,coordinator,model, and thews_event_proxyandws_review_proxy(these only forward MQTT to frontend websockets). Fromconfig["cameras"][name]: zonesobjects.trackreview.alertsandreview.detections, each withlabelsandrequired_zonesenabled
The topic prefix is config["mqtt"]["topic_prefix"]. - Proxy URLs: /api/frigate/notifications/{id}/{path}, with an optional {instance_id}/ after frigate/, which is left out for a single server. - By detection ID: snapshot.jpg, thumbnail.jpg, {camera}/clip.mp4 and {camera}/master.m3u8 (HLS through Frigate's VOD module). - By review ID: review_preview.gif and {camera}/review_thumbnail.webp. - Access: unauthenticated requests are allowed unless notification_proxy_expire_after_seconds is set on the Frigate entry, and notification_proxy_enable is on by default. Warn if a user has switched it off. - Review shape: id, camera, start_time, end_time, severity, thumb_path, and data with detections, objects, verified_objects, sub_labels, zones, audio, thumb_time and metadata (metadata holds the GenAI output, out of scope). - Test server: MQTT was added on 2026-10-22, after the first capture attempt. - Apple video (spike 5): tested on an iPhone through Supernotify mobile_push, using the Nabu Casa URL and a review that had already ended. - A: clip_url set to clip.mp4 showed a thumbnail the first time, but no image when sent again, so it's unreliable even for the image. - B: clip_url set to master.m3u8 shows no image at all. iOS can't attach the playlist, and the video attachment replaces the snapshot. - C: snapshot only, with a notification-level tap to master.m3u8, shows the snapshot and opens the video when tapped. It worked both times.
So never send the m3u8 as clip_url. The broken image in the recipe comes from that. - Tap: tapping C opens the video through master.m3u8. Tapping A or B only opens the Home Assistant app, since neither had a tap URI. - Long-press: long-pressing A shows the action buttons and a loading spinner that never finishes. The clip was 15 MB at 3040x1368, which iOS doesn't play inline.
Decision: never send clip_url. Every notification gets the snapshot, plus a notification-level action to master.m3u8, so tapping opens the video. The Apple fix is then simply how this component works, with no setting to turn it off. Android gets the same links, since clickAction to the playlist works there too (to confirm on an Android device). - Fixtures: tests/fixtures/reviews.json has 7 real reviews from the API, used as the after of an MQTT message ({"type": "new"|"update"|"end", "before": ..., "after": ...}). - Not seen: the sample has no detection severity, sub-labels or audio. Capture a live new, update and end sequence in the daytime, when Frigate is busier (7.5 minutes in the evening caught nothing). - Zones cross cameras: Pend shows up in reviews from both courtyard and driveway, so one zone can belong to more than one camera.
Phase 2 - Discovery and devices (discovery.py, device.py, frigate.py)¶
find_cameras(hass): the Frigate camera devices, each with its device ID, name, camera name, camera entity andfrigateentry IDasync_start_discovery, as in appliances: look now, and again on entity registrycreateevents for Frigate camera entities, then either import (auto-discover on) or offer throughintegration_discovery. The unique ID is the device IDasync_register_device: link to the camera's device by its shared identifiers, as appliances does withLINKED_DEVICES, and do without a device on older Home Assistant versionsfrigate.py: reads one camera's server details (topic prefix, client ID, zones, labels) and builds the URLs forsnapshot_url,thumbnail_url,hls_urlandreview_url(the base comes fromget_url(prefer_external=True))
Phase 3 - Review tracker (tracker.py)¶
Pure logic with no Home Assistant I/O, so it's the most heavily tested part.
ReviewTrackerper camera entry, holding in-memory state perreview_id: severity, objects, sub-labels, zones, last sent time, and count senton_message(payload) -> Decision | Noneapplies the blueprint's filters:- severity: alert or detection
- labels, zones,
zone_multi,zone_order_enforced cooldownfor new reviewsalert_once,update_sub_labelinitial_delay,final_delay,final_update,timeout, as timers throughasync_call_later- A
Decisioncarries what the notifier needs: the camera, label text with sub-labels merged, objects, zones, severity, the review and detection IDs, whether this is a new review, an update or the final notification, and why it was sent - Clean up state on
endand ontimeout - Tests are table-driven from the Phase 1 fixtures, one case per blueprint behaviour being replaced
Built (2026-10-22)¶
tracker.py is pure logic: the time is passed in, and anything held back comes back as a Wait for the caller to time and then call on_timer for.
- Messages:
on_messagegives aDecision(NEW,LABEL,ZONE,SUB_LABEL,SEVERITYorFINAL), aWait, orNone. - Matching: a zone tracker follows its zone on any camera, matched by slug.
- Audio: audio labels count as objects.
- Sub-labels: given as
[name, score]by newer Frigate, and read as the name. - Late matches: a review that starts out not matching the filters is notified as
NEWas soon as an update makes it match, for example a detection raised to an alert, or a chosen label appearing. - Cooldown: a review passed over for the cooldown gets none of its updates either.
- Already over: a review that has ended before its first notification is due gets that one notification and nothing more.
- Defaults:
- alerts only
cooldown30 stimeout600 salert_onceonupdate_sub_labelonfinal_updateoff- no delays
Phase 4 - Notifier (notifier.py)¶
Builds the supernotify.notify call in code:
titleandmessagefrom fixed wording with the label, camera and zones filled in. Users can't edit them, which keeps this free of templatesmedia:snapshot_urlandcamera_entity_id, and neverclip_url(see the spike 5 findings)actions: a notification-level tap URI tomaster.m3u8, as appliances does with its dashboard, plus a View Snapshot buttonpriority:highfor alerts andmediumfor detections, with Supernotify handling the mapping to critical and interruption level per platformextra_data:mobile_push_notification_tagset to the review ID, so updates replace the earlier notification, and a group by cameratarget,custom_targetanddelivery, from the settings, sent the same way as appliances_supernotify- Context: a fresh
Contextper review, with later notifications as children of it, as in appliances - Without Supernotify:
notify.send_messagewith the title and message only
Built (2026-10-22)¶
notifier.py builds the supernotify.notify call for each Decision.
- Title: the entry's name.
- Message:
Person detected in Barn Doorfor a camera, orPerson detected on Courtyardfor a zone., now endedis added to the final notification.- Priority:
highfor an alert,mediumfor a detection.lowfor an update with alert once, sincelowis iOS passive, so no sound on any delivery.- Grouping: tag
frigate_{review_id}and groupfrigate_{camera}. - Media:
- the camera entity, for Supernotify's camera snooze, with the snapshot still the image;
- the snapshot URL;
- a tap to the HLS link, and a View snapshot button.
- Options: deliveries and targets from the entry's options.
- Without an external or internal URL: no snapshot or links.
- Without Supernotify:
notify.send_messagefor the first notification of a review only, since each update would be a new notification with nothing to replace.
Phase 5 - Config flow (config_flow.py)¶
Same structure as appliances, reusing its sectioned() and flattened() helpers and its rule that a value left at its default isn't stored.
- User step: creates the
discoveryentry (auto-discover on by default) and lists the cameras found, with the Supernotify status text - Import and integration_discovery steps: a camera entry per discovered camera, titled with the camera name
- Camera options:
- main fields: severity, then labels and zones as multi-selects filled from the Frigate config
- collapsed Timing section: cooldown, initial and final delay, final update, timeout,
alert_once,update_sub_label - collapsed Notify section:
target,custom_targetanddeliveries. Without Supernotify, this section is only a notify entity select - Conditions such as presence, state and time aren't offered for now. They may suit the proposed quick selectors (Supernotify RFC 0007); noted only, not planned here
Built (2026-10-22)¶
Each camera or zone entry's settings form:
- Main fields:
- Severity: alerts and detections, with alerts chosen by default.
- Objects: tracked objects, audio labels and review labels from the camera's Frigate config. A zone offers those of every camera it's on.
- Zones, Every zone and Zones in order: for cameras only.
- Typed values: objects and zones accept values that aren't in the list, for a label Frigate's config doesn't list.
- Collapsed Timing section: the tracker's timing settings.
- Collapsed Notification targets section: as in appliances.
Storage: options are stored flat under the tracker's own setting names, so Settings.from_options reads them directly. Values left at the default aren't stored.
Phase 6 - Runtime wiring (__init__.py)¶
- One shared MQTT subscription for each topic prefix in use, made on the first camera entry's setup and dropped with the last one. It routes each review by camera name to that entry's tracker
async_setup_entry: build the tracker and notifier, and register the device. If the camera's Frigate entry isn't loaded, for example after a reload, raiseConfigEntryNotReady- Every timer and listener goes through
entry.async_on_unload. Options changes reload the entry (OptionsFlowWithReload) - Diagnostics: redacted server info and the tracker's current reviews
Built (2026-10-22)¶
hub.py has one shared {prefix}/reviews subscription for each topic prefix, dropped with its last entry. Each camera's or zone's entry has a ReviewWatcher:
- Settings: its tracker is built from the entry's options, and a reload on saving the settings applies them.
- Timing: it times each
Waitwithasync_call_later, and cancels the timers on unload. - Sending: it sends each
Decisionas a background task. - Context: each review gets a
Context, and each later notification for it is a child of that one. - Readiness: setup raises
ConfigEntryNotReadyuntil the Frigate entry's config and the MQTT client are ready. - Diagnostics: entry data, options with targets redacted, and the reviews being followed.
Phase 7 - Docs and release¶
- README and docs site: install, what gets discovered, a table mapping each blueprint input to where it lives now (here, in Supernotify, or dropped) for people migrating
- HACS
0.1.0pre-release, with a feedback thread linked from the Supernotify discussion
Built (2026-10-22)¶
- README, which is also the docs site's home page:
- what you need, installing, choosing cameras yourself, zones and the linked device;
- the settings, with their defaults, and what gets sent, with and without Supernotify;
- why the clip is never attached, in place of the Apple push fix;
- a table of each blueprint input and where it is now, for moving from the blueprint;
- known limits.
- Release:
0.3.0, already tagged, rather than0.1.0. The changelog describes it as the first pre-release. - Feedback: through the repository's issues, since it has no discussions. A thread in the Supernotify discussions is still to be made.
- Not built: the warning for a Frigate entry with its notification proxy off, listed as a known limit instead.
Supernotify candidates¶
Each one is built here first and reviewed once the component is in use. If Camera AutoNotify turns out to be the only user, it stays here.
| Need | Done here as | Could move into Supernotify as |
|---|---|---|
| Replace an earlier notification for the same review | Review ID as mobile_push_notification_tag | A first-class tag or notification ID (RFC 0009) |
| Cooldown between reviews | In the tracker | Per-source rate limiting |
Silent updates (alert_once) | priority: low | An explicit silent on the call, so it's not tied to priority |
| Video that works on iOS | Snapshot plus a tap to the HLS link, never clip_url | Supernotify warning, or dropping an m3u8 clip_url |
| Tap to open the clip | Notification-level action URI | n/a, already supported |
| Scenarios that match Frigate notifications | Message text only | A source or context passed on the call |
Out of scope (per RFC)¶
GenAI summaries and tracked_object_update, ANPR, Telegram inline keyboards, Android TV overlays, Live Activities, TTS, custom actions, and user-editable message text.
Rough sequencing¶
Phase 0 and Phase 1 first. Phases 2 and 3 can then run in parallel, since the tracker is pure logic over fixtures. Phase 4 needs the tracker's Decision, and Phase 5 needs Phase 2's discovery. Phase 6 joins them up and is the first end-to-end test on a real Frigate. Phase 7 comes last.