From de6048691ed8f300a972aea75ada4077ae0aadca Mon Sep 17 00:00:00 2001 From: Robert Ying Date: Fri, 23 Jun 2023 18:06:41 +0000 Subject: [PATCH] support presets --- custom_components/onvif_ptz/button.py | 56 +++++++++++ custom_components/onvif_ptz/config_flow.py | 1 - custom_components/onvif_ptz/const.py | 6 ++ custom_components/onvif_ptz/device.py | 110 ++++++++++++++++++++- custom_components/onvif_ptz/services.yaml | 102 +++++++++++++++++-- requirements.txt | 4 +- 6 files changed, 263 insertions(+), 16 deletions(-) diff --git a/custom_components/onvif_ptz/button.py b/custom_components/onvif_ptz/button.py index 4e088fd..4acbb90 100644 --- a/custom_components/onvif_ptz/button.py +++ b/custom_components/onvif_ptz/button.py @@ -17,10 +17,16 @@ ATTR_PANTILT, ATTR_ZOOM, ATTR_TIMEOUT, + ATTR_PRESET, + ATTR_NAME, SERVICE_RELATIVE_MOVE_PTZ, SERVICE_ABSOLUTE_MOVE_PTZ, SERVICE_CONTINUOUS_MOVE_PTZ, SERVICE_STOP_PTZ, + SERVICE_GOTO_HOME_POSITION, + SERVICE_GOTO_PRESET, + SERVICE_SET_HOME_POSITION, + SERVICE_SET_PRESET, DOMAIN, ) from .device import ONVIFDevice @@ -81,6 +87,34 @@ async def async_setup_entry( }, "async_perform_ptz_stop", ) + platform.async_register_entity_service( + SERVICE_SET_HOME_POSITION, + {}, + "async_perform_ptz_set_home_position", + ) + platform.async_register_entity_service( + SERVICE_GOTO_HOME_POSITION, + { + vol.Optional(ATTR_SPEED): PTZ_SCHEMA, + }, + "async_perform_ptz_goto_home_position", + ) + platform.async_register_entity_service( + SERVICE_SET_PRESET, + { + vol.Required(ATTR_PRESET): cv.string, + vol.Optional(ATTR_NAME): cv.string, + }, + "async_perform_ptz_set_preset", + ) + platform.async_register_entity_service( + SERVICE_GOTO_PRESET, + { + vol.Required(ATTR_PRESET): cv.string, + vol.Optional(ATTR_SPEED): PTZ_SCHEMA, + }, + "async_perform_ptz_goto_preset", + ) device = hass.data[DOMAIN][config_entry.unique_id] async_add_entities( @@ -133,3 +167,25 @@ async def async_perform_ptz_stop(self, pan_tilt=None, zoom=None) -> None: await self.device.async_perform_ptz_stop( self.profile, pan_tilt=pan_tilt, zoom=zoom ) + + async def async_perform_ptz_set_home_position(self) -> None: + """Perform a SetHomePosition PTZ action on the camera.""" + await self.device.async_perform_ptz_set_home_position(self.profile) + + async def async_perform_ptz_goto_home_position(self, speed=None) -> None: + """Perform a GotoHomePosition PTZ action on the camera.""" + await self.device.async_perform_ptz_goto_home_position( + self.profile, speed=speed + ) + + async def async_perform_ptz_set_preset(self, preset, name=None) -> None: + """Perform a SetPreset PTZ action on the camera.""" + await self.device.async_perform_ptz_set_preset( + self.profile, preset=preset, name=name + ) + + async def async_perform_ptz_goto_preset(self, preset, speed=None) -> None: + """Perform a GotoPreset PTZ action on the camera.""" + await self.device.async_perform_ptz_goto_preset( + self.profile, preset=preset, speed=speed + ) diff --git a/custom_components/onvif_ptz/config_flow.py b/custom_components/onvif_ptz/config_flow.py index cf05dea..26181a3 100644 --- a/custom_components/onvif_ptz/config_flow.py +++ b/custom_components/onvif_ptz/config_flow.py @@ -171,7 +171,6 @@ async def async_setup_profiles(self): ) device = get_device( - self.hass, self.onvif_config[CONF_HOST], self.onvif_config[CONF_PORT], self.onvif_config[CONF_USERNAME], diff --git a/custom_components/onvif_ptz/const.py b/custom_components/onvif_ptz/const.py index 49f2f79..6c98e98 100644 --- a/custom_components/onvif_ptz/const.py +++ b/custom_components/onvif_ptz/const.py @@ -19,8 +19,14 @@ ATTR_PANTILT = "pan_tilt" ATTR_ZOOM = "zoom" ATTR_TIMEOUT = "timeout" +ATTR_PRESET = "preset" +ATTR_NAME = "name" SERVICE_RELATIVE_MOVE_PTZ = "ptz_relative" SERVICE_ABSOLUTE_MOVE_PTZ = "ptz_absolute" SERVICE_CONTINUOUS_MOVE_PTZ = "ptz_continuous" SERVICE_STOP_PTZ = "ptz_stop" +SERVICE_SET_HOME_POSITION = "ptz_set_home_position" +SERVICE_GOTO_HOME_POSITION = "ptz_goto_home_position" +SERVICE_SET_PRESET = "ptz_set_preset" +SERVICE_GOTO_PRESET = "ptz_goto_preset" diff --git a/custom_components/onvif_ptz/device.py b/custom_components/onvif_ptz/device.py index 217324e..ef4513f 100644 --- a/custom_components/onvif_ptz/device.py +++ b/custom_components/onvif_ptz/device.py @@ -1,5 +1,6 @@ """ONVIF device abstraction.""" from __future__ import annotations +from typing import Optional from contextlib import suppress import datetime as dt @@ -75,7 +76,6 @@ def password(self) -> str: async def async_setup(self) -> bool: """Set up the device.""" self.device = get_device( - self.hass, host=self.config_entry.data[CONF_HOST], port=self.config_entry.data[CONF_PORT], username=self.config_entry.data[CONF_USERNAME], @@ -360,11 +360,14 @@ async def async_perform_ptz_continuous( self, profile: Profile, velocity, + timeout, ): """Perform a ContinuousMove PTZ action on the camera.""" ptz_service = await self.device.create_ptz_service() - LOGGER.debug("Calling ContinousMove PTZ: velocity: %s", velocity) + LOGGER.debug( + "Calling ContinousMove PTZ: velocity: %s, timeout: %s", velocity, timeout + ) try: req = ptz_service.create_type("ContinuousMove") req.ProfileToken = profile.token @@ -373,6 +376,7 @@ async def async_perform_ptz_continuous( return req.Velocity = velocity + req.Timeout = timeout LOGGER.debug("Making ContinuousMove request %s", req) await ptz_service.ContinuousMove(req) except ONVIFError as err: @@ -410,8 +414,108 @@ async def async_perform_ptz_relative( else: LOGGER.error("Error trying to perform PTZ action: %s", err) + async def async_perform_ptz_set_home_position( + self, + profile: Profile, + ): + """Perform a SetHomePosition PTZ action on the camera.""" + ptz_service = await self.device.create_ptz_service() + + LOGGER.debug("Calling SetHomePosition") + try: + if not profile.ptz: + LOGGER.warning( + "SetHomePosition not supported on device '%s'", self.name + ) + return + + LOGGER.debug("Making SetHomePosition request %s", profile.token) + await ptz_service.SetHomePosition(profile.token) + except ONVIFError as err: + if "Bad Request" in err.reason: + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) + else: + LOGGER.error("Error trying to perform PTZ action: %s", err) + + async def async_perform_ptz_goto_home_position(self, profile: Profile, speed=None): + """Perform a GotoHomePosition PTZ action on the camera.""" + ptz_service = await self.device.create_ptz_service() + + LOGGER.debug("Calling GotoHomePosition speed=%s", speed) + try: + if not profile.ptz: + LOGGER.warning( + "GotoHomePosition not supported on device '%s'", self.name + ) + return + + req = ptz_service.create_type("GotoHomePosition") + req.ProfileToken = profile.token + req.Speed = speed + + LOGGER.debug("Making GotoHomePosition request %s", req) + await ptz_service.GotoHomePosition(req) + except ONVIFError as err: + if "Bad Request" in err.reason: + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) + else: + LOGGER.error("Error trying to perform PTZ action: %s", err) + + async def async_perform_ptz_set_preset( + self, + profile: Profile, + preset: str, + name: Optional[str] = None, + ): + """Perform a SetPreset PTZ action on the camera.""" + ptz_service = await self.device.create_ptz_service() + + LOGGER.debug("Calling SetPreset preset=%s name=%s", preset, name) + try: + if not profile.ptz: + LOGGER.warning("SetPreset not supported on device '%s'", self.name) + return + + req = ptz_service.create_type("SetPreset") + req.ProfileToken = profile.token + req.PresetToken = preset + req.PresetName = name + + LOGGER.debug("Making SetPreset request %s", req) + await ptz_service.SetPreset(req) + except ONVIFError as err: + if "Bad Request" in err.reason: + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) + else: + LOGGER.error("Error trying to perform PTZ action: %s", err) + + async def async_perform_ptz_goto_preset( + self, profile: Profile, preset: str, speed=None + ): + """Perform a GotoPreset PTZ action on the camera.""" + ptz_service = await self.device.create_ptz_service() + + LOGGER.debug("Calling SetPreset preset=%s speed=%s", preset, speed) + try: + if not profile.ptz: + LOGGER.warning("GotoPrest not supported on device '%s'", self.name) + return + + req = ptz_service.create_type("GotoPreset") + req.ProfileToken = profile.token + req.PresetToken = preset + req.Speed = speed + + LOGGER.debug("Making GotoPreset request %s", req) + await ptz_service.GotoPreset(req) + except ONVIFError as err: + if "Bad Request" in err.reason: + LOGGER.warning("Device '%s' doesn't support PTZ", self.name) + else: + LOGGER.error("Error trying to perform PTZ action: %s", err) + -def get_device(hass, host, port, username, password) -> ONVIFCamera: +def get_device(host, port, username, password) -> ONVIFCamera: """Get ONVIFCamera instance.""" return ONVIFCamera( host, diff --git a/custom_components/onvif_ptz/services.yaml b/custom_components/onvif_ptz/services.yaml index 05993cf..9e2b209 100644 --- a/custom_components/onvif_ptz/services.yaml +++ b/custom_components/onvif_ptz/services.yaml @@ -28,7 +28,7 @@ ptz_absolute: description: > If your ONVIF camera supports AbsoluteMove PTZ, you will be able to pan, tilt or zoom your camera. Not all cameras which support PTZ support - absoluteMove. Camera devices may have multiple PTZ nodes, you usually want + AbsoluteMove. Camera devices may have multiple PTZ nodes, you usually want to select a single entity rather than a device. target: entity: @@ -53,7 +53,7 @@ ptz_continuous: description: > If your ONVIF camera supports ContinuousMove PTZ, you will be able to pan, tilt or zoom your camera. Not all cameras which support PTZ support - absoluteMove. Camera devices may have multiple PTZ nodes, you usually want + ContinuousMove. Camera devices may have multiple PTZ nodes, you usually want to select a single entity rather than a device. target: entity: @@ -67,19 +67,21 @@ ptz_continuous: description: "How to translate the camera (usually, {PanTilt: {x: 0, y: 0}, Zoom: 0})" selector: object: - speed: - name: Speed - description: How fast to translate the camera (optional, same format as translation) + timeout: + name: Timeout + description: "How long to move for (optional)" selector: - object: + number: + min: 0 + max: 100 ptz_stop: name: PTZ (Stop) description: > If your ONVIF camera supports PTZ, you will be able to pan, tilt or zoom - your camera. This command stops any ongoing movement. Not all cameras which - support PTZ support absoluteMove. Camera devices may have multiple PTZ - nodes, you usually want to select a single entity rather than a device. + your camera. This command stops any ongoing movement. Camera devices may + have multiple PTZ nodes, you usually want to select a single entity rather + than a device. target: entity: integration: onvif_ptz @@ -96,4 +98,84 @@ ptz_stop: name: Stop Zoom description: Stop zoom selector: - boolean: \ No newline at end of file + boolean: + +ptz_set_home_position: + name: PTZ (SetHomePosition) + description: > + Set the home position for the camera. Not all cameras which support PTZ + support setting the home position. Camera devices may have multiple PTZ + nodes, you usually want to select a single entity rather than a device. + target: + entity: + integration: onvif_ptz + domain: button + device: + integration: onvif_ptz + +ptz_goto_home_position: + name: PTZ (GotoHomePosition) + description: > + Go to the home position for the camera. Not all cameras which support PTZ + support the home position. Camera devices may have multiple PTZ nodes, you + usually want to select a single entity rather than a device. + target: + entity: + integration: onvif_ptz + domain: button + device: + integration: onvif_ptz + fields: + speed: + name: Speed + description: How fast to translate the camera (optional, same format as translation) + selector: + object: + +ptz_set_preset: + name: PTZ (SetPreset) + description: > + Set the specified preset for the camera. Not all cameras which support PTZ + support setting the home position. Camera devices may have multiple PTZ + nodes, you usually want to select a single entity rather than a device. + target: + entity: + integration: onvif_ptz + domain: button + device: + integration: onvif_ptz + fields: + preset: + name: Preset + description: Which preset to set (often, a number) + selector: + text: + name: + name: Name + description: The name for the preset (optional) + selector: + text: + +ptz_goto_preset: + name: PTZ (GotoPreset) + description: > + Go to the specified preset for the camera. Not all cameras which support PTZ + support the home position. Camera devices may have multiple PTZ nodes, you + usually want to select a single entity rather than a device. + target: + entity: + integration: onvif_ptz + domain: button + device: + integration: onvif_ptz + fields: + speed: + name: Speed + description: How fast to translate the camera (optional, same format as translation) + selector: + object: + preset: + name: Preset + description: Which preset to go to (often, a number) + selector: + text: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8197da1..f2ec718 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ WSDiscovery==2.0.0 colorlog==6.7.0 -homeassistant==2023.2.0 -onvif-zeep-async==1.2.1 +homeassistant==2023.6.0 +onvif-zeep-async==3.1.9 pip>=21.0,<23.2 ruff==0.0.267 \ No newline at end of file