diff --git a/frigate/api/app.py b/frigate/api/app.py index 17a04f770..de2f87a68 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -32,6 +32,7 @@ from frigate.config.camera.updater import ( CameraConfigUpdateEnum, CameraConfigUpdateTopic, ) +from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdatePublisher from frigate.models import Event, Timeline from frigate.stats.prometheus import get_metrics, update_metrics from frigate.util.builtin import ( @@ -390,17 +391,23 @@ def config_set(request: Request, body: AppConfigSetBody): ) if body.requires_restart == 0 or body.update_topic: + old_config = request.app.frigate_config request.app.frigate_config = config - if body.update_topic: - if body.update_topic.startswith("config/cameras/"): - _, _, camera, field = body.update_topic.split("/") + if body.update_topic and body.update_topic.startswith("config/cameras/"): + _, _, camera, field = body.update_topic.split("/") + if field == "add": + settings = config.cameras[camera] + elif field == "remove": + + else: settings = config.get_nested_object(body.update_topic) - request.app.config_publisher.publish_update( - CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), - settings, - ) + + request.app.config_publisher.publish_update( + CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), + settings, + ) return JSONResponse( content=( diff --git a/frigate/camera/maintainer.py b/frigate/camera/maintainer.py index 1bf92a6ca..cc5156c45 100644 --- a/frigate/camera/maintainer.py +++ b/frigate/camera/maintainer.py @@ -11,7 +11,10 @@ from multiprocessing.synchronize import Event as MpEvent from frigate.camera import CameraMetrics, PTZMetrics from frigate.config import FrigateConfig from frigate.config.camera import CameraConfig -from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdateSubscriber +from frigate.config.camera.updater import ( + CameraConfigUpdateEnum, + CameraConfigUpdateSubscriber, +) from frigate.const import SHM_FRAMES_VAR from frigate.models import Regions from frigate.util import Process as FrigateProcess @@ -44,11 +47,10 @@ class CameraMaintainer(threading.Thread): self.ptz_metrics = ptz_metrics self.frame_manager = SharedMemoryFrameManager() self.region_grids: dict[str, list[list[dict[str, int]]]] = {} - self.update_subscriber = GlobalConfigUpdateSubscriber( + self.update_subscriber = CameraConfigUpdateSubscriber( [ - GlobalConfigUpdateEnum.add_camera, - GlobalConfigUpdateEnum.debug_camera, - GlobalConfigUpdateEnum.remove_camera, + CameraConfigUpdateEnum.add, + CameraConfigUpdateEnum.remove, ] ) self.shm_count = self.__calculate_shm_frame_count() @@ -203,20 +205,20 @@ class CameraMaintainer(threading.Thread): while not self.stop_event.wait(1): updates = self.update_subscriber.check_for_updates() - for update_type, update_config in updates: - if update_type == GlobalConfigUpdateEnum.add_camera: - self.__start_camera_processor( - update_config.name, update_config, runtime=True - ) - self.__start_camera_capture(update_config.name, update_config) - elif update_type == GlobalConfigUpdateEnum.debug_camera: - pass - elif update_type == GlobalConfigUpdateEnum.remove_camera: - camera = update_config.name - - if camera: - self.__stop_camera_capture_process(camera) - self.__stop_camera_process(camera) + for update_type, updated_cameras in updates: + if update_type == CameraConfigUpdateEnum.add.name: + for camera in updated_cameras: + self.__start_camera_processor( + camera, + self.update_subscriber.camera_configs[camera], + runtime=True, + ) + self.__start_camera_capture( + camera, self.update_subscriber.camera_configs[camera] + ) + elif update_type == CameraConfigUpdateEnum.remove.name: + self.__stop_camera_capture_process(camera) + self.__stop_camera_process(camera) # ensure the capture processes are done for camera in self.camera_metrics.keys(): diff --git a/frigate/config/camera/updater.py b/frigate/config/camera/updater.py index 5ddc26d44..96ae3a3f0 100644 --- a/frigate/config/camera/updater.py +++ b/frigate/config/camera/updater.py @@ -11,6 +11,7 @@ from frigate.config import CameraConfig class CameraConfigUpdateEnum(str, Enum): """Supported camera config update types.""" + add = "add" # for adding a camera audio = "audio" audio_transcription = "audio_transcription" birdseye = "birdseye" @@ -20,6 +21,7 @@ class CameraConfigUpdateEnum(str, Enum): notifications = "notifications" objects = "objects" record = "record" + remove = "remove" # for removing a camera review = "review" snapshots = "snapshots" zones = "zones" @@ -68,14 +70,21 @@ class CameraConfigUpdateSubscriber: def __update_config( self, camera: str, update_type: CameraConfigUpdateEnum, updated_config: Any ) -> None: - config = self.camera_configs[camera] + if update_type == CameraConfigUpdateEnum.add: + self.camera_configs[camera] = updated_config + return + elif update_type == CameraConfigUpdateEnum.remove: + self.camera_configs.pop(camera) + return + + config = self.camera_configs.get(camera) if not config: return if update_type == CameraConfigUpdateEnum.audio: config.audio = updated_config - if update_type == CameraConfigUpdateEnum.audio_transcription: + elif update_type == CameraConfigUpdateEnum.audio_transcription: config.audio_transcription = updated_config elif update_type == CameraConfigUpdateEnum.birdseye: config.birdseye = updated_config diff --git a/frigate/config/updater.py b/frigate/config/updater.py deleted file mode 100644 index c2944d062..000000000 --- a/frigate/config/updater.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Convenience classes for updating global configurations dynamically.""" - -from dataclasses import dataclass -from enum import Enum -from typing import Any - -from frigate.comms.config_updater import ConfigPublisher, ConfigSubscriber -from frigate.config.camera import CameraConfig - - -class GlobalConfigUpdateEnum(str, Enum): - """Supported global config update types.""" - - add_camera = "add_camera" - debug_camera = "debug_camera" - remove_camera = "remove_camera" - - -@dataclass -class GlobalConfigUpdateTopic: - update_type: GlobalConfigUpdateEnum - - @property - def topic(self) -> str: - return f"config/{self.update_type.name}" - - -class GlobalConfigUpdatePublisher: - def __init__(self): - self.publisher = ConfigPublisher() - - def publish_update(self, topic: GlobalConfigUpdateTopic, config: Any) -> None: - self.publisher.publish(topic.topic, config) - - def stop(self) -> None: - self.publisher.stop() - - -class GlobalConfigUpdateSubscriber: - def __init__( - self, - topics: list[GlobalConfigUpdateEnum], - ): - self.topics = topics - self.subscriber = ConfigSubscriber( - "config/", - exact=False, - ) - - def check_for_updates(self) -> list[tuple[GlobalConfigUpdateEnum, CameraConfig]]: - updated_topics: list[tuple[GlobalConfigUpdateEnum, CameraConfig]] = [] - - # get all updates available - while True: - update_topic, update_config = self.subscriber.check_for_update() - - if update_topic is None or update_config is None: - break - - _, raw_type = update_topic.split("/") - update_type = GlobalConfigUpdateEnum[raw_type] - - if update_type in self.topics: - updated_topics.append((update_type, update_config)) - - return updated_topics - - def stop(self) -> None: - self.subscriber.stop() diff --git a/frigate/track/object_processing.py b/frigate/track/object_processing.py index 937086117..98791eed8 100644 --- a/frigate/track/object_processing.py +++ b/frigate/track/object_processing.py @@ -32,7 +32,6 @@ from frigate.config.camera.updater import ( CameraConfigUpdateEnum, CameraConfigUpdateSubscriber, ) -from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdateSubscriber from frigate.const import FAST_QUEUE_TIMEOUT, UPDATE_CAMERA_ACTIVITY from frigate.events.types import EventStateEnum, EventTypeEnum from frigate.models import Event, Timeline @@ -67,12 +66,14 @@ class TrackedObjectProcessor(threading.Thread): self.last_motion_detected: dict[str, float] = {} self.ptz_autotracker_thread = ptz_autotracker_thread - self.global_config_subscriber = GlobalConfigUpdateSubscriber( - [GlobalConfigUpdateEnum.add_camera, GlobalConfigUpdateEnum.remove_camera] - ) self.camera_config_subscriber = CameraConfigUpdateSubscriber( self.config.cameras, - [CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.zones], + [ + CameraConfigUpdateEnum.add, + CameraConfigUpdateEnum.enabled, + CameraConfigUpdateEnum.remove, + CameraConfigUpdateEnum.zones, + ], ) self.requestor = InterProcessRequestor() @@ -590,16 +591,6 @@ class TrackedObjectProcessor(threading.Thread): def run(self): while not self.stop_event.is_set(): - # check for global config updates - for topic, payload in self.global_config_subscriber.check_for_updates(): - if topic == GlobalConfigUpdateEnum.add_camera: - self.create_camera_state(payload["camera"]) - elif topic == GlobalConfigUpdateEnum.remove_camera: - camera = payload["camera"] - camera_state = self.camera_states[camera] - camera_state.shutdown() - del self.camera_states[camera] - # check for config updates updated_topics = self.camera_config_subscriber.check_for_updates() @@ -609,6 +600,17 @@ class TrackedObjectProcessor(threading.Thread): self.camera_states[camera].prev_enabled = self.config.cameras[ camera ].enabled + elif "add" in updated_topics: + for camera in updated_topics["add"]: + self.config.cameras[camera] = ( + self.camera_config_subscriber.camera_configs[camera] + ) + self.create_camera_state(camera) + elif "remove" in updated_topics: + for camera in updated_topics["remove"]: + camera_state = self.camera_states[camera] + camera_state.shutdown() + self.camera_states.pop(camera) # manage camera disabled state for camera, config in self.config.cameras.items():