diff --git a/.coveragerc b/.coveragerc index 0f9c3e4998a..75ed4663869 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1495,6 +1495,7 @@ omit = homeassistant/components/yolink/const.py homeassistant/components/yolink/coordinator.py homeassistant/components/yolink/entity.py + homeassistant/components/yolink/lock.py homeassistant/components/yolink/sensor.py homeassistant/components/yolink/siren.py homeassistant/components/yolink/switch.py diff --git a/homeassistant/components/yolink/__init__.py b/homeassistant/components/yolink/__init__.py index 7eb6b0229f0..21d36d33a30 100644 --- a/homeassistant/components/yolink/__init__.py +++ b/homeassistant/components/yolink/__init__.py @@ -24,7 +24,13 @@ from .coordinator import YoLinkCoordinator SCAN_INTERVAL = timedelta(minutes=5) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SIREN, Platform.SWITCH] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.LOCK, + Platform.SENSOR, + Platform.SIREN, + Platform.SWITCH, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/yolink/binary_sensor.py b/homeassistant/components/yolink/binary_sensor.py index d5c9ddedb84..17b25c57d94 100644 --- a/homeassistant/components/yolink/binary_sensor.py +++ b/homeassistant/components/yolink/binary_sensor.py @@ -96,7 +96,7 @@ async def async_setup_entry( if description.exists_fn(binary_sensor_device_coordinator.device): entities.append( YoLinkBinarySensorEntity( - binary_sensor_device_coordinator, description + config_entry, binary_sensor_device_coordinator, description ) ) async_add_entities(entities) @@ -109,11 +109,12 @@ class YoLinkBinarySensorEntity(YoLinkEntity, BinarySensorEntity): def __init__( self, + config_entry: ConfigEntry, coordinator: YoLinkCoordinator, description: YoLinkBinarySensorEntityDescription, ) -> None: """Init YoLink Sensor.""" - super().__init__(coordinator) + super().__init__(config_entry, coordinator) self.entity_description = description self._attr_unique_id = ( f"{coordinator.device.device_id} {self.entity_description.key}" diff --git a/homeassistant/components/yolink/const.py b/homeassistant/components/yolink/const.py index 16304e0de4b..44f4f3104f7 100644 --- a/homeassistant/components/yolink/const.py +++ b/homeassistant/components/yolink/const.py @@ -20,3 +20,4 @@ ATTR_DEVICE_LEAK_SENSOR = "LeakSensor" ATTR_DEVICE_VIBRATION_SENSOR = "VibrationSensor" ATTR_DEVICE_OUTLET = "Outlet" ATTR_DEVICE_SIREN = "Siren" +ATTR_DEVICE_LOCK = "Lock" diff --git a/homeassistant/components/yolink/entity.py b/homeassistant/components/yolink/entity.py index 5365681739e..02f063a282a 100644 --- a/homeassistant/components/yolink/entity.py +++ b/homeassistant/components/yolink/entity.py @@ -3,7 +3,11 @@ from __future__ import annotations from abc import abstractmethod +from yolink.exception import YoLinkAuthFailError, YoLinkClientError + +from homeassistant.config_entries import ConfigEntry from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -16,10 +20,12 @@ class YoLinkEntity(CoordinatorEntity[YoLinkCoordinator]): def __init__( self, + config_entry: ConfigEntry, coordinator: YoLinkCoordinator, ) -> None: """Init YoLink Entity.""" super().__init__(coordinator) + self.config_entry = config_entry @property def device_id(self) -> str: @@ -52,3 +58,15 @@ class YoLinkEntity(CoordinatorEntity[YoLinkCoordinator]): @abstractmethod def update_entity_state(self, state: dict) -> None: """Parse and update entity state, should be overridden.""" + + async def call_device_api(self, command: str, params: dict) -> None: + """Call device Api.""" + try: + # call_device_http_api will check result, fail by raise YoLinkClientError + await self.coordinator.device.call_device_http_api(command, params) + except YoLinkAuthFailError as yl_auth_err: + self.config_entry.async_start_reauth(self.hass) + raise HomeAssistantError(yl_auth_err) from yl_auth_err + except YoLinkClientError as yl_client_err: + self.coordinator.last_update_success = False + raise HomeAssistantError(yl_client_err) from yl_client_err diff --git a/homeassistant/components/yolink/lock.py b/homeassistant/components/yolink/lock.py new file mode 100644 index 00000000000..ca340c2e762 --- /dev/null +++ b/homeassistant/components/yolink/lock.py @@ -0,0 +1,65 @@ +"""YoLink Lock.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_COORDINATORS, ATTR_DEVICE_LOCK, DOMAIN +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink lock from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id][ATTR_COORDINATORS] + entities = [ + YoLinkLockEntity(config_entry, device_coordinator) + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type == ATTR_DEVICE_LOCK + ] + async_add_entities(entities) + + +class YoLinkLockEntity(YoLinkEntity, LockEntity): + """YoLink Lock Entity.""" + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: YoLinkCoordinator, + ) -> None: + """Init YoLink Lock.""" + super().__init__(config_entry, coordinator) + self._attr_unique_id = f"{coordinator.device.device_id}_lock_state" + self._attr_name = f"{coordinator.device.device_name}(LockState)" + + @callback + def update_entity_state(self, state: dict[str, Any]) -> None: + """Update HA Entity State.""" + state_value = state.get("state") + self._attr_is_locked = ( + state_value == "locked" if state_value is not None else None + ) + self.async_write_ha_state() + + async def call_lock_state_change(self, state: str) -> None: + """Call setState api to change lock state.""" + await self.call_device_api("setState", {"state": state}) + self._attr_is_locked = state == "lock" + self.async_write_ha_state() + + async def async_lock(self, **kwargs: Any) -> None: + """Lock device.""" + await self.call_lock_state_change("lock") + + async def async_unlock(self, **kwargs: Any) -> None: + """Unlock device.""" + await self.call_lock_state_change("unlock") diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index 917a93c310d..e0d746219fb 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -21,6 +21,7 @@ from homeassistant.util import percentage from .const import ( ATTR_COORDINATORS, ATTR_DEVICE_DOOR_SENSOR, + ATTR_DEVICE_LOCK, ATTR_DEVICE_MOTION_SENSOR, ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_VIBRATION_SENSOR, @@ -51,6 +52,7 @@ SENSOR_DEVICE_TYPE = [ ATTR_DEVICE_MOTION_SENSOR, ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_VIBRATION_SENSOR, + ATTR_DEVICE_LOCK, ] BATTERY_POWER_SENSOR = [ @@ -58,6 +60,7 @@ BATTERY_POWER_SENSOR = [ ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_MOTION_SENSOR, ATTR_DEVICE_VIBRATION_SENSOR, + ATTR_DEVICE_LOCK, ] @@ -112,6 +115,7 @@ async def async_setup_entry( if description.exists_fn(sensor_device_coordinator.device): entities.append( YoLinkSensorEntity( + config_entry, sensor_device_coordinator, description, ) @@ -126,11 +130,12 @@ class YoLinkSensorEntity(YoLinkEntity, SensorEntity): def __init__( self, + config_entry: ConfigEntry, coordinator: YoLinkCoordinator, description: YoLinkSensorEntityDescription, ) -> None: """Init YoLink Sensor.""" - super().__init__(coordinator) + super().__init__(config_entry, coordinator) self.entity_description = description self._attr_unique_id = ( f"{coordinator.device.device_id} {self.entity_description.key}" diff --git a/homeassistant/components/yolink/siren.py b/homeassistant/components/yolink/siren.py index 7e67dfb12f1..fd1c8e89e07 100644 --- a/homeassistant/components/yolink/siren.py +++ b/homeassistant/components/yolink/siren.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from typing import Any from yolink.device import YoLinkDevice -from yolink.exception import YoLinkAuthFailError, YoLinkClientError from homeassistant.components.siren import ( SirenEntity, @@ -15,7 +14,6 @@ from homeassistant.components.siren import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_COORDINATORS, ATTR_DEVICE_SIREN, DOMAIN @@ -79,8 +77,7 @@ class YoLinkSirenEntity(YoLinkEntity, SirenEntity): description: YoLinkSirenEntityDescription, ) -> None: """Init YoLink Siren.""" - super().__init__(coordinator) - self.config_entry = config_entry + super().__init__(config_entry, coordinator) self.entity_description = description self._attr_unique_id = ( f"{coordinator.device.device_id} {self.entity_description.key}" @@ -102,17 +99,7 @@ class YoLinkSirenEntity(YoLinkEntity, SirenEntity): async def call_state_change(self, state: bool) -> None: """Call setState api to change siren state.""" - try: - # call_device_http_api will check result, fail by raise YoLinkClientError - await self.coordinator.device.call_device_http_api( - "setState", {"state": {"alarm": state}} - ) - except YoLinkAuthFailError as yl_auth_err: - self.config_entry.async_start_reauth(self.hass) - raise HomeAssistantError(yl_auth_err) from yl_auth_err - except YoLinkClientError as yl_client_err: - self.coordinator.last_update_success = False - raise HomeAssistantError(yl_client_err) from yl_client_err + await self.call_device_api("setState", {"state": {"alarm": state}}) self._attr_is_on = self.entity_description.value("alert" if state else "normal") self.async_write_ha_state() diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py index f16dc781a9c..723043100b3 100644 --- a/homeassistant/components/yolink/switch.py +++ b/homeassistant/components/yolink/switch.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from typing import Any from yolink.device import YoLinkDevice -from yolink.exception import YoLinkAuthFailError, YoLinkClientError from homeassistant.components.switch import ( SwitchDeviceClass, @@ -15,7 +14,6 @@ from homeassistant.components.switch import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_COORDINATORS, ATTR_DEVICE_OUTLET, DOMAIN @@ -80,8 +78,7 @@ class YoLinkSwitchEntity(YoLinkEntity, SwitchEntity): description: YoLinkSwitchEntityDescription, ) -> None: """Init YoLink Outlet.""" - super().__init__(coordinator) - self.config_entry = config_entry + super().__init__(config_entry, coordinator) self.entity_description = description self._attr_unique_id = ( f"{coordinator.device.device_id} {self.entity_description.key}" @@ -100,17 +97,7 @@ class YoLinkSwitchEntity(YoLinkEntity, SwitchEntity): async def call_state_change(self, state: str) -> None: """Call setState api to change outlet state.""" - try: - # call_device_http_api will check result, fail by raise YoLinkClientError - await self.coordinator.device.call_device_http_api( - "setState", {"state": state} - ) - except YoLinkAuthFailError as yl_auth_err: - self.config_entry.async_start_reauth(self.hass) - raise HomeAssistantError(yl_auth_err) from yl_auth_err - except YoLinkClientError as yl_client_err: - self.coordinator.last_update_success = False - raise HomeAssistantError(yl_client_err) from yl_client_err + await self.call_device_api("setState", {"state": state}) self._attr_is_on = self.entity_description.value(state) self.async_write_ha_state()