From cc7a0e3c245d7296bf82233ae5a9d6828836390b Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 23 May 2022 15:41:56 +0200 Subject: [PATCH] Streamline setup of deCONZ sensor platform (#71905) --- homeassistant/components/deconz/gateway.py | 47 +------ homeassistant/components/deconz/sensor.py | 133 +++++++------------- homeassistant/components/deconz/services.py | 3 - 3 files changed, 49 insertions(+), 134 deletions(-) diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index d59c5e2d1609..1f6a45f3ad65 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -9,12 +9,7 @@ from typing import TYPE_CHECKING, Any, cast import async_timeout 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.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.const import CONF_API_KEY, CONF_HOST, CONF_PORT @@ -57,7 +52,6 @@ class DeconzGateway: self.config_entry = config_entry self.api = api - api.add_device_callback = self.async_add_device_callback api.connection_status_callback = self.async_connection_status_callback self.available = True @@ -67,14 +61,6 @@ class DeconzGateway: 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_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.entities: dict[str, set[str]] = {} self.events: list[DeconzAlarmEvent | DeconzEvent] = [] @@ -153,37 +139,6 @@ class DeconzGateway: self.ignore_state_updates = False 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: """Update device registry.""" if self.api.config.mac is None: @@ -237,7 +192,6 @@ class DeconzGateway: deconz_ids = [] if self.option_allow_clip_sensor: - self.async_add_device_callback(ResourceGroup.SENSOR.value) async_dispatcher_send(self.hass, self.signal_reload_clip_sensors) else: @@ -314,6 +268,7 @@ async def get_deconz_session( config[CONF_HOST], config[CONF_PORT], config[CONF_API_KEY], + legacy_add_device=False, ) try: async with async_timeout.timeout(10): diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index bf29e8db4784..021dcf7168aa 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from datetime import datetime from pydeconz.interfaces.sensors import SensorResources +from pydeconz.models.event import EventType from pydeconz.models.sensor.air_quality import AirQuality from pydeconz.models.sensor.consumption import Consumption from pydeconz.models.sensor.daylight import Daylight @@ -38,10 +39,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback 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.entities[DOMAIN] = set() - battery_handler = DeconzBatteryHandler(gateway) - @callback - def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: - """Add sensors from deCONZ. + def async_add_sensor(_: EventType, sensor_id: str) -> None: + """Add sensor from deCONZ.""" + sensor = gateway.api.sensors[sensor_id] - Create DeconzBattery if sensor has a battery attribute. - Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. - """ - entities: list[DeconzSensor] = [] + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + return - if sensors is None: - sensors = gateway.api.sensors.values() + if sensor.battery is None: + DeconzBatteryTracker(sensor_id, gateway, async_add_entities) - for sensor in sensors: - - if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + for description in ( + ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS + ): + if ( + not hasattr(sensor, description.key) + or description.value_fn(sensor) is None + ): continue - if sensor.battery is None: - battery_handler.create_tracker(sensor) + async_add_entities([DeconzSensor(sensor, gateway, description)]) - known_entities = set(gateway.entities[DOMAIN]) - for description in ( - ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS - ): + config_entry.async_on_unload( + gateway.api.sensors.subscribe( + gateway.evaluate_add_device(async_add_sensor), + EventType.ADDED, + ) + ) + for sensor_id in gateway.api.sensors: + async_add_sensor(EventType.ADDED, sensor_id) - if ( - not hasattr(sensor, description.key) - or description.value_fn(sensor) is None - ): - continue - - 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) + @callback + def async_reload_clip_sensors() -> None: + """Load clip sensor sensors from deCONZ.""" + for sensor_id, sensor in gateway.api.sensors.items(): + if sensor.type.startswith("CLIP"): + async_add_sensor(EventType.ADDED, sensor_id) config_entry.async_on_unload( async_dispatcher_connect( hass, - gateway.signal_new_sensor, - async_add_sensor, + gateway.signal_reload_clip_sensors, + async_reload_clip_sensors, ) ) - async_add_sensor( - [gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)] - ) - class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" @@ -398,52 +387,26 @@ class DeconzSensor(DeconzDevice, SensorEntity): return attr -class DeconzSensorStateTracker: - """Track sensors without a battery state and signal when battery state exist.""" +class DeconzBatteryTracker: + """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.""" - self.sensor = sensor + self.sensor = gateway.api.sensors[sensor_id] self.gateway = gateway - sensor.register_callback(self.async_update_callback) - - @callback - def close(self) -> None: - """Clean up tracker.""" - self.sensor.remove_callback(self.async_update_callback) + self.async_add_entities = async_add_entities + self.unsub = self.sensor.subscribe(self.async_update_callback) @callback def async_update_callback(self) -> None: - """Sensor state updated.""" + """Update the device's state.""" if "battery" in self.sensor.changed_keys: - async_dispatcher_send( - self.gateway.hass, - self.gateway.signal_new_sensor, - [self.sensor], + self.unsub() + self.async_add_entities( + [DeconzSensor(self.sensor, self.gateway, SENSOR_DESCRIPTIONS[0])] ) - - -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 diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index a9a5172d72ed..e4399e53524c 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -147,9 +147,6 @@ async def async_refresh_devices_service(gateway: DeconzGateway) -> None: gateway.load_ignored_devices() 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: """Remove orphaned deCONZ entries from device and entity registries."""