This commit is contained in:
Josh Hawkins 2025-06-25 16:05:05 -05:00
parent add68b8860
commit 685a9f7ce3

View File

@ -33,6 +33,8 @@ class OnvifCommandEnum(str, Enum):
stop = "stop"
zoom_in = "zoom_in"
zoom_out = "zoom_out"
focus_in = "focus_in"
focus_out = "focus_out"
class OnvifController:
@ -185,6 +187,16 @@ class OnvifController:
ptz: ONVIFService = await onvif.create_ptz_service()
self.cams[camera_name]["ptz"] = ptz
imaging: ONVIFService = await onvif.create_imaging_service()
self.cams[camera_name]["imaging"] = imaging
try:
video_sources = await media.GetVideoSources()
if video_sources and len(video_sources) > 0:
self.cams[camera_name]["video_source_token"] = video_sources[0].token
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.debug(f"Unable to get video sources for {camera_name}: {e}")
self.cams[camera_name]["video_source_token"] = None
# setup continuous moving request
move_request = ptz.create_type("ContinuousMove")
move_request.ProfileToken = profile.token
@ -265,9 +277,15 @@ class OnvifController:
"RelativeZoomTranslationSpace"
][zoom_space_id]["URI"]
else:
if "Zoom" in move_request["Translation"]:
if (
move_request["Translation"] is not None
and "Zoom" in move_request["Translation"]
):
del move_request["Translation"]["Zoom"]
if "Zoom" in move_request["Speed"]:
if (
move_request["Speed"] is not None
and "Zoom" in move_request["Speed"]
):
del move_request["Speed"]["Zoom"]
logger.debug(
f"{camera_name}: Relative move request after deleting zoom: {move_request}"
@ -360,7 +378,19 @@ class OnvifController:
f"Disabling autotracking zooming for {camera_name}: Absolute zoom not supported. Exception: {e}"
)
# set relative pan/tilt space for autotracker
if self.cams[camera_name]["video_source_token"] is not None:
try:
imaging_capabilities = await imaging.GetImagingSettings(
{"VideoSourceToken": self.cams[camera_name]["video_source_token"]}
)
if (
hasattr(imaging_capabilities, "Focus")
and imaging_capabilities.Focus
):
supported_features.append("focus")
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.debug(f"Focus not supported for {camera_name}: {e}")
if (
self.config.cameras[camera_name].onvif.autotracking.enabled_in_config
and self.config.cameras[camera_name].onvif.autotracking.enabled
@ -385,6 +415,18 @@ class OnvifController:
"Zoom": True,
}
)
if (
"focus" in self.cams[camera_name]["features"]
and self.cams[camera_name]["video_source_token"]
):
try:
stop_request = self.cams[camera_name]["imaging"].create_type("Stop")
stop_request.VideoSourceToken = self.cams[camera_name][
"video_source_token"
]
await self.cams[camera_name]["imaging"].Stop(stop_request)
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.warning(f"Failed to stop focus for {camera_name}: {e}")
self.cams[camera_name]["active"] = False
async def _move(self, camera_name: str, command: OnvifCommandEnum) -> None:
@ -593,6 +635,35 @@ class OnvifController:
self.cams[camera_name]["active"] = False
async def _focus(self, camera_name: str, command: OnvifCommandEnum) -> None:
if self.cams[camera_name]["active"]:
logger.warning(
f"{camera_name} is already performing an action, not moving..."
)
await self._stop(camera_name)
if (
"focus" not in self.cams[camera_name]["features"]
or not self.cams[camera_name]["video_source_token"]
):
logger.error(f"{camera_name} does not support ONVIF continuous focus.")
return
self.cams[camera_name]["active"] = True
move_request = self.cams[camera_name]["imaging"].create_type("Move")
move_request.VideoSourceToken = self.cams[camera_name]["video_source_token"]
move_request.Focus = {
"Continuous": {
"Speed": 0.5 if command == OnvifCommandEnum.focus_in else -0.5
}
}
try:
await self.cams[camera_name]["imaging"].Move(move_request)
except (Fault, ONVIFError, TransportError, Exception) as e:
logger.warning(f"Onvif sending focus request to {camera_name} failed: {e}")
self.cams[camera_name]["active"] = False
async def handle_command_async(
self, camera_name: str, command: OnvifCommandEnum, param: str = ""
) -> None:
@ -616,11 +687,10 @@ class OnvifController:
elif command == OnvifCommandEnum.move_relative:
_, pan, tilt = param.split("_")
await self._move_relative(camera_name, float(pan), float(tilt), 0, 1)
elif (
command == OnvifCommandEnum.zoom_in
or command == OnvifCommandEnum.zoom_out
):
elif command in (OnvifCommandEnum.zoom_in, OnvifCommandEnum.zoom_out):
await self._zoom(camera_name, command)
elif command in (OnvifCommandEnum.focus_in, OnvifCommandEnum.focus_out):
await self._focus(camera_name, command)
else:
await self._move(camera_name, command)
except (Fault, ONVIFError, TransportError, Exception) as e:
@ -631,7 +701,6 @@ class OnvifController:
) -> None:
"""
Handle ONVIF commands by scheduling them in the event loop.
This is the synchronous interface that schedules async work.
"""
future = asyncio.run_coroutine_threadsafe(
self.handle_command_async(camera_name, command, param), self.loop