Automatically adapt the brightness and color of lights based on the sun position and take over manual control
Try out this code by adding https://github.com/basnijholt/adaptive-lighting to your custom repos in HACS (Home Assistant Community Store) and install it!
The adaptive_lighting
platform changes the settings of your lights throughout the day.
It uses the position of the sun to calculate the color temperature and brightness that is most fitting for that time of the day.
Scientific research has shown that this helps to maintain your natural circadian rhythm (your biological clock) and might lead to improved sleep, mood, and general well-being.
In practical terms, this means that after the sun sets, the brightness of your lights will decrease to a certain minimum brightness, while the color temperature will be at its coolest color temperature at noon, after which it will decrease and reach its warmest color at sunset. Around sunrise, the opposite will happen.
Additionally, the integration provides a way to define and set your lights in "sleep mode". When "sleep mode" is enabled, the lights will be at a minimal brightness and have a very warm color.
The integration creates 4 switches (in this example the component's name is "living_room"
):
switch.adaptive_lighting_living_room
, which turns the Adaptive Lighting integration on or off. It has several attributes that show the current light settings.switch.adaptive_lighting_sleep_mode_living_room
, which when activated, turns on "sleep mode" (you can set a specificsleep_brightness
andsleep_color_temp
).switch.adaptive_lighting_adapt_brightness_living_room
, which sets whether the integration should adapt the brightness of the lights (if supported by the light).switch.adaptive_lighting_adapt_color_living_room
, which sets whether the integration should adapt the color of the lights (if supported by the light).
Although having your lights automatically adapt is great most of the time, there might be times at which you want to set the lights to a different color/brightness and keep it that way.
For this purpose, the integration (when take_over_control
is enabled) automatically detects whether someone (e.g., person toggling the light switch) or something (automation) changes the lights.
If this happens and the light is already on, the light that was changed gets marked as "manually controlled" and the Adaptive Lighting component will stop adapting that light until it turns off and on again (or if you use the service call adaptive_lighting.set_manual_control
).
This mechanism works by listening to all light.turn_on
calls that change the color or brightness and by noting that the component did not make the call.
Additionally, there is an option to detect all state changes (when detect_non_ha_changes
is enabled), so also changes to the lights that were not made by a light.turn_on
call (e.g., through an app or via something outside of Home Assistant.)
It does this by comparing a light's state to Adaptive Lighting's previously used settings.
Whenever a light gets marked as "manually controlled", an adaptive_lighting.manual_control
event is fired, such that one can use this information in automations.
This integration is both fully configurable through YAML and the frontend. (Configuration -> Integrations -> Adaptive Lighting, Adaptive Lighting -> Options) Here, the options in the frontend and in YAML have the same names.
# Example configuration.yaml entry
adaptive_lighting:
lights:
- light.living_room_lights
option | description | required | default | type |
---|---|---|---|---|
name |
The name to use when displaying this switch. | False | default | string |
include_config_in_attributes |
When set to true , will list all of the below options as attributes on the switch in Home Assistant. |
False | False | boolean |
lights |
List of light entities for Adaptive Lighting to control (may be empty). | False | list | [] |
prefer_rgb_color |
Whether to use RGB color adjustment instead of native light color temperature. | False | False | boolean |
initial_transition |
How long the first transition is when the lights go from off to on . |
False | 1 | time |
sleep_transition |
How long the transition is when when "sleep mode" is toggled | False | 1 | time |
transition |
How long the transition is when the lights change, in seconds. | False | 45 | integer |
interval |
How often to adapt the lights, in seconds. | False | 90 | integer |
min_brightness |
The minimum percent of brightness to set the lights to. | False | 1 | integer |
max_brightness |
The maximum percent of brightness to set the lights to. | False | 100 | integer |
min_color_temp |
The warmest color temperature to set the lights to, in Kelvin. | False | 2000 | integer |
max_color_temp |
The coldest color temperature to set the lights to, in Kelvin. | False | 5500 | integer |
sleep_brightness |
Brightness of lights while the sleep mode is enabled. | False | 1 | integer |
sleep_rgb_or_color_temp |
Use either 'rgb_color' or 'color_temp' when in sleep mode. | False | 'color_temp' | string |
sleep_rgb_color |
List of three numbers between 0-255, indicating the RGB color in sleep mode (only used when sleep_rgb_or_color_temp is 'rgb_color'). | False | [255, 56, 0] |
list |
sleep_color_temp |
Color temperature of lights while the sleep mode is enabled (only used when sleep_rgb_or_color_temp is 'color_temp'). | False | 1000 | integer |
sunrise_time |
Override the sunrise time with a fixed time. | False | None | time |
max_sunrise_time |
Make the virtual sun always rise at at most a specific time while still allowing for even earlier times based on the real sun | False | None | time |
sunrise_offset |
Change the sunrise time with a positive or negative offset. | False | 0 | time |
sunset_time |
Override the sunset time with a fixed time. | False | None | time |
min_sunset_time |
Make the virtual sun always set at at least a specific time while still allowing for even later times based on the real sun | False | None | time |
sunset_offset |
Change the sunset time with a positive or negative offset. | False | 0 | time |
only_once |
Whether to keep adapting the lights (false) or to only adapt the lights as soon as they are turned on (true). | False | False | boolean |
take_over_control |
If another source calls light.turn_on while the lights are on and being adapted, disable Adaptive Lighting. |
False | True | boolean |
detect_non_ha_changes |
Whether to detect state changes and stop adapting lights, even not from light.turn_on . Needs take_over_control to be enabled. Note that by enabling this option, it calls 'homeassistant.update_entity' every 'interval'! |
False | False | boolean |
separate_turn_on_commands |
Whether to use separate light.turn_on calls for color and brightness, needed for some types of lights |
False | False | boolean |
send_split_delay |
Wait between commands (milliseconds), when separate_turn_on_commands is used. May ensure that both commands are handled by the bulb correctly. | False | 0 | integer |
adapt_delay |
Wait time in seconds between light turn on, and Adaptive Lights applying changes to the light state. May avoid flickering. | False | 0 | integer |
Full example:
# Example configuration.yaml entry
adaptive_lighting:
- name: "default"
lights: []
prefer_rgb_color: false
transition: 45
initial_transition: 1
interval: 90
min_brightness: 1
max_brightness: 100
min_color_temp: 2000
max_color_temp: 5500
sleep_brightness: 1
sleep_color_temp: 1000
sunrise_time: "08:00:00" # override the sunrise time
sunrise_offset:
sunset_time:
sunset_offset: 1800 # in seconds or '00:15:00'
take_over_control: true
detect_non_ha_changes: false
only_once: false
adaptive_lighting.apply
applies Adaptive Lighting settings to lights on demand.
Service data attribute | Optional | Description |
---|---|---|
entity_id |
no | The entity_id of the switch with the settings to apply. |
lights |
yes | A light (or list of lights) to apply the settings to. |
transition |
yes | The number of seconds for the transition. |
adapt_brightness |
yes | Whether to change the brightness of the light or not. |
adapt_color |
yes | Whether to adapt the color on supporting lights. |
prefer_rgb_color |
yes | Whether to prefer RGB color adjustment over of native light color temperature when possible. |
turn_on_lights |
yes | Whether to turn on lights that are currently off. |
adaptive_lighting.set_manual_control
can mark (or unmark) whether a light is "manually controlled", meaning that when a light has manual_control
, the light is not adapted.
Service data attribute | Optional | Description |
---|---|---|
entity_id |
no | The entity_id of the switch in which to (un)mark the light as being "manually controlled". |
lights |
yes | entity_id(s) of lights, if not specified, all lights in the switch are selected. |
manual_control |
yes | Whether to add ('true') or remove ('false') the light from the 'manual_control' list, default: true |
adaptive_lighting.change_switch_settings
(new in 1.7.0) Change any of the above configuration options of Adaptive Lighting (such as sunrise_time
or prefer_rgb_color
) with a service call directly from your script/automation.
Service data attribute | Description |
---|---|
use_defaults |
(default: 'current' for current settings) You can set this to 'factory', 'configuration', or 'current' to reset the variables not being set with this service call. 'current' leaves them as is, 'configuration' resets to whatever already initializes at startup, 'factory' resets to the default values listed in the documentation. |
all other keys except the ones in the table below | See above, you may call adaptive_lighting.apply with your lights or create a new config instead |
DISALLOWED service data | Description |
---|---|
entity_id |
You cannot change the switch's entity_id , it's already been registered |
lights |
See above, you may call adaptive_lighting.apply with your lights or create a new config instead |
name |
See above. You can already rename your switch's display name in Home Assistant's UI. |
interval |
The interval is only used once when the config loads. A config change and restart is required |
Reset the manual_control
status of a light after an hour.
- 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
Toggle multiple Adaptive Lighting switches to "sleep mode" using an input_boolean.sleep_mode
.
- alias: "Adaptive lighting: toggle 'sleep mode'"
trigger:
- platform: state
entity_id: input_boolean.sleep_mode
- platform: homeassistant
event: start # in case the states aren't properly restored
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.
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
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
See the documentation of the PR at https://deploy-preview-14877--home-assistant-docs.netlify.app/integrations/adaptive_lighting/ and this video on Reddit to see how to add the integration and set the options.
This integration was originally based of the great work of @claytonjn https://github.com/claytonjn/hass-circadian_lighting, but has been 100% rewritten and extended with new features.
Please enable debug logging by putting this in configuration.yaml
:
logger:
default: warning
logs:
custom_components.adaptive_lighting: debug
and after the problem occurs please create an issue with the log (/config/home-assistant.log
).
This addon sends many more commands to lights compared to what humans would typically send. If the network used to send light commands is not healthy:
- Manual commands like turning lights on or off may feel laggy.
- Lights may not respond to commands at all.
- Home Assistant may think a light is on, when it's actually off. Adaptive Lights will send it's regular adjustments causing the light to turn on after it's turned off.
What's important is that many bugs that seem to be caused by this integration are really due to other unrelated issues. Fixing those will make your Home Assistant experience much better. Consider this integration a great stress test of your Home Assistant setup!
Make sure bulbs have a solid connection to your Wifi network. In general, if the signal is less than -70dBm, the connection is weak and may drop messages.
These types of mesh networks usually need powered devices that act as routers (that repeat messages) back to the central coordinator (the radio connected to Home Assistant). Most Philips lights are routers, but Ikea, Sengled, and generic Tuya bulbs often are not. If devices become unavailable or miss responding to commands, Adaptive Lighting will only make things worse. Use reporting tools such as network maps (available in ZHA, zigbee2mqtt, deCONZ, and ZWaveJS UI) to check your network. Smart plugs are often a cost-effective way to add additional routers to your network.
For most Zigbee networks, groups are absolutely required for good performance. For example, imagine you want to use Adaptive Lighting in a hallway with 6 bulbs. If you add each individual bulb in the Adaptive Lighting configuration, then six individual commands will be sent to adjust them, which can eventually overwhelm a network. Instead, create a group in your Zigbee software (but not a regular Home Assistant group), and add the one group to the Adaptive Lighting configuration. This will send only a single broadcast command to adjust the bulbs, giving much better response times and keeping the bulbs adjusting in sync with each other.
A good rule to follow is that if you always control lights together (like bulbs in a ceiling fixture), then they should be in a Zigbee group. Then, only expose the group (and not individual bulbs) in Home Assistant Dashboards and external systems like Google Home or Apple HomeKit.
Bulbs made by different manufacturers or of different models may have different specifications for the color temperatures they support. For example you have two Adaptive Lighting configurations:
- The first configuration has only Philips Hue White Ambiance bulbs.
- The second has the a few of the same model of White Ambiance bulbs as well as a few Sengled bulbs.
Even with identical settings, the Philips Hue bulbs may appear to have different color temperatures set at the same time.
To avoid this:
- Only put bulbs of the same make and model in a single Adaptive Lighting configuration.
- Move where bulbs are installed so you can't see different light temperatures at the same time.
Some bulbs have buggy behaviour with long light transition commands.
- Sengled Z01-A19NAE26: If Adaptive lighting sends a long transition time (like the default 45 seconds), and the bulb is turned off in that time, it will turn itself back on after 10 seconds or so to continue the transition command. Since the bulb is turning itself on, there will be no obvious trigger in Home Assistant or other logs showing what caused the light to turn on. Fix this by setting a much shorter transition time such as 1 second.
- As well, the same bulbs peform poorly when in typical enclosed "dome" style ceiling lights. When hot, their performance becomes marginal at best. While most LEDs (even non-smart ones) say in the small print that they do not support working in enclosed fixtures, in practice more expensive bulbs like Philips Hue perform better. Fix this by moving suspect bulbs to open-air fixtures.
These graphs were generated using the values calculated by the Adaptive Lighting sensor/switch(es).