From 9b88b77b6619a89c8aa641bf59331666072bf900 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Sat, 25 Jun 2022 00:55:01 +0200 Subject: [PATCH] Use attributes in wilight (#73898) Co-authored-by: Franck Nijhof --- homeassistant/components/wilight/__init__.py | 52 ++++++---------- homeassistant/components/wilight/cover.py | 21 ++++--- homeassistant/components/wilight/fan.py | 22 +++---- homeassistant/components/wilight/light.py | 60 ++++++++++--------- .../components/wilight/parent_device.py | 32 +++++----- 5 files changed, 88 insertions(+), 99 deletions(-) diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index 932ce1538bf..2cdcf20c1ea 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -1,5 +1,9 @@ """The WiLight integration.""" +from typing import Any + +from pywilight.wilight_device import Device as PyWiLightDevice + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback @@ -51,61 +55,43 @@ class WiLightDevice(Entity): Contains the common logic for WiLight entities. """ - def __init__(self, api_device, index, item_name): + _attr_should_poll = False + + def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None: """Initialize the device.""" # WiLight specific attributes for every component type self._device_id = api_device.device_id - self._sw_version = api_device.swversion self._client = api_device.client - self._model = api_device.model - self._name = item_name self._index = index - self._unique_id = f"{self._device_id}_{self._index}" - self._status = {} + self._status: dict[str, Any] = {} - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def name(self): - """Return a name for this WiLight item.""" - return self._name - - @property - def unique_id(self): - """Return the unique ID for this WiLight item.""" - return self._unique_id - - @property - def device_info(self) -> DeviceInfo: - """Return the device info.""" - return DeviceInfo( - name=self._name, - identifiers={(DOMAIN, self._unique_id)}, - model=self._model, + self._attr_name = item_name + self._attr_unique_id = f"{self._device_id}_{index}" + self._attr_device_info = DeviceInfo( + name=item_name, + identifiers={(DOMAIN, self._attr_unique_id)}, + model=api_device.model, manufacturer="WiLight", - sw_version=self._sw_version, + sw_version=api_device.swversion, via_device=(DOMAIN, self._device_id), ) @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return bool(self._client.is_connected) @callback - def handle_event_callback(self, states): + def handle_event_callback(self, states: dict[str, Any]) -> None: """Propagate changes through ha.""" self._status = states self.async_write_ha_state() - async def async_update(self): + async def async_update(self) -> None: """Synchronize state with api_device.""" await self._client.status(self._index) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Register update callback.""" self._client.register_status_callback(self.handle_event_callback, self._index) await self._client.status(self._index) diff --git a/homeassistant/components/wilight/cover.py b/homeassistant/components/wilight/cover.py index 6ee4a857d36..cd0a3cc21ac 100644 --- a/homeassistant/components/wilight/cover.py +++ b/homeassistant/components/wilight/cover.py @@ -1,4 +1,6 @@ """Support for WiLight Cover.""" +from __future__ import annotations + from typing import Any from pywilight.const import ( @@ -18,16 +20,18 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, WiLightDevice +from .parent_device import WiLightParent async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up WiLight covers from a config entry.""" - parent = hass.data[DOMAIN][entry.entry_id] + parent: WiLightParent = hass.data[DOMAIN][entry.entry_id] # Handle a discovered WiLight device. entities = [] + assert parent.api for item in parent.api.items: if item["type"] != ITEM_COVER: continue @@ -35,18 +39,17 @@ async def async_setup_entry( item_name = item["name"] if item["sub_type"] != COVER_V1: continue - entity = WiLightCover(parent.api, index, item_name) - entities.append(entity) + entities.append(WiLightCover(parent.api, index, item_name)) async_add_entities(entities) -def wilight_to_hass_position(value): +def wilight_to_hass_position(value: int) -> int: """Convert wilight position 1..255 to hass format 0..100.""" return min(100, round((value * 100) / 255)) -def hass_to_wilight_position(value): +def hass_to_wilight_position(value: int) -> int: """Convert hass position 0..100 to wilight 1..255 scale.""" return min(255, round((value * 255) / 100)) @@ -55,7 +58,7 @@ class WiLightCover(WiLightDevice, CoverEntity): """Representation of a WiLights cover.""" @property - def current_cover_position(self): + def current_cover_position(self) -> int | None: """Return current position of cover. None is unknown, 0 is closed, 100 is fully open. @@ -65,21 +68,21 @@ class WiLightCover(WiLightDevice, CoverEntity): return None @property - def is_opening(self): + def is_opening(self) -> bool | None: """Return if the cover is opening or not.""" if "motor_state" not in self._status: return None return self._status["motor_state"] == WL_OPENING @property - def is_closing(self): + def is_closing(self) -> bool | None: """Return if the cover is closing or not.""" if "motor_state" not in self._status: return None return self._status["motor_state"] == WL_CLOSING @property - def is_closed(self): + def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" if "motor_state" not in self._status or "position_current" not in self._status: return None diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index a93e0eb9447..c598e6db397 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -13,6 +13,7 @@ from pywilight.const import ( WL_SPEED_LOW, WL_SPEED_MEDIUM, ) +from pywilight.wilight_device import Device as PyWiLightDevice from homeassistant.components.fan import DIRECTION_FORWARD, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry @@ -24,6 +25,7 @@ from homeassistant.util.percentage import ( ) from . import DOMAIN, WiLightDevice +from .parent_device import WiLightParent ORDERED_NAMED_FAN_SPEEDS = [WL_SPEED_LOW, WL_SPEED_MEDIUM, WL_SPEED_HIGH] @@ -32,10 +34,11 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up WiLight lights from a config entry.""" - parent = hass.data[DOMAIN][entry.entry_id] + parent: WiLightParent = hass.data[DOMAIN][entry.entry_id] # Handle a discovered WiLight device. entities = [] + assert parent.api for item in parent.api.items: if item["type"] != ITEM_FAN: continue @@ -43,8 +46,7 @@ async def async_setup_entry( item_name = item["name"] if item["sub_type"] != FAN_V1: continue - entity = WiLightFan(parent.api, index, item_name) - entities.append(entity) + entities.append(WiLightFan(parent.api, index, item_name)) async_add_entities(entities) @@ -52,19 +54,16 @@ async def async_setup_entry( class WiLightFan(WiLightDevice, FanEntity): """Representation of a WiLights fan.""" + _attr_icon = "mdi:fan" + _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION - def __init__(self, api_device, index, item_name): + def __init__(self, api_device: PyWiLightDevice, index: str, item_name: str) -> None: """Initialize the device.""" super().__init__(api_device, index, item_name) # Initialize the WiLights fan. self._direction = WL_DIRECTION_FORWARD - @property - def icon(self): - """Return the icon of device based on its type.""" - return "mdi:fan" - @property def is_on(self) -> bool: """Return true if device is on.""" @@ -83,11 +82,6 @@ class WiLightFan(WiLightDevice, FanEntity): return None return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, wl_speed) - @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - return len(ORDERED_NAMED_FAN_SPEEDS) - @property def current_direction(self) -> str: """Return the current direction of the fan.""" diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index 10ff79fe60d..ea9e19dcb30 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -1,5 +1,10 @@ """Support for WiLight lights.""" +from __future__ import annotations + +from typing import Any + from pywilight.const import ITEM_LIGHT, LIGHT_COLOR, LIGHT_DIMMER, LIGHT_ON_OFF +from pywilight.wilight_device import Device as PyWiLightDevice from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -12,25 +17,23 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN, WiLightDevice +from .parent_device import WiLightParent -def entities_from_discovered_wilight(hass, api_device): +def entities_from_discovered_wilight(api_device: PyWiLightDevice) -> list[LightEntity]: """Parse configuration and add WiLight light entities.""" - entities = [] + entities: list[LightEntity] = [] for item in api_device.items: if item["type"] != ITEM_LIGHT: continue index = item["index"] item_name = item["name"] if item["sub_type"] == LIGHT_ON_OFF: - entity = WiLightLightOnOff(api_device, index, item_name) + entities.append(WiLightLightOnOff(api_device, index, item_name)) elif item["sub_type"] == LIGHT_DIMMER: - entity = WiLightLightDimmer(api_device, index, item_name) + entities.append(WiLightLightDimmer(api_device, index, item_name)) elif item["sub_type"] == LIGHT_COLOR: - entity = WiLightLightColor(api_device, index, item_name) - else: - continue - entities.append(entity) + entities.append(WiLightLightColor(api_device, index, item_name)) return entities @@ -39,10 +42,11 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up WiLight lights from a config entry.""" - parent = hass.data[DOMAIN][entry.entry_id] + parent: WiLightParent = hass.data[DOMAIN][entry.entry_id] # Handle a discovered WiLight device. - entities = entities_from_discovered_wilight(hass, parent.api) + assert parent.api + entities = entities_from_discovered_wilight(parent.api) async_add_entities(entities) @@ -53,15 +57,15 @@ class WiLightLightOnOff(WiLightDevice, LightEntity): _attr_supported_color_modes = {ColorMode.ONOFF} @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._status.get("on") - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" await self._client.turn_on(self._index) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self._client.turn_off(self._index) @@ -73,16 +77,16 @@ class WiLightLightDimmer(WiLightDevice, LightEntity): _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of this light between 0..255.""" return int(self._status.get("brightness", 0)) @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._status.get("on") - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on,set brightness if needed.""" # Dimmer switches use a range of [0, 255] to control # brightness. Level 255 might mean to set it to previous value @@ -92,27 +96,27 @@ class WiLightLightDimmer(WiLightDevice, LightEntity): else: await self._client.turn_on(self._index) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self._client.turn_off(self._index) -def wilight_to_hass_hue(value): +def wilight_to_hass_hue(value: int) -> float: """Convert wilight hue 1..255 to hass 0..360 scale.""" return min(360, round((value * 360) / 255, 3)) -def hass_to_wilight_hue(value): +def hass_to_wilight_hue(value: float) -> int: """Convert hass hue 0..360 to wilight 1..255 scale.""" return min(255, round((value * 255) / 360)) -def wilight_to_hass_saturation(value): +def wilight_to_hass_saturation(value: int) -> float: """Convert wilight saturation 1..255 to hass 0..100 scale.""" return min(100, round((value * 100) / 255, 3)) -def hass_to_wilight_saturation(value): +def hass_to_wilight_saturation(value: float) -> int: """Convert hass saturation 0..100 to wilight 1..255 scale.""" return min(255, round((value * 255) / 100)) @@ -124,24 +128,24 @@ class WiLightLightColor(WiLightDevice, LightEntity): _attr_supported_color_modes = {ColorMode.HS} @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of this light between 0..255.""" return int(self._status.get("brightness", 0)) @property - def hs_color(self): + def hs_color(self) -> tuple[float, float]: """Return the hue and saturation color value [float, float].""" - return [ + return ( wilight_to_hass_hue(int(self._status.get("hue", 0))), wilight_to_hass_saturation(int(self._status.get("saturation", 0))), - ] + ) @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if device is on.""" return self._status.get("on") - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on,set brightness if needed.""" # Brightness use a range of [0, 255] to control # Hue use a range of [0, 360] to control @@ -161,6 +165,6 @@ class WiLightLightColor(WiLightDevice, LightEntity): else: await self._client.turn_on(self._index) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" await self._client.turn_off(self._index) diff --git a/homeassistant/components/wilight/parent_device.py b/homeassistant/components/wilight/parent_device.py index faf71b74f72..17a33fef633 100644 --- a/homeassistant/components/wilight/parent_device.py +++ b/homeassistant/components/wilight/parent_device.py @@ -1,12 +1,16 @@ """The WiLight Device integration.""" +from __future__ import annotations + import asyncio import logging import pywilight +from pywilight.wilight_device import Device as PyWiLightDevice import requests +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send _LOGGER = logging.getLogger(__name__) @@ -15,23 +19,23 @@ _LOGGER = logging.getLogger(__name__) class WiLightParent: """Manages a single WiLight Parent Device.""" - def __init__(self, hass, config_entry): + def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Initialize the system.""" - self._host = config_entry.data[CONF_HOST] + self._host: str = config_entry.data[CONF_HOST] self._hass = hass - self._api = None + self._api: PyWiLightDevice | None = None @property - def host(self): + def host(self) -> str: """Return the host of this parent.""" return self._host @property - def api(self): + def api(self) -> PyWiLightDevice | None: """Return the api of this parent.""" return self._api - async def async_setup(self): + async def async_setup(self) -> bool: """Set up a WiLight Parent Device based on host parameter.""" host = self._host hass = self._hass @@ -42,7 +46,7 @@ class WiLightParent: return False @callback - def disconnected(): + def disconnected() -> None: # Schedule reconnect after connection has been lost. _LOGGER.warning("WiLight %s disconnected", api_device.device_id) async_dispatcher_send( @@ -50,14 +54,14 @@ class WiLightParent: ) @callback - def reconnected(): + def reconnected() -> None: # Schedule reconnect after connection has been lost. _LOGGER.warning("WiLight %s reconnect", api_device.device_id) async_dispatcher_send( hass, f"wilight_device_available_{api_device.device_id}", True ) - async def connect(api_device): + async def connect(api_device: PyWiLightDevice) -> None: # Set up connection and hook it into HA for reconnect/shutdown. _LOGGER.debug("Initiating connection to %s", api_device.device_id) @@ -81,7 +85,7 @@ class WiLightParent: return True - async def async_reset(self): + async def async_reset(self) -> None: """Reset api.""" # If the initialization was not wrong. @@ -89,15 +93,13 @@ class WiLightParent: self._api.client.stop() -def create_api_device(host): +def create_api_device(host: str) -> PyWiLightDevice: """Create an API Device.""" try: - device = pywilight.device_from_host(host) + return pywilight.device_from_host(host) except ( requests.exceptions.ConnectionError, requests.exceptions.Timeout, ) as err: _LOGGER.error("Unable to access WiLight at %s (%s)", host, err) return None - - return device