mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-20 13:54:36 +01:00
Dynamically enable/disable cameras (#16894)
* config options * metrics * stop and restart ffmpeg processes * dispatcher * frontend websocket * buttons for testing * don't recreate log pipe * add/remove cam from birdseye when enabling/disabling * end all objects and send empty camera activity * enable/disable switch in ui * disable buttons when camera is disabled * use enabled_in_config for some frontend checks * tweaks * handle settings pane with disabled cameras * frontend tweaks * change to debug log * mqtt docs * tweak * ensure all ffmpeg processes are initially started * clean up * use zmq * remove camera metrics * remove camera metrics * tweaks * frontend tweaks
This commit is contained in:
@@ -10,6 +10,7 @@ from typing import Callable, Optional
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from frigate.comms.config_updater import ConfigSubscriber
|
||||
from frigate.comms.detections_updater import DetectionPublisher, DetectionTypeEnum
|
||||
from frigate.comms.dispatcher import Dispatcher
|
||||
from frigate.comms.events_updater import EventEndSubscriber, EventUpdatePublisher
|
||||
@@ -61,6 +62,7 @@ class CameraState:
|
||||
self.previous_frame_id = None
|
||||
self.callbacks = defaultdict(list)
|
||||
self.ptz_autotracker_thread = ptz_autotracker_thread
|
||||
self.prev_enabled = self.camera_config.enabled
|
||||
|
||||
def get_current_frame(self, draw_options={}):
|
||||
with self.current_frame_lock:
|
||||
@@ -310,6 +312,7 @@ class CameraState:
|
||||
# TODO: can i switch to looking this up and only changing when an event ends?
|
||||
# maintain best objects
|
||||
camera_activity: dict[str, list[any]] = {
|
||||
"enabled": True,
|
||||
"motion": len(motion_boxes) > 0,
|
||||
"objects": [],
|
||||
}
|
||||
@@ -437,6 +440,11 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
self.last_motion_detected: dict[str, float] = {}
|
||||
self.ptz_autotracker_thread = ptz_autotracker_thread
|
||||
|
||||
self.enabled_subscribers = {
|
||||
camera: ConfigSubscriber(f"config/enabled/{camera}", True)
|
||||
for camera in config.cameras.keys()
|
||||
}
|
||||
|
||||
self.requestor = InterProcessRequestor()
|
||||
self.detection_publisher = DetectionPublisher(DetectionTypeEnum.video)
|
||||
self.event_sender = EventUpdatePublisher()
|
||||
@@ -679,8 +687,55 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
"""Returns the latest frame time for a given camera."""
|
||||
return self.camera_states[camera].current_frame_time
|
||||
|
||||
def force_end_all_events(self, camera: str, camera_state: CameraState):
|
||||
"""Ends all active events on camera when disabling."""
|
||||
last_frame_name = camera_state.previous_frame_id
|
||||
for obj_id, obj in list(camera_state.tracked_objects.items()):
|
||||
if "end_time" not in obj.obj_data:
|
||||
logger.debug(f"Camera {camera} disabled, ending active event {obj_id}")
|
||||
obj.obj_data["end_time"] = datetime.datetime.now().timestamp()
|
||||
# end callbacks
|
||||
for callback in camera_state.callbacks["end"]:
|
||||
callback(camera, obj, last_frame_name)
|
||||
|
||||
# camera activity callbacks
|
||||
for callback in camera_state.callbacks["camera_activity"]:
|
||||
callback(
|
||||
camera,
|
||||
{"enabled": False, "motion": 0, "objects": []},
|
||||
)
|
||||
|
||||
def _get_enabled_state(self, camera: str) -> bool:
|
||||
_, config_data = self.enabled_subscribers[camera].check_for_update()
|
||||
if config_data:
|
||||
enabled = config_data.enabled
|
||||
if self.camera_states[camera].prev_enabled is None:
|
||||
self.camera_states[camera].prev_enabled = enabled
|
||||
return enabled
|
||||
return (
|
||||
self.camera_states[camera].prev_enabled
|
||||
if self.camera_states[camera].prev_enabled is not None
|
||||
else self.config.cameras[camera].enabled
|
||||
)
|
||||
|
||||
def run(self):
|
||||
while not self.stop_event.is_set():
|
||||
for camera, config in self.config.cameras.items():
|
||||
if not config.enabled_in_config:
|
||||
continue
|
||||
|
||||
current_enabled = self._get_enabled_state(camera)
|
||||
camera_state = self.camera_states[camera]
|
||||
|
||||
if camera_state.prev_enabled and not current_enabled:
|
||||
logger.debug(f"Not processing objects for disabled camera {camera}")
|
||||
self.force_end_all_events(camera, camera_state)
|
||||
|
||||
camera_state.prev_enabled = current_enabled
|
||||
|
||||
if not current_enabled:
|
||||
continue
|
||||
|
||||
try:
|
||||
(
|
||||
camera,
|
||||
@@ -693,6 +748,10 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
if not self._get_enabled_state(camera):
|
||||
logger.debug(f"Camera {camera} disabled, skipping update")
|
||||
continue
|
||||
|
||||
camera_state = self.camera_states[camera]
|
||||
|
||||
camera_state.update(
|
||||
@@ -735,4 +794,7 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
self.detection_publisher.stop()
|
||||
self.event_sender.stop()
|
||||
self.event_end_subscriber.stop()
|
||||
for subscriber in self.enabled_subscribers.values():
|
||||
subscriber.stop()
|
||||
|
||||
logger.info("Exiting object processor...")
|
||||
|
||||
Reference in New Issue
Block a user