Simplify config updates

This commit is contained in:
Nicolas Mowen 2025-06-11 06:25:14 -06:00
parent 7e45371eed
commit 785e655ff5
5 changed files with 63 additions and 112 deletions

View File

@ -32,6 +32,7 @@ from frigate.config.camera.updater import (
CameraConfigUpdateEnum, CameraConfigUpdateEnum,
CameraConfigUpdateTopic, CameraConfigUpdateTopic,
) )
from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdatePublisher
from frigate.models import Event, Timeline from frigate.models import Event, Timeline
from frigate.stats.prometheus import get_metrics, update_metrics from frigate.stats.prometheus import get_metrics, update_metrics
from frigate.util.builtin import ( 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: if body.requires_restart == 0 or body.update_topic:
old_config = request.app.frigate_config
request.app.frigate_config = config request.app.frigate_config = config
if body.update_topic: if body.update_topic and body.update_topic.startswith("config/cameras/"):
if body.update_topic.startswith("config/cameras/"): _, _, camera, field = body.update_topic.split("/")
_, _, 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) settings = config.get_nested_object(body.update_topic)
request.app.config_publisher.publish_update(
CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera), request.app.config_publisher.publish_update(
settings, CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera),
) settings,
)
return JSONResponse( return JSONResponse(
content=( content=(

View File

@ -11,7 +11,10 @@ from multiprocessing.synchronize import Event as MpEvent
from frigate.camera import CameraMetrics, PTZMetrics from frigate.camera import CameraMetrics, PTZMetrics
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.config.camera import CameraConfig 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.const import SHM_FRAMES_VAR
from frigate.models import Regions from frigate.models import Regions
from frigate.util import Process as FrigateProcess from frigate.util import Process as FrigateProcess
@ -44,11 +47,10 @@ class CameraMaintainer(threading.Thread):
self.ptz_metrics = ptz_metrics self.ptz_metrics = ptz_metrics
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 = GlobalConfigUpdateSubscriber( self.update_subscriber = CameraConfigUpdateSubscriber(
[ [
GlobalConfigUpdateEnum.add_camera, CameraConfigUpdateEnum.add,
GlobalConfigUpdateEnum.debug_camera, CameraConfigUpdateEnum.remove,
GlobalConfigUpdateEnum.remove_camera,
] ]
) )
self.shm_count = self.__calculate_shm_frame_count() self.shm_count = self.__calculate_shm_frame_count()
@ -203,20 +205,20 @@ class CameraMaintainer(threading.Thread):
while not self.stop_event.wait(1): while not self.stop_event.wait(1):
updates = self.update_subscriber.check_for_updates() updates = self.update_subscriber.check_for_updates()
for update_type, update_config in updates: for update_type, updated_cameras in updates:
if update_type == GlobalConfigUpdateEnum.add_camera: if update_type == CameraConfigUpdateEnum.add.name:
self.__start_camera_processor( for camera in updated_cameras:
update_config.name, update_config, runtime=True self.__start_camera_processor(
) camera,
self.__start_camera_capture(update_config.name, update_config) self.update_subscriber.camera_configs[camera],
elif update_type == GlobalConfigUpdateEnum.debug_camera: runtime=True,
pass )
elif update_type == GlobalConfigUpdateEnum.remove_camera: self.__start_camera_capture(
camera = update_config.name camera, self.update_subscriber.camera_configs[camera]
)
if camera: elif update_type == CameraConfigUpdateEnum.remove.name:
self.__stop_camera_capture_process(camera) self.__stop_camera_capture_process(camera)
self.__stop_camera_process(camera) self.__stop_camera_process(camera)
# ensure the capture processes are done # ensure the capture processes are done
for camera in self.camera_metrics.keys(): for camera in self.camera_metrics.keys():

View File

@ -11,6 +11,7 @@ from frigate.config import CameraConfig
class CameraConfigUpdateEnum(str, Enum): class CameraConfigUpdateEnum(str, Enum):
"""Supported camera config update types.""" """Supported camera config update types."""
add = "add" # for adding a camera
audio = "audio" audio = "audio"
audio_transcription = "audio_transcription" audio_transcription = "audio_transcription"
birdseye = "birdseye" birdseye = "birdseye"
@ -20,6 +21,7 @@ class CameraConfigUpdateEnum(str, Enum):
notifications = "notifications" notifications = "notifications"
objects = "objects" objects = "objects"
record = "record" record = "record"
remove = "remove" # for removing a camera
review = "review" review = "review"
snapshots = "snapshots" snapshots = "snapshots"
zones = "zones" zones = "zones"
@ -68,14 +70,21 @@ class CameraConfigUpdateSubscriber:
def __update_config( def __update_config(
self, camera: str, update_type: CameraConfigUpdateEnum, updated_config: Any self, camera: str, update_type: CameraConfigUpdateEnum, updated_config: Any
) -> None: ) -> 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: if not config:
return return
if update_type == CameraConfigUpdateEnum.audio: if update_type == CameraConfigUpdateEnum.audio:
config.audio = updated_config config.audio = updated_config
if update_type == CameraConfigUpdateEnum.audio_transcription: elif update_type == CameraConfigUpdateEnum.audio_transcription:
config.audio_transcription = updated_config config.audio_transcription = updated_config
elif update_type == CameraConfigUpdateEnum.birdseye: elif update_type == CameraConfigUpdateEnum.birdseye:
config.birdseye = updated_config config.birdseye = updated_config

View File

@ -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()

View File

@ -32,7 +32,6 @@ from frigate.config.camera.updater import (
CameraConfigUpdateEnum, CameraConfigUpdateEnum,
CameraConfigUpdateSubscriber, CameraConfigUpdateSubscriber,
) )
from frigate.config.updater import GlobalConfigUpdateEnum, GlobalConfigUpdateSubscriber
from frigate.const import FAST_QUEUE_TIMEOUT, UPDATE_CAMERA_ACTIVITY from frigate.const import FAST_QUEUE_TIMEOUT, UPDATE_CAMERA_ACTIVITY
from frigate.events.types import EventStateEnum, EventTypeEnum from frigate.events.types import EventStateEnum, EventTypeEnum
from frigate.models import Event, Timeline from frigate.models import Event, Timeline
@ -67,12 +66,14 @@ class TrackedObjectProcessor(threading.Thread):
self.last_motion_detected: dict[str, float] = {} self.last_motion_detected: dict[str, float] = {}
self.ptz_autotracker_thread = ptz_autotracker_thread self.ptz_autotracker_thread = ptz_autotracker_thread
self.global_config_subscriber = GlobalConfigUpdateSubscriber(
[GlobalConfigUpdateEnum.add_camera, GlobalConfigUpdateEnum.remove_camera]
)
self.camera_config_subscriber = CameraConfigUpdateSubscriber( self.camera_config_subscriber = CameraConfigUpdateSubscriber(
self.config.cameras, self.config.cameras,
[CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.zones], [
CameraConfigUpdateEnum.add,
CameraConfigUpdateEnum.enabled,
CameraConfigUpdateEnum.remove,
CameraConfigUpdateEnum.zones,
],
) )
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
@ -590,16 +591,6 @@ class TrackedObjectProcessor(threading.Thread):
def run(self): def run(self):
while not self.stop_event.is_set(): 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 # check for config updates
updated_topics = self.camera_config_subscriber.check_for_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[ self.camera_states[camera].prev_enabled = self.config.cameras[
camera camera
].enabled ].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 # manage camera disabled state
for camera, config in self.config.cameras.items(): for camera, config in self.config.cameras.items():