Cleanup for updating the cameras config

This commit is contained in:
Nicolas Mowen 2025-06-11 09:06:42 -06:00
parent 1d0e9829f6
commit 86b5e0f9ae
11 changed files with 72 additions and 24 deletions

View File

@ -3,7 +3,7 @@
from collections import Counter from collections import Counter
from typing import Any, Callable from typing import Any, Callable
from frigate.config.config import FrigateConfig from frigate.config import CameraConfig, FrigateConfig
class CameraActivityManager: class CameraActivityManager:
@ -23,6 +23,9 @@ class CameraActivityManager:
if not camera_config.enabled_in_config: if not camera_config.enabled_in_config:
continue continue
self.__init_camera(camera_config)
def __init_camera(self, camera_config: CameraConfig) -> None:
self.last_camera_activity[camera_config.name] = {} self.last_camera_activity[camera_config.name] = {}
self.camera_all_object_counts[camera_config.name] = Counter() self.camera_all_object_counts[camera_config.name] = Counter()
self.camera_active_object_counts[camera_config.name] = Counter() self.camera_active_object_counts[camera_config.name] = Counter()
@ -43,6 +46,10 @@ class CameraActivityManager:
all_objects: list[dict[str, Any]] = [] all_objects: list[dict[str, Any]] = []
for camera in new_activity.keys(): for camera in new_activity.keys():
# handle cameras that were added dynamically
if camera not in self.camera_all_object_counts:
self.__init_camera(self.config.cameras[camera])
new_objects = new_activity[camera].get("objects", []) new_objects = new_activity[camera].get("objects", [])
all_objects.extend(new_objects) all_objects.extend(new_objects)

View File

@ -45,6 +45,7 @@ class CameraMaintainer(threading.Thread):
self.frame_manager = SharedMemoryFrameManager() self.frame_manager = SharedMemoryFrameManager()
self.region_grids: dict[str, list[list[dict[str, int]]]] = {} self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
self.update_subscriber = CameraConfigUpdateSubscriber( self.update_subscriber = CameraConfigUpdateSubscriber(
self.config,
{}, {},
[ [
CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.add,
@ -170,13 +171,15 @@ class CameraMaintainer(threading.Thread):
camera_process.start() camera_process.start()
logger.info(f"Camera processor started for {config.name}: {camera_process.pid}") logger.info(f"Camera processor started for {config.name}: {camera_process.pid}")
def __start_camera_capture(self, name: str, config: CameraConfig) -> None: def __start_camera_capture(
if not self.config.cameras[name].enabled_in_config: self, name: str, config: CameraConfig, runtime: bool = False
) -> None:
if not config.enabled_in_config:
logger.info(f"Capture process not started for disabled camera {name}") logger.info(f"Capture process not started for disabled camera {name}")
return return
# pre-create shms # pre-create shms
for i in range(self.shm_count): for i in range(10 if runtime else self.shm_count):
frame_size = config.frame_shape_yuv[0] * config.frame_shape_yuv[1] frame_size = config.frame_shape_yuv[0] * config.frame_shape_yuv[1]
self.frame_manager.create(f"{config.name}_frame{i}", frame_size) self.frame_manager.create(f"{config.name}_frame{i}", frame_size)

View File

@ -81,7 +81,7 @@ class WebPushClient(Communicator): # type: ignore[misc]
"config/notifications", exact=True "config/notifications", exact=True
) )
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
self.config.cameras, [CameraConfigUpdateEnum.notifications] self.config, self.config.cameras, [CameraConfigUpdateEnum.notifications]
) )
def subscribe(self, receiver: Callable) -> None: def subscribe(self, receiver: Callable) -> None:
@ -170,7 +170,12 @@ class WebPushClient(Communicator): # type: ignore[misc]
if updated_notification_config: if updated_notification_config:
self.config.notifications = updated_notification_config self.config.notifications = updated_notification_config
self.config_subscriber.check_for_updates() updates = self.config_subscriber.check_for_updates()
if "add" in updates:
for camera in updates["add"]:
self.suspended_cameras[camera] = 0
self.last_camera_notification_time[camera] = 0
if topic == "reviews": if topic == "reviews":
decoded = json.loads(payload) decoded = json.loads(payload)

