Coverage for custom_components/camera_autonotify/frigate.py: 100%
40 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:32 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:32 +0000
1"""What's known of a Frigate server, read from the Frigate integration, and the links its notification proxy serves"""
3from __future__ import annotations
5from typing import Any
7from homeassistant.core import HomeAssistant, callback
8from homeassistant.helpers import entity_registry as er
9from homeassistant.helpers.network import NoURLAvailableError, get_url
10from homeassistant.util import slugify
12from .const import FRIGATE_DOMAIN
14DEFAULT_TOPIC_PREFIX = "frigate"
15# the Frigate integration keeps each server's config, as Frigate itself reports it, under its entry id
16FRIGATE_CONFIG = "config"
17# served without a login, so a phone can fetch it, for as long as the Frigate entry allows
18NOTIFICATIONS_PATH = "/api/frigate/notifications"
21@callback
22def frigate_config(hass: HomeAssistant, source_entry_id: str) -> dict[str, Any] | None:
23 """The config of a Frigate server, once its entry is set up"""
24 data = hass.data.get(FRIGATE_DOMAIN, {}).get(source_entry_id)
25 return data.get(FRIGATE_CONFIG) if isinstance(data, dict) else None
28def topic_prefix(config: dict[str, Any] | None) -> str:
29 return ((config or {}).get("mqtt") or {}).get("topic_prefix") or DEFAULT_TOPIC_PREFIX
32@callback
33def base_url(hass: HomeAssistant) -> str | None:
34 """The address a phone can reach Home Assistant at, away from home if there's one for that"""
35 try:
36 return get_url(hass, prefer_external=True)
37 except NoURLAvailableError:
38 return None
41def snapshot_url(base: str, detection_id: str) -> str:
42 return f"{base}{NOTIFICATIONS_PATH}/{detection_id}/snapshot.jpg"
45def hls_url(base: str, detection_id: str, camera: str) -> str:
46 """The clip as HLS, which iOS plays when tapped, where an attached `clip.mp4` fails to show at all"""
47 return f"{base}{NOTIFICATIONS_PATH}/{detection_id}/{camera}/master.m3u8"
50def review_thumbnail_url(base: str, review_id: str, camera: str) -> str:
51 return f"{base}{NOTIFICATIONS_PATH}/{review_id}/{camera}/review_thumbnail.webp"
54@callback
55def camera_entity_id(hass: HomeAssistant, source_entry_id: str, camera: str) -> str | None:
56 """The camera entity the Frigate integration has for a camera, by the unique id it gives it"""
57 return er.async_get(hass).async_get_entity_id("camera", FRIGATE_DOMAIN, f"{source_entry_id}:camera:{camera}")
60def _labels(camera: dict[str, Any]) -> set[str]:
61 """What a camera can see or hear, and what its reviews are made of"""
62 review = camera.get("review") or {}
63 return {
64 *((camera.get("objects") or {}).get("track") or []),
65 *((camera.get("audio") or {}).get("listen") or []),
66 *((review.get("alerts") or {}).get("labels") or []),
67 *((review.get("detections") or {}).get("labels") or []),
68 }
71def camera_choices(config: dict[str, Any] | None, camera: str) -> tuple[list[str], list[str]]:
72 """The labels and zones of a camera, as its config has them, to choose from"""
73 settings = ((config or {}).get("cameras") or {}).get(camera) or {}
74 return sorted(_labels(settings)), list(settings.get("zones") or {})
77def zone_labels(config: dict[str, Any] | None, zone: str) -> list[str]:
78 """The labels of every camera a zone is on, the zone as slugified for its device"""
79 cameras = ((config or {}).get("cameras") or {}).values()
80 return sorted({
81 label for camera in cameras if zone in {slugify(z) for z in camera.get("zones") or {}} for label in _labels(camera)
82 })