feat: add expand_light_groups option

Some light group entities act as a proxy that must receive a single combined
`light.turn_on` call to function correctly — virtual mixers like
<https://github.com/mion00/color-temperature-light-mixer> for instance,
that blend a warm and a cold white channel into one entity. In such setups the
individual member entities only expose `ColorMode.BRIGHTNESS`, so sending
separate per-member commands bypasses the mixing logic.

Setting `expand_light_groups: false` keeps the group entity in `self.lights`
instead of expanding it to its members. Adaptation commands go to the group,
and the interceptor no longer skips group entities for that switch.

Default is `true` — no behaviour change for existing configurations.
This commit is contained in:
Leonhard Hesse 2026-04-16 23:11:24 +02:00
commit da4f486826
5 changed files with 36 additions and 11 deletions

View file

@ -272,6 +272,13 @@ DOCS[CONF_MULTI_LIGHT_INTERCEPT] = (
"Requires `intercept` to be enabled."
)
CONF_EXPAND_LIGHT_GROUPS, DEFAULT_EXPAND_LIGHT_GROUPS = "expand_light_groups", True
DOCS[CONF_EXPAND_LIGHT_GROUPS] = (
"Expand light groups to their individual member entities (`true`, default). "
"Set to `false` to send adaptation commands to the group entity directly "
"instead of its members."
)
SLEEP_MODE_SWITCH = "sleep_mode_switch"
ADAPT_COLOR_SWITCH = "adapt_color_switch"
ADAPT_BRIGHTNESS_SWITCH = "adapt_brightness_switch"
@ -405,6 +412,7 @@ VALIDATION_TUPLES: list[tuple[str, Any, Any]] = [
(CONF_INTERCEPT, DEFAULT_INTERCEPT, bool),
(CONF_MULTI_LIGHT_INTERCEPT, DEFAULT_MULTI_LIGHT_INTERCEPT, bool),
(CONF_INCLUDE_CONFIG_IN_ATTRIBUTES, DEFAULT_INCLUDE_CONFIG_IN_ATTRIBUTES, bool),
(CONF_EXPAND_LIGHT_GROUPS, DEFAULT_EXPAND_LIGHT_GROUPS, bool),
]

View file

@ -64,7 +64,8 @@
"skip_redundant_commands": "skip_redundant_commands: Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. 📉Disable if physical light states get out of sync with HA's recorded state.",
"intercept": "intercept: Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled.",
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝"
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
"expand_light_groups": "expand_light_groups: Expand light groups to individual member entities (`true`) or control the group entity directly (`false`)."
},
"data_description": {
"interval": "Frequency to adapt the lights, in seconds. 🔄",
@ -89,7 +90,8 @@
"take_over_control_mode": "The adaptation pausing mode when other sources change brightness and/or color of lights. `pause_all` always pauses both brightness and color adaptation. `pause_changed` pauses the adaptation of only the changed attributes and continues adapting unchanged attributes, e.g., continues color adaptation when only brightness was changed.",
"autoreset_control_seconds": "Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️",
"send_split_delay": "Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
"adapt_delay": "Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️"
"adapt_delay": "Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
"expand_light_groups": "When set to `false`, adaptation commands are sent to the group entity directly instead of its members."
}
}
},

View file

