Add typing to deCONZ Number and Sensor platforms (#59604)
This commit is contained in:
parent
29e0ef604e
commit
0339761e72
2 changed files with 84 additions and 44 deletions
|
@ -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."""
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue