From f0b979556cd57ae18fcaf6ed6c75b5e2927e048b Mon Sep 17 00:00:00 2001 From: Matrix Date: Tue, 29 Nov 2022 21:10:38 +0800 Subject: [PATCH] Add YoLink MultiOutlet support (#82622) --- homeassistant/components/yolink/const.py | 1 + homeassistant/components/yolink/entity.py | 13 ++++ homeassistant/components/yolink/manifest.json | 2 +- homeassistant/components/yolink/switch.py | 72 ++++++++++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/yolink/const.py b/homeassistant/components/yolink/const.py index 14279ebeae0..c36f01bd4fd 100644 --- a/homeassistant/components/yolink/const.py +++ b/homeassistant/components/yolink/const.py @@ -16,6 +16,7 @@ ATTR_DEVICE_ID = "deviceId" ATTR_DEVICE_DOOR_SENSOR = "DoorSensor" ATTR_DEVICE_TH_SENSOR = "THSensor" ATTR_DEVICE_MOTION_SENSOR = "MotionSensor" +ATTR_DEVICE_MULTI_OUTLET = "MultiOutlet" ATTR_DEVICE_LEAK_SENSOR = "LeakSensor" ATTR_DEVICE_VIBRATION_SENSOR = "VibrationSensor" ATTR_DEVICE_OUTLET = "Outlet" diff --git a/homeassistant/components/yolink/entity.py b/homeassistant/components/yolink/entity.py index 02f063a282a..0ae960d726f 100644 --- a/homeassistant/components/yolink/entity.py +++ b/homeassistant/components/yolink/entity.py @@ -3,6 +3,7 @@ from __future__ import annotations from abc import abstractmethod +from yolink.client_request import ClientRequest from yolink.exception import YoLinkAuthFailError, YoLinkClientError from homeassistant.config_entries import ConfigEntry @@ -70,3 +71,15 @@ class YoLinkEntity(CoordinatorEntity[YoLinkCoordinator]): except YoLinkClientError as yl_client_err: self.coordinator.last_update_success = False raise HomeAssistantError(yl_client_err) from yl_client_err + + async def call_device(self, request: ClientRequest) -> None: + """Call device api.""" + try: + # call_device will check result, fail by raise YoLinkClientError + await self.coordinator.device.call_device(request) + 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/manifest.json b/homeassistant/components/yolink/manifest.json index 665b17d9f22..3a2a1f6a390 100644 --- a/homeassistant/components/yolink/manifest.json +++ b/homeassistant/components/yolink/manifest.json @@ -3,7 +3,7 @@ "name": "YoLink", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/yolink", - "requirements": ["yolink-api==0.1.0"], + "requirements": ["yolink-api==0.1.5"], "dependencies": ["auth", "application_credentials"], "codeowners": ["@matrixd2"], "iot_class": "cloud_push" diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py index 03bb2a26183..2ac322002d5 100644 --- a/homeassistant/components/yolink/switch.py +++ b/homeassistant/components/yolink/switch.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from typing import Any from yolink.device import YoLinkDevice +from yolink.outlet_request_builder import OutletRequestBuilder from homeassistant.components.switch import ( SwitchDeviceClass, @@ -19,6 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( ATTR_COORDINATORS, ATTR_DEVICE_MANIPULATOR, + ATTR_DEVICE_MULTI_OUTLET, ATTR_DEVICE_OUTLET, ATTR_DEVICE_SWITCH, DOMAIN, @@ -32,8 +34,7 @@ class YoLinkSwitchEntityDescription(SwitchEntityDescription): """YoLink SwitchEntityDescription.""" exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True - value: Callable[[Any], bool | None] = lambda _: None - state_key: str = "state" + plug_index: int | None = None DEVICE_TYPES: tuple[YoLinkSwitchEntityDescription, ...] = ( @@ -41,26 +42,63 @@ DEVICE_TYPES: tuple[YoLinkSwitchEntityDescription, ...] = ( key="outlet_state", device_class=SwitchDeviceClass.OUTLET, name="State", - value=lambda value: value == "open" if value is not None else None, exists_fn=lambda device: device.device_type == ATTR_DEVICE_OUTLET, ), YoLinkSwitchEntityDescription( key="manipulator_state", name="State", icon="mdi:pipe", - value=lambda value: value == "open" if value is not None else None, exists_fn=lambda device: device.device_type == ATTR_DEVICE_MANIPULATOR, ), YoLinkSwitchEntityDescription( key="switch_state", name="State", device_class=SwitchDeviceClass.SWITCH, - value=lambda value: value == "open" if value is not None else None, exists_fn=lambda device: device.device_type == ATTR_DEVICE_SWITCH, ), + YoLinkSwitchEntityDescription( + key="multi_outlet_usb_ports", + name="UsbPorts", + device_class=SwitchDeviceClass.OUTLET, + exists_fn=lambda device: device.device_type == ATTR_DEVICE_MULTI_OUTLET, + plug_index=0, + ), + YoLinkSwitchEntityDescription( + key="multi_outlet_plug_1", + name="Plug1", + device_class=SwitchDeviceClass.OUTLET, + exists_fn=lambda device: device.device_type == ATTR_DEVICE_MULTI_OUTLET, + plug_index=1, + ), + YoLinkSwitchEntityDescription( + key="multi_outlet_plug_2", + name="Plug2", + device_class=SwitchDeviceClass.OUTLET, + exists_fn=lambda device: device.device_type == ATTR_DEVICE_MULTI_OUTLET, + plug_index=2, + ), + YoLinkSwitchEntityDescription( + key="multi_outlet_plug_3", + name="Plug3", + device_class=SwitchDeviceClass.OUTLET, + exists_fn=lambda device: device.device_type == ATTR_DEVICE_MULTI_OUTLET, + plug_index=3, + ), + YoLinkSwitchEntityDescription( + key="multi_outlet_plug_4", + name="Plug4", + device_class=SwitchDeviceClass.OUTLET, + exists_fn=lambda device: device.device_type == ATTR_DEVICE_MULTI_OUTLET, + plug_index=4, + ), ) -DEVICE_TYPE = [ATTR_DEVICE_MANIPULATOR, ATTR_DEVICE_OUTLET, ATTR_DEVICE_SWITCH] +DEVICE_TYPE = [ + ATTR_DEVICE_MANIPULATOR, + ATTR_DEVICE_MULTI_OUTLET, + ATTR_DEVICE_OUTLET, + ATTR_DEVICE_SWITCH, +] async def async_setup_entry( @@ -108,18 +146,30 @@ class YoLinkSwitchEntity(YoLinkEntity, SwitchEntity): f"{coordinator.device.device_name} ({self.entity_description.name})" ) + def _get_state( + self, state_value: str | list[str] | None, plug_index: int | None + ) -> bool | None: + """Parse state value.""" + if isinstance(state_value, list) and plug_index is not None: + return state_value[plug_index] == "open" + return state_value == "open" if state_value is not None else None + @callback - def update_entity_state(self, state: dict[str, Any]) -> None: + def update_entity_state(self, state: dict[str, str | list[str]]) -> None: """Update HA Entity State.""" - self._attr_is_on = self.entity_description.value( - state.get(self.entity_description.state_key) + self._attr_is_on = self._get_state( + state.get("state"), self.entity_description.plug_index ) self.async_write_ha_state() async def call_state_change(self, state: str) -> None: """Call setState api to change switch state.""" - await self.call_device_api("setState", {"state": state}) - self._attr_is_on = self.entity_description.value(state) + await self.call_device( + OutletRequestBuilder.set_state_request( + state, self.entity_description.plug_index + ) + ) + self._attr_is_on = self._get_state(state, self.entity_description.plug_index) self.async_write_ha_state() async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/requirements_all.txt b/requirements_all.txt index ee8423fe8c8..11a9922382c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2609,7 +2609,7 @@ yeelight==0.7.10 yeelightsunflower==0.0.10 # homeassistant.components.yolink -yolink-api==0.1.0 +yolink-api==0.1.5 # homeassistant.components.youless youless-api==0.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dde2a9f9dd1..bb2164f68b3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1816,7 +1816,7 @@ yalexs==1.2.6 yeelight==0.7.10 # homeassistant.components.yolink -yolink-api==0.1.0 +yolink-api==0.1.5 # homeassistant.components.youless youless-api==0.16