Add typing to deCONZ Number and Sensor platforms (#59604)

This commit is contained in:
Robert Svensson 2021-11-17 15:11:51 +01:00 committed by GitHub
parent 29e0ef604e
commit 0339761e72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 44 deletions

View file

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import ValuesView
from dataclasses import dataclass from dataclasses import dataclass
from pydeconz.sensor import PRESENCE_DELAY, Presence from pydeconz.sensor import PRESENCE_DELAY, Presence
@ -11,25 +12,35 @@ from homeassistant.components.number import (
NumberEntity, NumberEntity,
NumberEntityDescription, NumberEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.const import ENTITY_CATEGORY_CONFIG
from homeassistant.core import callback 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 .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
@dataclass @dataclass
class DeconzNumberEntityDescription(NumberEntityDescription): class DeconzNumberEntityDescriptionBase:
"""Required values when describing deCONZ number entities."""
device_property: str
suffix: str
update_key: str
max_value: int
min_value: int
step: int
@dataclass
class DeconzNumberEntityDescription(
NumberEntityDescription, DeconzNumberEntityDescriptionBase
):
"""Class describing deCONZ number entities.""" """Class describing deCONZ number entities."""
entity_category = ENTITY_CATEGORY_CONFIG entity_category = ENTITY_CATEGORY_CONFIG
device_property: str | None = None
suffix: str | None = None
update_key: str | None = None
max_value: int | None = None
min_value: int | None = None
step: int | None = None
ENTITY_DESCRIPTIONS = { ENTITY_DESCRIPTIONS = {
@ -47,13 +58,19 @@ ENTITY_DESCRIPTIONS = {
} }
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 number entity.""" """Set up the deCONZ number entity."""
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_sensor(sensors=gateway.api.sensors.values()): def async_add_sensor(
sensors: list[Presence] | ValuesView[Presence] = gateway.api.sensors.values(),
) -> None:
"""Add number config sensor from deCONZ.""" """Add number config sensor from deCONZ."""
entities = [] entities = []
@ -92,13 +109,19 @@ class DeconzNumber(DeconzDevice, NumberEntity):
"""Representation of a deCONZ number entity.""" """Representation of a deCONZ number entity."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Presence
def __init__(self, device, gateway, description): def __init__(
self,
device: Presence,
gateway: DeconzGateway,
description: DeconzNumberEntityDescription,
) -> None:
"""Initialize deCONZ number entity.""" """Initialize deCONZ number entity."""
self.entity_description = description self.entity_description: DeconzNumberEntityDescription = description
super().__init__(device, gateway) super().__init__(device, gateway)
self._attr_name = f"{self._device.name} {description.suffix}" self._attr_name = f"{device.name} {description.suffix}"
self._attr_max_value = description.max_value self._attr_max_value = description.max_value
self._attr_min_value = description.min_value self._attr_min_value = description.min_value
self._attr_step = description.step self._attr_step = description.step
@ -113,7 +136,7 @@ class DeconzNumber(DeconzDevice, NumberEntity):
@property @property
def value(self) -> float: def value(self) -> float:
"""Return the value of the sensor property.""" """Return the value of the sensor property."""
return getattr(self._device, self.entity_description.device_property) return getattr(self._device, self.entity_description.device_property) # type: ignore[no-any-return]
async def async_set_value(self, value: float) -> None: async def async_set_value(self, value: float) -> None:
"""Set sensor config.""" """Set sensor config."""

View file

@ -1,9 +1,14 @@
"""Support for deCONZ sensors.""" """Support for deCONZ sensors."""
from __future__ import annotations
from collections.abc import ValuesView
from pydeconz.sensor import ( from pydeconz.sensor import (
AirQuality, AirQuality,
Battery, Battery,
Consumption, Consumption,
Daylight, Daylight,
DeconzSensor as PydeconzSensor,
GenericStatus, GenericStatus,
Humidity, Humidity,
LightLevel, LightLevel,
@ -22,6 +27,7 @@ from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
ATTR_VOLTAGE, ATTR_VOLTAGE,
@ -40,15 +46,17 @@ from homeassistant.const import (
PRESSURE_HPA, PRESSURE_HPA,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import ATTR_DARK, ATTR_ON from .const import ATTR_DARK, ATTR_ON
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_SENSORS = ( DECONZ_SENSORS = (
AirQuality, AirQuality,
@ -119,7 +127,11 @@ ENTITY_DESCRIPTIONS = {
} }
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 sensors.""" """Set up the deCONZ sensors."""
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()
@ -127,13 +139,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
battery_handler = DeconzBatteryHandler(gateway) battery_handler = DeconzBatteryHandler(gateway)
@callback @callback
def async_add_sensor(sensors=gateway.api.sensors.values()): def async_add_sensor(
sensors: list[PydeconzSensor]
| ValuesView[PydeconzSensor] = gateway.api.sensors.values(),
) -> None:
"""Add sensors from deCONZ. """Add sensors from deCONZ.
Create DeconzBattery if sensor has a battery attribute. Create DeconzBattery if sensor has a battery attribute.
Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor.
""" """
entities = [] entities: list[DeconzBattery | DeconzSensor | DeconzTemperature] = []
for sensor in sensors: for sensor in sensors:
@ -184,8 +199,9 @@ class DeconzSensor(DeconzDevice, SensorEntity):
"""Representation of a deCONZ sensor.""" """Representation of a deCONZ sensor."""
TYPE = DOMAIN TYPE = DOMAIN
_device: PydeconzSensor
def __init__(self, device, gateway): def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None:
"""Initialize deCONZ binary sensor.""" """Initialize deCONZ binary sensor."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -193,19 +209,19 @@ class DeconzSensor(DeconzDevice, SensorEntity):
self.entity_description = entity_description self.entity_description = entity_description
@callback @callback
def async_update_callback(self): def async_update_callback(self) -> None:
"""Update the sensor's state.""" """Update the sensor's state."""
keys = {"on", "reachable", "state"} keys = {"on", "reachable", "state"}
if self._device.changed_keys.intersection(keys): if self._device.changed_keys.intersection(keys):
super().async_update_callback() super().async_update_callback()
@property @property
def native_value(self): def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._device.state return self._device.state # type: ignore[no-any-return]
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, bool | float | int | None]:
"""Return the state attributes of the sensor.""" """Return the state attributes of the sensor."""
attr = {} attr = {}
@ -243,8 +259,9 @@ class DeconzTemperature(DeconzDevice, SensorEntity):
""" """
TYPE = DOMAIN TYPE = DOMAIN
_device: PydeconzSensor
def __init__(self, device, gateway): def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None:
"""Initialize deCONZ temperature sensor.""" """Initialize deCONZ temperature sensor."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -252,29 +269,30 @@ class DeconzTemperature(DeconzDevice, SensorEntity):
self._attr_name = f"{self._device.name} Temperature" self._attr_name = f"{self._device.name} Temperature"
@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 f"{self.serial}-temperature" return f"{self.serial}-temperature"
@callback @callback
def async_update_callback(self): def async_update_callback(self) -> None:
"""Update the sensor's state.""" """Update the sensor's state."""
keys = {"temperature", "reachable"} keys = {"temperature", "reachable"}
if self._device.changed_keys.intersection(keys): if self._device.changed_keys.intersection(keys):
super().async_update_callback() super().async_update_callback()
@property @property
def native_value(self): def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._device.secondary_temperature return self._device.secondary_temperature # type: ignore[no-any-return]
class DeconzBattery(DeconzDevice, SensorEntity): class DeconzBattery(DeconzDevice, SensorEntity):
"""Battery class for when a device is only represented as an event.""" """Battery class for when a device is only represented as an event."""
TYPE = DOMAIN TYPE = DOMAIN
_device: PydeconzSensor
def __init__(self, device, gateway): def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None:
"""Initialize deCONZ battery level sensor.""" """Initialize deCONZ battery level sensor."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -282,14 +300,14 @@ class DeconzBattery(DeconzDevice, SensorEntity):
self._attr_name = f"{self._device.name} Battery Level" self._attr_name = f"{self._device.name} Battery Level"
@callback @callback
def async_update_callback(self): def async_update_callback(self) -> None:
"""Update the battery's state, if needed.""" """Update the battery's state, if needed."""
keys = {"battery", "reachable"} keys = {"battery", "reachable"}
if self._device.changed_keys.intersection(keys): if self._device.changed_keys.intersection(keys):
super().async_update_callback() super().async_update_callback()
@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.
Normally there should only be one battery sensor per device from deCONZ. Normally there should only be one battery sensor per device from deCONZ.
@ -305,12 +323,12 @@ class DeconzBattery(DeconzDevice, SensorEntity):
return f"{self.serial}-battery" return f"{self.serial}-battery"
@property @property
def native_value(self): def native_value(self) -> StateType:
"""Return the state of the battery.""" """Return the state of the battery."""
return self._device.battery return self._device.battery # type: ignore[no-any-return]
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes of the battery.""" """Return the state attributes of the battery."""
attr = {} attr = {}
@ -325,21 +343,20 @@ class DeconzBattery(DeconzDevice, SensorEntity):
class DeconzSensorStateTracker: class DeconzSensorStateTracker:
"""Track sensors without a battery state and signal when battery state exist.""" """Track sensors without a battery state and signal when battery state exist."""
def __init__(self, sensor, gateway): def __init__(self, sensor: PydeconzSensor, gateway: DeconzGateway) -> None:
"""Set up tracker.""" """Set up tracker."""
self.sensor = sensor self.sensor = sensor
self.gateway = gateway self.gateway = gateway
sensor.register_callback(self.async_update_callback) sensor.register_callback(self.async_update_callback)
@callback @callback
def close(self): def close(self) -> None:
"""Clean up tracker.""" """Clean up tracker."""
self.sensor.remove_callback(self.async_update_callback) self.sensor.remove_callback(self.async_update_callback)
self.gateway = None
self.sensor = None self.sensor = None
@callback @callback
def async_update_callback(self): def async_update_callback(self) -> None:
"""Sensor state updated.""" """Sensor state updated."""
if "battery" in self.sensor.changed_keys: if "battery" in self.sensor.changed_keys:
async_dispatcher_send( async_dispatcher_send(
@ -352,13 +369,13 @@ class DeconzSensorStateTracker:
class DeconzBatteryHandler: class DeconzBatteryHandler:
"""Creates and stores trackers for sensors without a battery state.""" """Creates and stores trackers for sensors without a battery state."""
def __init__(self, gateway): def __init__(self, gateway: DeconzGateway) -> None:
"""Set up battery handler.""" """Set up battery handler."""
self.gateway = gateway self.gateway = gateway
self._trackers = set() self._trackers: set[DeconzSensorStateTracker] = set()
@callback @callback
def create_tracker(self, sensor): def create_tracker(self, sensor: PydeconzSensor) -> None:
"""Create new tracker for battery state.""" """Create new tracker for battery state."""
for tracker in self._trackers: for tracker in self._trackers:
if sensor == tracker.sensor: if sensor == tracker.sensor:
@ -366,7 +383,7 @@ class DeconzBatteryHandler:
self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway)) self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway))
@callback @callback
def remove_tracker(self, sensor): def remove_tracker(self, sensor: PydeconzSensor) -> None:
"""Remove tracker of battery state.""" """Remove tracker of battery state."""
for tracker in self._trackers: for tracker in self._trackers:
if sensor == tracker.sensor: if sensor == tracker.sensor: