Streamline setup of deCONZ sensor platform (#71905)

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

View File

@ -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):

View File

@ -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

View File

@ -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."""