- Automatically adapt the brightness and color of lights based on the sun position and take over manual control
- Use cases
- Plans/Goals
- More Background Info
Automatically adapt the brightness and color of lights based on the sun position and take over manual control
Go to https://github.com/basnijholt/adaptive-lighting for the original project and instructions.
This is a very simple fork designed for my own personal system, but you are welcome to use this if you'd like.
I work very odd hours that require me to wake up at a different time everyday, so I was looking for a way to adapt my lights throughout the day based on whenever my Home Assistant alarm runs. Unfortunately after a lot of searching it seemed like this simple feature didn't exist anywhere, so I decided to make it myself. I know next to nothing about python so my priorities were:
- Getting the damn thing to work
- Trying my best to 'add' the feature instead of rewrite the entire code, therefore allowing me to merge future updates into this fork.
- Useability & Flexibility.
adaptive_lighting.change_switch_settings
Change any configuration option on the fly with a service call.
This fork is PERFECT if you, like I, have your smart phone's alarm synced up with Home Assistant. Simply have your phone call your home assistant alarm script/automation, then set the sunrise time to Python's now() and, optionally, the sunset time to ~12 hours in the future like so:
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: "{{ ((states.switch\n | map(attribute='entity_id')\n \
\ | select(\">\",\"switch.adaptive_lighting_al_\")\n | select(\"<\"\
, \"switch.adaptive_lighting_al_z\")\n | join(\",\")).split(\",\"\
))[repeat.index-1] }}"
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
This script loops through all of my adaptive lighting switches and applies the new sunrise/sunset times on EACH switch.
option | description | required | default | type |
---|---|---|---|---|
name |
The name to use when displaying this switch. | False | default | string |
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 |
- I'm still having issues with the 'detect changes outside of HA' option in the original integration. If I don't find a fix soon I'm going to code something myself. Sometimes we turn off lights and they turn right back on
interval
seconds later. Wth?? - Have service calls default to every adaptive-lighting switch. Looping around a naming scheme to me seems very hacky and I'd love a way to leave the switch entity_id blank and the integration be smart enough to just friggin apply the settings to every switch... Maybe internally using group.set and a naming scheme?
- Have lights follow closer to the actual weather forecast for the day. i.e if it's snowy or cloudy outside, have the bulbs dim to 80% brightness and apply a colder color temperature to give a milky/foggy feel to the room.
- Mark the time a light has been set to manual control, for easily automating a reset after an allotted amount of time
I actually have an AdaptiveLighting switch per every smart bulb group in my home. Home assistant doesn't measure the brightness in lumens (I mean how could they) so it just doesn't make sense to use the same color temperatures/brightnesses for every single bulb of my home. Unfortunately I ran into some hiccups trying to use any of the original service calls due to the integration taking a singular switch entity_id instead of allowing multiple.
This means I either needed to hard code ALL of my scripts for each bulb group I have (over 10 currently) and hate myself later whenever I have to rewrite most of the code whenever I add a new bulb group, or I was going to need to figure out a way to group multiple switches.
I first looked into the group.set command from home assistant. This would take a lot of the hard-coding out but it still wasn't quite as simple as I wanted it to be. The goal was to be able to use one service call to apply any global settings I wanted to all of my adaptive switches.
I eventually figured out a somewhat-functional method. Using templates I was actually able to select all adaptive light switches that followed a naming scheme, specifically 'switch.adaptive_lighting_al_[light_entity_id]'
It's apparently possible to manually add an adaptive-lighting integration via the config file, so that's how I did it. Add to home assistant configuration.yaml: adaptive_lighting: !include adaptive_lighting.yaml
And then create adaptive-lighting.yaml and fill it in with your preferences. Here's part of mine.
- name: "al_bedroom_ceilingfan_lights"
lights: light.bedroom_ceilingfan_lights
prefer_rgb_color: false
transition: 45
initial_transition: 1
interval: 90
min_brightness: 35
max_brightness: 100
min_color_temp: 2000
max_color_temp: 5500
sleep_brightness: 1
sleep_color_temp: 1000
take_over_control: true
detect_non_ha_changes: false
only_once: false
- name: "al_den_ceilingfan_lights"
lights: light.den_ceilingfan_lights
prefer_rgb_color: false
transition: 45
initial_transition: 1
interval: 90
min_brightness: 35
max_brightness: 100
min_color_temp: 2000
max_color_temp: 5500
sleep_brightness: 1
sleep_color_temp: 1000
take_over_control: true
detect_non_ha_changes: false
only_once: false
- name: "al_linkind_dimmer_bulb"
lights: light.linkind_dimmer_bulb
prefer_rgb_color: false
transition: 45
initial_transition: 0
interval: 90
min_brightness: 45
max_brightness: 100
sleep_brightness: 8
take_over_control: true
detect_non_ha_changes: false
only_once: false
You can then create several scripts:
adaptive_lighting_set_manual_control:
alias: 'Adaptive Lighting: Set Manual Control'
sequence:
- choose:
- conditions:
- condition: template
value_template: '{{ lights is string }}'
sequence:
- service: switch.turn_off
target:
entity_id: '{{ "switch.adaptive_lighting_al" ~ lights[6:] }}'
- conditions:
- condition: template
value_template: '{{ lights is mapping }}'
sequence:
- service: switch.turn_off
target:
entity_id: '{{ "switch.adaptive_lighting_al" ~ lights[6:]|join(",switch.adaptive_lighting_al")
}}'
default:
- service: switch.turn_off
target:
entity_id: "{{ states.switch\n | map(attribute='entity_id')\n | select(\"\
>\", \"switch.adaptive_lighting_al_\")\n | select(\"<\", \"switch.adaptive_lighting_al_z\"\
)\n | join(\",\") }}"
mode: single
adaptive_lighting_disable_sleep_mode:
alias: 'Adaptive Lighting: Disable Sleep Mode'
sequence:
- choose:
- conditions:
- condition: template
value_template: '{{ lights is string }}'
sequence:
- service: switch.turn_off
target:
entity_id: '{{ "switch.adaptive_lighting_sleep_mode_al_" ~ lights[6:] }}'
- conditions:
- condition: template
value_template: '{{ lights is mapping }}'
sequence:
- service: switch.turn_off
target:
entity_id: '{{ "switch.adaptive_lighting_sleep_mode_al_" ~ lights[6:]|join(",switch.adaptive_lighting_sleep_mode_al_")
}}'
default:
- service: switch.turn_off
target:
entity_id: "{{ states.switch\n | map(attribute='entity_id')\n | select(\"\
>\", \"switch.adaptive_lighting_sleep_mode_al_\")\n | select(\"<\", \"\
switch.adaptive_lighting_sleep_mode_al_z\")\n | join(\",\") }}"
- condition: template
value_template: '{{ force == true }}'
- service: script.adaptive_lighting_apply
data:
lights: lights
mode: single
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 |
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
Custom automation for detecting manual control events
alias: "[Adaptive Lighting] State Changed -> Set manual control DEV"
description: ""
trigger:
- platform: event
event_type: state_changed
condition:
- condition: template
value_template: "{{ domain == \"light\" }}"
- condition: template
value_template: "{{ newstate == \"on\" }}"
- condition: template
value_template: "{{ oldstate == \"on\" }}"
- condition: template
value_template: "{{ context_parent_id == none }}"
- condition: template
value_template: "{{ context_user_id == none }}"
- condition: template
value_template: "{{ \"adapt_lgt_\" not in context_id }}"
- condition: or
conditions:
- condition: template
value_template: "{{ (new_color_temp|float(0) - old_color_temp|float(0))|abs >= 20 }}"
- condition: template
value_template: "{{ (new_brightness|float(0) - old_brightness|float(0))|abs >= 25 }}"
action:
- service: system_log.write
data:
message: >-
Monitor Adaptive Lighting ({{ light }}/{{ newlight }}/{{ oldlight }})
changed by {{ context_user_id }}/{{ context_id }}/{{ context_parent_id
}}. Brightness ({{ old_brightness }}-> {{ new_brightness }}). Color Temp
({{ old_color_temp }}-> {{ new_color_temp }})
level: warning
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ \"switch.adaptive_lighting_al_\" ~ light[6:] }}"
lights: "{{ light }}"
manual_control: true
mode: parallel
max: 100
variables:
light: "{{ trigger.event.data.entity_id | default }}"
newlight: "{{ trigger.event.data.new_state.entity_id | default }}"
oldlight: "{{ trigger.event.data.old_state.entity_id | default }}"
domain: "{{ trigger.event.data.new_state.domain | default }}"
newstate: "{{ trigger.event.data.new_state.state | default }}"
oldstate: "{{ trigger.event.data.old_state.state | default }}"
context_user_id: |-
{% if trigger.event.data.new_state != None %}
{{ trigger.event.data.new_state.context.user_id | default }}
{% else %}
{{ None }}
{% endif %}
context_id: |-
{% if trigger.event.data.new_state != None %}
{{ trigger.event.data.new_state.context.id | default }}
{% else %}
{{ None }}
{% endif %}
context_parent_id: |-
{% if trigger.event.data.new_state != None %}
{{ trigger.event.data.new_state.context.parent_id | default }}
{% else %}
{{ None }}
{% endif %}
new_brightness: |-
{% if trigger.event.data.new_state != None %}
{{ trigger.event.data.new_state.attributes.brightness | default }}
{% else %}
{{ None }}
{% endif %}
old_brightness: |-
{% if trigger.event.data.old_state != None %}
{{ trigger.event.data.old_state.attributes.brightness | default }}
{% else %}
{{ None }}
{% endif %}
new_color_temp: |-
{% if trigger.event.data.new_state != None %}
{{ trigger.event.data.new_state.attributes.color_temp | default }}
{% else %}
{{ None }}
{% endif %}
old_color_temp: |-
{% if trigger.event.data.old_state != None %}
{{ trigger.event.data.old_state.attributes.color_temp | default }}
{% else %}
{{ None }}
{% endif %}
Using templates you can loop through all of your Adaptive Lighting switches since they're following the same naming scheme, thus removing the hard-coding problem and allowing yourself to benefit from all future updates to the integration!
These graphs were generated using the values calculated by the Adaptive Lighting sensor/switch(es).
Go support the original project! @basnijholt https://github.com/basnijholt/adaptive-lighting