mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-27 20:34:20 +02:00
fix: don't cancel adaptation when a light group turns on via a member with a reused context (#1483)
* fix: don't cancel adaptation when a light group turns on via a member with a reused context (#1378) When a member of a light group is turned on (e.g., by a motion sensor automation) while the group is off, the group turns on as a side effect, but Home Assistant may reuse the context of the earlier turn_off call for the group's state change. just_turned_off() saw matching context IDs and treated the state change as a polling artifact, cancelling adaptation. - Check whether the off->on state change comes from a light.turn_on call before the matching-context polling-artifact check, so automations that turn a light off and back on with a single (automation) context adapt correctly. - For light groups, allow adaptation when a member's turn_on event falls between the group's on->off and off->on state changes, bounded on both sides so stale member events are never treated as explanatory. - Document that integration-level groups (e.g., Zigbee2MQTT groups) should not be nested inside HA Light Groups managed by Adaptive Lighting. * fix: time-bound the same-context turn_on check instead of reordering Address review findings: - Reordering the turn_on-service check above the matching-context check reintroduced stale-event false negatives: turn_on_event entries are never cleaned up, so a 'turn_on -> delay -> turn_off(transition)' automation (one shared context) would defeat the polling-artifact guard and AL could turn a light back on right after it was turned off. Restore main's check order and instead add a time-bounded own-turn_on check inside the matching-context branch, symmetric with the group-member check. This also avoids emitting the 'should not happen' warning for self-context polling artifacts. - Add a regression test for the stale same-context turn_on case. - Add an end-to-end test driving the event-bus listeners for the #1378 scenario (group kept in manager.lights, as in the reported setups). - Docs: drop the inaccurate 'expands only one level deep' claim; explain that integration-level groups cannot be expanded and nested groups make tracking unpredictable.
This commit is contained in:
parent
d4d3d50ada
commit
3638fb3013
4 changed files with 282 additions and 1 deletions
|
|
@ -2746,7 +2746,45 @@ class AdaptiveLightingManager:
|
|||
id_off_to_on = off_to_on_event.context.id
|
||||
return turn_on_event is not None and id_off_to_on == turn_on_event.context.id
|
||||
|
||||
async def just_turned_off( # noqa: PLR0911
|
||||
def _member_turn_on_explains_group_turn_on(
|
||||
self,
|
||||
entity_id: str,
|
||||
on_to_off_event: Event[EventStateChangedData],
|
||||
off_to_on_event: Event[EventStateChangedData],
|
||||
) -> bool:
|
||||
"""Check if a light group's 'off' → 'on' is caused by a member's 'light.turn_on'.
|
||||
|
||||
When a member of a light group is turned on while the group is off, the
|
||||
group turns on as a side effect. Home Assistant may reuse the context of
|
||||
an earlier 'light.turn_off' call for the group's state change (entities
|
||||
keep their context for a few seconds), which makes the group's turn-on
|
||||
look like a polling artifact of the turn-off.
|
||||
See https://github.com/basnijholt/adaptive-lighting/issues/1378
|
||||
"""
|
||||
state = self.hass.states.get(entity_id)
|
||||
if state is None or not _is_light_group(state):
|
||||
return False
|
||||
members: list[str] = state.attributes[ATTR_ENTITY_ID]
|
||||
for member in members:
|
||||
member_turn_on = self.turn_on_event.get(member)
|
||||
if (
|
||||
member_turn_on is not None
|
||||
and on_to_off_event.time_fired
|
||||
< member_turn_on.time_fired
|
||||
<= off_to_on_event.time_fired
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"just_turned_off: Light group '%s' turned on because its member"
|
||||
" '%s' was turned on (context.id='%s'), so this is a legitimate"
|
||||
" turn-on, not a polling artifact.",
|
||||
entity_id,
|
||||
member,
|
||||
member_turn_on.context.id,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
async def just_turned_off( # noqa: PLR0911, PLR0912
|
||||
self,
|
||||
entity_id: str,
|
||||
) -> bool:
|
||||
|
|
@ -2774,6 +2812,34 @@ class AdaptiveLightingManager:
|
|||
return False
|
||||
|
||||
if off_to_on_event.context.id == on_to_off_event.context.id:
|
||||
# Matching context IDs usually mean a polling artifact (HA briefly
|
||||
# reports 'on' while the light is still turning off). However, the
|
||||
# context is also reused when e.g. one automation turns the light
|
||||
# off and later back on, or when an integration writes the state
|
||||
# with the entity's cached context. Only treat the state change as
|
||||
# a legitimate turn-on if a 'light.turn_on' call for this light (or
|
||||
# for a member of this light group) fired between the two state
|
||||
# changes.
|
||||
turn_on_event = self.turn_on_event.get(entity_id)
|
||||
if (
|
||||
turn_on_event is not None
|
||||
and on_to_off_event.time_fired
|
||||
< turn_on_event.time_fired
|
||||
<= off_to_on_event.time_fired
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"just_turned_off: 'light.turn_on' was called for '%s' between its"
|
||||
" 'on' → 'off' and 'off' → 'on' state changes, so this is a"
|
||||
" legitimate turn-on, not a polling artifact.",
|
||||
entity_id,
|
||||
)
|
||||
return False
|
||||
if self._member_turn_on_explains_group_turn_on(
|
||||
entity_id,
|
||||
on_to_off_event,
|
||||
off_to_on_event,
|
||||
):
|
||||
return False
|
||||
_LOGGER.debug(
|
||||
"just_turned_off: 'on' → 'off' state change has the same context.id as the"
|
||||
" 'off' → 'on' state change for '%s'. This is probably a false positive.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue