Files
home-information/docs/dev
Tony C 0eee15864c Frigate NVR integration (v1) (#347)
* Scaffold Frigate integration (issue #233)

Lays down the directory + plumbing for hi.services.frigate and
hi.simulator.services.frigate so both apps load under their respective
Django settings, the FrigateGateway is auto-discovered by
IntegrationManager, and the Frigate simulator module appears in the
simulator's services-tab dispatch. All concrete methods stubbed
(test_connection returns 'not yet implemented (scaffolding)',
do_work records a healthy heartbeat without polling, etc.).

Includes user-facing and developer-facing doc stubs structured around
the v1 scope, and adds Frigate to the integrations landing page.

* B1: Frigate simulator Camera entity definition

Adds FrigateCameraSimEntityFields plus two SimState subclasses —
FrigateCameraMotionState (MOVEMENT) and FrigateCameraObjectPresenceState
(DISCRETE). Registers a single Camera SimEntityDefinition in
FRIGATE_SIM_ENTITY_DEFINITION_LIST so the simulator's services-tab UI
exposes 'ADD Frigate ENTITY' -> Camera.

The object-presence choices carry raw Frigate-style labels (person /
car / truck / dog / cat / package) plus a deliberately unmapped
'unicorn' label so the HI converter's 'other' bucket stays on the
demo path. Canonical OBJECT_PRESENCE bucketing happens later on the
HI side, not at the simulator boundary.

* B2: Frigate simulator /api/config endpoint

Adds FrigateSimCamera accessor wrapper (mirrors ZmSimMonitor),
FrigateSimulator.get_sim_cameras(), and a Frigate-shape ConfigView at
GET /services/frigate/api/config that returns the active profile's
cameras keyed by camera_name. Real Frigate emits a much larger config
document; HI's integration only needs the cameras map plus a stable
JSON shape to recognize as Frigate.

* Fix Frigate attribute lookup to use integration_key, not integration_attr_type

The IntegrationAttribute model exposes integration_key (an
IntegrationKey wrapping integration_id + integration_name), not a
direct integration_attr_type field. The lookup pattern from
HassManager._build_hass_attr_type_to_attribute_map is the canonical
shape: build {attr.integration_key: attr} once, look up by
constructing IntegrationKey(integration_id, str(attr_type)).

* C1: FrigateClient.ping() + get_cameras() real implementations

Both methods route through a private _get_config() helper that hits
/api/config, validates status code + content-type, and parses JSON
with boundary-level diagnostic errors (same shape as HassClient).
ping() returns None on success; get_cameras() returns one dict per
entry in the response's 'cameras' map carrying the camera name and
raw per-camera config.

14 unit tests covering init / auth header / success / non-2xx /
HTML body / malformed JSON / network error / empty cameras /
multi-camera / config passthrough / missing or malformed
'cameras' field.

* C2: Wire FrigateManager.test_connection to FrigateClient.ping

Builds a temporary FrigateClient via FrigateClientFactory from the
proposed attributes and exercises ping() against /api/config.
ValueErrors from the client (configuration or response-shape
failures) surface as failure messages verbatim; other exceptions
get prefixed 'Connection error: '. Bounded by the provided
timeout_secs so the Configure form fails interactively.

* C3: Frigate sync creates camera entities with Movement + ObjectPresence sensors

Adds the new EntityStateType.OBJECT_PRESENCE (canonical value
range: NONE / PERSON / CAR / ANIMAL / PACKAGE / OTHER) plus the
matching EntityStateValue members, type-default EntityStateRole,
role-ordering entry (placed after MOVEMENT/PRESENCE), and a
HiModelHelper.create_object_presence_sensor factory.

FrigateManager grows a real _reload_implementation that builds the
FrigateClient from current integration attributes, a frigate_client
property gated on integration-enabled state, plus _to_integration_key
and _frigate_integration_key helpers for stable key construction.

FrigateSynchronizer._sync_impl mirrors the ZM pattern: fetch cameras
from /api/config via the client, reconcile against existing HI
camera entities by integration_key, create CAMERA entities for new
upstream cameras (with Movement + ObjectPresence sensors), preserve
the user-editable name on update, and remove entities whose upstream
camera disappears. check_needs_sync wires the Issue #283 drift probe.

6 sync tests cover client-missing, create, idempotent re-sync,
remove on upstream drop, user-name preservation, and multi-camera
placement input.

* Use camera friendly_name for HI entity display name

Real Frigate cameras carry a friendly_name on each camera's config
for display; the camera key itself is the snake_case technical
identifier. Sync now prefers config.friendly_name when present and
falls back to the camera key otherwise. Simulator's /api/config
endpoint emits friendly_name from the camera sim_entity's
display name field (the operator-set 'name' on the
FrigateCameraSimEntityFields), keeping the simulator faithful to
Frigate's response shape.

* D1: Frigate simulator events synthesized from motion-state toggles

Mirrors the ZM event-manager pattern: per-camera FrigateSimEventHistory
ring buffers held by a FrigateSimEventManager singleton, keyed by
camera_name. Events are pure transient FrigateSimEvent dataclasses
(no DB, no CRUD UI) — the operator drives them by toggling the
camera's existing Motion sim-state.

FrigateSimulator.set_sim_state intercepts Motion-state changes and
calls into the event manager. The event's label comes from the
camera's current ObjectPresence value at the moment motion-ON
fires (label fixed at first detection, matching real Frigate's
behavior closely enough). ObjectPresence changes during an open
event don't relabel it; operator toggles motion off-then-on to
start a new event with a new label.

Following the project convention of no simulator-internal unit
tests — verification is via manual hands-on testing through the
running simulator. A smoke run confirmed motion-ON opens a labeled
event and motion-OFF closes it.

* D2: Frigate simulator /api/events list + detail endpoints

GET /api/events returns the active profile's events as a top-level
JSON array (Frigate's convention), sorted newest-start_time-first.
Supports the 'after' query parameter (epoch seconds) used by the HI
polling cursor; other Frigate filters (before / cameras / labels /
zones) are accepted and silently ignored — they're not yet on the
HI integration's read path.

GET /api/events/<id> returns a single event object; 404 for unknown
ids so the HI client can distinguish 'event doesn't exist' from a
broken Frigate.

Verified end-to-end via the test client: motion-state toggles
synthesize events that surface through both endpoints with correct
ordering, filtering, and 404 behavior.

* E1: FrigateClient.get_events() + get_event() real implementations

Both methods route through a shared _get_json(path, params=None)
helper that handles status-code, content-type, and JSON-parse
validation in one place (the prior _get_config now delegates to it).
get_events accepts the polling cursor 'after' and an optional limit
as query params; get_event maps the URL onto /api/events/<id>.

Non-2xx responses (including 404 from get_event for unknown ids)
surface as ValueError with the status code in the message so
monitor / test_connection paths can record meaningful diagnostics.

11 new unit tests covering get_events (empty / parsed list / 'after'
+ 'limit' query params / no-params / not-a-list response / non-2xx)
and get_event (parsed dict / URL targeting / 404 / not-a-dict
response). End-to-end smoke against the live simulator verifies
both methods return data toggled in via the Motion sim-state.

* E2: FrigateMonitor polls events and emits MOVEMENT sensor responses

Mirrors the ZoneMinder monitor's order of operations exactly (those
invariants took several debugging rounds to settle on the ZM side):

  1. Fetch events with after=<polling-cursor>.
  2. Two-phase collate: filter out fully-processed events, then
     partition the rest into open vs closed.
  3. Per-camera aggregation: pick ONE canonical event per camera
     (earliest open -> ACTIVE; latest closed -> IDLE). Prevents
     multiple events on one camera from overwriting each other in
     the sensor-response map.
  4. Emit one MOVEMENT SensorResponse per aggregated state with
     correlation_role (START/END) + correlation_id set.
  5. Emit explicit IDLE SensorResponses for cameras that produced
     no events this cycle so their state doesn't go stale.
  6. Cursor advance: hold back to the earliest open event's start
     when any open events remain; otherwise advance to the latest
     closed event's end; with NO events do NOT advance (would risk
     missing an event that started right after the poll).

_fully_processed_event_ids TTLCache prevents closed events from
being re-emitted on subsequent polls. Open events are deliberately
NOT marked fully-processed — the cursor keeps them visible so we
can detect the close transition.

FrigateEvent.from_api_dict parses one /api/events JSON record into
the typed wrapper. AggregatedCameraState (in frigate_models.py for
parity with ZM's AggregatedMonitorState) carries the canonical
event + all_events for cache bookkeeping. FrigateManager grows
sync + async get_events / get_cameras shims used by the monitor.

21 unit tests cover aggregation across the open x closed
cross-product, sensor-response correlation, the no-event IDLE
case, cache bookkeeping, FrigateEvent.from_api_dict, and the
four cursor-advance invariants (no events / all closed / has open
/ fully-processed re-skip).

* E3: FrigateMonitor emits OBJECT_PRESENCE alongside MOVEMENT

FrigateConverter.to_canonical_object_class gets the real lookup
table: person -> OBJECT_PERSON; car/truck/bus/motorcycle/bicycle ->
OBJECT_CAR; dog/cat/bird/horse/sheep/cow/bear/deer/raccoon/fox/
squirrel/rabbit -> OBJECT_ANIMAL; package -> OBJECT_PACKAGE; anything
else -> OBJECT_OTHER. Lookup is case-insensitive. Empty / unknown
labels deliberately bucket into OBJECT_OTHER (not OBJECT_NONE) so a
custom-model class that hasn't been bucketed yet still surfaces in
HI as 'something is here'.

FrigateMonitor's per-camera sensor-response generation now emits
TWO responses per aggregated state: the MOVEMENT response that
already existed (ACTIVE/IDLE with correlation_role/id) plus a new
OBJECT_PRESENCE response carrying the canonical class. IDLE camera
states emit OBJECT_NONE — once an event closes, nothing is being
detected. The idle-for-unseen-camera pass also pairs MOVEMENT IDLE
with OBJECT_NONE so the object-presence state stays fresh even on
quiet poll cycles.

7 converter tests + expanded monitor tests cover the table
mapping, case-insensitive lookup, the unknown -> OTHER fallback,
and per-cycle OBJECT_PRESENCE emission across ACTIVE / IDLE /
no-events paths.

* Frigate: collapse MOVEMENT + OBJECT_PRESENCE into a single per-camera state

Frigate's data model couples motion to object detection — its events
API never emits motion-without-class — so a separate MOVEMENT sensor
on the HI side would always mirror OBJECT_PRESENCE. Removing it also
eliminates the simulator's ordering trap where the operator had to
toggle Motion before picking a label or the synthesized event landed
with no object class.

OBJECT_PRESENCE is now the single per-camera state across the
integration. Any non-OBJECT_NONE value implies motion is currently
happening and names the class; OBJECT_NONE means no current detection.

Simulator: drop the Motion sim-state entirely; the ObjectPresence
discrete control drives the event lifecycle directly. Transitioning
to a new label closes the current event and opens a new one;
transitioning to 'none' closes the current event.

* Frigate: snapshot URLs (live + event) and gateway VideoSnapshot plumbing

Real Frigate exposes still images at two paths the HI integration now
consumes:

- /api/<camera>/latest.jpg — the most recent decoded frame, used by
  the presentation layer as a live snapshot via the gateway's
  get_entity_video_snapshot method.
- /api/events/<id>/snapshot.jpg — the frame captured at the time of
  detection, attached to the OBJECT_PRESENCE SensorResponse as
  source_image_url so the alert / history views can show what the
  camera saw.

URL helpers on FrigateManager build both with a cache-bust timestamp
so a re-rendered <img> tag with the same src refetches rather than
showing the prior frame. Both helpers return None when the client
isn't built yet, propagating "no snapshot capability" through the
gateway.

Simulator endpoints serve synthesized placeholder JPEGs stamped with
the camera or event identifiers so artifacts viewed inside HI are
obviously coming from the simulator. /api/events/<id>/snapshot.jpg
is routed ahead of the existing /api/events/<id> JSON route so the
trailing path segment dispatches correctly.

MP4 clip endpoint deferred — v1 displays use snapshot-as-stream
(via video_snapshot_stream_fps) rather than recorded-event playback.

* Frigate: switch camera entities to snapshot-as-stream and clear manager health UNKNOWN

Two bugs surfacing on the Live Feed page:

(1) Frigate cameras were flagged with has_video_stream=True even
though v1 has no native MJPEG/RTSP — without go2rtc, Frigate exposes
only JPEG endpoints. The Live View template routed to the native-
stream branch which fell through to "Live Stream Unavailable"
because no gateway implementation existed. Flip the canonical sync
shape to has_video_snapshot=True + video_snapshot_stream_fps=1.0
(matching the HASS camera pattern) so the pane routes through the
snapshot-as-stream branch and consumes the snapshot URL plumbing
that already exists. _update_entity self-heals existing rows by
flipping the stale flag off and backfilling the missing fps.

(2) The integration banner showed "Frigate: Warning" while the
FrigateMonitor was reporting Healthy. The manager registers itself
as an API health provider, but its api_health_status default
(UNKNOWN) leaks into the aggregate — UNKNOWN counts as not-healthy
under ALL_SOURCES_HEALTHY, dragging the aggregate to WARNING. Set
the slot explicitly on every _reload_implementation: HEALTHY on a
clean client build, DISABLED when the integration row is off or
absent, UNAVAILABLE on a build failure.

* Frigate: wrap manager API methods in api_call_context

FrigateManager.get_cameras and get_events were making bare HTTP calls
without feeding the manager's API-health slot. Result: total_calls
stayed at 0, the slot stayed UNKNOWN, and the aggregate health stayed
at WARNING. Wrap both methods in api_call_context so each successful
poll increments the slot and moves it to HEALTHY — matching how
ZoneMinderManager.get_zm_states/monitors/events handle the same
concern.

The reload-time update_api_health_status calls are retained so the
slot also reflects a clean configuration even before the first poll
lands.

Comment cleanup pass on Phase F additions.

* Frigate: seed simulator profiles (empty, baseline, baseline-changed, volume)

Adds the standard four-profile catalog for the Frigate sub-app so the
sync result modal can be exercised through created / removed /
detached / reconnected transitions, plus a 10-camera stress profile.

* OBJECT_PRESENCE styling: collapse onto Movement vocabulary, give priority over MOVEMENT

OBJECT_PRESENCE is conceptually MOVEMENT with a larger value space.
For visual purposes the two collapse onto the same status_value
vocabulary (active / recent / past / idle), reusing existing CSS
rules. Where a camera has both — uncommon but possible —
OBJECT_PRESENCE wins because it carries finer-grained information.

Changes:
- EntityStateDisplayData gains _get_object_presence_status_style:
  same decay logic as MOVEMENT, discriminator is value != OBJECT_NONE
  (any detected class counts as active).
- DEFAULT_ENTITY_STATE_ROLE_ORDER and ConsoleManager.STATUS_ENTITY_STATE_PRIORITY
  put OBJECT_PRESENCE ahead of MOVEMENT so it wins priority pickers.
- EntityPairingManager.CREATE_BY_DEFAULT_MAP gets OBJECT_PRESENCE so
  a Frigate camera auto-creates an AREA delegate on first placement,
  matching MOVEMENT behavior.
- Camera panel declares OBJECT_PRESENCE in optional_roles with an
  object_data alias. Templates prefer object_data over motion_data
  for the chip, and render OBJECT_PRESENCE as a standard state row
  (via the fallback row template) so the user can see the current
  class and click through to history.

Tests cover all four OBJECT_PRESENCE decay states (active / recent
/ past / idle) plus the "other class is still active" branch.

* Frigate: auto-create OBJECT_PRESENCE EventDefinition on sync (gated by Add Alarm Events)

Brings Frigate to parity with ZoneMinder and Home Assistant on the
default alarm-wiring concern. Sync now creates an OBJECT_PRESENCE
EventDefinition per camera when the operator opts in via the new
ADD_ALARM_EVENTS integration attribute.

The default rule is the conservative EQ OBJECT_PERSON — person-only
alarms — to sidestep the EventClauseOperator vocabulary's lack of
NEQ/IN (tracked separately in #346). Operators wanting broader
detection rules can author additional EventDefinitions in the UI.

Changes:
- FrigateAttributeType.ADD_ALARM_EVENTS: BOOLEAN attribute,
  default off.
- FrigateManager._attribute_map: populated during reload from the
  current integration attributes; backs the new
  should_add_alarm_events property.
- HiModelHelper.create_object_presence_event_definition: new
  helper, mirrors create_movement_event_definition with the
  conservative OBJECT_PERSON default value.
- FrigateManager.OBJECT_PRESENCE_EVENT_PREFIX: integration-key
  prefix for the auto-created event definitions.
- frigate_sync.py: creates the EventDefinition when
  should_add_alarm_events is true.

Tests cover should_add_alarm_events (default off / true / false
attribute values) and the sync's create / skip / idempotent-on-
refresh paths.

* Frigate: implement per-camera Detect on/off control end-to-end

Outbound (HI -> Frigate):
- FrigateClient.set_camera_detect issues POST /api/<camera>/detect/set
  with the Frigate-side state value ('ON' / 'OFF'). New _post helper.
- FrigateConverter.hi_control_to_detect_state — explicit HI on/off ->
  Frigate ON/OFF mapping. Independent vocabularies; no string
  transforms.
- FrigateManager.set_camera_detect wraps the client call in
  api_call_context.
- FrigateController.do_control dispatches the detect-controller
  integration_name shape; surfaces translation and manager errors as
  IntegrationControlResult.error_list entries.
- frigate_sync creates an on/off controller per camera paired with
  the OBJECT_PRESENCE sensor.

Inbound (Frigate -> HI):
- FrigateConverter.detect_enabled_to_hi_value reads /api/config's
  cameras.<name>.detect.enabled boolean and maps to HI's on/off.
- FrigateMonitor emits a detect SensorResponse per camera each
  poll cycle so the panel display reflects the actual upstream
  state (closes the prior gap where the toggle would snap back
  for lack of confirming sensor data).

Simulator:
- New FrigateCameraDetectState sim-state per camera, defaulting to
  'ON' (real Frigate's startup default). Independent of the
  object-presence event lifecycle so each signal exercises HI's
  wiring on its own.
- /api/config exposes cameras.<name>.detect.enabled from the
  sim-state value.
- POST /api/<camera>/detect/set writes the value into the
  sim-state via the simulator so the operator observes the
  round-trip in the simulator UI.

* Rename sensor / event video-clip / video-snapshot fields to disambiguate from entity-level live-feed vocabulary

The Sensor/SensorResponse/SensorHistory/EntityStateHistoryValue
fields named has_video_stream / provides_video_stream / source_image_url
predated the entity-level live-feed model (has_video_stream /
has_video_snapshot / video_snapshot_stream_fps), and the name overlap
made it hard to tell at a call site whether you were dealing with
live observation (entity level) or historical event playback
(sensor/event level).

Rename to make the level explicit and reserve "event_" prefix for
historical playback concepts:

| Old                                          | New                              |
|----------------------------------------------|----------------------------------|
| Sensor.provides_video_stream                 | Sensor.provides_event_video_clip |
| (new)                                        | Sensor.provides_event_video_snapshot |
| SensorHistory.has_video_stream               | SensorHistory.has_event_video_clip |
| SensorHistory.source_image_url               | SensorHistory.event_video_snapshot_url |
| SensorResponse.has_video_stream              | SensorResponse.has_event_video_clip |
| SensorResponse.source_image_url              | SensorResponse.event_video_snapshot_url |
| EntityStateHistoryValue.has_video_stream     | EntityStateHistoryValue.has_event_video_clip |
| EntityStateHistoryValue.provides_video_stream | EntityStateHistoryValue.provides_event_video_clip |

Entity-level has_video_stream is unchanged — it remains the
live-feed capability flag, distinct from the per-event flags above.

DB migration uses RenameField only (no column drops); the lone new
column is the Sensor.provides_event_video_snapshot capability flag
to mirror provides_event_video_clip on the snapshot side.

* Frigate: event video clip playback (and HI render-layer MP4 support)

HI's Video Browse rendered event recordings via <img> alone — fine for
ZM (multipart MJPEG) but breaks Frigate's MP4 clip semantics. Add an
explicit media-type axis on VideoStream and a parallel <video> render
branch so MP4 event recordings play natively.

VideoStreamType vocabulary:
  URL  ->  MJPEG  (multipart, <img> renders)
  (new)    MP4    (recording, <video> renders)
  OTHER    OTHER

Render layer:
- entity_video_sensor_history.html branches on stream_type: <video>
  for MP4 (with controls + autoplay + muted + playsinline), <img>
  for MJPEG (existing path, unchanged).
- video-timeline.js replay button: <video> branch seeks to 0 and
  plays; <img> branch keeps the cache-bust URL trick (ZM).
- ZoneMinder gateway updated from VideoStreamType.URL to MJPEG to
  match the new explicit naming.

Frigate side:
- FrigateApi.EVENT_CLIP_PATH_TEMPLATE + FrigateManager.get_event_clip_url.
- FrigateEvent.has_clip / has_snapshot parsed from /api/events
  payload; default True for backward compat with older Frigate.
- FrigateMonitor propagates has_clip from the canonical event into
  the OBJECT_PRESENCE SensorResponse's has_event_video_clip.
- FrigateGateway overrides get_sensor_response_video_stream to return
  a VideoStream(stream_type=MP4, source_url=<clip url>).
- FrigateSync flags the OBJECT_PRESENCE sensor with both
  provides_event_video_clip and provides_event_video_snapshot.

Simulator side:
- New POST endpoint /api/events/<id>/clip.mp4 serves a pre-generated
  H.264 baseline placeholder (~14 KB) with labeled frame counter and
  ticking clock so the operator can verify the playback round-trip.

Tests cover the wire-to-model has_clip parsing, the monitor's
propagation, the gateway's MP4 stream return shape, the sync's
sensor flags, and the controller's URL routing.

* Replace SensorResponse event_video_snapshot_url with has_event_video_snapshot flag

Bring event snapshot URLs in line with the clip URL pattern: gateways
build the URL on demand from the event id rather than storing it.
Robust to operator-side base URL changes — an integration host
relocation no longer leaves historical SensorHistory rows with stale
URLs pointing at the previous host.

Symmetry now between the two affordances:

  Clip      has_event_video_clip       (flag)
            gateway.get_sensor_response_video_stream  (URL builder)

  Snapshot  has_event_video_snapshot   (flag, new)
            gateway.get_sensor_response_event_snapshot_url  (URL builder, new)

Migration:
  0013_replace_event_video_snapshot_url_with_flag adds the boolean,
  backfills True for every row that had a non-empty URL, then drops
  the URL column.

Frigate side:
  - has_event_video_snapshot propagates from FrigateEvent.has_snapshot
    through the monitor to the SensorResponse.
  - FrigateGateway.get_sensor_response_event_snapshot_url builds the
    URL from the response's correlation_id.

ZoneMinder side:
  - has_event_video_snapshot set True on START + END responses when
    detail_attrs carries an event_id.
  - ZoneMinderManager.get_event_snapshot_url returns the portal
    view=image URL fresh; gateway override builds per render.

Render side:
  - Templates that displayed event_video_snapshot_url now use the new
    sensor_response_event_snapshot_url template tag.
  - alert.py's get_first_visual_content dict no longer carries the
    URL — templates resolve via the tag.

* Drop misleading "video may be processing" caption from snapshot fallback

The caption fired unconditionally on the snapshot-fallback branch,
but the code has no actual signal distinguishing "clip is being
processed" from "no clip exists for this event". For an event that
will never have a clip (operator opted out, integration didn't
capture one), the caption was misinformation. The surrounding
heading + timestamp already communicate what the image is.

* Camera modal: render MOVEMENT state row alongside chip

The camera modal already rendered an OBJECT_PRESENCE state row
below the stream so Frigate users could click through to event
history. ZM-style MOVEMENT was driving the chip but had no row
treatment, leaving the operator with no path to the history /
video-browse view. Render both roles' rows when present.

* Rename event-playback "recording" vocabulary to "clip" for consistency with model fields

The model layer settled on "clip" (Sensor.provides_event_video_clip,
SensorResponse.has_event_video_clip, etc.) but the CSS / JS / template
layer was still named around "recording" from an earlier iteration.
Future readers had to do an unnecessary translation step between the
two halves of the same concept.

View-layer renames:
  CSS  .hi-video-recording             -> .hi-video-clip
       .hi-video-recording-replay      -> .hi-video-clip-replay
       .hi-video-recording--mp4        -> .hi-video-clip--mp4
  HTML data-video-recording (attr)     -> data-video-clip
  Tmpl video_recording_replay_button   -> video_clip_replay_button
  JS   videoRecordingReplayBuster()    -> videoClipReplayBuster()
       img.dataset.videoRecordingSrc   -> img.dataset.videoClipSrc

Plus prose / docstring / aria-label / alt-text updates from "video
recording" to "video clip" in the same view-layer files.

Unchanged (different meanings of "recording"):
  HassApi.CAMERA_STATE_RECORDING — Home Assistant wire-value verbatim.
  TestFrigateManagerHealthRecording — recording-of-health concept.
  auto-view.js "interaction recording" — UI tracking concept.
  weather/daily_weather_tracker — weather-domain concept.

* Sensor Response Details modal: add STATUS / HISTORY footer nav

The modal is reached from the status and history modals, so a
back-nav path was missing. Add STATUS / HISTORY / DONE footer
buttons keyed on the underlying entity, mirroring the Entity Edit
modal's footer shape.

* Document correlation_id as sensor-scoped, not globally unique

Make explicit that correlation_id pairs START/END readings within a
single sensor's history but is not guaranteed unique across sensors
or integrations. The integration's upstream event_id namespace is
opaque to us; ZM and Frigate could theoretically produce
overlapping strings. Every lookup MUST be scoped by sensor.

* Integration config: seed and repair attribute order_id from enum definition order

IntegrationAttribute rows had order_id=0 across the board, so the
config page render relied on row-id tiebreak — definition-order
preserved only by coincidence. Frigate's mid-life addition of
ADD_ALARM_EVENTS exposed the brittleness when row creation ordering
diverged from the operator-facing enum order.

LabeledEnum auto-numbers members 1, 2, 3, ... in definition order,
so attribute_type.value is exactly the order_id we want. Set it on
new rows in _create_integration_attribute and self-repair existing
rows in ensure_all_attributes_exist so any installed environment
heals on next startup without a separate migration.

* Frigate: pack event metadata into SensorResponse detail_attrs

Mirror ZoneMinder's pattern of attaching event metadata to the
SensorResponse so HI's event-detail UI surfaces it as label/value
rows. Per FrigateDetailKeys: event id, start time, object class,
score, sub-label, zones; plus duration on END responses (omitted
on START since the event is still open).

The pre-existing SNAPSHOT_URL / CLIP_URL keys in FrigateDetailKeys
were never populated — URLs are generated by the gateway on demand.
Drop them from the constants class so future readers don't expect
them in detail_attrs.

* Frigate: switch detect toggle to PUT /api/config/set (real Frigate's actual endpoint)

The previous implementation called POST /api/<camera>/detect/set,
which does not exist in real Frigate's HTTP API (checked against
v0.17.1 source). Real Frigate exposes detect toggling only through
the admin-only PUT /api/config/set runtime config update; the
HTTP detect-toggle our simulator was honoring was a fabrication.

Wire change:
  Old (fabricated): POST /api/<camera>/detect/set?state=ON
  New (real):       PUT /api/config/set?cameras.<name>.detect.enabled=true

The new endpoint requires admin role on real Frigate — the
operator must supply an admin-scoped Authorization header in the
existing FrigateAttributeType.AUTH_HEADER attribute.

HI vocabulary translation:
  HI 'on'  <-> Frigate 'true'    (was 'ON')
  HI 'off' <-> Frigate 'false'   (was 'OFF')

Simulator endpoint replaced: CameraDetectSetView removed, ConfigSetView
added at PUT /api/config/set. It recognizes the
cameras.<name>.detect.enabled key in the query string and updates the
camera's detect sim-state accordingly. Other config keys are
accepted but no-op (real Frigate would persist them; the simulator
has no config to mutate).

* Frigate sim-state: correct the stale "wire representation" comment

The previous comment claimed the sim-state's "ON"/"OFF" values
WERE Frigate's wire vocabulary. After the detect-toggle switch to
PUT /api/config/set, Frigate's wire vocabulary is "true"/"false".
The sim-state values are now an internal-only representation that
the simulator's ConfigSetView / ConfigView translate to/from
Frigate's wire format. The values stay as-is (no data migration
concern — sim-state is runtime, not persisted).

* Frigate: drop the per-camera Detect on/off control surface from v1

Real Frigate has no transient detect-toggle endpoint reachable over
HTTP — the only mechanism is PUT /api/config/set, which is a
persistent config edit. Mapping a HI control surface onto a config
edit creates surprising operator-facing behavior (the toggle
permanently rewrites Frigate's YAML, with last-writer-wins between
HI and Frigate's own UI). HI controls have generally meant
transient state.

Removing v1's Detect controller end-to-end:
- FrigateClient.set_camera_detect, _put, _post all gone (none used
  elsewhere).
- FrigateConverter.hi_control_to_detect_enabled and
  detect_enabled_to_hi_value mappings removed.
- FrigateManager.set_camera_detect and DETECT_CONTROLLER_PREFIX
  removed.
- FrigateController is back to a stub returning "no control
  mapping" for any incoming key.
- frigate_sync no longer creates the on/off controller per camera.
- FrigateMonitor no longer emits the detect SensorResponse on each
  poll cycle.
- Simulator's FrigateCameraDetectState and ConfigSetView removed;
  /api/config no longer emits detect.enabled.

Future revision can expose the transient detect toggle through
Frigate's MQTT frigate/<cam>/detect/set topic, which is what
Frigate's own UI button uses for the live update path.

* Frigate: replace cursor-hold polling with per-id open-event tracking

Frigate's /api/events?after=T filters strictly on start_time > T, so
a cursor-hold approach (cursor pinned at an open event's start_time
to keep it visible across polls) excludes that event from subsequent
scans even after it closes — the END transition was never observed,
the SensorHistory correlation pair was never completed, and the
operator saw a bare OBJECT_NONE heartbeat instead.

The replacement keeps the cursor strictly monotonic over each event's
start_time, tracks open events by id in a dedicated set, and refreshes
each via GET /api/events/<id> until closed (or force-closed on 404 or
the MAX_OPEN_EVENT_AGE_SECS timeout). FrigateClient._get_json now
raises Http404 directly so callers can distinguish a vanished event
from a transport failure. Removes the AggregatedCameraState model and
the per-camera open/closed aggregation that the old pipeline needed.

Simulator's get_events_after switched to strict > to match real Frigate
so behavior validated against the simulator matches production.

Frigate user-facing docs gain CSP / event-clip-codec / authentication
caveat sections; dev docs document the new polling model and Frigate's
strict-> filter semantics.

* Frigate monitor: post-review cleanups and rename for clarity

Code-review follow-ups on the polling pipeline rewrite. Mostly
refactor and naming polish; no behavior change.

Refactor:
- Phases return (response_map, touched_cameras) tuples instead of
  threading a mutable cameras_touched set as an out-parameter.
- Extract _force_close() helper consolidating the three identical
  emit-response / mark-camera-touched / drop-from-tracking call
  sites in the refresh phase.
- Drop trailing dead `continue` at end-of-loop bodies.
- Cursor-regression guard kept (and its comment rewritten) as
  defensive protection against upstream contract violations.

Rename for clarity:
- OpenFrigateEvent → TrackedFrigateEvent (the wrapper holds HI
  tracking metadata, not just "openness").
- _open_events → _tracked_events; _refresh_open_events_phase →
  _refresh_tracked_events_phase; matching test class/method names.
- Local variable `tracker` → `tracked_event` (stale leftover from
  the original OpenEventTracker dataclass name).
- In the refresh phase, local `event` → `frigate_event` to avoid
  shadowing the wrapper's `.event` field at the assignment site.

Tests:
- _PipelineTestBase switched from TestCase to AsyncTaskFastTestCase
  so per-method asyncio.run() / `import asyncio` are replaced with
  self.run_async() — picks up the project's event-loop tracking.
- Add coverage for cursor no-regression invariant, duplicate-id
  defensive guard, malformed phase-2 refresh payload, and
  camera-list fetch failure in the heartbeat phase.

Dev doc updated for the renamed symbols.
2026-05-21 09:11:20 -05:00
..
2026-04-20 10:05:04 -05:00
2025-09-25 18:31:38 -05:00

Home Information Logo

Developer Documentation

See the markdown files in this directory for various developer-related documentation.

The code is the best documentation for lower-level details, but there are some higher-level concepts that are useful to help orient developers. This is the place for that high-level, developer-specific documentation.