docs: add automation alternatives for custom lighting profiles (#1535)

* docs: add automation alternatives for custom lighting profiles

* docs: make automation restart behavior explicit

* docs: validate automation examples in Home Assistant

* docs: clarify automation prerequisites

* test: exercise automation startup lifecycle
This commit is contained in:
Bas Nijholt 2026-09-06 13:08:17 +02:00 committed by GitHub
commit 08e3b817a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1229 additions and 142 deletions

344
README.md
View file

@ -268,29 +268,27 @@ The following keys are disallowed:
<!-- SECTION:automation-examples:START -->
## :robot: Automation examples
Replace every entity ID below with the IDs from your Home Assistant instance. Fresh Adaptive Lighting profiles use child IDs such as `switch.adaptive_lighting_living_room_sleep_mode`; profiles created before the device-based entity change may retain older IDs.
Blocks that begin with `- alias` are entries for `automations.yaml`. Blocks with a top-level `script:` or `adaptive_lighting:` key are complete `configuration.yaml` examples. If your configuration uses `script: !include scripts.yaml`, omit that outer key and place its contents in `scripts.yaml`.
`change_switch_settings` updates a profile while its main switch is off, but lights are adapted only while that switch is on. It preserves manual-control flags, so manually controlled lights remain paused.
<details markdown="1">
<summary>Reset the <code>manual_control</code> status of a light after an hour.</summary>
<summary>Automatically reset manual control after one hour.</summary>
Use the built-in timeout so every new manual change renews a single timer for that light:
```yaml
- alias: "Adaptive lighting: reset manual_control after 1 hour"
mode: parallel
trigger:
platform: event
event_type: adaptive_lighting.manual_control
variables:
light: "{{ trigger.event.data.entity_id }}"
switch: "{{ trigger.event.data.switch }}"
action:
- delay: "01:00:00"
- condition: template
value_template: "{{ light in state_attr(switch, 'manual_control') }}"
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ switch }}"
lights: "{{ light }}"
manual_control: false
adaptive_lighting:
- name: "Living Room"
lights:
- light.living_room
autoreset_control_seconds: 3600
```
This is a top-level `configuration.yaml` example. The timer clears manual control and immediately readapts a light when both it and the Adaptive Lighting switch are on.
</details>
<details markdown="1">
@ -302,67 +300,267 @@ The following keys are disallowed:
- platform: state
entity_id: input_boolean.sleep_mode
- platform: homeassistant
event: start # in case the states aren't properly restored
event: start # apply the helper's restored state
variables:
sleep_mode: "{{ states('input_boolean.sleep_mode') }}"
action:
service: "switch.turn_{{ sleep_mode }}"
entity_id:
- switch.adaptive_lighting_sleep_mode_living_room
- switch.adaptive_lighting_sleep_mode_bedroom
```
Set your sunrise and sunset time based on your alarm. The below script sets sunset_time exactly 12 hours after the custom sunrise time.
```yaml
iphone_carly_wakeup:
alias: iPhone Carly Wakeup
sequence:
- condition: state
entity_id: input_boolean.carly_iphone_wakeup
state: "off"
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.carly_iphone_wakeup
data:
time: '{{ now().strftime("%H:%M:%S") }}'
- service: input_boolean.turn_on
target:
entity_id: input_boolean.carly_iphone_wakeup
- repeat:
count: >
{{ (states.switch
| map(attribute="entity_id")
| select(">","switch.adaptive_lighting_al_")
| select("<", "switch.adaptive_lighting_al_z")
| join(",")
).split(",") | length }}
sequence:
- service: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_al_den_ceilingfan_lights
sunrise_time: '{{ now().strftime("%H:%M:%S") }}'
sunset_time: >
{{ (as_timestamp(now()) + 12*60*60) | timestamp_custom("%H:%M:%S") }}
- service: script.turn_on
target:
entity_id: script.run_wakeup_routine
- service: input_boolean.turn_off
conditions:
- condition: template
value_template: "{{ sleep_mode in ['on', 'off'] }}"
actions:
- action: "switch.turn_{{ sleep_mode }}"
target:
entity_id:
- input_boolean.carly_iphone_winddown
- input_boolean.carly_iphone_bedtime
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.wakeup_time
data:
time: '{{ now().strftime("%H:%M:%S") }}'
- service: script.adaptive_lighting_disable_sleep_mode
mode: queued
icon: mdi:weather-sunset
max: 10
- switch.adaptive_lighting_living_room_sleep_mode
- switch.adaptive_lighting_bedroom_sleep_mode
```
</details>
<details markdown="1">
<summary>Set sunrise and sunset from an alarm.</summary>
Call this script from your alarm automation. It sets one Adaptive Lighting profile's sunrise to the current time and its sunset to 12 hours later on the local clock.
```yaml
script:
set_adaptive_lighting_alarm_times:
alias: "Adaptive lighting: set times from alarm"
variables:
alarm_time: '{{ now().strftime("%H:%M:%S") }}'
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_alarm_lights
sunrise_time: "{{ alarm_time }}"
sunset_time: >
{{ (strptime(alarm_time, "%H:%M:%S") + timedelta(hours=12))
.strftime("%H:%M:%S") }}
```
</details>
<details markdown="1">
<summary>Use a Schedule helper as a step-based custom lighting profile.</summary>
Create a [Schedule helper](https://www.home-assistant.io/integrations/schedule/) named `Adaptive Lighting Profile`. Add time blocks with Additional data like this:
```yaml
brightness_pct: 20
color_temp_kelvin: 2500
```
Use different values for each block. The automation below applies the active block whenever the schedule state or its attributes change. Setting both brightness limits and both color temperature limits to the same value keeps each block at its setpoint. Outside a block, the configured Adaptive Lighting settings are restored.
```yaml
- alias: "Adaptive lighting: apply scheduled profile"
triggers:
- trigger: state
entity_id: schedule.adaptive_lighting_profile
- trigger: homeassistant
event: start
actions:
- choose:
- conditions:
- condition: state
entity_id: schedule.adaptive_lighting_profile
state: "on"
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
min_brightness: >
{{ state_attr('schedule.adaptive_lighting_profile', 'brightness_pct') | int(1) }}
max_brightness: >
{{ state_attr('schedule.adaptive_lighting_profile', 'brightness_pct') | int(1) }}
min_color_temp: >
{{ state_attr('schedule.adaptive_lighting_profile', 'color_temp_kelvin') | int(2000) }}
max_color_temp: >
{{ state_attr('schedule.adaptive_lighting_profile', 'color_temp_kelvin') | int(2000) }}
default:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
use_defaults: configuration
mode: restart
```
This creates step changes at block boundaries. It does not interpolate between schedule points. Runtime settings also reset when Home Assistant restarts, so the startup trigger reapplies the active block. The default branch restores every configured setting; restore only the four fields explicitly if other automations also change runtime settings.
</details>
<details markdown="1">
<summary>Reduce daytime brightness when an illuminance sensor detects strong daylight.</summary>
Keep a low configured `min_brightness` for late night and let an automation lower `max_brightness` while the room has ample daylight. Use a sensor that is not significantly affected by the controlled lights to avoid a feedback loop.
```yaml
- alias: "Adaptive lighting: limit brightness in daylight"
triggers:
- trigger: numeric_state
entity_id: sensor.living_room_illuminance
above: 300
- trigger: numeric_state
entity_id: sensor.living_room_illuminance
below: 200
- trigger: homeassistant
event: start
id: startup
actions:
- if:
- condition: trigger
id: startup
then:
- wait_template: >
{{ is_number(states('sensor.living_room_illuminance')) }}
timeout: "00:05:00"
continue_on_timeout: false
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.living_room_illuminance
above: 300
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
max_brightness: 30
- conditions:
- condition: numeric_state
entity_id: sensor.living_room_illuminance
below: 200
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
max_brightness: 100
mode: restart
```
The separate 200 and 300 lux thresholds add hysteresis. After a restart, the automation waits for a numeric sensor state before evaluating it. If the initial value is between the thresholds, Adaptive Lighting keeps its configured maximum. Replace `30` and `100` with your desired daytime limit and normal maximum.
`min_brightness` and `max_brightness` are the solar-midnight and daytime endpoints of the brightness curve. Setting `min_brightness` higher than `max_brightness` is supported and creates an inverted curve that is brighter at night and dimmer during the day. If you only want a daytime limit, keep the reduced maximum at or above the configured minimum.
</details>
<details markdown="1">
<summary>Turn on Hue-controlled lights with the current Adaptive Lighting values.</summary>
For a Hue button exposed to Home Assistant, call this script from the button automation. It turns on the listed lights directly with the current Adaptive Lighting brightness and color.
```yaml
script:
living_room_adaptive_lighting:
alias: "Living room: adaptive lighting"
sequence:
- action: adaptive_lighting.apply
data:
entity_id: switch.adaptive_lighting_living_room
lights:
- light.living_room_ceiling
- light.living_room_table
turn_on_lights: true
transition: 0
```
This requires Home Assistant to receive the button event. The one-shot `apply` call works while the main Adaptive Lighting switch is off, turns on the listed lights, and applies values even if a light is marked as manually controlled. It leaves the profile switch and manual-control state unchanged.
Adaptive Lighting does not update scenes stored on the Hue Bridge, so scenes activated only inside Hue cannot use this script and retain Hue's operation when Home Assistant is unavailable.
</details>
<details markdown="1">
<summary>Use a fixed RGB stage before sleep mode.</summary>
This script starts sleep mode with a fixed dim red color, waits 30 minutes, and then restores the configured Adaptive Lighting settings. The main profile switch and the light must already be on.
```yaml
script:
adaptive_lighting_bedtime:
alias: "Adaptive lighting: bedtime"
mode: restart
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_bedroom
sleep_rgb_or_color_temp: rgb_color
sleep_rgb_color: [255, 56, 0]
sleep_brightness: 20
- action: switch.turn_on
target:
entity_id: switch.adaptive_lighting_bedroom_sleep_mode
- delay: "00:30:00"
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_bedroom
use_defaults: configuration
```
The light must support RGB color. The first stage uses a fixed brightness rather than following the normal brightness curve. When sleep mode changes from off to on, the default `reset_manual_control_on_sleep_mode_change: true` returns manually controlled lights to Adaptive Lighting control so they receive the stage. If you disable that option, manually controlled lights remain paused. Restoring configuration defaults resets every runtime setting on this Adaptive Lighting switch, so restore only the sleep fields explicitly if other automations also change runtime settings.
Stopping this script or reloading scripts during the delay prevents the final action, leaving the runtime overrides active. To recover, call `adaptive_lighting.change_switch_settings` for the profile with `use_defaults: configuration`. A Home Assistant restart reloads the configured settings.
</details>
<details markdown="1">
<summary>Run a fixed virtual day across midnight.</summary>
Fixed virtual sunrise and sunset times can cross midnight. This configuration ramps an indoor garden from its minimum at 16:00 to its maximum at 22:00, then back to its minimum at 04:00.
```yaml
adaptive_lighting:
- name: "Indoor Garden"
lights:
- light.indoor_garden
sunrise_time: "16:00:00"
sunset_time: "04:00:00"
min_brightness: 10
max_brightness: 100
brightness_mode: linear
brightness_mode_time_dark: 0
brightness_mode_time_light: 21600 # 6 hours
```
Adaptive Lighting changes brightness and color while a light is on; it does not manage the light's power schedule. This separate automation turns the example light on and off:
```yaml
- alias: "Indoor garden: power schedule"
triggers:
- trigger: time
at: "16:00:00"
id: turn_on
- trigger: time
at: "04:00:00"
id: turn_off
- trigger: homeassistant
event: start
id: startup
actions:
- choose:
- conditions:
- condition: trigger
id: turn_on
sequence:
- action: light.turn_on
target:
entity_id: light.indoor_garden
- conditions:
- condition: trigger
id: startup
- condition: time
after: "16:00:00"
before: "04:00:00"
sequence:
- action: light.turn_on
target:
entity_id: light.indoor_garden
default:
- action: light.turn_off
target:
entity_id: light.indoor_garden
```
Use `min_sunrise_time`, `max_sunrise_time`, `min_sunset_time`, or `max_sunset_time` instead when you want to constrain astronomical sunrise or sunset to an earliest or latest time rather than replace it.
</details>
<!-- SECTION:automation-examples:END -->

View file

@ -12,29 +12,27 @@ Real-world automation examples showing how to integrate Adaptive Lighting with y
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
Replace every entity ID below with the IDs from your Home Assistant instance. Fresh Adaptive Lighting profiles use child IDs such as `switch.adaptive_lighting_living_room_sleep_mode`; profiles created before the device-based entity change may retain older IDs.
Blocks that begin with `- alias` are entries for `automations.yaml`. Blocks with a top-level `script:` or `adaptive_lighting:` key are complete `configuration.yaml` examples. If your configuration uses `script: !include scripts.yaml`, omit that outer key and place its contents in `scripts.yaml`.
`change_switch_settings` updates a profile while its main switch is off, but lights are adapted only while that switch is on. It preserves manual-control flags, so manually controlled lights remain paused.
<details markdown="1">
<summary>Reset the <code>manual_control</code> status of a light after an hour.</summary>
<summary>Automatically reset manual control after one hour.</summary>
Use the built-in timeout so every new manual change renews a single timer for that light:
```yaml
- alias: "Adaptive lighting: reset manual_control after 1 hour"
mode: parallel
trigger:
platform: event
event_type: adaptive_lighting.manual_control
variables:
light: "{{ trigger.event.data.entity_id }}"
switch: "{{ trigger.event.data.switch }}"
action:
- delay: "01:00:00"
- condition: template
value_template: "{{ light in state_attr(switch, 'manual_control') }}"
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ switch }}"
lights: "{{ light }}"
manual_control: false
adaptive_lighting:
- name: "Living Room"
lights:
- light.living_room
autoreset_control_seconds: 3600
```
This is a top-level `configuration.yaml` example. The timer clears manual control and immediately readapts a light when both it and the Adaptive Lighting switch are on.
</details>
<details markdown="1">
@ -46,69 +44,269 @@ Real-world automation examples showing how to integrate Adaptive Lighting with y
- platform: state
entity_id: input_boolean.sleep_mode
- platform: homeassistant
event: start # in case the states aren't properly restored
event: start # apply the helper's restored state
variables:
sleep_mode: "{{ states('input_boolean.sleep_mode') }}"
action:
service: "switch.turn_{{ sleep_mode }}"
entity_id:
- switch.adaptive_lighting_sleep_mode_living_room
- switch.adaptive_lighting_sleep_mode_bedroom
```
Set your sunrise and sunset time based on your alarm. The below script sets sunset_time exactly 12 hours after the custom sunrise time.
```yaml
iphone_carly_wakeup:
alias: iPhone Carly Wakeup
sequence:
- condition: state
entity_id: input_boolean.carly_iphone_wakeup
state: "off"
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.carly_iphone_wakeup
data:
time: '{{ now().strftime("%H:%M:%S") }}'
- service: input_boolean.turn_on
target:
entity_id: input_boolean.carly_iphone_wakeup
- repeat:
count: >
{{ (states.switch
| map(attribute="entity_id")
| select(">","switch.adaptive_lighting_al_")
| select("<", "switch.adaptive_lighting_al_z")
| join(",")
).split(",") | length }}
sequence:
- service: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_al_den_ceilingfan_lights
sunrise_time: '{{ now().strftime("%H:%M:%S") }}'
sunset_time: >
{{ (as_timestamp(now()) + 12*60*60) | timestamp_custom("%H:%M:%S") }}
- service: script.turn_on
target:
entity_id: script.run_wakeup_routine
- service: input_boolean.turn_off
conditions:
- condition: template
value_template: "{{ sleep_mode in ['on', 'off'] }}"
actions:
- action: "switch.turn_{{ sleep_mode }}"
target:
entity_id:
- input_boolean.carly_iphone_winddown
- input_boolean.carly_iphone_bedtime
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.wakeup_time
data:
time: '{{ now().strftime("%H:%M:%S") }}'
- service: script.adaptive_lighting_disable_sleep_mode
mode: queued
icon: mdi:weather-sunset
max: 10
- switch.adaptive_lighting_living_room_sleep_mode
- switch.adaptive_lighting_bedroom_sleep_mode
```
</details>
<details markdown="1">
<summary>Set sunrise and sunset from an alarm.</summary>
Call this script from your alarm automation. It sets one Adaptive Lighting profile's sunrise to the current time and its sunset to 12 hours later on the local clock.
```yaml
script:
set_adaptive_lighting_alarm_times:
alias: "Adaptive lighting: set times from alarm"
variables:
alarm_time: '{{ now().strftime("%H:%M:%S") }}'
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_alarm_lights
sunrise_time: "{{ alarm_time }}"
sunset_time: >
{{ (strptime(alarm_time, "%H:%M:%S") + timedelta(hours=12))
.strftime("%H:%M:%S") }}
```
</details>
<details markdown="1">
<summary>Use a Schedule helper as a step-based custom lighting profile.</summary>
Create a [Schedule helper](https://www.home-assistant.io/integrations/schedule/) named `Adaptive Lighting Profile`. Add time blocks with Additional data like this:
```yaml
brightness_pct: 20
color_temp_kelvin: 2500
```
Use different values for each block. The automation below applies the active block whenever the schedule state or its attributes change. Setting both brightness limits and both color temperature limits to the same value keeps each block at its setpoint. Outside a block, the configured Adaptive Lighting settings are restored.
```yaml
- alias: "Adaptive lighting: apply scheduled profile"
triggers:
- trigger: state
entity_id: schedule.adaptive_lighting_profile
- trigger: homeassistant
event: start
actions:
- choose:
- conditions:
- condition: state
entity_id: schedule.adaptive_lighting_profile
state: "on"
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
min_brightness: >
{{ state_attr('schedule.adaptive_lighting_profile', 'brightness_pct') | int(1) }}
max_brightness: >
{{ state_attr('schedule.adaptive_lighting_profile', 'brightness_pct') | int(1) }}
min_color_temp: >
{{ state_attr('schedule.adaptive_lighting_profile', 'color_temp_kelvin') | int(2000) }}
max_color_temp: >
{{ state_attr('schedule.adaptive_lighting_profile', 'color_temp_kelvin') | int(2000) }}
default:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
use_defaults: configuration
mode: restart
```
This creates step changes at block boundaries. It does not interpolate between schedule points. Runtime settings also reset when Home Assistant restarts, so the startup trigger reapplies the active block. The default branch restores every configured setting; restore only the four fields explicitly if other automations also change runtime settings.
</details>
<details markdown="1">
<summary>Reduce daytime brightness when an illuminance sensor detects strong daylight.</summary>
Keep a low configured `min_brightness` for late night and let an automation lower `max_brightness` while the room has ample daylight. Use a sensor that is not significantly affected by the controlled lights to avoid a feedback loop.
```yaml
- alias: "Adaptive lighting: limit brightness in daylight"
triggers:
- trigger: numeric_state
entity_id: sensor.living_room_illuminance
above: 300
- trigger: numeric_state
entity_id: sensor.living_room_illuminance
below: 200
- trigger: homeassistant
event: start
id: startup
actions:
- if:
- condition: trigger
id: startup
then:
- wait_template: >
{{ is_number(states('sensor.living_room_illuminance')) }}
timeout: "00:05:00"
continue_on_timeout: false
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.living_room_illuminance
above: 300
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
max_brightness: 30
- conditions:
- condition: numeric_state
entity_id: sensor.living_room_illuminance
below: 200
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_living_room
max_brightness: 100
mode: restart
```
The separate 200 and 300 lux thresholds add hysteresis. After a restart, the automation waits for a numeric sensor state before evaluating it. If the initial value is between the thresholds, Adaptive Lighting keeps its configured maximum. Replace `30` and `100` with your desired daytime limit and normal maximum.
`min_brightness` and `max_brightness` are the solar-midnight and daytime endpoints of the brightness curve. Setting `min_brightness` higher than `max_brightness` is supported and creates an inverted curve that is brighter at night and dimmer during the day. If you only want a daytime limit, keep the reduced maximum at or above the configured minimum.
</details>
<details markdown="1">
<summary>Turn on Hue-controlled lights with the current Adaptive Lighting values.</summary>
For a Hue button exposed to Home Assistant, call this script from the button automation. It turns on the listed lights directly with the current Adaptive Lighting brightness and color.
```yaml
script:
living_room_adaptive_lighting:
alias: "Living room: adaptive lighting"
sequence:
- action: adaptive_lighting.apply
data:
entity_id: switch.adaptive_lighting_living_room
lights:
- light.living_room_ceiling
- light.living_room_table
turn_on_lights: true
transition: 0
```
This requires Home Assistant to receive the button event. The one-shot `apply` call works while the main Adaptive Lighting switch is off, turns on the listed lights, and applies values even if a light is marked as manually controlled. It leaves the profile switch and manual-control state unchanged.
Adaptive Lighting does not update scenes stored on the Hue Bridge, so scenes activated only inside Hue cannot use this script and retain Hue's operation when Home Assistant is unavailable.
</details>
<details markdown="1">
<summary>Use a fixed RGB stage before sleep mode.</summary>
This script starts sleep mode with a fixed dim red color, waits 30 minutes, and then restores the configured Adaptive Lighting settings. The main profile switch and the light must already be on.
```yaml
script:
adaptive_lighting_bedtime:
alias: "Adaptive lighting: bedtime"
mode: restart
sequence:
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_bedroom
sleep_rgb_or_color_temp: rgb_color
sleep_rgb_color: [255, 56, 0]
sleep_brightness: 20
- action: switch.turn_on
target:
entity_id: switch.adaptive_lighting_bedroom_sleep_mode
- delay: "00:30:00"
- action: adaptive_lighting.change_switch_settings
data:
entity_id: switch.adaptive_lighting_bedroom
use_defaults: configuration
```
The light must support RGB color. The first stage uses a fixed brightness rather than following the normal brightness curve. When sleep mode changes from off to on, the default `reset_manual_control_on_sleep_mode_change: true` returns manually controlled lights to Adaptive Lighting control so they receive the stage. If you disable that option, manually controlled lights remain paused. Restoring configuration defaults resets every runtime setting on this Adaptive Lighting switch, so restore only the sleep fields explicitly if other automations also change runtime settings.
Stopping this script or reloading scripts during the delay prevents the final action, leaving the runtime overrides active. To recover, call `adaptive_lighting.change_switch_settings` for the profile with `use_defaults: configuration`. A Home Assistant restart reloads the configured settings.
</details>
<details markdown="1">
<summary>Run a fixed virtual day across midnight.</summary>
Fixed virtual sunrise and sunset times can cross midnight. This configuration ramps an indoor garden from its minimum at 16:00 to its maximum at 22:00, then back to its minimum at 04:00.
```yaml
adaptive_lighting:
- name: "Indoor Garden"
lights:
- light.indoor_garden
sunrise_time: "16:00:00"
sunset_time: "04:00:00"
min_brightness: 10
max_brightness: 100
brightness_mode: linear
brightness_mode_time_dark: 0
brightness_mode_time_light: 21600 # 6 hours
```
Adaptive Lighting changes brightness and color while a light is on; it does not manage the light's power schedule. This separate automation turns the example light on and off:
```yaml
- alias: "Indoor garden: power schedule"
triggers:
- trigger: time
at: "16:00:00"
id: turn_on
- trigger: time
at: "04:00:00"
id: turn_off
- trigger: homeassistant
event: start
id: startup
actions:
- choose:
- conditions:
- condition: trigger
id: turn_on
sequence:
- action: light.turn_on
target:
entity_id: light.indoor_garden
- conditions:
- condition: trigger
id: startup
- condition: time
after: "16:00:00"
before: "04:00:00"
sequence:
- action: light.turn_on
target:
entity_id: light.indoor_garden
default:
- action: light.turn_off
target:
entity_id: light.indoor_garden
```
Use `min_sunrise_time`, `max_sunrise_time`, `min_sunset_time`, or `max_sunset_time` instead when you want to constrain astronomical sunrise or sunset to an earliest or latest time rather than replace it.
</details>
<!-- OUTPUT:END -->
> [!TIP]

View file

@ -0,0 +1,691 @@
"""Execute the automation examples published in README.md."""
from __future__ import annotations
import asyncio
import re
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import patch
import pytest
import yaml
from homeassistant.components import automation, script
from homeassistant.components.adaptive_lighting.adaptation_utils import (
LightControlAttributes,
)
from homeassistant.components.adaptive_lighting.const import (
CONF_INITIAL_TRANSITION,
CONF_LIGHTS,
CONF_MAX_BRIGHTNESS,
CONF_MAX_COLOR_TEMP,
CONF_MIN_BRIGHTNESS,
CONF_MIN_COLOR_TEMP,
CONF_NAME,
CONF_SLEEP_BRIGHTNESS,
CONF_SLEEP_COLOR_TEMP,
CONF_SLEEP_RGB_OR_COLOR_TEMP,
CONF_SUNRISE_TIME,
CONF_SUNSET_TIME,
CONF_TRANSITION,
DOMAIN,
)
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
)
from homeassistant.components.light import (
DOMAIN as LIGHT_DOMAIN,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
EVENT_CALL_SERVICE,
EVENT_STATE_CHANGED,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
)
from homeassistant.core import CoreState, Event, HomeAssistant, State, callback
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.common import async_fire_time_changed
from .test_switch import setup_switch
if TYPE_CHECKING:
from collections.abc import Callable
README = Path(__file__).resolve().parents[1] / "README.md"
def _yaml_documents(summary: str) -> list[object]:
"""Load every YAML fence from one README details block."""
readme = README.read_text()
details = re.search(
rf"<summary>{re.escape(summary)}</summary>(.*?)</details>",
readme,
flags=re.DOTALL,
)
assert details is not None, f"README example not found: {summary}"
blocks = re.findall(r"```yaml\n(.*?)```", details.group(1), flags=re.DOTALL)
assert blocks, f"README example has no YAML: {summary}"
return [yaml.safe_load(block) for block in blocks]
async def _setup_automation(hass: HomeAssistant, config: object) -> None:
"""Load an extracted automation through Home Assistant."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{automation.DOMAIN: config},
)
await hass.async_block_till_done()
async def _setup_script(hass: HomeAssistant, config: dict) -> None:
"""Load an extracted script through Home Assistant."""
assert await async_setup_component(hass, script.DOMAIN, config)
await hass.async_block_till_done()
async def _setup_template_lights(
hass: HomeAssistant,
names: list[str],
) -> None:
"""Create color-temperature and RGB lights with documented entity IDs."""
lights = [
{
"name": name,
"unique_id": name.lower().replace(" ", "_"),
"turn_on": None,
"turn_off": None,
"set_level": None,
"set_temperature": None,
"set_rgb": None,
}
for name in names
]
assert await async_setup_component(
hass,
"template",
{"template": {"light": lights}},
)
await hass.async_block_till_done()
def _state_waiter(
hass: HomeAssistant,
entity_id: str,
predicate: Callable[[State], bool],
) -> tuple[asyncio.Future[State], Callable[[], None]]:
"""Return a future that resolves when an entity state matches a predicate."""
future = hass.loop.create_future()
@callback
def state_changed(event: Event) -> None:
new_state = event.data.get("new_state")
if (
not future.done()
and new_state is not None
and new_state.entity_id == entity_id
and predicate(new_state)
):
future.set_result(new_state)
remove_listener = hass.bus.async_listen(EVENT_STATE_CHANGED, state_changed)
return future, remove_listener
def _prepare_hass_startup(hass: HomeAssistant) -> None:
"""Reset the standard running test fixture to exercise a real HA start."""
hass.set_state(CoreState.not_running)
async def test_schedule_profile_executes_blocks_and_restore(
hass: HomeAssistant,
) -> None:
"""Catch ignored attribute changes, incomplete restore, or switch coupling."""
summary = "Use a Schedule helper as a step-based custom lighting profile."
automation_config = _yaml_documents(summary)[-1]
_, adaptive_switch = await setup_switch(
hass,
{
CONF_NAME: "Living Room",
CONF_LIGHTS: ["light.manually_controlled"],
CONF_MIN_BRIGHTNESS: 8,
CONF_MAX_BRIGHTNESS: 88,
CONF_MIN_COLOR_TEMP: 2100,
CONF_MAX_COLOR_TEMP: 5100,
},
)
adaptive_switch.manager.set_manual_control_attributes(
"light.manually_controlled",
LightControlAttributes.BRIGHTNESS,
)
hass.states.async_set(
"schedule.adaptive_lighting_profile",
STATE_OFF,
)
await _setup_automation(hass, automation_config)
hass.states.async_set(
"schedule.adaptive_lighting_profile",
STATE_ON,
{"brightness_pct": 20, "color_temp_kelvin": 2500},
)
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.min_brightness == 20
assert adaptive_switch._sun_light_settings.max_brightness == 20
assert adaptive_switch._sun_light_settings.min_color_temp == 2500
assert adaptive_switch._sun_light_settings.max_color_temp == 2500
hass.states.async_set(
"schedule.adaptive_lighting_profile",
STATE_ON,
{"brightness_pct": 60, "color_temp_kelvin": 4000},
)
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.min_brightness == 60
assert adaptive_switch._sun_light_settings.max_brightness == 60
assert adaptive_switch._sun_light_settings.min_color_temp == 4000
assert adaptive_switch._sun_light_settings.max_color_temp == 4000
hass.states.async_set("schedule.adaptive_lighting_profile", STATE_OFF)
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.min_brightness == 8
assert adaptive_switch._sun_light_settings.max_brightness == 88
assert adaptive_switch._sun_light_settings.min_color_temp == 2100
assert adaptive_switch._sun_light_settings.max_color_temp == 5100
assert adaptive_switch.manager.manual_control["light.manually_controlled"]
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: adaptive_switch.entity_id},
blocking=True,
)
hass.states.async_set(
"schedule.adaptive_lighting_profile",
STATE_ON,
{"brightness_pct": 35, "color_temp_kelvin": 2750},
)
await hass.async_block_till_done()
assert adaptive_switch.is_on is False
assert adaptive_switch._sun_light_settings.min_brightness == 35
assert adaptive_switch._sun_light_settings.max_color_temp == 2750
async def test_schedule_profile_reapplies_at_startup(hass: HomeAssistant) -> None:
"""Verify startup applies the already-active schedule block."""
_prepare_hass_startup(hass)
summary = "Use a Schedule helper as a step-based custom lighting profile."
automation_config = _yaml_documents(summary)[-1]
_, adaptive_switch = await setup_switch(hass, {CONF_NAME: "Living Room"})
hass.states.async_set(
"schedule.adaptive_lighting_profile",
STATE_ON,
{"brightness_pct": 20, "color_temp_kelvin": 2500},
)
await _setup_automation(hass, automation_config)
await hass.async_start()
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.min_brightness == 20
assert adaptive_switch._sun_light_settings.max_brightness == 20
assert adaptive_switch._sun_light_settings.min_color_temp == 2500
assert adaptive_switch._sun_light_settings.max_color_temp == 2500
async def test_lux_profile_executes_hysteresis(hass: HomeAssistant) -> None:
"""Catch missing threshold actions or changes inside the dead band."""
summary = (
"Reduce daytime brightness when an illuminance sensor detects strong daylight."
)
automation_config = _yaml_documents(summary)[0]
_, adaptive_switch = await setup_switch(
hass,
{CONF_NAME: "Living Room", CONF_MAX_BRIGHTNESS: 80},
)
hass.states.async_set("sensor.living_room_illuminance", "250")
await _setup_automation(hass, automation_config)
hass.states.async_set("sensor.living_room_illuminance", "350")
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.max_brightness == 30
hass.states.async_set("sensor.living_room_illuminance", "250")
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.max_brightness == 30
hass.states.async_set("sensor.living_room_illuminance", "150")
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.max_brightness == 100
hass.states.async_set("sensor.living_room_illuminance", "250")
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.max_brightness == 100
async def test_lux_profile_executes_unknown_recovery_at_startup(
hass: HomeAssistant,
) -> None:
"""Catch a startup hang or failure to recover from an unknown sensor."""
_prepare_hass_startup(hass)
summary = (
"Reduce daytime brightness when an illuminance sensor detects strong daylight."
)
automation_config = _yaml_documents(summary)[0]
_, adaptive_switch = await setup_switch(
hass,
{CONF_NAME: "Living Room", CONF_MAX_BRIGHTNESS: 80},
)
hass.states.async_set("sensor.living_room_illuminance", "unknown")
await _setup_automation(hass, automation_config)
waiting, remove_listener = _state_waiter(
hass,
"automation.adaptive_lighting_limit_brightness_in_daylight",
lambda state: state.attributes.get("current") == 1,
)
await hass.async_start()
await asyncio.wait_for(waiting, timeout=1)
remove_listener()
assert adaptive_switch._sun_light_settings.max_brightness == 80
hass.states.async_set("sensor.living_room_illuminance", "350")
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.max_brightness == 30
async def test_hue_script_applies_current_values_to_fresh_profile_targets(
hass: HomeAssistant,
) -> None:
"""Catch an invalid script wrapper or a one-shot apply that skips off lights."""
summary = "Turn on Hue-controlled lights with the current Adaptive Lighting values."
script_config = _yaml_documents(summary)[0]
await _setup_template_lights(
hass,
["Living Room Ceiling", "Living Room Table"],
)
_, adaptive_switch = await setup_switch(
hass,
{
CONF_NAME: "Living Room",
CONF_LIGHTS: [
"light.living_room_ceiling",
"light.living_room_table",
],
CONF_SUNRISE_TIME: "06:00:00",
CONF_SUNSET_TIME: "18:00:00",
CONF_MIN_BRIGHTNESS: 10,
CONF_MAX_BRIGHTNESS: 80,
CONF_MIN_COLOR_TEMP: 2000,
CONF_MAX_COLOR_TEMP: 5000,
CONF_INITIAL_TRANSITION: 0,
CONF_TRANSITION: 0,
},
)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: adaptive_switch.entity_id},
blocking=True,
)
adaptive_switch.manager.set_manual_control_attributes(
"light.living_room_ceiling",
LightControlAttributes.BRIGHTNESS | LightControlAttributes.COLOR,
)
await _setup_script(hass, script_config)
noon = datetime(2026, 9, 6, 12, tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(UTC)
with patch(
"homeassistant.components.adaptive_lighting.color_and_brightness.utcnow",
return_value=noon,
):
await hass.services.async_call(
script.DOMAIN,
"living_room_adaptive_lighting",
blocking=True,
)
await hass.async_block_till_done()
for entity_id in ("light.living_room_ceiling", "light.living_room_table"):
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 204
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == 5000
assert adaptive_switch.is_on is False
assert adaptive_switch.manager.manual_control["light.living_room_ceiling"]
async def test_rgb_bedtime_script_applies_stage_then_restores_configuration(
hass: HomeAssistant,
) -> None:
"""Catch a wrong fresh child ID, delayed first stage, or missing restoration."""
summary = "Use a fixed RGB stage before sleep mode."
script_config = _yaml_documents(summary)[0]
await _setup_template_lights(hass, ["Bedroom"])
_, adaptive_switch = await setup_switch(
hass,
{
CONF_NAME: "Bedroom",
CONF_LIGHTS: ["light.bedroom"],
CONF_SLEEP_BRIGHTNESS: 7,
CONF_SLEEP_COLOR_TEMP: 2300,
CONF_SLEEP_RGB_OR_COLOR_TEMP: "color_temp",
CONF_INITIAL_TRANSITION: 0,
CONF_TRANSITION: 0,
},
)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.bedroom"},
blocking=True,
)
adaptive_switch.manager.set_manual_control_attributes(
"light.bedroom",
LightControlAttributes.ALL,
)
await _setup_script(hass, script_config)
light_staged, remove_light_listener = _state_waiter(
hass,
"light.bedroom",
lambda state: (
state.attributes.get(ATTR_BRIGHTNESS) == 51
and state.attributes.get(ATTR_RGB_COLOR) == (255, 56, 0)
),
)
sleep_enabled, remove_sleep_listener = _state_waiter(
hass,
"switch.adaptive_lighting_bedroom_sleep_mode",
lambda state: state.state == STATE_ON,
)
await hass.services.async_call(
script.DOMAIN,
"adaptive_lighting_bedtime",
blocking=False,
)
await asyncio.wait_for(asyncio.gather(light_staged, sleep_enabled), timeout=1)
remove_light_listener()
remove_sleep_listener()
# Let the script continue from the completed switch action into its delay.
await asyncio.sleep(0)
sleep_state = hass.states.get("switch.adaptive_lighting_bedroom_sleep_mode")
assert sleep_state is not None
assert sleep_state.state == STATE_ON
assert adaptive_switch._sun_light_settings.sleep_brightness == 20
assert adaptive_switch._sun_light_settings.sleep_rgb_or_color_temp == "rgb_color"
assert adaptive_switch._sun_light_settings.sleep_rgb_color == [255, 56, 0]
assert not adaptive_switch.manager.manual_control["light.bedroom"]
bedroom_state = hass.states.get("light.bedroom")
assert bedroom_state is not None
assert bedroom_state.attributes[ATTR_BRIGHTNESS] == 51
assert bedroom_state.attributes[ATTR_RGB_COLOR] == (255, 56, 0)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30))
await hass.async_block_till_done()
assert adaptive_switch._sun_light_settings.sleep_brightness == 7
assert adaptive_switch._sun_light_settings.sleep_rgb_or_color_temp == "color_temp"
assert adaptive_switch._sun_light_settings.sleep_color_temp == 2300
bedroom_state = hass.states.get("light.bedroom")
assert bedroom_state is not None
assert bedroom_state.attributes[ATTR_COLOR_TEMP_KELVIN] == pytest.approx(
2300,
abs=5,
)
async def test_fixed_virtual_day_curve_and_power_automation(
hass: HomeAssistant,
freezer,
) -> None:
"""Catch a broken cross-midnight curve or power-trigger branch."""
summary = "Run a fixed virtual day across midnight."
integration_config, automation_config = _yaml_documents(summary)
await _setup_template_lights(hass, ["Indoor Garden"])
assert isinstance(integration_config, dict)
assert await async_setup_component(hass, DOMAIN, integration_config)
await hass.async_block_till_done()
entry = hass.config_entries.async_entries(DOMAIN)[0]
adaptive_switch = hass.data[DOMAIN][entry.entry_id][SWITCH_DOMAIN]
expected_brightness = {
datetime(2026, 9, 6, 16, tzinfo=dt_util.DEFAULT_TIME_ZONE): 10,
datetime(2026, 9, 6, 22, tzinfo=dt_util.DEFAULT_TIME_ZONE): 100,
datetime(2026, 9, 7, 4, tzinfo=dt_util.DEFAULT_TIME_ZONE): 10,
}
for now, expected in expected_brightness.items():
assert adaptive_switch._sun_light_settings.brightness_pct(
now,
False,
) == pytest.approx(
expected,
)
freezer.move_to(
datetime(2026, 9, 6, 15, 59, tzinfo=dt_util.DEFAULT_TIME_ZONE),
)
await _setup_automation(hass, automation_config)
await hass.async_start()
async_fire_time_changed(
hass,
datetime(2026, 9, 6, 16, tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(UTC),
)
await hass.async_block_till_done()
garden_state = hass.states.get("light.indoor_garden")
assert garden_state is not None
assert garden_state.state == STATE_ON
async_fire_time_changed(
hass,
datetime(2026, 9, 7, 4, tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(UTC),
)
await hass.async_block_till_done()
garden_state = hass.states.get("light.indoor_garden")
assert garden_state is not None
assert garden_state.state == STATE_OFF
@pytest.mark.parametrize(
("start_hour", "expected_state"),
[(10, STATE_OFF), (22, STATE_ON)],
)
async def test_fixed_virtual_day_reconciles_power_at_startup(
hass: HomeAssistant,
freezer,
start_hour: int,
expected_state: str,
) -> None:
"""Catch a power schedule that misses a trigger while HA is offline."""
_prepare_hass_startup(hass)
summary = "Run a fixed virtual day across midnight."
_, automation_config = _yaml_documents(summary)
await _setup_template_lights(hass, ["Indoor Garden"])
await _setup_automation(hass, automation_config)
freezer.move_to(
datetime(2026, 9, 6, start_hour, tzinfo=dt_util.DEFAULT_TIME_ZONE),
)
await hass.async_start()
await hass.async_block_till_done()
garden_state = hass.states.get("light.indoor_garden")
assert garden_state is not None
assert garden_state.state == expected_state
async def test_autoreset_manual_control_uses_one_renewable_timer(
hass: HomeAssistant,
) -> None:
"""Validate the documented built-in timeout and its renewal behavior."""
summary = "Automatically reset manual control after one hour."
integration_config = _yaml_documents(summary)[0]
await _setup_template_lights(hass, ["Living Room"])
assert isinstance(integration_config, dict)
assert await async_setup_component(hass, DOMAIN, integration_config)
await hass.async_block_till_done()
entry = hass.config_entries.async_entries(DOMAIN)[0]
adaptive_switch = hass.data[DOMAIN][entry.entry_id][SWITCH_DOMAIN]
assert adaptive_switch._auto_reset_manual_control_time == 3600
service_data = {
ATTR_ENTITY_ID: adaptive_switch.entity_id,
CONF_LIGHTS: ["light.living_room"],
"manual_control": True,
}
await hass.services.async_call(
DOMAIN,
"set_manual_control",
service_data,
blocking=True,
)
first_timer = adaptive_switch.manager.auto_reset_manual_control_timers[
"light.living_room"
]
first_task = first_timer.task
await hass.services.async_call(
DOMAIN,
"set_manual_control",
service_data,
blocking=True,
)
renewed_timer = adaptive_switch.manager.auto_reset_manual_control_timers[
"light.living_room"
]
await asyncio.sleep(0)
assert renewed_timer is first_timer
assert renewed_timer.task is not first_task
assert first_task is not None
assert first_task.cancelled()
assert adaptive_switch.manager.manual_control["light.living_room"]
await renewed_timer.callback()
assert not adaptive_switch.manager.manual_control["light.living_room"]
async def test_sleep_toggle_uses_fresh_profile_entity_ids(
hass: HomeAssistant,
) -> None:
"""Execute state triggers against fresh child entity IDs."""
summary = (
'Toggle multiple Adaptive Lighting switches to "sleep mode" using an '
"<code>input_boolean.sleep_mode</code>."
)
automation_config = _yaml_documents(summary)[0]
assert await async_setup_component(
hass,
"input_boolean",
{"input_boolean": {"sleep_mode": {}}},
)
await setup_switch(hass, {CONF_NAME: "Living Room"})
await setup_switch(hass, {CONF_NAME: "Bedroom"})
await _setup_automation(hass, automation_config)
await hass.services.async_call(
"input_boolean",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "input_boolean.sleep_mode"},
blocking=True,
)
await hass.async_block_till_done()
for entity_id in (
"switch.adaptive_lighting_living_room_sleep_mode",
"switch.adaptive_lighting_bedroom_sleep_mode",
):
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_ON
await hass.services.async_call(
"input_boolean",
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "input_boolean.sleep_mode"},
blocking=True,
)
await hass.async_block_till_done()
for entity_id in (
"switch.adaptive_lighting_living_room_sleep_mode",
"switch.adaptive_lighting_bedroom_sleep_mode",
):
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_OFF
async def test_sleep_toggle_applies_restored_state_at_startup(
hass: HomeAssistant,
) -> None:
"""Verify startup applies the input boolean's restored state."""
_prepare_hass_startup(hass)
summary = (
'Toggle multiple Adaptive Lighting switches to "sleep mode" using an '
"<code>input_boolean.sleep_mode</code>."
)
automation_config = _yaml_documents(summary)[0]
assert await async_setup_component(
hass,
"input_boolean",
{"input_boolean": {"sleep_mode": {}}},
)
await setup_switch(hass, {CONF_NAME: "Living Room"})
await hass.services.async_call(
"input_boolean",
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "input_boolean.sleep_mode"},
blocking=True,
)
await _setup_automation(hass, automation_config)
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("switch.adaptive_lighting_living_room_sleep_mode")
assert state is not None
assert state.state == STATE_ON
async def test_alarm_script_updates_its_profile_once(
hass: HomeAssistant,
freezer,
) -> None:
"""Execute the alarm script and verify one profile update with a 12-hour day."""
summary = "Set sunrise and sunset from an alarm."
script_config = _yaml_documents(summary)[0]
freezer.move_to(
datetime(2026, 11, 1, 0, 30, tzinfo=dt_util.DEFAULT_TIME_ZONE),
)
_, adaptive_switch = await setup_switch(hass, {CONF_NAME: "Alarm Lights"})
await _setup_script(hass, script_config)
calls = []
def record_service_call(event) -> None:
if (
event.data["domain"] == DOMAIN
and event.data["service"] == "change_switch_settings"
):
calls.append(event)
hass.bus.async_listen(EVENT_CALL_SERVICE, record_service_call)
await hass.services.async_call(
script.DOMAIN,
"set_adaptive_lighting_alarm_times",
blocking=True,
)
await hass.async_block_till_done()
assert len(calls) == 1
sunrise = adaptive_switch._sun_light_settings.sunrise_time
sunset = adaptive_switch._sun_light_settings.sunset_time
assert sunrise is not None
assert sunset is not None
sunrise_seconds = sunrise.hour * 3600 + sunrise.minute * 60 + sunrise.second
sunset_seconds = sunset.hour * 3600 + sunset.minute * 60 + sunset.second
assert (sunset_seconds - sunrise_seconds) % (24 * 3600) == 12 * 3600