@ -97,6 +97,7 @@ from .const import (
CONF_BRIGHTNESS_MODE_TIME_DARK,
CONF_BRIGHTNESS_MODE_TIME_LIGHT,
CONF_DETECT_NON_HA_CHANGES,
CONF_EXPAND_LIGHT_GROUPS,
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
CONF_INITIAL_TRANSITION,
CONF_INTERCEPT,
@ -239,8 +240,10 @@ def _switches_with_lights(
continue
switch = data[config.entry_id][SWITCH_DOMAIN]
switch._expand_light_groups(hass=hass)
# Check if any of the lights are in the switch's lights
if set(switch.lights) & set(all_check_lights):
# A switch with expand_light_groups=False stores the group entity in
# switch.lights (not its children), so don't expand the incoming lights for it.
check_lights = all_check_lights if switch._expand_light_groups_flag else lights
if set(switch.lights) & set(check_lights):
switches.append(switch)
return switches
@ -943,6 +946,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
self._name,
)
self._multi_light_intercept = False
self._expand_light_groups_flag = data[CONF_EXPAND_LIGHT_GROUPS]
self._expand_light_groups() # updates manual control timers
location, _ = get_astral_location(self.hass)
@ -1027,7 +1031,10 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
def _expand_light_groups(self, hass: HomeAssistant | None = None) -> None:
hass = hass or self.hass
all_lights = _expand_light_groups(hass, self.lights)
if self._expand_light_groups_flag:
all_lights = _expand_light_groups(hass, self.lights)
else:
all_lights = list(self.lights) # keep group entities as-is
self.manager.lights.update(all_lights)
self.manager.set_auto_reset_manual_control_times(
all_lights,
@ -1848,8 +1855,12 @@ class AdaptiveLightingManager:
if (
not switch.is_on
or not switch._intercept
# Never adapt on light groups, because HA will make a separate light.turn_on
or ((e := self.hass.states.get(entity_id)) and _is_light_group(e))
# Never adapt on light groups when expanding, because HA will make a separate light.turn_on
or (
switch._expand_light_groups_flag
and (e := self.hass.states.get(entity_id))
and _is_light_group(e)
)
# Prevent adaptation of TURN_ON calls when light is already on,
# and of TOGGLE calls when toggling off.
or self.hass.states.is_state(entity_id, STATE_ON)

View file

@ -58,7 +58,8 @@
"include_config_in_attributes": "include_config_in_attributes: Alle Optionen als Attribute auf dem Schalter im Home Assistant anzeigen, wenn auf `true` gesetzt. 📝",
"multi_light_intercept": "multi_light_intercept: Abfangen und Anpassen von `light.turn_on`-Aufrufen, die auf mehrere Lichter aufrufen. ➗⚠️ Dies kann dazu führen, dass ein einzelner `light.turn_on`-Aufruf in mehrere Aufrufe aufgeteilt wird, z.B. wenn Lichter in verschiedenen Schaltern sind. Erfordert, dass `intercept` aktiviert ist.",
"transition_until_sleep": "transition_until_sleep: Wenn diese Option aktiviert ist, behandelt die adaptive Beleuchtung die Schlafeinstellungen als Minimum und geht nach Sonnenuntergang zu diesen Werten über. 🌙",
"intercept": "intercept: Abfangen und Anpassen von `light.turn_on`-Aufrufen, um eine sofortige Anpassung von Farbe und Helligkeit zu ermöglichen. 🏎️ Deaktivieren für Leuchten, die `light.turn_on` mit Farbe und Helligkeit nicht unterstützen."
"intercept": "intercept: Abfangen und Anpassen von `light.turn_on`-Aufrufen, um eine sofortige Anpassung von Farbe und Helligkeit zu ermöglichen. 🏎️ Deaktivieren für Leuchten, die `light.turn_on` mit Farbe und Helligkeit nicht unterstützen.",
"expand_light_groups": "expand_light_groups: Lichtgruppen auf einzelne Mitgliedsentitäten erweitern (`true`) oder die Gruppenentität direkt steuern (`false`)."
},
"data_description": {
"sunrise_offset": "Anpassung der Sonnenaufgangszeit mit positivem oder negativem Versatz in Sekunden. ⏰",
@ -82,7 +83,8 @@
"initial_transition": "Dauer des ersten Übergangs, wenn das Licht von `off` auf `on` schaltet, in Sekunden. ⏲️",
"sleep_rgb_or_color_temp": "Verwende entweder `rgb_color` oder `color_temp` im Schlafmodus. 🌙",
"sleep_transition": "Dauer des Übergangs, wenn der \"Schlafmodus\" umgeschaltet wird, in Sekunden. 😴",
"sunrise_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenaufgang ein. 🌅"
"sunrise_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenaufgang ein. 🌅",
"expand_light_groups": "Wenn auf `false` gesetzt, werden Anpassungsbefehle direkt an die Gruppenentität gesendet und nicht an die einzelnen Mitglieder."
}
}
},

View file

@ -65,7 +65,8 @@
"skip_redundant_commands": "skip_redundant_commands: Skip sending adaptation commands whose target state already equals the light's known state. Minimizes network traffic and improves the adaptation responsivity in some situations. 📉Disable if physical light states get out of sync with HA's recorded state.",
"intercept": "intercept: Intercept and adapt `light.turn_on` calls to enabling instantaneous color and brightness adaptation. 🏎️ Disable for lights that do not support `light.turn_on` with color and brightness.",
"multi_light_intercept": "multi_light_intercept: Intercept and adapt `light.turn_on` calls that target multiple lights. ➗⚠️ This might result in splitting up a single `light.turn_on` call into multiple calls, e.g., when lights are in different switches. Requires `intercept` to be enabled.",
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝"
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
"expand_light_groups": "expand_light_groups: Expand light groups to individual member entities (`true`) or control the group entity directly (`false`)."
},
"data_description": {
"interval": "Frequency to adapt the lights, in seconds. 🔄",
@ -90,7 +91,8 @@
"take_over_control_mode": "The adaptation pausing mode when other sources change brightness and/or color of lights. `pause_all` always pauses both brightness and color adaptation. `pause_changed` pauses the adaptation of only the changed attributes and continues adapting unchanged attributes, e.g., continues color adaptation when only brightness was changed.",
"autoreset_control_seconds": "Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️",
"send_split_delay": "Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
"adapt_delay": "Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️"
"adapt_delay": "Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
"expand_light_groups": "When set to `false`, adaptation commands are sent to the group entity directly instead of its members."
}
}
},