Skip to content
Draft
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
33 changes: 33 additions & 0 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,39 @@ async def test_entity_recomputation(zha_gateway: Gateway) -> None:
]


async def test_async_configure_does_not_queue_entities(zha_gateway: Gateway) -> None:
"""Test a reconfigure does not leave half-added entities behind."""
zigpy_dev = await zigpy_device_from_json(
zha_gateway.application_controller,
"tests/data/devices/ikea-of-sweden-tradfri-bulb-gu10-ws-400lm-0x23095631.json",
)
zha_device = await join_zigpy_device(zha_gateway, zigpy_dev)

entities_before = dict(zha_device.platform_entities)

await zha_device.async_configure()

# Discovery ran for the cluster config aggregation, but no entities were
# activated via `on_add()` and queued (nothing would ever flush them here)
assert zha_device._discovered_entities
assert not zha_device._pending_entities
assert zha_device.platform_entities == entities_before

# After a rebuild (same-quirk re-interview), the device has no live entities
# anymore, so configuration must activate and queue the entities again (e.g.
# the IAS zone enrollment entity has to listen during CIE configuration)
await zha_device.async_rebuild_from_zigpy_device(zigpy_dev)
await zha_device.async_configure()

assert zha_device._pending_entities

# The following initialization adds them, like on the initial join
await zha_device.async_initialize(from_cache=True)

assert not zha_device._pending_entities
assert set(zha_device.platform_entities) == set(entities_before)


async def test_add_entity_duplicate(zha_gateway: Gateway) -> None:
"""Test that adding a duplicate entity raises an error."""
zigpy_dev = await zigpy_device_from_json(
Expand Down
23 changes: 21 additions & 2 deletions zha/zigbee/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,17 @@ async def async_configure(self) -> None:
self.debug("applying quirks custom device configuration")
await self._zigpy_device.apply_custom_configuration()

self._discover_new_entities()
# On a reconfigure of a live device, discovery is only needed to
# aggregate the cluster configs. The entities must not be queued for
# adding then: nothing flushes the pending entities, so they would
# linger with active `on_add()` side effects (e.g. cluster listeners)
# without ever being added — the live entities already listen. Without
# live entities (initial join, or a rebuild after a re-interview), the
# entities must be activated, however: e.g. the IAS zone enrollment
# entity has to answer a device-initiated enroll request right after
# `configure_cluster_configs` writes the CIE address below.
# `async_initialize()` adds them and cleans up its own re-discoveries.
self._discover_new_entities(add_entities=not self._platform_entities)

# Configure binding and reporting from entity-level cluster configs
aggregated = aggregate_cluster_configs(self._discovered_entities)
Expand Down Expand Up @@ -1078,7 +1088,13 @@ def discover_entities(self) -> Iterator[BaseEntity]:
)
yield from discovery.discover_entities_for_endpoint(endpoint)

def _discover_new_entities(self) -> None:
def _discover_new_entities(self, *, add_entities: bool = True) -> None:
"""Discover entities and queue them to be added to the device.

With `add_entities=False`, only `self._discovered_entities` is populated
(e.g. for aggregating cluster configs): the entities are neither activated
via `on_add()` nor queued for `_add_pending_entities`.
"""
self._discovered_entities.clear()

# Iterate defensively so a failure in any single entity construction
Expand All @@ -1101,6 +1117,9 @@ def _discover_new_entities(self) -> None:
# Apply any metadata changes from quirks v2
self._apply_entity_metadata_changes(entity)

if not add_entities:
continue

entity.on_add()
self._pending_entities.append(entity)

Expand Down
Loading