Use attributes in wilight (#73898)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
pull/73047/head
epenet 2022-06-25 00:55:01 +02:00 committed by GitHub
parent 15b7564171
commit 9b88b77b66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 99 deletions

View File

@ -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)

View File

@ -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

View File

@ -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."""

View File

@ -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)

View File

@ -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