Streamline setup of deCONZ sensor platform ()

This commit is contained in:
Robert Svensson 2022-05-23 15:41:56 +02:00 committed by GitHub
parent 82d4d96672
commit cc7a0e3c24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 134 deletions
homeassistant/components/deconz

View file

@ -9,12 +9,7 @@ from typing import TYPE_CHECKING, Any, cast
import async_timeout import async_timeout
from pydeconz import DeconzSession, errors from pydeconz import DeconzSession, errors
from pydeconz.models import ResourceGroup
from pydeconz.models.alarm_system import AlarmSystem as DeconzAlarmSystem
from pydeconz.models.event import EventType from pydeconz.models.event import EventType
from pydeconz.models.group import Group as DeconzGroup
from pydeconz.models.light import LightBase as DeconzLight
from pydeconz.models.sensor import SensorBase as DeconzSensor
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
@ -57,7 +52,6 @@ class DeconzGateway:
self.config_entry = config_entry self.config_entry = config_entry
self.api = api self.api = api
api.add_device_callback = self.async_add_device_callback
api.connection_status_callback = self.async_connection_status_callback api.connection_status_callback = self.async_connection_status_callback
self.available = True self.available = True
@ -67,14 +61,6 @@ class DeconzGateway:
self.signal_reload_groups = f"deconz_reload_group_{config_entry.entry_id}" self.signal_reload_groups = f"deconz_reload_group_{config_entry.entry_id}"
self.signal_reload_clip_sensors = f"deconz_reload_clip_{config_entry.entry_id}" self.signal_reload_clip_sensors = f"deconz_reload_clip_{config_entry.entry_id}"
self.signal_new_light = f"deconz_new_light_{config_entry.entry_id}"
self.signal_new_sensor = f"deconz_new_sensor_{config_entry.entry_id}"
self.deconz_resource_type_to_signal_new_device = {
ResourceGroup.LIGHT.value: self.signal_new_light,
ResourceGroup.SENSOR.value: self.signal_new_sensor,
}
self.deconz_ids: dict[str, str] = {} self.deconz_ids: dict[str, str] = {}
self.entities: dict[str, set[str]] = {} self.entities: dict[str, set[str]] = {}
self.events: list[DeconzAlarmEvent | DeconzEvent] = [] self.events: list[DeconzAlarmEvent | DeconzEvent] = []
@ -153,37 +139,6 @@ class DeconzGateway:
self.ignore_state_updates = False self.ignore_state_updates = False
async_dispatcher_send(self.hass, self.signal_reachable) async_dispatcher_send(self.hass, self.signal_reachable)
@callback
def async_add_device_callback(
self,
resource_type: str,
device: DeconzAlarmSystem
| DeconzGroup
| DeconzLight
| DeconzSensor
| list[DeconzAlarmSystem | DeconzGroup | DeconzLight | DeconzSensor]
| None = None,
force: bool = False,
) -> None:
"""Handle event of new device creation in deCONZ."""
if (
not force
and not self.option_allow_new_devices
or resource_type not in self.deconz_resource_type_to_signal_new_device
):
return
args = []
if device is not None and not isinstance(device, list):
args.append([device])
async_dispatcher_send(
self.hass,
self.deconz_resource_type_to_signal_new_device[resource_type],
*args, # Don't send device if None, it would override default value in listeners
)
async def async_update_device_registry(self) -> None: async def async_update_device_registry(self) -> None:
"""Update device registry.""" """Update device registry."""
if self.api.config.mac is None: if self.api.config.mac is None:
@ -237,7 +192,6 @@ class DeconzGateway:
deconz_ids = [] deconz_ids = []
if self.option_allow_clip_sensor: if self.option_allow_clip_sensor:
self.async_add_device_callback(ResourceGroup.SENSOR.value)
async_dispatcher_send(self.hass, self.signal_reload_clip_sensors) async_dispatcher_send(self.hass, self.signal_reload_clip_sensors)
else: else:
@ -314,6 +268,7 @@ async def get_deconz_session(
config[CONF_HOST], config[CONF_HOST],
config[CONF_PORT], config[CONF_PORT],
config[CONF_API_KEY], config[CONF_API_KEY],
legacy_add_device=False,
) )
try: try:
async with async_timeout.timeout(10): async with async_timeout.timeout(10):

View file

