Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 1 addition & 46 deletions homeassistant/components/deconz/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@

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
Expand Down Expand Up @@ -57,7 +52,6 @@ def __init__(
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
Expand All @@ -67,14 +61,6 @@ def __init__(
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] = []
Expand Down Expand Up @@ -153,37 +139,6 @@ def async_connection_status_callback(self, available: bool) -> None:
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:
Expand Down Expand Up @@ -237,7 +192,6 @@ async def options_updated(self) -> None:
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:
Expand Down Expand Up @@ -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):
Expand Down
133 changes: 48 additions & 85 deletions homeassistant/components/deconz/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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
Expand Down Expand Up @@ -38,10 +39,7 @@
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
Expand Down Expand Up @@ -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.

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 sensors is None:
sensors = gateway.api.sensors.values()
def async_add_sensor(_: EventType, sensor_id: str) -> None:
"""Add sensor from deCONZ."""
sensor = gateway.api.sensors[sensor_id]

for sensor in sensors:
if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"):
return

if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"):
continue

if sensor.battery is None:
battery_handler.create_tracker(sensor)
if sensor.battery is None:
DeconzBatteryTracker(sensor_id, gateway, async_add_entities)

known_entities = set(gateway.entities[DOMAIN])
for description in (
ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS
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 (
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)
async_add_entities([DeconzSensor(sensor, gateway, description)])

if description.key == "battery":
battery_handler.remove_tracker(sensor)
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 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."""
Expand Down Expand Up @@ -398,52 +387,26 @@ def extra_state_attributes(self) -> dict[str, bool | float | int | str | None]:
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
3 changes: 0 additions & 3 deletions homeassistant/components/deconz/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down