View File

@ -5,7 +5,7 @@ from enum import Enum
from typing import Any from typing import Any
from frigate.comms.config_updater import ConfigPublisher, ConfigSubscriber from frigate.comms.config_updater import ConfigPublisher, ConfigSubscriber
from frigate.config import CameraConfig from frigate.config import CameraConfig, FrigateConfig
class CameraConfigUpdateEnum(str, Enum): class CameraConfigUpdateEnum(str, Enum):
@ -51,9 +51,11 @@ class CameraConfigUpdatePublisher:
class CameraConfigUpdateSubscriber: class CameraConfigUpdateSubscriber:
def __init__( def __init__(
self, self,
config: FrigateConfig | None,
camera_configs: dict[str, CameraConfig], camera_configs: dict[str, CameraConfig],
topics: list[CameraConfigUpdateEnum], topics: list[CameraConfigUpdateEnum],
): ):
self.config = config
self.camera_configs = camera_configs self.camera_configs = camera_configs
self.topics = topics self.topics = topics
@ -71,9 +73,11 @@ class CameraConfigUpdateSubscriber:
self, camera: str, update_type: CameraConfigUpdateEnum, updated_config: Any self, camera: str, update_type: CameraConfigUpdateEnum, updated_config: Any
) -> None: ) -> None:
if update_type == CameraConfigUpdateEnum.add: if update_type == CameraConfigUpdateEnum.add:
self.config.cameras[camera] = updated_config
self.camera_configs[camera] = updated_config self.camera_configs[camera] = updated_config
return return
elif update_type == CameraConfigUpdateEnum.remove: elif update_type == CameraConfigUpdateEnum.remove:
self.config.cameras.pop(camera)
self.camera_configs.pop(camera) self.camera_configs.pop(camera)
return return

View File

@ -33,6 +33,10 @@ from frigate.const import (
CLIPS_DIR, CLIPS_DIR,
UPDATE_EVENT_DESCRIPTION, UPDATE_EVENT_DESCRIPTION,
) )
from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
CameraConfigUpdateSubscriber,
)
from frigate.data_processing.common.license_plate.model import ( from frigate.data_processing.common.license_plate.model import (
LicensePlateModelRunner, LicensePlateModelRunner,
) )
@ -87,6 +91,11 @@ class EmbeddingMaintainer(threading.Thread):
self.config = config self.config = config
self.metrics = metrics self.metrics = metrics
self.embeddings = None self.embeddings = None
self.config_updater = CameraConfigUpdateSubscriber(
self.config,
self.config.cameras,
[CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.remove],
)
if config.semantic_search.enabled: if config.semantic_search.enabled:
self.embeddings = Embeddings(config, db, metrics) self.embeddings = Embeddings(config, db, metrics)
@ -198,6 +207,7 @@ class EmbeddingMaintainer(threading.Thread):
def run(self) -> None: def run(self) -> None:
"""Maintain a SQLite-vec database for semantic search.""" """Maintain a SQLite-vec database for semantic search."""
while not self.stop_event.is_set(): while not self.stop_event.is_set():
self.config_updater.check_for_updates()
self._process_requests() self._process_requests()
self._process_updates() self._process_updates()
self._process_recordings_updates() self._process_recordings_updates()
@ -206,6 +216,7 @@ class EmbeddingMaintainer(threading.Thread):
self._process_finalized() self._process_finalized()
self._process_event_metadata() self._process_event_metadata()
self.config_updater.stop()
self.event_subscriber.stop() self.event_subscriber.stop()
self.event_end_subscriber.stop() self.event_end_subscriber.stop()
self.recordings_subscriber.stop() self.recordings_subscriber.stop()

View File