@ -6,6 +6,7 @@ from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from pydeconz.interfaces.sensors import SensorResources from pydeconz.interfaces.sensors import SensorResources
from pydeconz.models.event import EventType
from pydeconz.models.sensor.air_quality import AirQuality from pydeconz.models.sensor.air_quality import AirQuality
from pydeconz.models.sensor.consumption import Consumption from pydeconz.models.sensor.consumption import Consumption
from pydeconz.models.sensor.daylight import Daylight from pydeconz.models.sensor.daylight import Daylight
@ -38,10 +39,7 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant, 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,
)
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
@ -244,61 +242,52 @@ async def async_setup_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()
battery_handler = DeconzBatteryHandler(gateway)
@callback @callback
def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: def async_add_sensor(_: EventType, sensor_id: str) -> None:
"""Add sensors from deCONZ. """Add sensor from deCONZ."""
sensor = gateway.api.sensors[sensor_id]
Create DeconzBattery if sensor has a battery attribute. if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"):
Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. return
"""
entities: list[DeconzSensor] = []
if sensors is None: if sensor.battery is None:
sensors = gateway.api.sensors.values() DeconzBatteryTracker(sensor_id, gateway, async_add_entities)
for sensor in sensors: for description in (
ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS
if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): ):
if (
not hasattr(sensor, description.key)
or description.value_fn(sensor) is None
):
continue continue
if sensor.battery is None: async_add_entities([DeconzSensor(sensor, gateway, description)])
battery_handler.create_tracker(sensor)
known_entities = set(gateway.entities[DOMAIN]) config_entry.async_on_unload(
for description in ( gateway.api.sensors.subscribe(
ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS gateway.evaluate_add_device(async_add_sensor),
): EventType.ADDED,
)
)
for sensor_id in gateway.api.sensors:
async_add_sensor(EventType.ADDED, sensor_id)
if ( @callback
not hasattr(sensor, description.key) def async_reload_clip_sensors() -> None:
or description.value_fn(sensor) is None """Load clip sensor sensors from deCONZ."""
): for sensor_id, sensor in gateway.api.sensors.items():
continue if sensor.type.startswith("CLIP"):
async_add_sensor(EventType.ADDED, sensor_id)
new_entity = DeconzSensor(sensor, gateway, description)
if new_entity.unique_id not in known_entities:
entities.append(new_entity)
if description.key == "battery":
battery_handler.remove_tracker(sensor)
if entities:
async_add_entities(entities)
config_entry.async_on_unload( config_entry.async_on_unload(
async_dispatcher_connect( async_dispatcher_connect(
hass, hass,
gateway.signal_new_sensor, gateway.signal_reload_clip_sensors,
async_add_sensor, async_reload_clip_sensors,
) )
) )
async_add_sensor(
[gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)]
)
class DeconzSensor(DeconzDevice, SensorEntity): class DeconzSensor(DeconzDevice, SensorEntity):
"""Representation of a deCONZ sensor.""" """Representation of a deCONZ sensor."""
@ -398,52 +387,26 @@ class DeconzSensor(DeconzDevice, SensorEntity):
return attr return attr
class DeconzSensorStateTracker: class DeconzBatteryTracker:
"""Track sensors without a battery state and signal when battery state exist.""" """Track sensors without a battery state and add entity when battery state exist."""
def __init__(self, sensor: SensorResources, gateway: DeconzGateway) -> None: def __init__(
self,
sensor_id: str,
gateway: DeconzGateway,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up tracker.""" """Set up tracker."""
self.sensor = sensor self.sensor = gateway.api.sensors[sensor_id]
self.gateway = gateway self.gateway = gateway
sensor.register_callback(self.async_update_callback) self.async_add_entities = async_add_entities
self.unsub = self.sensor.subscribe(self.async_update_callback)
@callback
def close(self) -> None:
"""Clean up tracker."""
self.sensor.remove_callback(self.async_update_callback)
@callback @callback
def async_update_callback(self) -> None: def async_update_callback(self) -> None:
"""Sensor state updated.""" """Update the device's state."""
if "battery" in self.sensor.changed_keys: if "battery" in self.sensor.changed_keys:
async_dispatcher_send( self.unsub()
self.gateway.hass, self.async_add_entities(
self.gateway.signal_new_sensor, [DeconzSensor(self.sensor, self.gateway, SENSOR_DESCRIPTIONS[0])]
[self.sensor],
) )
class DeconzBatteryHandler:
"""Creates and stores trackers for sensors without a battery state."""
def __init__(self, gateway: DeconzGateway) -> None:
"""Set up battery handler."""
self.gateway = gateway
self._trackers: set[DeconzSensorStateTracker] = set()
@callback
def create_tracker(self, sensor: SensorResources) -> None:
"""Create new tracker for battery state."""
for tracker in self._trackers:
if sensor == tracker.sensor:
return
self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway))
@callback
def remove_tracker(self, sensor: SensorResources) -> None:
"""Remove tracker of battery state."""
for tracker in self._trackers:
if sensor == tracker.sensor:
tracker.close()
self._trackers.remove(tracker)
break

View file

@ -147,9 +147,6 @@ async def async_refresh_devices_service(gateway: DeconzGateway) -> None:
gateway.load_ignored_devices() gateway.load_ignored_devices()
gateway.ignore_state_updates = False gateway.ignore_state_updates = False
for resource_type in gateway.deconz_resource_type_to_signal_new_device:
gateway.async_add_device_callback(resource_type, force=True)
async def async_remove_orphaned_entries_service(gateway: DeconzGateway) -> None: async def async_remove_orphaned_entries_service(gateway: DeconzGateway) -> None:
"""Remove orphaned deCONZ entries from device and entity registries.""" """Remove orphaned deCONZ entries from device and entity registries."""