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,
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=(

View File

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

View File

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

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,
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():