Add typing to deCONZ Alarm Control Panel and Binary Sensor platforms (#59611)

* Add typing to deCONZ Alarm Control Panel and Binary Sensor platforms

* Address review comments

* Don't use asserts, use # type: ignore[no-any-return]

* Improve lazy typing of dict
This commit is contained in:
Robert Svensson 2021-11-16 20:01:10 +01:00 committed by GitHub
parent 9faf3996db
commit 4642a70651
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 27 deletions

View File

@ -1,6 +1,9 @@
"""Support for deCONZ alarm control panel devices.""" """Support for deCONZ alarm control panel devices."""
from __future__ import annotations from __future__ import annotations
from collections.abc import ValuesView
from pydeconz.alarm_system import AlarmSystem
from pydeconz.sensor import ( from pydeconz.sensor import (
ANCILLARY_CONTROL_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_AWAY,
ANCILLARY_CONTROL_ARMED_NIGHT, ANCILLARY_CONTROL_ARMED_NIGHT,
@ -23,6 +26,7 @@ from homeassistant.components.alarm_control_panel import (
SUPPORT_ALARM_ARM_NIGHT, SUPPORT_ALARM_ARM_NIGHT,
AlarmControlPanelEntity, AlarmControlPanelEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME,
@ -32,11 +36,12 @@ from homeassistant.const import (
STATE_ALARM_PENDING, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, STATE_ALARM_TRIGGERED,
) )
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
DECONZ_TO_ALARM_STATE = { DECONZ_TO_ALARM_STATE = {
ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY, ANCILLARY_CONTROL_ARMED_AWAY: STATE_ALARM_ARMED_AWAY,
@ -52,14 +57,21 @@ DECONZ_TO_ALARM_STATE = {
} }
def get_alarm_system_for_unique_id(gateway, unique_id: str): def get_alarm_system_for_unique_id(
gateway: DeconzGateway, unique_id: str
) -> AlarmSystem | None:
"""Retrieve alarm system unique ID is registered to.""" """Retrieve alarm system unique ID is registered to."""
for alarm_system in gateway.api.alarmsystems.values(): for alarm_system in gateway.api.alarmsystems.values():
if unique_id in alarm_system.devices: if unique_id in alarm_system.devices:
return alarm_system return alarm_system
return None
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 the deCONZ alarm control panel devices. """Set up the deCONZ alarm control panel devices.
Alarm control panels are based on the same device class as sensors in deCONZ. Alarm control panels are based on the same device class as sensors in deCONZ.
@ -68,7 +80,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_alarm_control_panel(sensors=gateway.api.sensors.values()) -> None: def async_add_alarm_control_panel(
sensors: list[AncillaryControl]
| ValuesView[AncillaryControl] = gateway.api.sensors.values(),
) -> None:
"""Add alarm control panel devices from deCONZ.""" """Add alarm control panel devices from deCONZ."""
entities = [] entities = []
@ -77,10 +92,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities) -> None:
if ( if (
isinstance(sensor, AncillaryControl) isinstance(sensor, AncillaryControl)
and sensor.unique_id not in gateway.entities[DOMAIN] and sensor.unique_id not in gateway.entities[DOMAIN]
and get_alarm_system_for_unique_id(gateway, sensor.unique_id) and (
alarm_system := get_alarm_system_for_unique_id(
gateway, sensor.unique_id
)
)
is not None
): ):
entities.append(DeconzAlarmControlPanel(sensor, gateway)) entities.append(DeconzAlarmControlPanel(sensor, gateway, alarm_system))
if entities: if entities:
async_add_entities(entities) async_add_entities(entities)
@ -100,16 +120,22 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity):
"""Representation of a deCONZ alarm control panel.""" """Representation of a deCONZ alarm control panel."""
TYPE = DOMAIN TYPE = DOMAIN
_device: AncillaryControl
_attr_code_format = FORMAT_NUMBER _attr_code_format = FORMAT_NUMBER
_attr_supported_features = ( _attr_supported_features = (
SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT
) )
def __init__(self, device, gateway) -> None: def __init__(
self,
device: AncillaryControl,
gateway: DeconzGateway,
alarm_system: AlarmSystem,
) -> None:
"""Set up alarm control panel device.""" """Set up alarm control panel device."""
super().__init__(device, gateway) super().__init__(device, gateway)
self.alarm_system = get_alarm_system_for_unique_id(gateway, device.unique_id) self.alarm_system = alarm_system
@callback @callback
def async_update_callback(self) -> None: def async_update_callback(self) -> None:
@ -124,20 +150,20 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity):
@property @property
def state(self) -> str | None: def state(self) -> str | None:
"""Return the state of the control panel.""" """Return the state of the control panel."""
return DECONZ_TO_ALARM_STATE.get(self._device.state) return DECONZ_TO_ALARM_STATE.get(self._device.panel)
async def async_alarm_arm_away(self, code: None = None) -> None: async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command.""" """Send arm away command."""
await self.alarm_system.arm_away(code) await self.alarm_system.arm_away(code)
async def async_alarm_arm_home(self, code: None = None) -> None: async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command.""" """Send arm home command."""
await self.alarm_system.arm_stay(code) await self.alarm_system.arm_stay(code)
async def async_alarm_arm_night(self, code: None = None) -> None: async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command.""" """Send arm night command."""
await self.alarm_system.arm_night(code) await self.alarm_system.arm_night(code)
async def async_alarm_disarm(self, code: None = None) -> None: async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
await self.alarm_system.disarm(code) await self.alarm_system.disarm(code)

View File

@ -1,7 +1,13 @@
"""Support for deCONZ binary sensors.""" """Support for deCONZ binary sensors."""
from __future__ import annotations
from collections.abc import ValuesView
from pydeconz.sensor import ( from pydeconz.sensor import (
Alarm, Alarm,
CarbonMonoxide, CarbonMonoxide,
DeconzBinarySensor as PydeconzBinarySensor,
DeconzSensor as PydeconzSensor,
Fire, Fire,
GenericFlag, GenericFlag,
OpenClose, OpenClose,
@ -22,13 +28,15 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, ENTITY_CATEGORY_DIAGNOSTIC from homeassistant.const import ATTR_TEMPERATURE, ENTITY_CATEGORY_DIAGNOSTIC
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 .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_BINARY_SENSORS = ( DECONZ_BINARY_SENSORS = (
Alarm, Alarm,
@ -73,15 +81,22 @@ 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 binary sensor.""" """Set up the deCONZ binary sensor."""
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[PydeconzSensor]
| ValuesView[PydeconzSensor] = gateway.api.sensors.values(),
) -> None:
"""Add binary sensor from deCONZ.""" """Add binary sensor from deCONZ."""
entities = [] entities: list[DeconzBinarySensor | DeconzTampering] = []
for sensor in sensors: for sensor in sensors:
@ -120,8 +135,9 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity):
"""Representation of a deCONZ binary sensor.""" """Representation of a deCONZ binary sensor."""
TYPE = DOMAIN TYPE = DOMAIN
_device: PydeconzBinarySensor
def __init__(self, device, gateway): def __init__(self, device: PydeconzBinarySensor, gateway: DeconzGateway) -> None:
"""Initialize deCONZ binary sensor.""" """Initialize deCONZ binary sensor."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -129,21 +145,21 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity):
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 is_on(self): def is_on(self) -> bool:
"""Return true if sensor is on.""" """Return true if sensor is on."""
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 | list | None]:
"""Return the state attributes of the sensor.""" """Return the state attributes of the sensor."""
attr = {} attr: dict[str, bool | float | int | list | None] = {}
if self._device.on is not None: if self._device.on is not None:
attr[ATTR_ON] = self._device.on attr[ATTR_ON] = self._device.on
@ -168,11 +184,12 @@ class DeconzTampering(DeconzDevice, BinarySensorEntity):
"""Representation of a deCONZ tampering sensor.""" """Representation of a deCONZ tampering sensor."""
TYPE = DOMAIN TYPE = DOMAIN
_device: PydeconzSensor
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC _attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
_attr_device_class = DEVICE_CLASS_TAMPER _attr_device_class = DEVICE_CLASS_TAMPER
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,4 +210,4 @@ class DeconzTampering(DeconzDevice, BinarySensorEntity):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._device.tampered return self._device.tampered # type: ignore[no-any-return]