Add typing to deCONZ Fan and Light platforms (#59607)

This commit is contained in:
Robert Svensson 2021-11-16 12:39:51 +01:00 committed by GitHub
parent 476a59d248
commit 41e341028e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 57 deletions

View File

@ -1,6 +1,9 @@
"""Support for deCONZ fans.""" """Support for deCONZ fans."""
from __future__ import annotations from __future__ import annotations
from collections.abc import ValuesView
from typing import Any
from pydeconz.light import ( from pydeconz.light import (
FAN_SPEED_25_PERCENT, FAN_SPEED_25_PERCENT,
FAN_SPEED_50_PERCENT, FAN_SPEED_50_PERCENT,
@ -19,15 +22,17 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, FanEntity,
) )
from homeassistant.core import callback from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.percentage import ( from homeassistant.util.percentage import (
ordered_list_item_to_percentage, ordered_list_item_to_percentage,
percentage_to_ordered_list_item, percentage_to_ordered_list_item,
) )
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry from .gateway import DeconzGateway, get_gateway_from_config_entry
ORDERED_NAMED_FAN_SPEEDS = [ ORDERED_NAMED_FAN_SPEEDS = [
FAN_SPEED_25_PERCENT, FAN_SPEED_25_PERCENT,
@ -50,13 +55,19 @@ LEGACY_DECONZ_TO_SPEED = {
} }
async def async_setup_entry(hass, config_entry, async_add_entities) -> None: async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up fans for deCONZ component.""" """Set up fans for deCONZ component."""
gateway = get_gateway_from_config_entry(hass, config_entry) gateway = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_fan(lights=gateway.api.lights.values()) -> None: def async_add_fan(
lights: list[Fan] | ValuesView[Fan] = gateway.api.lights.values(),
) -> None:
"""Add fan from deCONZ.""" """Add fan from deCONZ."""
entities = [] entities = []
@ -86,8 +97,11 @@ class DeconzFan(DeconzDevice, FanEntity):
"""Representation of a deCONZ fan.""" """Representation of a deCONZ fan."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Fan
def __init__(self, device, gateway) -> None: _attr_supported_features = SUPPORT_SET_SPEED
def __init__(self, device: Fan, gateway: DeconzGateway) -> None:
"""Set up fan.""" """Set up fan."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -95,12 +109,10 @@ class DeconzFan(DeconzDevice, FanEntity):
if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: if self._device.speed in ORDERED_NAMED_FAN_SPEEDS:
self._default_on_speed = self._device.speed self._default_on_speed = self._device.speed
self._attr_supported_features = SUPPORT_SET_SPEED
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return true if fan is on.""" """Return true if fan is on."""
return self._device.speed != FAN_SPEED_OFF return self._device.speed != FAN_SPEED_OFF # type: ignore[no-any-return]
@property @property
def percentage(self) -> int | None: def percentage(self) -> int | None:
@ -153,11 +165,6 @@ class DeconzFan(DeconzDevice, FanEntity):
SPEED_MEDIUM, SPEED_MEDIUM,
) )
@property
def supported_features(self) -> int:
"""Flag supported features."""
return self._attr_supported_features
@callback @callback
def async_update_callback(self) -> None: def async_update_callback(self) -> None:
"""Store latest configured speed from the device.""" """Store latest configured speed from the device."""
@ -183,10 +190,10 @@ class DeconzFan(DeconzDevice, FanEntity):
async def async_turn_on( async def async_turn_on(
self, self,
speed: str = None, speed: str | None = None,
percentage: int = None, percentage: int | None = None,
preset_mode: str = None, preset_mode: str | None = None,
**kwargs, **kwargs: Any,
) -> None: ) -> None:
"""Turn on fan.""" """Turn on fan."""
new_speed = self._default_on_speed new_speed = self._default_on_speed
@ -198,6 +205,6 @@ class DeconzFan(DeconzDevice, FanEntity):
await self._device.set_speed(new_speed) await self._device.set_speed(new_speed)
async def async_turn_off(self, **kwargs) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off fan.""" """Turn off fan."""
await self._device.set_speed(FAN_SPEED_OFF) await self._device.set_speed(FAN_SPEED_OFF)

View File

@ -2,6 +2,9 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import ValuesView
from typing import Any
from pydeconz.group import DeconzGroup as Group from pydeconz.group import DeconzGroup as Group
from pydeconz.light import ( from pydeconz.light import (
ALERT_LONG, ALERT_LONG,
@ -33,27 +36,35 @@ from homeassistant.components.light import (
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
LightEntity, LightEntity,
) )
from homeassistant.core import callback from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.color import color_hs_to_xy from homeassistant.util.color import color_hs_to_xy
from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry from .gateway import DeconzGateway, get_gateway_from_config_entry
DECONZ_GROUP = "is_deconz_group" DECONZ_GROUP = "is_deconz_group"
EFFECT_TO_DECONZ = {EFFECT_COLORLOOP: EFFECT_COLOR_LOOP, "None": EFFECT_NONE} EFFECT_TO_DECONZ = {EFFECT_COLORLOOP: EFFECT_COLOR_LOOP, "None": EFFECT_NONE}
FLASH_TO_DECONZ = {FLASH_SHORT: ALERT_SHORT, FLASH_LONG: ALERT_LONG} FLASH_TO_DECONZ = {FLASH_SHORT: ALERT_SHORT, FLASH_LONG: ALERT_LONG}
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the deCONZ lights and groups from a config entry.""" """Set up the deCONZ lights and groups from a config entry."""
gateway = get_gateway_from_config_entry(hass, config_entry) gateway = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_light(lights=gateway.api.lights.values()): def async_add_light(
lights: list[Light] | ValuesView[Light] = gateway.api.lights.values(),
) -> None:
"""Add light from deCONZ.""" """Add light from deCONZ."""
entities = [] entities = []
@ -77,7 +88,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
) )
@callback @callback
def async_add_group(groups=gateway.api.groups.values()): def async_add_group(
groups: list[Group] | ValuesView[Group] = gateway.api.groups.values(),
) -> None:
"""Add group from deCONZ.""" """Add group from deCONZ."""
if not gateway.option_allow_deconz_groups: if not gateway.option_allow_deconz_groups:
return return
@ -113,11 +126,11 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
TYPE = DOMAIN TYPE = DOMAIN
def __init__(self, device, gateway): def __init__(self, device: Group | Light, gateway: DeconzGateway) -> None:
"""Set up light.""" """Set up light."""
super().__init__(device, gateway) super().__init__(device, gateway)
self._attr_supported_color_modes = set() self._attr_supported_color_modes: set[str] = set()
if device.color_temp is not None: if device.color_temp is not None:
self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
@ -158,83 +171,83 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
return color_mode return color_mode
@property @property
def brightness(self): def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
return self._device.brightness return self._device.brightness # type: ignore[no-any-return]
@property @property
def color_temp(self): def color_temp(self) -> int:
"""Return the CT color value.""" """Return the CT color value."""
return self._device.color_temp return self._device.color_temp # type: ignore[no-any-return]
@property @property
def hs_color(self) -> tuple: def hs_color(self) -> tuple[float, float]:
"""Return the hs color value.""" """Return the hs color value."""
return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100) return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100)
@property @property
def xy_color(self) -> tuple | None: def xy_color(self) -> tuple[float, float] | None:
"""Return the XY color value.""" """Return the XY color value."""
return self._device.xy return self._device.xy # type: ignore[no-any-return]
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if light is on.""" """Return true if light is on."""
return self._device.state return self._device.state # type: ignore[no-any-return]
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on light.""" """Turn on light."""
data = {"on": True} data: dict[str, bool | float | int | str | tuple[float, float]] = {"on": True}
if ATTR_BRIGHTNESS in kwargs: if attr_brightness := kwargs.get(ATTR_BRIGHTNESS):
data["brightness"] = kwargs[ATTR_BRIGHTNESS] data["brightness"] = attr_brightness
if ATTR_COLOR_TEMP in kwargs: if attr_color_temp := kwargs.get(ATTR_COLOR_TEMP):
data["color_temperature"] = kwargs[ATTR_COLOR_TEMP] data["color_temperature"] = attr_color_temp
if ATTR_HS_COLOR in kwargs: if attr_hs_color := kwargs.get(ATTR_HS_COLOR):
if COLOR_MODE_XY in self._attr_supported_color_modes: if COLOR_MODE_XY in self._attr_supported_color_modes:
data["xy"] = color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) data["xy"] = color_hs_to_xy(*attr_hs_color)
else: else:
data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535) data["hue"] = int(attr_hs_color[0] / 360 * 65535)
data["saturation"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255) data["saturation"] = int(attr_hs_color[1] / 100 * 255)
if ATTR_XY_COLOR in kwargs: if ATTR_XY_COLOR in kwargs:
data["xy"] = kwargs[ATTR_XY_COLOR] data["xy"] = kwargs[ATTR_XY_COLOR]
if ATTR_TRANSITION in kwargs: if attr_transition := kwargs.get(ATTR_TRANSITION):
data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) data["transition_time"] = int(attr_transition * 10)
elif "IKEA" in self._device.manufacturer: elif "IKEA" in self._device.manufacturer:
data["transition_time"] = 0 data["transition_time"] = 0
if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH))) is not None: if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None:
data["alert"] = alert data["alert"] = alert
del data["on"] del data["on"]
if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT))) is not None: if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT, ""))) is not None:
data["effect"] = effect data["effect"] = effect
await self._device.set_state(**data) await self._device.set_state(**data)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off light.""" """Turn off light."""
if not self._device.state: if not self._device.state:
return return
data = {"on": False} data: dict[str, bool | int | str] = {"on": False}
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
data["brightness"] = 0 data["brightness"] = 0
data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10)
if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH))) is not None: if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None:
data["alert"] = alert data["alert"] = alert
del data["on"] del data["on"]
await self._device.set_state(**data) await self._device.set_state(**data)
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, bool]:
"""Return the device state attributes.""" """Return the device state attributes."""
return {DECONZ_GROUP: isinstance(self._device, Group)} return {DECONZ_GROUP: isinstance(self._device, Group)}
@ -242,13 +255,15 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
class DeconzLight(DeconzBaseLight): class DeconzLight(DeconzBaseLight):
"""Representation of a deCONZ light.""" """Representation of a deCONZ light."""
_device: Light
@property @property
def max_mireds(self): def max_mireds(self) -> int:
"""Return the warmest color_temp that this light supports.""" """Return the warmest color_temp that this light supports."""
return self._device.max_color_temp or super().max_mireds return self._device.max_color_temp or super().max_mireds
@property @property
def min_mireds(self): def min_mireds(self) -> int:
"""Return the coldest color_temp that this light supports.""" """Return the coldest color_temp that this light supports."""
return self._device.min_color_temp or super().min_mireds return self._device.min_color_temp or super().min_mireds
@ -256,13 +271,15 @@ class DeconzLight(DeconzBaseLight):
class DeconzGroup(DeconzBaseLight): class DeconzGroup(DeconzBaseLight):
"""Representation of a deCONZ group.""" """Representation of a deCONZ group."""
def __init__(self, device, gateway): _device: Group
def __init__(self, device: Group, gateway: DeconzGateway) -> None:
"""Set up group and create an unique id.""" """Set up group and create an unique id."""
self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}"
super().__init__(device, gateway) super().__init__(device, gateway)
@property @property
def unique_id(self): def unique_id(self) -> str:
"""Return a unique identifier for this device.""" """Return a unique identifier for this device."""
return self._unique_id return self._unique_id
@ -278,7 +295,7 @@ class DeconzGroup(DeconzBaseLight):
) )
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, bool]:
"""Return the device state attributes.""" """Return the device state attributes."""
attributes = dict(super().extra_state_attributes) attributes = dict(super().extra_state_attributes)
attributes["all_on"] = self._device.all_on attributes["all_on"] = self._device.all_on