From 28bc1b70ed11410cab2377b93c02d451ed8400ff Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 13 Jan 2026 02:53:32 -0800 Subject: [PATCH] refactor: extract light group detection into dedicated method Extract the light group turn-on detection logic into _member_turn_on_explains_group_turn_on() for better clarity: - Each method now has a single responsibility - The helper method has a descriptive name and docstring - The code flow is easier to follow This is a refactoring of the fix for issue #1378 with no behavior changes. --- custom_components/adaptive_lighting/switch.py | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 921d2c0d..1fdf509a 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -2704,11 +2704,50 @@ class AdaptiveLightingManager: ) return changed_attributes + def _member_turn_on_explains_group_turn_on(self, entity_id: str) -> bool: + """Check if a light group turned on because a member light was turned on. + + When a parent light group is turned off and then a child light is turned on + (e.g., by a motion sensor), the group turns back on as a side effect. + This method detects that scenario by checking if any member has a turn_on_event + that happened after the group's on→off event. + + 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 + + on_to_off_event = self.on_to_off_event.get(entity_id) + member_lights: list[str] = state.attributes.get("entity_id", []) + + for member in member_lights: + member_turn_on = self.turn_on_event.get(member) + if member_turn_on is None: + continue + # Only count if member was turned on AFTER the group was turned off + if on_to_off_event is None or ( + member_turn_on.time_fired > on_to_off_event.time_fired + ): + _LOGGER.debug( + "Light group '%s' turned on because member '%s' was turned on", + entity_id, + member, + ) + return True + + return False + def _off_to_on_state_event_is_from_turn_on( self, entity_id: str, off_to_on_event: Event[EventStateChangedData], ) -> bool: + """Check if an off→on state change was triggered by a light.turn_on call. + + For light groups, also checks if any member's turn_on explains the group's + turn-on (see _member_turn_on_explains_group_turn_on). + """ # Adaptive Lighting should never turn on lights itself if is_our_context(off_to_on_event.context) and not is_our_context( off_to_on_event.context, @@ -2723,36 +2762,17 @@ class AdaptiveLightingManager: off_to_on_event.context.id, off_to_on_event, ) - turn_on_event: Event | None = self.turn_on_event.get(entity_id) - id_off_to_on = off_to_on_event.context.id - if turn_on_event is not None and id_off_to_on == turn_on_event.context.id: + + # Check if this entity was directly turned on via service call + turn_on_event = self.turn_on_event.get(entity_id) + if ( + turn_on_event is not None + and off_to_on_event.context.id == turn_on_event.context.id + ): return True - # For light groups: check if any member light has a turn_on_event that could - # explain why the group turned on. This handles the case where child lights - # are turned on by an automation, causing the parent group to turn on as a - # side effect. See: https://github.com/basnijholt/adaptive-lighting/issues/1378 - state = self.hass.states.get(entity_id) - if state is not None and _is_light_group(state): - on_to_off_event = self.on_to_off_event.get(entity_id) - member_lights: list[str] = state.attributes.get("entity_id", []) - for member in member_lights: - member_turn_on = self.turn_on_event.get(member) - if member_turn_on is None: - continue - # Check if the member's turn_on happened after the group's on→off event. - # If so, the member's turn_on likely caused the group to turn on. - if on_to_off_event is None or ( - member_turn_on.time_fired > on_to_off_event.time_fired - ): - _LOGGER.debug( - "Light group '%s' turned on because member '%s' was turned on", - entity_id, - member, - ) - return True - - return False + # For light groups: check if a member's turn_on explains the group's turn-on + return self._member_turn_on_explains_group_turn_on(entity_id) async def just_turned_off( # noqa: PLR0911 self,