@ -162,6 +162,7 @@ class AudioEventMaintainer(threading.Thread):
# create communication for audio detections # create communication for audio detections
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
None,
{self.camera_config.name: self.camera_config}, {self.camera_config.name: self.camera_config},
[ [
CameraConfigUpdateEnum.audio, CameraConfigUpdateEnum.audio,

View File

@ -103,8 +103,10 @@ def output_frames(
detection_subscriber = DetectionSubscriber(DetectionTypeEnum.video) detection_subscriber = DetectionSubscriber(DetectionTypeEnum.video)
config_subscriber = CameraConfigUpdateSubscriber( config_subscriber = CameraConfigUpdateSubscriber(
config,
config.cameras, config.cameras,
[ [
CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.birdseye, CameraConfigUpdateEnum.birdseye,
CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.enabled,
CameraConfigUpdateEnum.record, CameraConfigUpdateEnum.record,
@ -135,7 +137,15 @@ def output_frames(
while not stop_event.is_set(): while not stop_event.is_set():
# check if there is an updated config # check if there is an updated config
config_subscriber.check_for_updates() updates = config_subscriber.check_for_updates()
if "add" in updates:
for camera in updates["add"]:
jsmpeg_cameras[camera] = JsmpegCamera(
cam_config, stop_event, websocket_server
)
preview_recorders[camera] = PreviewRecorder(cam_config)
preview_write_times[camera] = 0
(topic, data) = detection_subscriber.check_for_update(timeout=1) (topic, data) = detection_subscriber.check_for_update(timeout=1)
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()

View File

@ -75,7 +75,9 @@ class RecordingMaintainer(threading.Thread):
# create communication for retained recordings # create communication for retained recordings
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
self.config.cameras, [CameraConfigUpdateEnum.record] self.config,
self.config.cameras,
[CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.record],
) )
self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all) self.detection_subscriber = DetectionSubscriber(DetectionTypeEnum.all)
self.recordings_publisher = RecordingsDataPublisher( self.recordings_publisher = RecordingsDataPublisher(

View File

@ -154,10 +154,13 @@ class ReviewSegmentMaintainer(threading.Thread):
# create communication for review segments # create communication for review segments
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
config,
config.cameras, config.cameras,
[ [
CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.enabled,
CameraConfigUpdateEnum.record, CameraConfigUpdateEnum.record,
CameraConfigUpdateEnum.remove,
CameraConfigUpdateEnum.review, CameraConfigUpdateEnum.review,
], ],
) )

View File

@ -67,6 +67,7 @@ class TrackedObjectProcessor(threading.Thread):
self.ptz_autotracker_thread = ptz_autotracker_thread self.ptz_autotracker_thread = ptz_autotracker_thread
self.camera_config_subscriber = CameraConfigUpdateSubscriber( self.camera_config_subscriber = CameraConfigUpdateSubscriber(
self.config,
self.config.cameras, self.config.cameras,
[ [
CameraConfigUpdateEnum.add, CameraConfigUpdateEnum.add,

View File

@ -116,7 +116,7 @@ def capture_frames(
skipped_eps = EventsPerSecond() skipped_eps = EventsPerSecond()
skipped_eps.start() skipped_eps.start()
config_subscriber = CameraConfigUpdateSubscriber( config_subscriber = CameraConfigUpdateSubscriber(
{config.name: config}, [CameraConfigUpdateEnum.enabled] None, {config.name: config}, [CameraConfigUpdateEnum.enabled]
) )
def get_enabled_state(): def get_enabled_state():
@ -196,7 +196,7 @@ class CameraWatchdog(threading.Thread):
self.sleeptime = self.config.ffmpeg.retry_interval self.sleeptime = self.config.ffmpeg.retry_interval
self.config_subscriber = CameraConfigUpdateSubscriber( self.config_subscriber = CameraConfigUpdateSubscriber(
{config.name: config}, [CameraConfigUpdateEnum.enabled] None, {config.name: config}, [CameraConfigUpdateEnum.enabled]
) )
self.was_enabled = self.config.enabled self.was_enabled = self.config.enabled
@ -596,6 +596,7 @@ def process_frames(
): ):
next_region_update = get_tomorrow_at_time(2) next_region_update = get_tomorrow_at_time(2)
config_subscriber = CameraConfigUpdateSubscriber( config_subscriber = CameraConfigUpdateSubscriber(
None,
{camera_name: camera_config}, {camera_name: camera_config},
[ [
CameraConfigUpdateEnum.detect, CameraConfigUpdateEnum.detect,