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 collections.abc import ValuesView
from dataclasses import dataclass
from pydeconz.sensor import PRESENCE_DELAY, Presence
@ -11,25 +12,35 @@ from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
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.entity_platform import AddEntitiesCallback
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
from .gateway import DeconzGateway, get_gateway_from_config_entry
@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."""
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 = {
@ -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."""
gateway = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set()
@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."""
entities = []
@ -92,13 +109,19 @@ class DeconzNumber(DeconzDevice, NumberEntity):
"""Representation of a deCONZ number entity."""
TYPE = DOMAIN
_device: Presence
def __init__(self, device, gateway, description):
def __init__(
self,
device: Presence,
gateway: DeconzGateway,
description: DeconzNumberEntityDescription,
) -> None:
"""Initialize deCONZ number entity."""
self.entity_description = description
self.entity_description: DeconzNumberEntityDescription = description
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_min_value = description.min_value
self._attr_step = description.step
@ -113,7 +136,7 @@ class DeconzNumber(DeconzDevice, NumberEntity):
@property
def value(self) -> float:
"""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:
"""Set sensor config."""

View file

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