diff --git a/.coveragerc b/.coveragerc index db191405522..1032ac2db0a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -316,6 +316,7 @@ omit = homeassistant/components/ezviz/__init__.py homeassistant/components/ezviz/alarm_control_panel.py homeassistant/components/ezviz/binary_sensor.py + homeassistant/components/ezviz/button.py homeassistant/components/ezviz/camera.py homeassistant/components/ezviz/image.py homeassistant/components/ezviz/light.py diff --git a/homeassistant/components/ezviz/__init__.py b/homeassistant/components/ezviz/__init__.py index 59dfb7c269c..c007de78130 100644 --- a/homeassistant/components/ezviz/__init__.py +++ b/homeassistant/components/ezviz/__init__.py @@ -35,6 +35,7 @@ PLATFORMS_BY_TYPE: dict[str, list] = { ATTR_TYPE_CLOUD: [ Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CAMERA, Platform.IMAGE, Platform.LIGHT, diff --git a/homeassistant/components/ezviz/button.py b/homeassistant/components/ezviz/button.py new file mode 100644 index 00000000000..1c04de956c6 --- /dev/null +++ b/homeassistant/components/ezviz/button.py @@ -0,0 +1,131 @@ +"""Support for EZVIZ button controls.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from pyezviz import EzvizClient +from pyezviz.constants import SupportExt +from pyezviz.exceptions import HTTPError, PyEzvizError + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DATA_COORDINATOR, DOMAIN +from .coordinator import EzvizDataUpdateCoordinator +from .entity import EzvizEntity + +PARALLEL_UPDATES = 1 + + +@dataclass +class EzvizButtonEntityDescriptionMixin: + """Mixin values for EZVIZ button entities.""" + + method: Callable[[EzvizClient, str, str], Any] + supported_ext: str + + +@dataclass +class EzvizButtonEntityDescription( + ButtonEntityDescription, EzvizButtonEntityDescriptionMixin +): + """Describe a EZVIZ Button.""" + + +BUTTON_ENTITIES = ( + EzvizButtonEntityDescription( + key="ptz_up", + translation_key="ptz_up", + icon="mdi:pan", + method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( + "UP", serial, run + ), + supported_ext=str(SupportExt.SupportPtz.value), + ), + EzvizButtonEntityDescription( + key="ptz_down", + translation_key="ptz_down", + icon="mdi:pan", + method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( + "DOWN", serial, run + ), + supported_ext=str(SupportExt.SupportPtz.value), + ), + EzvizButtonEntityDescription( + key="ptz_left", + translation_key="ptz_left", + icon="mdi:pan", + method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( + "LEFT", serial, run + ), + supported_ext=str(SupportExt.SupportPtz.value), + ), + EzvizButtonEntityDescription( + key="ptz_right", + translation_key="ptz_right", + icon="mdi:pan", + method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( + "RIGHT", serial, run + ), + supported_ext=str(SupportExt.SupportPtz.value), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up EZVIZ button based on a config entry.""" + coordinator: EzvizDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + + # Add button entities if supportExt value indicates PTZ capbility. + # Could be missing or "0" for unsupported. + # If present with value of "1" then add button entity. + + async_add_entities( + EzvizButtonEntity(coordinator, camera, entity_description) + for camera in coordinator.data + for capibility, value in coordinator.data[camera]["supportExt"].items() + for entity_description in BUTTON_ENTITIES + if capibility == entity_description.supported_ext + if value == "1" + ) + + +class EzvizButtonEntity(EzvizEntity, ButtonEntity): + """Representation of a EZVIZ button entity.""" + + entity_description: EzvizButtonEntityDescription + _attr_has_entity_name = True + + def __init__( + self, + coordinator: EzvizDataUpdateCoordinator, + serial: str, + description: EzvizButtonEntityDescription, + ) -> None: + """Initialize the button.""" + super().__init__(coordinator, serial) + self._attr_unique_id = f"{serial}_{description.key}" + self.entity_description = description + + def press(self) -> None: + """Execute the button action.""" + try: + self.entity_description.method( + self.coordinator.ezviz_client, self._serial, "START" + ) + self.entity_description.method( + self.coordinator.ezviz_client, self._serial, "STOP" + ) + except (HTTPError, PyEzvizError) as err: + raise HomeAssistantError( + f"Cannot perform PTZ action on {self.name}" + ) from err diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index 01e8425c13b..7f03aef1d97 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -263,6 +263,17 @@ class EzvizCamera(EzvizEntity, Camera): def perform_ptz(self, direction: str, speed: int) -> None: """Perform a PTZ action on the camera.""" + ir.async_create_issue( + self.hass, + DOMAIN, + "service_depreciation_ptz", + breaks_in_ha_version="2024.2.0", + is_fixable=True, + is_persistent=True, + severity=ir.IssueSeverity.WARNING, + translation_key="service_depreciation_ptz", + ) + try: self.coordinator.ezviz_client.ptz_control( str(direction).upper(), self._serial, "START", speed diff --git a/homeassistant/components/ezviz/strings.json b/homeassistant/components/ezviz/strings.json index 0245edc0e3e..d60c4816d24 100644 --- a/homeassistant/components/ezviz/strings.json +++ b/homeassistant/components/ezviz/strings.json @@ -81,6 +81,17 @@ } } } + }, + "service_depreciation_ptz": { + "title": "EZVIZ PTZ service is being removed", + "fix_flow": { + "step": { + "confirm": { + "title": "[%key:component::ezviz::issues::service_depreciation_ptz::title%]", + "description": "EZVIZ PTZ service is deprecated and will be removed.\nTo move the camera, you can instead use the `button.press` service targetting the PTZ* entities.\n\nPlease remove the use of this service from your automations and scripts and select **submit** to close this issue." + } + } + } } }, "entity": { @@ -99,6 +110,20 @@ "name": "Last motion image" } }, + "button": { + "ptz_up": { + "name": "PTZ up" + }, + "ptz_down": { + "name": "PTZ down" + }, + "ptz_left": { + "name": "PTZ left" + }, + "ptz_right": { + "name": "PTZ right" + } + }, "binary_sensor": { "alarm_schedules_enabled": { "name": "Alarm schedules enabled"