From c4fa05065940462e5767759e748d276c2716ed49 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:01:33 -0500 Subject: [PATCH] ensure camera supports ONVIF MoveStatus --- frigate/ptz/autotrack.py | 11 +++++++ frigate/ptz/onvif.py | 68 ++++++++++++++++++++++++++-------------- frigate/util/builtin.py | 12 +++++++ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index 5678e8d00..3260edb1a 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -208,6 +208,17 @@ class PtzAutoTracker: return + movestatus_supported = self.onvif.get_service_capabilities(camera_name) + + if movestatus_supported is None or movestatus_supported.lower() != "true": + cam.onvif.autotracking.enabled = False + self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = False + logger.warning( + f"Disabling autotracking for {camera_name}: ONVIF MoveStatus not supported" + ) + + return + self.onvif.get_camera_status(camera_name) # movement thread per camera diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index 71810f968..b8794b6f1 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -9,6 +9,7 @@ from onvif import ONVIFCamera, ONVIFError from frigate.config import FrigateConfig, ZoomingModeEnum from frigate.types import PTZMetricsTypes +from frigate.util.builtin import find_by_key logger = logging.getLogger(__name__) @@ -83,10 +84,9 @@ class OnvifController: logger.debug(f"Onvif config for {camera_name}: {ptz_config}") service_capabilities_request = ptz.create_type("GetServiceCapabilities") - service_capabilities = ptz.GetServiceCapabilities(service_capabilities_request) - logger.debug( - f"Onvif service capabilities for {camera_name}: {service_capabilities}" - ) + self.cams[camera_name][ + "service_capabilities_request" + ] = service_capabilities_request fov_space_id = next( ( @@ -453,7 +453,30 @@ class OnvifController: "presets": list(self.cams[camera_name]["presets"].keys()), } - def get_camera_status(self, camera_name: str) -> dict[str, any]: + def get_service_capabilities(self, camera_name: str) -> None: + if camera_name not in self.cams.keys(): + logger.error(f"Onvif is not setup for {camera_name}") + return {} + + if not self.cams[camera_name]["init"]: + self._init_onvif(camera_name) + + onvif: ONVIFCamera = self.cams[camera_name]["onvif"] + service_capabilities_request = self.cams[camera_name][ + "service_capabilities_request" + ] + service_capabilities = onvif.get_service("ptz").GetServiceCapabilities( + service_capabilities_request + ) + + logger.debug( + f"Onvif service capabilities for {camera_name}: {service_capabilities}" + ) + + # MoveStatus is required for autotracking - should return "true" if supported + return find_by_key(vars(service_capabilities), "MoveStatus") + + def get_camera_status(self, camera_name: str) -> None: if camera_name not in self.cams.keys(): logger.error(f"Onvif is not setup for {camera_name}") return {} @@ -465,17 +488,28 @@ class OnvifController: status_request = self.cams[camera_name]["status_request"] status = onvif.get_service("ptz").GetStatus(status_request) - # zooming is optional + # there doesn't seem to be an onvif standard with this optional parameter + # some cameras can report MoveStatus with or without PanTilt or Zoom attributes pan_tilt_status = getattr(status.MoveStatus, "PanTilt", None) zoom_status = getattr(status.MoveStatus, "Zoom", None) + # if it's not an attribute, see if MoveStatus even exists in the status result if pan_tilt_status is None: - logger.error( - f"Camera {camera_name} does not support the ONVIF GetStatus method. Autotracking will not function correctly and must be disabled in your config." - ) - return + pan_tilt_status = getattr(status, "MoveStatus", None) - if pan_tilt_status == "IDLE" and (zoom_status is None or zoom_status == "IDLE"): + # we're unsupported + if pan_tilt_status is None or pan_tilt_status.lower() not in [ + "idle", + "moving", + ]: + logger.error( + f"Camera {camera_name} does not support the ONVIF GetStatus method. Autotracking will not function correctly and must be disabled in your config." + ) + return + + if pan_tilt_status.lower() == "idle" and ( + zoom_status is None or zoom_status.lower() == "idle" + ): self.cams[camera_name]["active"] = False if not self.ptz_metrics[camera_name]["ptz_stopped"].is_set(): self.ptz_metrics[camera_name]["ptz_stopped"].set() @@ -517,15 +551,3 @@ class OnvifController: logger.debug( f'Camera zoom level: {self.ptz_metrics[camera_name]["ptz_zoom_level"].value}' ) - - return { - "pan": status.Position.PanTilt.x, - "tilt": status.Position.PanTilt.y, - "zoom": status.Position.Zoom.x - if self.config.cameras[camera_name].onvif.autotracking.zooming - else 0, - "pantilt_moving": status.MoveStatus.PanTilt, - "zoom_moving": status.MoveStatus.Zoom - if self.config.cameras[camera_name].onvif.autotracking.zooming - else "IDLE", - } diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 9c776f5df..929ada010 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -249,3 +249,15 @@ def update_yaml(data, key_path, new_value): temp[last_key] = new_value return data + + +def find_by_key(dictionary, target_key): + if target_key in dictionary: + return dictionary[target_key] + else: + for value in dictionary.values(): + if isinstance(value, dict): + result = find_by_key(value, target_key) + if result is not None: + return result + return None