dynamically update onvif config

This commit is contained in:
Josh Hawkins 2026-03-23 17:08:32 -05:00
parent 6215501ae8
commit 932cca62c7
3 changed files with 61 additions and 10 deletions

View File

@ -131,6 +131,8 @@ class CameraConfigUpdateSubscriber:
config.lpr = updated_config config.lpr = updated_config
elif update_type == CameraConfigUpdateEnum.snapshots: elif update_type == CameraConfigUpdateEnum.snapshots:
config.snapshots = updated_config config.snapshots = updated_config
elif update_type == CameraConfigUpdateEnum.onvif:
config.onvif = updated_config
elif update_type == CameraConfigUpdateEnum.zones: elif update_type == CameraConfigUpdateEnum.zones:
config.zones = updated_config config.zones = updated_config

View File

@ -15,6 +15,10 @@ from zeep.exceptions import Fault, TransportError
from frigate.camera import PTZMetrics from frigate.camera import PTZMetrics
from frigate.config import FrigateConfig, ZoomingModeEnum from frigate.config import FrigateConfig, ZoomingModeEnum
from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
CameraConfigUpdateSubscriber,
)
from frigate.util.builtin import find_by_key from frigate.util.builtin import find_by_key
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -65,7 +69,14 @@ class OnvifController:
self.camera_configs[cam_name] = cam self.camera_configs[cam_name] = cam
self.status_locks[cam_name] = asyncio.Lock() self.status_locks[cam_name] = asyncio.Lock()
self.config_subscriber = CameraConfigUpdateSubscriber(
self.config,
self.config.cameras,
[CameraConfigUpdateEnum.onvif],
)
asyncio.run_coroutine_threadsafe(self._init_cameras(), self.loop) asyncio.run_coroutine_threadsafe(self._init_cameras(), self.loop)
asyncio.run_coroutine_threadsafe(self._poll_config_updates(), self.loop)
def _run_event_loop(self) -> None: def _run_event_loop(self) -> None:
"""Run the event loop in a separate thread.""" """Run the event loop in a separate thread."""
@ -80,6 +91,52 @@ class OnvifController:
for cam_name in self.camera_configs: for cam_name in self.camera_configs:
await self._init_single_camera(cam_name) await self._init_single_camera(cam_name)
async def _poll_config_updates(self) -> None:
"""Poll for ONVIF config updates and re-initialize cameras as needed."""
while True:
await asyncio.sleep(1)
try:
updates = self.config_subscriber.check_for_updates()
for update_type, cameras in updates.items():
if update_type == CameraConfigUpdateEnum.onvif.name:
for cam_name in cameras:
await self._reinit_camera(cam_name)
except Exception:
logger.error("Error checking for ONVIF config updates")
async def _close_camera(self, cam_name: str) -> None:
"""Close the ONVIF client session for a camera."""
cam_state = self.cams.get(cam_name)
if cam_state and "onvif" in cam_state:
try:
await cam_state["onvif"].close()
except Exception:
logger.debug(f"Error closing ONVIF session for {cam_name}")
async def _reinit_camera(self, cam_name: str) -> None:
"""Re-initialize a camera after config change."""
logger.info(f"Re-initializing ONVIF for {cam_name} due to config change")
# close existing session before re-init
await self._close_camera(cam_name)
cam = self.config.cameras.get(cam_name)
if not cam or not cam.onvif.host:
# ONVIF removed from config, clean up
self.cams.pop(cam_name, None)
self.camera_configs.pop(cam_name, None)
self.failed_cams.pop(cam_name, None)
return
# update stored config and reset state
self.camera_configs[cam_name] = cam
if cam_name not in self.status_locks:
self.status_locks[cam_name] = asyncio.Lock()
self.cams.pop(cam_name, None)
self.failed_cams.pop(cam_name, None)
await self._init_single_camera(cam_name)
async def _init_single_camera(self, cam_name: str) -> bool: async def _init_single_camera(self, cam_name: str) -> bool:
"""Initialize a single camera by name. """Initialize a single camera by name.
@ -1041,6 +1098,7 @@ class OnvifController:
return return
logger.info("Exiting ONVIF controller...") logger.info("Exiting ONVIF controller...")
self.config_subscriber.stop()
def stop_and_cleanup(): def stop_and_cleanup():
try: try:

View File

@ -19,16 +19,7 @@ const onvif: SectionConfigOverrides = {
], ],
advancedFields: ["tls_insecure", "ignore_time_mismatch"], advancedFields: ["tls_insecure", "ignore_time_mismatch"],
overrideFields: [], overrideFields: [],
restartRequired: [ restartRequired: ["autotracking.calibrate_on_startup"],
"host",
"port",
"user",
"password",
"profile",
"tls_insecure",
"ignore_time_mismatch",
"autotracking.calibrate_on_startup",
],
uiSchema: { uiSchema: {
host: { host: {
"ui:options": { size: "sm" }, "ui:options": { size: "sm" },