🐛 Follow the profile's adapt switches when applying a time

`adaptive_lighting.apply` with `time` now adapts (and marks as manually
controlled) only what the profile adapts itself, going by its Adapt
Brightness and Adapt Color switches, unless `adapt_brightness` or
`adapt_color` is passed. Without `time`, `apply` still adapts both by
default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mathias Berglund 2026-09-24 16:24:49 +03:00
commit 92ae6d9ccc
5 changed files with 65 additions and 14 deletions

View file

@ -233,7 +233,7 @@ Provide a switch in `entity_id`, a list of `lights`, or both.
With `time`, the lights get the values Adaptive Lighting would use at that time today, for example to dim a room to its evening settings early.
The adapted attributes are then marked as manually controlled, exactly as if you had changed them yourself: the regular adaptation leaves them alone until manual control is reset (see `autoreset_control_seconds` and `adaptive_lighting.set_manual_control`), and an `adaptive_lighting.manual_control` event is fired.
When sleep mode is on, the sleep settings are applied instead, whatever the time.
When sleep mode is on, the sleep settings are applied instead, whatever the time. Unless `adapt_brightness` or `adapt_color` is given, only what the profile adapts (its Adapt Brightness and Adapt Color switches) is applied.
```yaml
action: adaptive_lighting.apply

View file

@ -521,8 +521,8 @@ def apply_service_schema() -> vol.Schema:
vol.Optional(CONF_ENTITY_ID): cv.entity_ids, # type: ignore[arg-type]
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids, # type: ignore[arg-type]
vol.Optional(CONF_TRANSITION): VALID_TRANSITION,
vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean,
vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean,
vol.Optional(ATTR_ADAPT_BRIGHTNESS): cv.boolean,
vol.Optional(ATTR_ADAPT_COLOR): cv.boolean,
vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean,
vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean,
vol.Optional(CONF_APPLY_TIME): cv.time,

View file

@ -424,22 +424,31 @@ async def handle_apply_service(hass: HomeAssistant, service_call: ServiceCall) -
switches = _switches_from_service_call(hass, service_call)
lights = data[CONF_LIGHTS]
apply_time: datetime.time | None = data.get(CONF_APPLY_TIME)
# Applying the settings of another time of day only sticks if the regular
# adaptation leaves the light alone, so treat it like a manual change.
manual_attributes = LightControlAttributes.NONE
if apply_time is not None:
if data[ATTR_ADAPT_BRIGHTNESS]:
manual_attributes |= LightControlAttributes.BRIGHTNESS
if data[ATTR_ADAPT_COLOR]:
manual_attributes |= LightControlAttributes.COLOR
for switch in switches:
all_lights = switch._resolve_lights(lights or None)
switch.manager.lights.update(all_lights)
adapt_brightness = data.get(ATTR_ADAPT_BRIGHTNESS)
adapt_color = data.get(ATTR_ADAPT_COLOR)
at_time = None
manual_attributes = LightControlAttributes.NONE
if apply_time is not None:
tz = switch._sun_light_settings.timezone # pylint: disable=protected-access
today = dt_util.now(tz).date()
at_time = datetime.datetime.combine(today, apply_time, tzinfo=tz)
# Adapt what the regular adaptation would, unless specified.
if adapt_brightness is None:
adapt_brightness = switch.adapt_brightness_switch.is_on
if adapt_color is None:
adapt_color = switch.adapt_color_switch.is_on
# Applying the settings of another time of day only sticks if the
# regular adaptation leaves the light alone, so treat it like a
# manual change.
if adapt_brightness:
manual_attributes |= LightControlAttributes.BRIGHTNESS
if adapt_color:
manual_attributes |= LightControlAttributes.COLOR
adapt_brightness = True if adapt_brightness is None else adapt_brightness
adapt_color = True if adapt_color is None else adapt_color
for light in all_lights:
if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light):
context = switch.create_context(
@ -460,8 +469,8 @@ async def handle_apply_service(hass: HomeAssistant, service_call: ServiceCall) -
light,
context=context,
transition=transition,
adapt_brightness=data[ATTR_ADAPT_BRIGHTNESS],
adapt_color=data[ATTR_ADAPT_COLOR],
adapt_brightness=adapt_brightness,
adapt_color=adapt_color,
prefer_rgb_color=data[CONF_PREFER_RGB_COLOR],
force=True,
at_time=at_time,

View file

@ -66,7 +66,7 @@ data:
time: "22:00:00"
```
With `time`, the lights get the values Adaptive Lighting would use at that time today. The adapted attributes are then marked as manually controlled, exactly as if you had changed them yourself: the regular adaptation leaves them alone until manual control is reset (for example by `autoreset_control_seconds` or `adaptive_lighting.set_manual_control`), and an `adaptive_lighting.manual_control` event is fired. When sleep mode is on, the sleep settings are applied instead, whatever the time.
With `time`, the lights get the values Adaptive Lighting would use at that time today. The adapted attributes are then marked as manually controlled, exactly as if you had changed them yourself: the regular adaptation leaves them alone until manual control is reset (for example by `autoreset_control_seconds` or `adaptive_lighting.set_manual_control`), and an `adaptive_lighting.manual_control` event is fired. When sleep mode is on, the sleep settings are applied instead, whatever the time. Unless `adapt_brightness` or `adapt_color` is given, only what the profile adapts (its Adapt Brightness and Adapt Color switches) is applied.
---

View file

@ -2354,6 +2354,48 @@ async def test_apply_service_at_time_marks_manual_before_adapting(hass):
assert manual_during_adaptation == [LightControlAttributes.ALL]
async def test_apply_service_at_time_follows_adapt_switches(hass):
"""With 'time', only what the profile adapts is applied and made manual."""
switch, _ = await setup_lights_and_switch(hass)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: switch.adapt_color_switch.entity_id},
blocking=True,
)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ENTITY_LIGHT_1,
ATTR_BRIGHTNESS: 10,
ATTR_COLOR_TEMP_KELVIN: 3000,
},
blocking=True,
)
await hass.async_block_till_done()
switch.manager.reset(ENTITY_LIGHT_1)
await hass.services.async_call(
DOMAIN,
SERVICE_APPLY,
{
ATTR_ENTITY_ID: switch.entity_id,
CONF_LIGHTS: [ENTITY_LIGHT_1],
CONF_APPLY_TIME: "12:00:00",
},
blocking=True,
)
await hass.async_block_till_done()
attributes = hass.states.get(ENTITY_LIGHT_1).attributes
assert attributes[ATTR_BRIGHTNESS] != 10
assert attributes[ATTR_COLOR_TEMP_KELVIN] == 3000
assert (
switch.manager.get_manual_control_attributes(ENTITY_LIGHT_1)
== LightControlAttributes.BRIGHTNESS
)
async def test_apply_service_at_time_sleep_mode_and_partial(hass):
"""Sleep mode takes precedence, and only adapted attributes become manual."""
switch, _ = await setup_lights_and_switch(hass, {CONF_SLEEP_TRANSITION: 0})