Streamline setup of deCONZ binary sensor platform (#71820)

This commit is contained in:
Robert Svensson 2022-05-17 00:04:57 +02:00 committed by GitHub
parent 03e98a9a32
commit 007c6d2236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 89 additions and 54 deletions

View File

@ -81,7 +81,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.sensors.ancillary_control.subscribe( gateway.api.sensors.ancillary_control.subscribe(
async_add_sensor, gateway.evaluate_add_device(async_add_sensor),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -5,6 +5,7 @@ from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from pydeconz.interfaces.sensors import SensorResources from pydeconz.interfaces.sensors import SensorResources
from pydeconz.models.event import EventType
from pydeconz.models.sensor.alarm import Alarm from pydeconz.models.sensor.alarm import Alarm
from pydeconz.models.sensor.carbon_monoxide import CarbonMonoxide from pydeconz.models.sensor.carbon_monoxide import CarbonMonoxide
from pydeconz.models.sensor.fire import Fire from pydeconz.models.sensor.fire import Fire
@ -188,48 +189,48 @@ async def async_setup_entry(
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_sensor(sensors: list[SensorResources] | None = None) -> None: def async_add_sensor(_: EventType, sensor_id: str) -> None:
"""Add binary sensor from deCONZ.""" """Add sensor from deCONZ."""
entities: list[DeconzBinarySensor] = [] sensor = gateway.api.sensors[sensor_id]
if sensors is None:
sensors = gateway.api.sensors.values()
for sensor in sensors:
if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"):
continue return
known_entities = set(gateway.entities[DOMAIN])
for description in ( for description in (
ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS
): ):
if ( if (
not hasattr(sensor, description.key) not hasattr(sensor, description.key)
or description.value_fn(sensor) is None or description.value_fn(sensor) is None
): ):
continue continue
new_sensor = DeconzBinarySensor(sensor, gateway, description) async_add_entities([DeconzBinarySensor(sensor, gateway, description)])
if new_sensor.unique_id not in known_entities:
entities.append(new_sensor)
if entities: config_entry.async_on_unload(
async_add_entities(entities) 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)
@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( 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 DeconzBinarySensor(DeconzDevice, BinarySensorEntity): class DeconzBinarySensor(DeconzDevice, BinarySensorEntity):
"""Representation of a deCONZ binary sensor.""" """Representation of a deCONZ binary sensor."""

View File

@ -106,7 +106,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.sensors.thermostat.subscribe( gateway.api.sensors.thermostat.subscribe(
async_add_climate, gateway.evaluate_add_device(async_add_climate),
EventType.ADDED, EventType.ADDED,
) )
) )
@ -115,7 +115,7 @@ async def async_setup_entry(
@callback @callback
def async_reload_clip_sensors() -> None: def async_reload_clip_sensors() -> None:
"""Load clip climate sensors from deCONZ.""" """Load clip sensors from deCONZ."""
for climate_id, climate in gateway.api.sensors.thermostat.items(): for climate_id, climate in gateway.api.sensors.thermostat.items():
if climate.type.startswith("CLIP"): if climate.type.startswith("CLIP"):
async_add_climate(EventType.ADDED, climate_id) async_add_climate(EventType.ADDED, climate_id)

View File

@ -45,7 +45,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.covers.subscribe( gateway.api.lights.covers.subscribe(
async_add_cover, gateway.evaluate_add_device(async_add_cover),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -66,14 +66,14 @@ async def async_setup_events(gateway: DeconzGateway) -> None:
gateway.config_entry.async_on_unload( gateway.config_entry.async_on_unload(
gateway.api.sensors.ancillary_control.subscribe( gateway.api.sensors.ancillary_control.subscribe(
async_add_sensor, gateway.evaluate_add_device(async_add_sensor),
EventType.ADDED, EventType.ADDED,
) )
) )
gateway.config_entry.async_on_unload( gateway.config_entry.async_on_unload(
gateway.api.sensors.switch.subscribe( gateway.api.sensors.switch.subscribe(
async_add_sensor, gateway.evaluate_add_device(async_add_sensor),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -50,7 +50,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.fans.subscribe( gateway.api.lights.fans.subscribe(
async_add_fan, gateway.evaluate_add_device(async_add_fan),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable
from types import MappingProxyType from types import MappingProxyType
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
@ -10,6 +11,7 @@ import async_timeout
from pydeconz import DeconzSession, errors from pydeconz import DeconzSession, errors
from pydeconz.models import ResourceGroup from pydeconz.models import ResourceGroup
from pydeconz.models.alarm_system import AlarmSystem as DeconzAlarmSystem 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.group import Group as DeconzGroup
from pydeconz.models.light import LightBase as DeconzLight from pydeconz.models.light import LightBase as DeconzLight
from pydeconz.models.sensor import SensorBase as DeconzSensor from pydeconz.models.sensor import SensorBase as DeconzSensor
@ -76,10 +78,14 @@ class DeconzGateway:
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] = []
self.ignored_devices: set[tuple[Callable[[EventType, str], None], str]] = set()
self._option_allow_deconz_groups = self.config_entry.options.get( self._option_allow_deconz_groups = self.config_entry.options.get(
CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS
) )
self.option_allow_new_devices = self.config_entry.options.get(
CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES
)
@property @property
def bridgeid(self) -> str: def bridgeid(self) -> str:
@ -112,12 +118,31 @@ class DeconzGateway:
CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS
) )
@property @callback
def option_allow_new_devices(self) -> bool: def evaluate_add_device(
"""Allow automatic adding of new devices.""" self, add_device_callback: Callable[[EventType, str], None]
return self.config_entry.options.get( ) -> Callable[[EventType, str], None]:
CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES """Wrap add_device_callback to check allow_new_devices option."""
)
def async_add_device(event: EventType, device_id: str) -> None:
"""Add device or add it to ignored_devices set.
If ignore_state_updates is True means device_refresh service is used.
Device_refresh is expected to load new devices.
"""
if not self.option_allow_new_devices and not self.ignore_state_updates:
self.ignored_devices.add((add_device_callback, device_id))
return
add_device_callback(event, device_id)
return async_add_device
@callback
def load_ignored_devices(self) -> None:
"""Load previously ignored devices."""
for add_entities, device_id in self.ignored_devices:
add_entities(EventType.ADDED, device_id)
self.ignored_devices.clear()
# Callbacks # Callbacks
@ -230,6 +255,14 @@ class DeconzGateway:
self._option_allow_deconz_groups = self.option_allow_deconz_groups self._option_allow_deconz_groups = self.option_allow_deconz_groups
option_allow_new_devices = self.config_entry.options.get(
CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES
)
if option_allow_new_devices and not self.option_allow_new_devices:
self.load_ignored_devices()
self.option_allow_new_devices = option_allow_new_devices
entity_registry = er.async_get(self.hass) entity_registry = er.async_get(self.hass)
for entity_id, deconz_id in self.deconz_ids.items(): for entity_id, deconz_id in self.deconz_ids.items():

View File

@ -95,7 +95,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.lights.subscribe( gateway.api.lights.lights.subscribe(
async_add_light, gateway.evaluate_add_device(async_add_light),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -34,7 +34,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.locks.subscribe( gateway.api.lights.locks.subscribe(
async_add_lock_from_light, gateway.evaluate_add_device(async_add_lock_from_light),
EventType.ADDED, EventType.ADDED,
) )
) )
@ -49,7 +49,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.sensors.door_lock.subscribe( gateway.api.sensors.door_lock.subscribe(
async_add_lock_from_sensor, gateway.evaluate_add_device(async_add_lock_from_sensor),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -77,7 +77,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.sensors.presence.subscribe( gateway.api.sensors.presence.subscribe(
async_add_sensor, gateway.evaluate_add_device(async_add_sensor),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -144,6 +144,7 @@ async def async_refresh_devices_service(gateway: DeconzGateway) -> None:
"""Refresh available devices from deCONZ.""" """Refresh available devices from deCONZ."""
gateway.ignore_state_updates = True gateway.ignore_state_updates = True
await gateway.api.refresh_state() await gateway.api.refresh_state()
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: for resource_type in gateway.deconz_resource_type_to_signal_new_device:

View File

@ -37,7 +37,7 @@ async def async_setup_entry(
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.sirens.subscribe( gateway.api.lights.sirens.subscribe(
async_add_siren, gateway.evaluate_add_device(async_add_siren),
EventType.ADDED, EventType.ADDED,
) )
) )

View File

@ -30,21 +30,21 @@ async def async_setup_entry(
gateway.entities[DOMAIN] = set() gateway.entities[DOMAIN] = set()
@callback @callback
def async_add_switch(_: EventType, light_id: str) -> None: def async_add_switch(_: EventType, switch_id: str) -> None:
"""Add switch from deCONZ.""" """Add switch from deCONZ."""
light = gateway.api.lights.lights[light_id] switch = gateway.api.lights.lights[switch_id]
if light.type not in POWER_PLUGS: if switch.type not in POWER_PLUGS:
return return
async_add_entities([DeconzPowerPlug(light, gateway)]) async_add_entities([DeconzPowerPlug(switch, gateway)])
config_entry.async_on_unload( config_entry.async_on_unload(
gateway.api.lights.lights.subscribe( gateway.api.lights.lights.subscribe(
async_add_switch, gateway.evaluate_add_device(async_add_switch),
EventType.ADDED, EventType.ADDED,
) )
) )
for light_id in gateway.api.lights.lights: for switch_id in gateway.api.lights.lights:
async_add_switch(EventType.ADDED, light_id) async_add_switch(EventType.ADDED, switch_id)
class DeconzPowerPlug(DeconzDevice, SwitchEntity): class DeconzPowerPlug(DeconzDevice, SwitchEntity):