Skip to content

Instantly share code, notes, and snippets.

@dimkaram
Last active January 30, 2026 18:46
Show Gist options
  • Select an option

  • Save dimkaram/39cfe8e996aaa7f3fe3727495b120ce5 to your computer and use it in GitHub Desktop.

Select an option

Save dimkaram/39cfe8e996aaa7f3fe3727495b120ce5 to your computer and use it in GitHub Desktop.
Homeassistant Automation Blueprint for Advanced Circadian Lighting
blueprint:
# INTRODUCTION
name: Advanced Circadian Lighting (dimkaram) — v2.0-dev1
description: >-
This automation adjusts light brightness, temperature, and color hue,
based on presence, current time and sun position.
IMPORTANT – Breaking change in v2.0:
- You MUST create the helper `input_text.adaptive_circadian_manual_hold`.
- This `input_text` is used internally to remember lights that are under
manual control (manual hold/override).
- If this helper is missing or not created, the automation continues to
run, but the manual-override feature will be disabled.
- The lights input has changed to `lights_entities` (entity list selector). This is a breaking change.
- Migration notes:
- Re-select lights under `lights_entities` (multi-select).
- Create helper `input_text.adaptive_circadian_manual_hold` (required).
- After updating, re-save automations created from this blueprint (selector changed).
Available features:
- Works with lights supporting different types of color modes.
- Adjusts brightness, temperature and hue based on the circadian cycle.
- The circadian cycle can be defined based on sunrise and sunset times.
- Sunrise and sunset times can also be defined manually.
- Light attrubutes can be adjusted following various types of functions.
- The minimum and maximum brightness and temperature can be configured.
- This option can be deactivated.
- Turns the lights on at sunset and off at sunrise.
- Option to inverse this behaviour for daytime lights.
- Sunrise and sunset can be defined in terms of sun elevation.
- Gradually dims lights on or off within a sun elevation range.
- This option can be deactivated.
- Turns the lights on and off based on presence.
- Presence is defined in terms of an entity, that can be on or home.
- Can also be defined in terms of a media player that is playing or paused.
- Option to inverse this behaviour for away lights.
- This option is deacivated when no entity is selected.
- Allows to define a sleeping-mode entity that can be turned on.
- When this entity is turned on, lights turn off.
- Alternatively, lights can switch to midnight attributes.
- This option is deactivated when no entity is selected.
- Gets automatically activated when a light is turned on.
- Runs when the turn on service does not define light brightness or color.
- Does not run when the turn on service also defines light attributes, e.g. in scenes.
- Stops running when light attributes are manually changed.
- The light will still turn off automatically.
This is now under active development and testing. Please do not update for now.
domain: automation
source_url: https://gist.githubusercontent.com/dimkaram/39cfe8e996aaa7f3fe3727495b120ce5/raw/31dc4aeb615af947b342840720b397d0f51226ec/advanced_circadian_lighting.yaml
# INPUT
input:
# Input - Lights (BREAKING)
lights_entities:
name: Lights
description: >-
List of light entities to adjust (breaking).
On/off, brightness, temperature and color modes are supported.
selector:
entity:
domain: light
multiple: true
# Input - Circadian
circadian_type:
name: Circadian cycle
description: >-
Determines how the circadian cycle will be defined.
Select sun to use actual sunrise and sunset times.
Select time to override with manual sunrise and sunset times.
selector:
select:
options:
- Sun
- Time
default: Time
circadian_sunrise:
name: Circadian manual sunrise time
description: >-
Defines the start of daytime when the circadian rhythm is time-based.
This value is ignored when using actual sunrise and sunset times.
selector:
time:
default: "07:30:00"
circadian_sunset:
name: Circadian manual sunset time
description: >-
Defines the end of daytime when the circadian rhythm is time-based.
This value is ignored when using sunrise and sunset times.
selector:
time:
default: "21:30:00"
circadian_function:
name: Circadian function
description: >-
Determines the function used for circadian cycle adjustments.
Use day-only or night-only to adjust lights in daytime or nightime.
Day & night half-sine functions are similar to the full cycle cosine.
Select day & night cosine functions for smoother transitions.
Select none to skip circadian cycle adjustments.
default: Day-only (half-sine)
selector:
select:
options:
- Day-only (half-sine)
- Day-only (cosine)
- Night-only (half-sine)
- Night-only (cosine)
- Day & night (cosine)
- None
circadian_brightness_midday:
name: Circadian brightness at midday
description: >-
Sets the maximum (or minimum) value that light brightness
will reach at midday.
This value is also used when circadian lighting is disabled.
default: 100
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
mode: slider
step: 5
circadian_brightness_midnight:
name: Circadian brightness at midnight
description: >-
Sets the minimum (or maximum) value that light brightness
will reach at midnight.
This value is ignored when circadian lighting is disabled.
default: 10
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
mode: slider
step: 5
circadian_temperature_midday:
name: Circadian temperature at midday
description: >-
Sets the maximum (or minimum) value that color temperature
will reach at midday.
This value is also used when circadian lighting is disabled.
default: 4100
selector:
number:
min: 2700
max: 6500
unit_of_measurement: "Kelvin"
mode: slider
step: 100
circadian_temperature_midnight:
name: Circadian temperature at midnight
description: >-
Sets the minimum (or maximum) value that color temperature
will reach at midnight.
This value is ignored when circadian lighting is disabled.
default: 2700
selector:
number:
min: 2700
max: 6500
unit_of_measurement: "Kelvin"
mode: slider
step: 100
circadian_hue_default:
name: Circadian default hue
description: >-
Sets the hue value to be used for when circadian lighting is disabled.
default: 180
selector:
number:
min: 0
max: 360
unit_of_measurement: "°"
mode: slider
step: 5
circadian_saturation_default:
name: Circadian default saturation
description: >-
Sets the color saturation value to be used for circadian adjustments.
default: 100
selector:
number:
min: 0
max: 100
unit_of_measurement: "%"
mode: slider
step: 5
# Input Elevation
elevation_switch_on:
name: Dim lights on based on sun elevation
description: >-
Normally, the lights will dim on at sunset,
and they will be turned on during nightime.
default: true
selector:
boolean:
elevation_switch_off:
name: Dim lights off based on sun elevation
description: >-
Normally, the lights will dim off at sunrise,
and they will be turned off during daytime.
default: true
selector:
boolean:
elevation_inverse:
name: Inverse light dimming behaviour
description: >-
Dim the lights on at sunrise and off at sunset.
The lights will be turned on during daytime
and off during nightime.
default: false
selector:
boolean:
elevation_sunrise_start:
name: Sunrise start elevation
description: >-
Defines the beginning of sunrise in terms of sun elevation.
Normally, this is when lights start to dim off.
When dimming is inversed, this is when lights turn on.
default: -8
selector:
number:
min: -20
max: 10
unit_of_measurement: "°"
mode: slider
step: 1
elevation_sunrise_end:
name: Sunrise end elevation
description: >-
Defines the end of sunrise in terms of sun elevation.
Normally, this is when lights turn off.
When dimming is inversed, this is when lights have fully dimmed on.
default: 6
selector:
number:
min: -10
max: 20
unit_of_measurement: "°"
mode: slider
step: 1
elevation_sunset_start:
name: Sunset start elevation
description: >-
Defines the beginning of sunset in terms of sun elevation.
Normally, this is when lights turn on.
When dimming is inversed, this is when lights start to dim off.
default: 6
selector:
number:
min: -10
max: 20
unit_of_measurement: "°"
mode: slider
step: 1
elevation_sunset_end:
name: Sunset end elevation
description: >-
Defines the end of sunset in terms of sun elevation.
Normally, this is when light have fully dimmed on.
When dimming is inversed, this is when lights turn off.
default: -8
selector:
number:
min: -20
max: 10
unit_of_measurement: "°"
mode: slider
step: 1
# Input Presence
presence_switch_on:
name: Turn lights on based on presence
description: >-
Normally, the lights will turn on when presence turns on,
and they will be on when at home.
default: true
selector:
boolean:
presence_switch_off:
name: Turn lights off based on presence
description: >-
Normally, the lights will turn off when presence turns off,
and they will be off when away.
default: true
selector:
boolean:
presence_inverse:
name: Inverse presence light behaviour
description: >-
Turn the lights on when presence turns off
and off when presence turns on.
The lights will be off when at home
and on when away.
default: false
selector:
boolean:
presence_entity:
name: Presence entity_id
description: >-
This can be a device_tracker, an input_boolean, a binary_sensor,
or any entity that can switch to "on" or "home".
It can also be a media_player that can swith to "playing" or "paused".
default: "device_tracker.me"
selector:
entity:
# Input Sleep Mode
sleep_mode:
name: Sleep Mode
description: >-
Defines what will happen when the sleep mode switch is activated.
On and off will turn the lights on and off, respectively.
Day and night will switch to midday and midnight attributes, respectively.
None will do nothing.
selector:
select:
options:
- Turn on
- Turn off
- Day
- Night
- None
default: Turn off
sleep_entity:
name: Sleep entity_id
description: >-
This can be a device_tracker, an input_boolean, a binary_sensor,
or any entity that can switch to "on" or "home".
default: "input_boolean.sleep"
selector:
entity:
# Input Debug Level
debug_level:
name: Debug level
description: >-
Select debug verbosity used for Logbook and System Log.
default: Off
selector:
select:
options:
- Off
- Basic
- Verbose
# Input - Manual hold helper
manual_hold_helper:
name: Manual hold helper
description: >-
An `input_text` helper that stores a comma-separated list of entity_ids
for lights that are currently under manual hold/override.
Manual override is disabled only when this helper is missing or
unavailable. An empty string means "no held lights" (feature still active).
default: input_text.adaptive_circadian_manual_hold
selector:
entity:
domain: input_text
# VARIABLES
variables:
# Version
blueprint_version: "v2.0-dev1"
# Constants / tolerances
brightness_follow_tolerance: 15
brightness_dim_buffer: 15
hue_follow_tolerance: 30
circadian_temperature_tolerance_kelvin: 250
# Manual-change thresholds (detect user edits)
manual_change_brightness_delta: 20
manual_change_ct_delta_kelvin: 200
manual_change_hue_delta: 20
# Output min-change thresholds (throttle automation updates)
output_min_brightness_delta: 5
output_min_ct_delta_kelvin: 50
output_min_hue_delta: 5
# Manual hold helper (safe read + parsed list)
manual_hold_helper: !input manual_hold_helper
manual_hold_raw: >-
{# Safely read helper value; treat missing/unknown as empty; allow empty string as valid (no held lights) #}
{{ states(manual_hold_helper) if ( manual_hold_helper is string and states(manual_hold_helper) not in ['unknown','unavailable'] ) else '' }}
manual_hold_list: >-
{%- set raw = manual_hold_raw|default('') -%}
{%- set ns = namespace(items=[]) -%}
{%- for item in raw.split(',') -%}
{%- set s = item.strip() -%}
{%- if s != '' -%}
{%- set ns.items = ns.items + [s] -%}
{%- endif -%}
{%- endfor -%}
{{ ns.items }}
# Variables Lights - raw input copy
lights_entities_input: !input lights_entities
# Canonical list of managed light entity_ids
lights_all: >-
{# Treat input as plain list; accept a single string or a list #}
{%- if lights_entities_input is string -%}
{{ [ lights_entities_input ] }}
{%- else -%}
{{ lights_entities_input }}
{%- endif -%}
# Trigger helpers
# Manual-hold add: compute from light.turn_on service events (mode-aware)
manual_hold_add_entities: >-
{%- set res = namespace(items=[]) -%}
{%- if trigger is defined and trigger.platform == 'event' and trigger.event.data.service == 'turn_on' and trigger.event.data.service_data is defined -%}
{%- set svc = trigger.event.data.service_data -%}
{%- if svc.entity_id is defined -%}
{%- if svc.entity_id is string -%}
{%- set lights_list = [ svc.entity_id ] -%}
{%- else -%}
{%- set lights_list = svc.entity_id -%}
{%- endif -%}
{%- else -%}
{%- set lights_list = [] -%}
{%- endif -%}
{# expand group members if present #}
{%- set group_list = namespace(entities=[]) -%}
{%- for i_entity in lights_list -%}
{%- if state_attr(i_entity,'entity_id') is not none -%}
{%- if state_attr(i_entity,'entity_id') is string -%}
{%- set group_list.entities = group_list.entities + [state_attr(i_entity,'entity_id')] -%}
{%- else -%}
{%- set group_list.entities = group_list.entities + state_attr(i_entity,'entity_id') -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set lights_list = lights_list + group_list.entities -%}
{# Determine requested attributes presence #}
{%- set req_color = (svc.hs_color is defined) or (svc.xy_color is defined) or (svc.rgb_color is defined) or (svc.rgbw_color is defined) or (svc.rgbww_color is defined) or (svc.color_name is defined) -%}
{%- set req_white = (svc.color_temp_kelvin is defined) or (svc.kelvin is defined) or (svc.color_temp is defined) -%}
{%- set req_brightness = (svc.brightness is defined) or (svc.brightness_pct is defined) -%}
{%- for i in lights_list -%}
{%- if is_state(i,'on') -%}
{# current mode detection #}
{%- set cur_color = (state_attr(i,'hs_color') is not none) -%}
{%- set hold = false -%}
{# mode switch checks: white->color or color->white => do not hold #}
{%- if (not cur_color and req_color) or (cur_color and req_white) -%}
{%- set hold = false -%}
{%- else -%}
{# brightness check #}
{%- if req_brightness and state_attr(i,'brightness') is not none -%}
{%- set cur_b = state_attr(i,'brightness')|int -%}
{%- set tgt_b = (svc.brightness if svc.brightness is defined else ( (svc.brightness_pct|float * 2.55)|int ) ) -%}
{%- if (cur_b - tgt_b)|abs >= manual_change_brightness_delta -%}
{%- set hold = true -%}
{%- endif -%}
{%- endif -%}
{# white mode CT check #}
{%- if not cur_color and req_white -%}
{%- if state_attr(i,'color_temp_kelvin') is not none -%}
{%- set cur_k = state_attr(i,'color_temp_kelvin')|int -%}
{%- elif state_attr(i,'color_temp') is not none -%}
{%- set cur_k = (1000000 / (state_attr(i,'color_temp')|float) + 0.5)|int -%}
{%- else -%}
{%- set cur_k = none -%}
{%- endif -%}
{%- if svc.color_temp_kelvin is defined -%}
{%- set tgt_k = svc.color_temp_kelvin|int -%}
{%- elif svc.color_temp is defined -%}
{%- set tgt_k = (1000000 / (svc.color_temp|float) + 0.5)|int -%}
{%- elif svc.kelvin is defined -%}
{%- set tgt_k = svc.kelvin|int -%}
{%- else -%}
{%- set tgt_k = none -%}
{%- endif -%}
{%- if cur_k is not none and tgt_k is not none and (cur_k - tgt_k)|abs >= manual_change_ct_delta_kelvin -%}
{%- set hold = true -%}
{%- endif -%}
{%- endif -%}
{# color mode hue check #}
{%- if cur_color and req_color -%}
{%- if svc.hs_color is defined -%}
{%- set cur_h = (state_attr(i,'hs_color')[0]|float) -%}
{%- set tgt_h = (svc.hs_color[0]|float) -%}
{%- set diff = ((cur_h - tgt_h + 180) % 360) - 180 -%}
{%- if (diff|abs) >= manual_change_hue_delta -%}
{%- set hold = true -%}
{%- endif -%}
{%- else -%}
{# color request without explicit hs -> treat as manual hold #}
{%- set hold = true -%}
{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- if hold and ( i in lights_all ) -%}
{%- set res.items = res.items + [ i ] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{{ res.items }}
# Manual-hold remove: compute from light.turn_off service events
manual_hold_remove_entities: >-
{%- set rem = namespace(items=[]) -%}
{%- if trigger is defined and trigger.platform == 'event' and trigger.event.data.service == 'turn_off' and trigger.event.data.service_data is defined -%}
{%- set svc = trigger.event.data.service_data -%}
{%- if svc.entity_id is defined -%}
{%- if svc.entity_id is string -%}
{%- set lights_list = [ svc.entity_id ] -%}
{%- else -%}
{%- set lights_list = svc.entity_id -%}
{%- endif -%}
{%- else -%}
{%- set lights_list = [] -%}
{%- endif -%}
{%- set group_list = namespace(entities=[]) -%}
{%- for i_entity in lights_list -%}
{%- if state_attr(i_entity,'entity_id') is not none -%}
{%- if state_attr(i_entity,'entity_id') is string -%}
{%- set group_list.entities = group_list.entities + [state_attr(i_entity,'entity_id')] -%}
{%- else -%}
{%- set group_list.entities = group_list.entities + state_attr(i_entity,'entity_id') -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set lights_list = lights_list + group_list.entities -%}
{%- for i in lights_list -%}
{%- if trigger.event.data.service == 'turn_off' and ( i in lights_all ) -%}
{%- set rem.items = rem.items + [ i ] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{# Also remove manual hold when a managed light changes state to 'off' (physical/HA) #}
{%- if trigger is defined and trigger.platform == 'state' and trigger.to_state is not none and trigger.to_state.state == 'off' and trigger.entity_id is defined and ( trigger.entity_id in lights_all ) -%}
{%- set rem.items = rem.items + [ trigger.entity_id ] -%}
{%- endif -%}
{# Deduplicate while preserving order #}
{%- set uniq = namespace(items=[]) -%}
{%- for e in rem.items -%}
{%- if e not in uniq.items -%}
{%- set uniq.items = uniq.items + [ e ] -%}
{%- endif -%}
{%- endfor -%}
{{ uniq.items }}
# Off->On entities (from call_service turn_on where current state == 'off')
off_to_on_entities: >-
{%- set res = namespace(items=[]) -%}
{%- if trigger is defined and trigger.platform == 'state'
and trigger.from_state is not none and trigger.to_state is not none
and trigger.from_state.state == 'off' and trigger.to_state.state == 'on'
and ( trigger.entity_id in lights_all ) -%}
{%- set res.items = [ trigger.entity_id ] -%}
{%- elif trigger is defined and trigger.platform == 'event' and trigger.event.data.service == 'turn_on' and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.brightness is not defined ) and ( trigger.event.data.service_data.brightness_pct is not defined )
and ( trigger.event.data.service_data.effect is not defined ) and ( trigger.event.data.service_data.flash is not defined ) -%}
{%- set svc = trigger.event.data.service_data -%}
{%- if svc.entity_id is defined -%}
{%- if svc.entity_id is string -%}
{%- set lights_list = [ svc.entity_id ] -%}
{%- else -%}
{%- set lights_list = svc.entity_id -%}
{%- endif -%}
{%- else -%}
{%- set lights_list = [] -%}
{%- endif -%}
{%- set group_list = namespace(entities=[]) -%}
{%- for i_entity in lights_list -%}
{%- if state_attr(i_entity,'entity_id') is not none -%}
{%- if state_attr(i_entity,'entity_id') is string -%}
{%- set group_list.entities = group_list.entities + [state_attr(i_entity,'entity_id')] -%}
{%- else -%}
{%- set group_list.entities = group_list.entities + state_attr(i_entity,'entity_id') -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set lights_list = lights_list + group_list.entities -%}
{%- for i in lights_list -%}
{%- if states(i) == 'off' and ( i in lights_all ) -%}
{%- set res.items = res.items + [ i ] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{{ res.items }}
# Variables Sleep
sleep_mode: !input sleep_mode
sleep_entity: !input sleep_entity
sleep_on: >-
{# Boolean for sleep on trigger #}
{{ trigger is defined
and trigger.platform == 'state'
and trigger.from_state.entity_id is defined
and trigger.from_state.entity_id == sleep_entity
and trigger.to_state.state is defined
and ( ( sleep_mode == 'Turn on'
and trigger.to_state.state == 'on' )
or ( sleep_mode == 'Turn off'
and trigger.to_state.state == 'off' ) ) }}
sleep_off: >-
{# Boolean for sleep off trigger #}
{{ trigger is defined
and trigger.platform == 'state'
and trigger.from_state.entity_id is defined
and trigger.from_state.entity_id == sleep_entity
and trigger.to_state.state is defined
and ( ( sleep_mode == 'Turn on'
and trigger.to_state.state == 'off' )
or ( sleep_mode == 'Turn off'
and trigger.to_state.state == 'on' ) ) }}
sleep_trigger: >-
{# Boolean for sleep attributes trigger #}
{{ trigger is defined
and trigger.platform == 'state'
and trigger.from_state.entity_id is defined
and trigger.from_state.entity_id == sleep_entity
and trigger.to_state.state is defined
and sleep_mode in ['Day','Night'] }}
sleep_condition: >-
{# Boolean for lights on #}
{{ states(sleep_entity) == 'unknown'
or sleep_mode not in ['Turn on','Turn off']
or ( sleep_mode == 'Turn on'
and states(sleep_entity) == 'on' )
or ( sleep_mode == 'Turn off'
and states(sleep_entity) == 'off' ) }}
# Variables Circadian
circadian_type: !input circadian_type
circadian_sunrise: !input circadian_sunrise
circadian_sunset: !input circadian_sunset
circadian_function: !input circadian_function
circadian_position: >-
{# Define next sunrise and sunset times based on sun or manual input #}
{%- set dt_current = now() -%}
{%- if circadian_type == 'Sun'
and states('sun.sun') != 'unknown'
and state_attr('sun.sun','next_rising') is not none
and state_attr('sun.sun','next_setting') is not none -%}
{%- set dt_sunrise = as_datetime(state_attr('sun.sun','next_rising')) -%}
{%- set dt_sunset = as_datetime(state_attr('sun.sun','next_setting')) -%}
{%- else -%}
{%- set dt_sunrise = today_at(circadian_sunrise) -%}
{%- set dt_sunset = today_at(circadian_sunset) -%}
{# Fix input times so that they reflect the next sunrise and sunset (DST-safe) #}
{%- if dt_sunrise < dt_current -%}
{%- set dt_sunrise = dt_sunrise + timedelta(days=1) -%}
{%- endif -%}
{%- if dt_sunset < dt_current -%}
{%- set dt_sunset = dt_sunset + timedelta(days=1) -%}
{%- endif -%}
{%- endif -%}
{%- set time_current = dt_current|as_timestamp -%}
{%- set time_sunrise = dt_sunrise|as_timestamp -%}
{%- set time_sunset = dt_sunset|as_timestamp -%}
{# Calculate sun position, from -1 to 0 in nightime and 0 to 1 in daytime. #}
{%- if time_sunrise > time_sunset -%}
{%- set time_sunrise = [ (dt_sunrise - timedelta(days=1))|as_timestamp , time_current ]|min -%}
{%- set position = (time_current-time_sunrise) / (time_sunset-time_sunrise) -%}
{%- else -%}
{%- set time_sunset = [ (dt_sunset - timedelta(days=1))|as_timestamp , time_current ]|min -%}
{%- set position = (time_current-time_sunrise) / (time_sunrise-time_sunset) -%}
{%- endif -%}
{# Return calculated result #}
{{ position }}
circadian_coefficient: >-
{# Calculate coefficient based on selected circadian function #}
{%- if is_state(sleep_entity,'on') and sleep_mode == 'Night' -%}
{%- set coefficient = 0.0 -%}
{%- elif is_state(sleep_entity,'on') and sleep_mode == 'Day' -%}
{%- set coefficient = 1.0 -%}
{%- elif circadian_function == 'Day-only (half-sine)' -%}
{%- set coefficient = sin( pi*([circadian_position,0]|max) ) -%}
{%- elif circadian_function == 'Day-only (cosine)' -%}
{%- set coefficient = 0.5 - 0.5 * cos( 2*pi*([circadian_position,0]|max) ) -%}
{%- elif circadian_function == 'Night-only (half-sine)' -%}
{%- set coefficient = 1 - sin( pi*([circadian_position,0]|min) ) -%}
{%- elif circadian_function == 'Night-only (cosine)' -%}
{%- set coefficient = 0.5 + 0.5 * cos( 2*pi*([circadian_position,0]|min) ) -%}
{%- elif circadian_function == 'Day & night (cosine)' -%}
{%- set coefficient = 0.5 + 0.5 * sin( pi*circadian_position ) -%}
{%- else -%}
{%- set coefficient = -1.0 -%}
{%- endif -%}
{# Return calculated result #}
{{ coefficient }}
circadian_angle: >-
{# Calculate angle based on selected circadian function #}
{%- if is_state(sleep_entity,'on') and sleep_mode == 'Night' -%}
{%- set angle = 0.0 -%}
{%- elif is_state(sleep_entity,'on') and sleep_mode == 'Day' -%}
{%- set angle = 0.5 -%}
{%- elif circadian_function == 'Day-only (half-sine)'
or circadian_function == 'Day-only (cosine)' -%}
{%- set angle = [circadian_position,0]|max -%}
{%- elif circadian_function == 'Night-only (half-sine)'
or circadian_function == 'Night-only (cosine)' -%}
{%- if circadian_position < -0.5 -%}
{%- set angle = 1.5 + circadian_position -%}
{%- else -%}
{%- set angle = 0.5 + [circadian_position,0]|min -%}
{%- endif -%}
{%- elif circadian_function == 'Day & night (cosine)' -%}
{%- if circadian_position < -0.5 -%}
{%- set angle = 1.25 + 0.5 * circadian_position -%}
{%- else -%}
{%- set angle = 0.25 + 0.5 * circadian_position -%}
{%- endif -%}
{%- else -%}
{%- set angle = -1.0 -%}
{%- endif -%}
{# Return calculated result #}
{{ angle }}
circadian_brightness_midday: !input circadian_brightness_midday
circadian_brightness_midnight: !input circadian_brightness_midnight
circadian_brightness_value: >-
{# Light brightness value to adjust to #}
{%- if circadian_coefficient < 0 -%}
{{ [ ( 2.54 * circadian_brightness_midday|float + 0.5)|int , 1]|max }}
{%- else -%}
{{ [ ( 2.54 * ( circadian_brightness_midnight|float + circadian_coefficient * ( circadian_brightness_midday|float - circadian_brightness_midnight|float ) ) + 0.5)|int , 1]|max }}
{%- endif -%}
circadian_temperature_midday: !input circadian_temperature_midday
circadian_temperature_midnight: !input circadian_temperature_midnight
circadian_temperature_kelvin: >-
{# Color temperature value to adjust to (Kelvin) #}
{%- if circadian_coefficient < 0 -%}
{{ (circadian_temperature_midday|float + 0.5) | int }}
{%- else -%}
{{ (circadian_temperature_midnight|float
+ circadian_coefficient * (circadian_temperature_midday|float - circadian_temperature_midnight|float)
+ 0.5) | int }}
{%- endif -%}
circadian_hue_default_input: !input circadian_hue_default
circadian_hue_default: '{{ circadian_hue_default_input|float }}'
circadian_hue_value: >-
{# Color hue value to adjust to #}
{%- if circadian_angle < 0 -%}
{{ circadian_hue_default }}
{%- else -%}
{{ (360 * circadian_angle + 0.5)|int }}
{%- endif -%}
circadian_saturation_default_input: !input circadian_saturation_default
circadian_saturation_default: '{{ circadian_saturation_default_input|float }}'
# Variables Sunrise/Sunset
elevation_switch_on_input: !input elevation_switch_on
elevation_switch_on: "{{ states('sun.sun') != 'unknown' and elevation_switch_on_input }}"
elevation_switch_off_input: !input elevation_switch_off
elevation_switch_off: "{{ states('sun.sun') != 'unknown' and elevation_switch_off_input }}"
elevation_inverse: !input elevation_inverse
elevation_sunrise_start_input: !input elevation_sunrise_start
elevation_sunrise_end_input: !input elevation_sunrise_end
elevation_sunrise_start: "{{ [ elevation_sunrise_start_input|float , elevation_sunrise_end_input|float ] | min }}"
elevation_sunrise_end: "{{ [ elevation_sunrise_start_input|float , elevation_sunrise_end_input|float ] | max }}"
elevation_sunset_start_input: !input elevation_sunset_start
elevation_sunset_end_input: !input elevation_sunset_end
elevation_sunset_start: "{{ [ elevation_sunset_start_input|float , elevation_sunset_end_input|float ] | max }}"
elevation_sunset_end: "{{ [ elevation_sunset_start_input|float , elevation_sunset_end_input|float ] | min }}"
elevation_condition: >-
{# Boolean for lights on #}
{{ elevation_switch_off
and ( ( not elevation_inverse
and ( ( state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') <= elevation_sunrise_end )
or ( not state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') <= elevation_sunset_start ) ) )
or ( elevation_inverse
and ( ( state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') >= elevation_sunrise_start )
or ( not state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') >= elevation_sunset_end ) ) ) ) }}
elevation_dim_on: >-
{# Boolean for dimming on #}
{{ elevation_switch_on
and ( ( not elevation_inverse
and not state_attr('sun.sun','rising')
and ( ( state_attr('sun.sun','elevation') <= elevation_sunset_start
and state_attr('sun.sun','elevation') >= elevation_sunset_end )
or ( trigger is defined
and trigger.platform == 'numeric_state'
and trigger.from_state.entity_id == 'sun.sun'
and trigger.from_state.attributes.elevation <= elevation_sunset_start
and trigger.from_state.attributes.elevation >= elevation_sunset_end ) ) )
or ( elevation_inverse
and state_attr('sun.sun','rising')
and ( ( state_attr('sun.sun','elevation') >= elevation_sunrise_start
and state_attr('sun.sun','elevation') <= elevation_sunrise_end )
or ( trigger is defined
and trigger.platform == 'numeric_state'
and trigger.from_state.entity_id == 'sun.sun'
and trigger.from_state.attributes.elevation >= elevation_sunrise_start
and trigger.from_state.attributes.elevation <= elevation_sunrise_end ) ) ) ) }}
elevation_dim_off: >-
{# Boolean for dimming off #}
{{ elevation_switch_off
and ( ( not elevation_inverse
and state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') >= elevation_sunrise_start
and state_attr('sun.sun','elevation') <= elevation_sunrise_end )
or ( elevation_inverse
and not state_attr('sun.sun','rising')
and state_attr('sun.sun','elevation') <= elevation_sunset_start
and state_attr('sun.sun','elevation') >= elevation_sunset_end ) ) }}
elevation_turn_on: >-
{# Boolean for turning on #}
{{ elevation_switch_on
and trigger is defined
and trigger.platform == 'numeric_state'
and trigger.from_state.entity_id == 'sun.sun'
and ( ( not elevation_inverse
and trigger.from_state.attributes.elevation >= elevation_sunset_start
and trigger.to_state.attributes.elevation <= elevation_sunset_start)
or ( elevation_inverse
and trigger.from_state.attributes.elevation <= elevation_sunrise_start
and trigger.to_state.attributes.elevation >= elevation_sunrise_start)) }}
elevation_turn_off: >-
{# Boolean for turning off #}
{{ elevation_switch_off
and trigger is defined
and trigger.platform == 'numeric_state'
and trigger.from_state.entity_id == 'sun.sun'
and ( ( not elevation_inverse
and trigger.from_state.attributes.elevation <= elevation_sunrise_end
and trigger.to_state.attributes.elevation >= elevation_sunrise_end)
or ( elevation_inverse
and trigger.from_state.attributes.elevation >= elevation_sunset_end
and trigger.to_state.attributes.elevation <= elevation_sunset_end)) }}
elevation_brightness: >-
{# Calculate coefficient to be multiplied with brightness #}
{%- if elevation_dim_on or elevation_dim_off -%}
{%- set elevation_current = state_attr('sun.sun','elevation') -%}
{%- if state_attr('sun.sun','rising') -%}
{%- set coefficient = 1.0 - (elevation_current-elevation_sunrise_start) / (elevation_sunrise_end-elevation_sunrise_start) -%}
{%- else -%}
{%- set coefficient = (elevation_current-elevation_sunset_start) / (elevation_sunset_end-elevation_sunset_start) -%}
{%- endif -%}
{%- if elevation_inverse -%}
{%- set coefficient = 1.0 - coefficient -%}
{%- endif -%}
{%- else -%}
{%- set coefficient = 1.0 -%}
{%- endif -%}
{# Return calculated result #}
{# Alternative: 0.5 - 0.5*cos(pi*coefficient) #}
{{ [ ( coefficient * circadian_brightness_value + 0.5 )|int , 1]|max }}
# Variables Presence
presence_entity: !input presence_entity
presence_switch_on_input: !input presence_switch_on
presence_switch_on: "{{ states(presence_entity) != 'unknown' and presence_switch_on_input }}"
presence_switch_off_input: !input presence_switch_off
presence_switch_off: "{{ states(presence_entity) != 'unknown' and presence_switch_off_input }}"
presence_inverse: !input presence_inverse
presence_on: >-
{# Boolean for presence trigger #}
{{ presence_switch_on
and trigger is defined
and trigger.platform == 'state'
and trigger.from_state.entity_id is defined
and trigger.from_state.entity_id == presence_entity
and trigger.to_state.state is defined
and ( ( not presence_inverse
and trigger.to_state.state in ['on','home','playing','paused'] )
or ( presence_inverse
and trigger.to_state.state not in ['on','home','playing','paused'] ) ) }}
presence_off: >-
{# Boolean for presence trigger #}
{{ presence_switch_off
and trigger is defined
and trigger.platform == 'state'
and trigger.from_state.entity_id is defined
and trigger.from_state.entity_id == presence_entity
and trigger.to_state.state is defined
and ( ( not presence_inverse
and trigger.to_state.state not in ['on','home','playing','paused'] )
or ( presence_inverse
and trigger.to_state.state in ['on','home','playing','paused'] ) ) }}
presence_condition: >-
{# Boolean for lights on #}
{{ presence_switch_off
and ( ( not presence_inverse
and states(presence_entity) in ['on','home','playing','paused'] )
or ( presence_inverse
and states(presence_entity) not in ['on','home','playing','paused'] ) ) }}
# Variables Trigger
turnon_trigger: >-
{# Boolean for light.turn_on event trigger #}
{{ trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data.service_data is defined
and trigger.event.data.service_data.entity_id is defined }}
# Service-data analysis (safe extraction; supports call_service and missing keys)
svc_has_entity_id: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data is defined
and trigger.event.data.service == 'turn_on'
and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.entity_id is defined ) ) }}
svc_has_brightness: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data is defined
and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.brightness is defined or trigger.event.data.service_data.brightness_pct is defined ) ) }}
svc_has_color: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data is defined
and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.hs_color is defined
or trigger.event.data.service_data.xy_color is defined
or trigger.event.data.service_data.rgb_color is defined
or trigger.event.data.service_data.rgbw_color is defined
or trigger.event.data.service_data.rgbww_color is defined
or trigger.event.data.service_data.color_name is defined ) ) }}
svc_has_white: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data is defined
and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.color_temp is defined
or trigger.event.data.service_data.color_temp_kelvin is defined
or trigger.event.data.service_data.kelvin is defined ) ) }}
svc_has_effect: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data is defined
and trigger.event.data.service_data is defined
and ( trigger.event.data.service_data.effect is defined
or trigger.event.data.service_data.flash is defined ) ) }}
svc_is_plain_on: >-
{{ ( trigger is defined
and trigger.platform == 'event'
and trigger.event.data.service == 'turn_on'
and trigger.event.data is defined
and trigger.event.data.service_data is defined
and not ( ( trigger.event.data.service_data.brightness is defined )
or ( trigger.event.data.service_data.brightness_pct is defined )
or ( trigger.event.data.service_data.hs_color is defined )
or ( trigger.event.data.service_data.xy_color is defined )
or ( trigger.event.data.service_data.rgb_color is defined )
or ( trigger.event.data.service_data.rgbw_color is defined )
or ( trigger.event.data.service_data.rgbww_color is defined )
or ( trigger.event.data.service_data.color_name is defined )
or ( trigger.event.data.service_data.color_temp is defined )
or ( trigger.event.data.service_data.color_temp_kelvin is defined )
or ( trigger.event.data.service_data.kelvin is defined )
or ( trigger.event.data.service_data.effect is defined )
or ( trigger.event.data.service_data.flash is defined ) ) ) }}
svc_is_mode_switch: >-
{{ ( ( svc_has_color != svc_has_white ) and ( not svc_has_brightness ) and ( not svc_has_effect ) ) }}
svc_is_explicit_look: >-
{{ ( svc_has_brightness or svc_has_effect ) }}
svc_is_adoptable_turnon: >-
{{ ( svc_is_plain_on or svc_is_mode_switch ) }}
turnon_adoptable_event: >-
{{ ( turnon_trigger and svc_has_entity_id and svc_is_adoptable_turnon ) }}
circadian_adoption_allowed: >-
{{ ( turnon_adoptable_event or ( (off_to_on_entities is defined and off_to_on_entities|length > 0) ) ) }}
# Variables Lights
lights_dim_all: >-
{# This is the list of lights that are being dimmed #}
{%- set lights_select = namespace(entities=[]) -%}
{%- if elevation_dim_on or elevation_dim_off -%}
{%- for i_entity in lights_all -%}
{# Check that the light is being dimmed in one direction #}
{# Also check that other circadian values are being followed #}
{%- if is_state(i_entity,'on')
and ( not sleep_trigger )
and i_entity not in manual_hold_list
and ( state_attr(i_entity,'brightness') != none
and ( ( elevation_dim_on
and state_attr(i_entity,'brightness') < elevation_brightness+brightness_dim_buffer )
or ( elevation_dim_off
and state_attr(i_entity,'brightness') > elevation_brightness-brightness_dim_buffer ) ) )
and ( ( state_attr(i_entity,'color_temp') == none
and state_attr(i_entity,'hs_color') == none )
or ( state_attr(i_entity,'hs_color') != none
and state_attr(i_entity,'hs_color')|first > circadian_hue_value-hue_follow_tolerance
and state_attr(i_entity,'hs_color')|first < circadian_hue_value+hue_follow_tolerance )
or ( ( state_attr(i_entity,'color_temp_kelvin') != none
and (state_attr(i_entity,'color_temp_kelvin')|int) >= (circadian_temperature_kelvin|int - circadian_temperature_tolerance_kelvin|int)
and (state_attr(i_entity,'color_temp_kelvin')|int) <= (circadian_temperature_kelvin|int + circadian_temperature_tolerance_kelvin|int) )
or ( state_attr(i_entity,'color_temp') != none
and ( (1000000 / (state_attr(i_entity,'color_temp')|float) + 0.5) | int ) >= (circadian_temperature_kelvin|int - circadian_temperature_tolerance_kelvin|int)
and ( (1000000 / (state_attr(i_entity,'color_temp')|float) + 0.5) | int ) <= (circadian_temperature_kelvin|int + circadian_temperature_tolerance_kelvin|int) ) ) ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{{ lights_select.entities }}
lights_on_all: >-
{# This is the list of lights that follow the circadian cycle #}
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_all -%}
{# Check that the light is following all circadian values #}
{# Also check that the light is not being dimmed #}
{%- if is_state(i_entity,'on')
and ( sleep_trigger
or ( ( state_attr(i_entity,'brightness') == none
or ( state_attr(i_entity,'brightness') != none
and state_attr(i_entity,'brightness') > circadian_brightness_value-brightness_follow_tolerance
and state_attr(i_entity,'brightness') < circadian_brightness_value+brightness_follow_tolerance ) )
and ( ( state_attr(i_entity,'color_temp') == none
and state_attr(i_entity,'hs_color') == none )
or ( state_attr(i_entity,'hs_color') != none
and state_attr(i_entity,'hs_color')|first > circadian_hue_value-hue_follow_tolerance
and state_attr(i_entity,'hs_color')|first < circadian_hue_value+hue_follow_tolerance )
or ( (
state_attr(i_entity,'color_temp_kelvin') != none
and (state_attr(i_entity,'color_temp_kelvin')|int) >= (circadian_temperature_kelvin|int - circadian_temperature_tolerance_kelvin|int)
and (state_attr(i_entity,'color_temp_kelvin')|int) <= (circadian_temperature_kelvin|int + circadian_temperature_tolerance_kelvin|int)
)
or (
state_attr(i_entity,'color_temp') != none
and ( (1000000 / (state_attr(i_entity,'color_temp')|float) + 0.5) | int ) >= (circadian_temperature_kelvin|int - circadian_temperature_tolerance_kelvin|int)
and ( (1000000 / (state_attr(i_entity,'color_temp')|float) + 0.5) | int ) <= (circadian_temperature_kelvin|int + circadian_temperature_tolerance_kelvin|int)
) )
and i_entity not in lights_dim_all
and i_entity not in manual_hold_list ) ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_off_all: >-
{# This is the list of lights that are currently off #}
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_all -%}
{# Check that the light is off #}
{%- if is_state(i_entity,'off') -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_trigger_all: >-
{# This is the list of lights that have been triggered #}
{%- set lights_select = namespace(entities=[]) -%}
{%- if ( turnon_adoptable_event or ( off_to_on_entities | length > 0 ) ) -%}
{# Ensure that the triggered lights are a list #}
{%- if turnon_adoptable_event -%}
{%- if trigger.event.data.service_data.entity_id is string -%}
{%- set lights_list = [trigger.event.data.service_data.entity_id] -%}
{%- else -%}
{%- set lights_list = trigger.event.data.service_data.entity_id -%}
{%- endif -%}
{%- elif trigger is defined and trigger.platform == 'state' and trigger.entity_id is defined -%}
{# Non-call_service triggers: only use trigger.entity_id when platform is 'state' #}
{%- set lights_list = [ trigger.entity_id ] -%}
{%- else -%}
{%- set lights_list = [] -%}
{%- endif -%}
{# Check for individual entities within a group light #}
{%- set group_list = namespace(entities=[]) -%}
{%- for i_entity in lights_list -%}
{%- if state_attr(i_entity,'entity_id') is not none -%}
{%- if state_attr(i_entity,'entity_id') is string -%}
{%- set group_list.entities = group_list.entities + [state_attr(i_entity,'entity_id')] -%}
{%- else -%}
{%- set group_list.entities = group_list.entities + state_attr(i_entity,'entity_id') -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set lights_list = lights_list + group_list.entities -%}
{%- for i_entity in lights_list -%}
{# Check that the light is in the target list #}
{# Also check that its properties are not being set (allow off->on normalization) #}
{%- if i_entity in lights_all
and i_entity not in manual_hold_list
and ( turnon_adoptable_event or ( i_entity in off_to_on_entities ) ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{{ lights_select.entities }}
lights_dim_brightness: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_dim_all -%}
{%- if state_attr(i_entity,'color_mode') == 'brightness' -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_dim_color: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_dim_all -%}
{%- if state_attr(i_entity,'color_mode') in ['rgb','rgbw','rgbww','xy','hs'] -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_dim_temperature: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_dim_all -%}
{%- if state_attr(i_entity,'color_mode') == 'color_temp' -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_on_brightness: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_on_all -%}
{%- if state_attr(i_entity,'color_mode') == 'brightness' -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_on_color: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_on_all -%}
{%- if state_attr(i_entity,'color_mode') in ['rgb','rgbw','rgbww','xy','hs'] -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_on_temperature: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_on_all -%}
{%- if state_attr(i_entity,'color_mode') == 'color_temp' -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
# Apply lists (throttling): only include if change >= output_min_* or undefined OR off->on normalization
lights_on_brightness_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_on_brightness -%}
{%- set cur = state_attr(i,'brightness') -%}
{%- set tgt = circadian_brightness_value|int -%}
{%- if i in off_to_on_entities or cur is none or ( (cur|int - tgt)|abs >= output_min_brightness_delta ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_dim_brightness_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_dim_brightness -%}
{%- set cur = state_attr(i,'brightness') -%}
{%- set tgt = elevation_brightness|int -%}
{%- if i in off_to_on_entities or cur is none or ( (cur|int - tgt)|abs >= output_min_brightness_delta ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_on_temperature_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_on_temperature -%}
{%- set cur_k = ( state_attr(i,'color_temp_kelvin') if state_attr(i,'color_temp_kelvin') is not none else ( (1000000 / (state_attr(i,'color_temp')|float) )|int ) ) if (state_attr(i,'color_temp_kelvin') is not none or state_attr(i,'color_temp') is not none) else none -%}
{%- set tgt_k = circadian_temperature_kelvin|int -%}
{%- if i in off_to_on_entities or cur_k is none or ( (cur_k|int - tgt_k)|abs >= output_min_ct_delta_kelvin ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_dim_temperature_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_dim_temperature -%}
{%- set cur_k = ( state_attr(i,'color_temp_kelvin') if state_attr(i,'color_temp_kelvin') is not none else ( (1000000 / (state_attr(i,'color_temp')|float) )|int ) ) if (state_attr(i,'color_temp_kelvin') is not none or state_attr(i,'color_temp') is not none) else none -%}
{%- set tgt_k = circadian_temperature_kelvin|int -%}
{%- if i in off_to_on_entities or cur_k is none or ( (cur_k|int - tgt_k)|abs >= output_min_ct_delta_kelvin ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_on_color_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_on_color -%}
{%- set cur_h = (state_attr(i,'hs_color')[0]|float) if (state_attr(i,'hs_color') is not none) else none -%}
{%- set tgt_h = circadian_hue_value|float -%}
{%- set cur_b = state_attr(i,'brightness') if state_attr(i,'brightness') is not none else none -%}
{%- set tgt_b = circadian_brightness_value|int -%}
{%- set hue_diff = none -%}
{%- if cur_h is not none -%}
{%- set hue_diff = ((cur_h - tgt_h + 180) % 360) - 180 -%}
{%- endif -%}
{%- if i in off_to_on_entities or cur_h is none or cur_b is none or ( hue_diff is not none and (hue_diff|abs >= output_min_hue_delta) ) or ( cur_b is not none and ( (cur_b|int - tgt_b)|abs >= output_min_brightness_delta ) ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_dim_color_apply: >-
{%- set sel = namespace(entities=[]) -%}
{%- for i in lights_dim_color -%}
{%- set cur_h = (state_attr(i,'hs_color')[0]|float) if (state_attr(i,'hs_color') is not none) else none -%}
{%- set tgt_h = circadian_hue_value|float -%}
{%- set cur_b = state_attr(i,'brightness') if state_attr(i,'brightness') is not none else none -%}
{%- set tgt_b = elevation_brightness|int -%}
{%- set hue_diff = none -%}
{%- if cur_h is not none -%}
{%- set hue_diff = ((cur_h - tgt_h + 180) % 360) - 180 -%}
{%- endif -%}
{%- if i in off_to_on_entities or cur_h is none or cur_b is none or ( hue_diff is not none and (hue_diff|abs >= output_min_hue_delta) ) or ( cur_b is not none and ( (cur_b|int - tgt_b)|abs >= output_min_brightness_delta ) ) -%}
{%- set sel.entities = sel.entities + [i] -%}
{%- endif -%}
{%- endfor -%}
{{ sel.entities }}
lights_off_onoff: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_off_all -%}
{%- set scm = state_attr(i_entity,'supported_color_modes') or [] -%}
{%- if 'onoff' in scm -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_off_brightness: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_off_all -%}
{%- set scm = state_attr(i_entity,'supported_color_modes') or [] -%}
{%- if 'brightness' in scm
and 'color_temp' not in scm
and 'rgb' not in scm
and 'rgbw' not in scm
and 'rgbww' not in scm
and 'xy' not in scm
and 'hs' not in scm -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_off_color: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_off_all -%}
{%- set scm = state_attr(i_entity,'supported_color_modes') or [] -%}
{%- if 'color_temp' not in scm
and ('rgb' in scm
or 'rgbw' in scm
or 'rgbww' in scm
or 'xy' in scm
or 'hs' in scm ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_off_temperature: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_off_all -%}
{%- set scm = state_attr(i_entity,'supported_color_modes') or [] -%}
{%- if 'color_temp' in scm -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_trigger_brightness: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_trigger_all -%}
{%- if i_entity in lights_off_brightness
or ( turnon_adoptable_event and trigger.event.data.service_data|length == 1
and state_attr(i_entity,'color_mode') == 'brightness' )
or ( i_entity in off_to_on_entities and state_attr(i_entity,'color_mode') == 'brightness' ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_trigger_color: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_trigger_all -%}
{%- if i_entity in lights_off_color
or ( turnon_adoptable_event and trigger.event.data.service_data|length == 1
and state_attr(i_entity,'color_mode') in ['rgb','rgbw','rgbww','xy','hs'] )
or ( i_entity in off_to_on_entities and state_attr(i_entity,'color_mode') in ['rgb','rgbw','rgbww','xy','hs'] )
or ( turnon_adoptable_event and i_entity in lights_on_temperature + lights_dim_temperature
and ( trigger.event.data.service_data.hs_color is defined
or trigger.event.data.service_data.xy_color is defined
or trigger.event.data.service_data.rgb_color is defined
or trigger.event.data.service_data.rgbw_color is defined
or trigger.event.data.service_data.rgbww_color is defined
or trigger.event.data.service_data.color_name is defined ) ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
lights_trigger_temperature: >-
{%- set lights_select = namespace(entities=[]) -%}
{%- for i_entity in lights_trigger_all -%}
{%- if i_entity in lights_off_temperature
or ( turnon_adoptable_event and trigger.event.data.service_data|length == 1
and state_attr(i_entity,'color_mode') == 'color_temp' )
or ( i_entity in off_to_on_entities and state_attr(i_entity,'color_mode') == 'color_temp' )
or ( turnon_adoptable_event and i_entity in lights_on_color + lights_dim_color
and ( trigger.event.data.service_data.color_temp is defined
or trigger.event.data.service_data.kelvin is defined
or trigger.event.data.service_data.color_temp_kelvin is defined ) ) -%}
{%- set lights_select.entities = lights_select.entities + [i_entity] -%}
{%- endif -%}
{%- endfor -%}
{{ lights_select.entities }}
# Debug
debug_level: !input debug_level
debug_basic: "{{ debug_level in ['Basic','Verbose'] }}"
debug_verbose: "{{ debug_level == 'Verbose' }}"
debug_name: "Advanced Circadian Lighting (dimkaram) — {{ blueprint_version }}"
debug_message: >-
trig={{ trigger.platform if trigger is defined and trigger.platform is defined else 'n/a' }},
elev={{ state_attr('sun.sun','elevation') if state_attr('sun.sun','elevation') is not none else 'n/a' }},
rising={{ state_attr('sun.sun','rising') if state_attr('sun.sun','rising') is not none else 'n/a' }},
circ_pos={{ circadian_position }},
circ_coef={{ circadian_coefficient }},
bri_circ={{ circadian_brightness_value }},
bri_elev={{ elevation_brightness }},
k={{ circadian_temperature_kelvin }},
pres={{ states(presence_entity) if presence_entity is defined else 'n/a' }},
sleep={{ states(sleep_entity) if sleep_entity is defined else 'n/a' }},
targets={{ lights_all|length }},
off_b={{ lights_off_brightness|length }},
off_t={{ lights_off_temperature|length }},
on_b={{ lights_on_brightness|length }},
on_t={{ lights_on_temperature|length }},
svc_has_entity_id={{ ( svc_has_entity_id if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_has_brightness={{ ( svc_has_brightness if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_has_color={{ ( svc_has_color if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_has_white={{ ( svc_has_white if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_has_effect={{ ( svc_has_effect if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_is_plain_on={{ ( svc_is_plain_on if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_is_mode_switch={{ ( svc_is_mode_switch if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_is_explicit_look={{ ( svc_is_explicit_look if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
svc_is_adoptable_turnon={{ ( svc_is_adoptable_turnon if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
circadian_adoption_allowed={{ ( circadian_adoption_allowed if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }},
turnon_adoptable_event={{ ( turnon_adoptable_event if ( trigger is defined and trigger.platform == 'event' ) else 'n/a' ) }}
# AUTOMATION
mode: restart
# TRIGGER
trigger:
- platform: time_pattern
minutes: "/1"
- platform: numeric_state
entity_id: sun.sun
attribute: elevation
above: !input elevation_sunrise_start
- platform: numeric_state
entity_id: sun.sun
attribute: elevation
above: !input elevation_sunrise_end
- platform: numeric_state
entity_id: sun.sun
attribute: elevation
below: !input elevation_sunset_start
- platform: numeric_state
entity_id: sun.sun
attribute: elevation
below: !input elevation_sunset_end
- platform: state
entity_id: !input presence_entity
- platform: state
entity_id: !input sleep_entity
- platform: state
entity_id: !input lights_entities
from: "off"
to: "on"
- platform: state
entity_id: !input lights_entities
to: "off"
- platform: event
event_type: call_service
event_data:
domain: light
service: turn_on
- platform: event
event_type: call_service
event_data:
domain: light
service: turn_off
# ACTION
action:
- choose:
- conditions: "{{ debug_verbose }}"
sequence:
- service: logbook.log
data:
name: "{{ debug_name }}"
message: "{{ debug_message }}"
entity_id: "{{ sleep_entity }}"
- service: system_log.write
data:
level: debug
message: "{{ debug_name }} | {{ debug_message }}"
- choose:
- conditions: >-
{{ ( manual_hold_add_entities | length > 0 )
and ( manual_hold_helper is string )
and ( states(manual_hold_helper) not in ['unknown','unavailable'] ) }}
sequence:
- service: input_text.set_value
data:
entity_id: "{{ manual_hold_helper }}"
value: >-
{%- set ns = namespace(items=manual_hold_list) -%}
{%- for e in manual_hold_add_entities -%}
{%- if e not in ns.items -%}
{%- set ns.items = ns.items + [e] -%}
{%- endif -%}
{%- endfor -%}
{{ ns.items | join(',') }}
- choose:
- conditions: >-
{{ ( manual_hold_remove_entities | length > 0 )
and ( manual_hold_helper is string )
and ( states(manual_hold_helper) not in ['unknown','unavailable'] ) }}
sequence:
- service: input_text.set_value
data:
entity_id: "{{ manual_hold_helper }}"
value: >-
{%- set ns = namespace(items=[]) -%}
{%- for it in manual_hold_list -%}
{%- if it not in manual_hold_remove_entities -%}
{%- set ns.items = ns.items + [it] -%}
{%- endif -%}
{%- endfor -%}
{{ ns.items | join(',') }}
- choose:
# Elevation/Presence/Sleep: Turn OFF (target lights)
- conditions: "{{ elevation_turn_off or presence_off or sleep_off }}"
sequence:
- choose:
- conditions: "{{ debug_basic }}"
sequence:
- service: logbook.log
data:
name: "{{ debug_name }}"
message: >-
branch=TURN_OFF,
elevation_turn_off={{ elevation_turn_off }},
presence_off={{ presence_off }},
sleep_off={{ sleep_off }},
lights={{ lights_all }}
entity_id: "{{ sleep_entity }}"
- choose:
- conditions: "{{ lights_all | length > 0 }}"
sequence:
- service: light.turn_off
data:
entity_id: "{{ lights_all }}"
# Elevation/Presence: Turn ON (off lights to elevation brightness)
- conditions: >-
{{ (elevation_turn_on and presence_condition and sleep_condition)
or (presence_on and elevation_condition and sleep_condition)
or (sleep_on and presence_condition and elevation_condition) }}
sequence:
- choose:
- conditions: "{{ debug_basic }}"
sequence:
- service: logbook.log
data:
name: "{{ debug_name }}"
message: >-
branch=TURN_ON_OFF_LIGHTS,
elevation_turn_on={{ elevation_turn_on }},
presence_on={{ presence_on }},
sleep_on={{ sleep_on }},
bri_elev={{ elevation_brightness }},
k={{ circadian_temperature_kelvin }}
entity_id: "{{ sleep_entity }}"
- choose:
- conditions: "{{ lights_off_onoff | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_off_onoff }}"
- choose:
- conditions: "{{ lights_off_brightness | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_off_brightness }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
- choose:
- conditions: "{{ lights_off_color | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_off_color }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
hs_color:
- "{{ circadian_hue_value }}"
- "{{ circadian_saturation_default }}"
- choose:
- conditions: "{{ lights_off_temperature | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_off_temperature }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
color_temp_kelvin: >-
{%- set tgt = lights_off_temperature -%}
{%- set ns = namespace(min_k=0, max_k=1000000) -%}
{%- for e in tgt -%}
{%- set a_min = state_attr(e,'min_color_temp_kelvin') -%}
{%- set a_max = state_attr(e,'max_color_temp_kelvin') -%}
{%- if a_min is number -%}
{%- set ns.min_k = [ns.min_k, a_min]|max -%}
{%- endif -%}
{%- if a_max is number -%}
{%- set ns.max_k = [ns.max_k, a_max]|min -%}
{%- endif -%}
{%- endfor -%}
{%- set k = circadian_temperature_kelvin|int -%}
{%- if ns.min_k > 0 -%}{%- set k = [k, ns.min_k]|max -%}{%- endif -%}
{%- if ns.max_k < 1000000 -%}{%- set k = [k, ns.max_k]|min -%}{%- endif -%}
{{ k }}
# Event: Turn on (trigger lights to circadian brightness)
- conditions: "{{ turnon_adoptable_event }}"
sequence:
- choose:
- conditions: "{{ debug_basic }}"
sequence:
- service: logbook.log
data:
name: "{{ debug_name }}"
message: >-
branch=TURNON_TRIGGER,
trigger_entity={{ trigger.event.data.service_data.entity_id
if trigger is defined and trigger.platform == 'event'
else 'n/a' }},
bri_circ={{ circadian_brightness_value }},
k={{ circadian_temperature_kelvin }},
trg_b={{ lights_trigger_brightness | length }},
trg_t={{ lights_trigger_temperature | length }},
trg_c={{ lights_trigger_color | length }}
entity_id: "{{ sleep_entity }}"
- choose:
- conditions: "{{ lights_trigger_brightness | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_trigger_brightness }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
- choose:
- conditions: "{{ lights_trigger_color | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_trigger_color }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
hs_color:
- "{{ circadian_hue_value }}"
- "{{ circadian_saturation_default }}"
- choose:
- conditions: "{{ lights_trigger_temperature | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_trigger_temperature }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
color_temp_kelvin: >-
{%- set tgt = lights_trigger_temperature -%}
{%- set ns = namespace(min_k=0, max_k=1000000) -%}
{%- for e in tgt -%}
{%- set a_min = state_attr(e,'min_color_temp_kelvin') -%}
{%- set a_max = state_attr(e,'max_color_temp_kelvin') -%}
{%- if a_min is number -%}
{%- set ns.min_k = [ns.min_k, a_min]|max -%}
{%- endif -%}
{%- if a_max is number -%}
{%- set ns.max_k = [ns.max_k, a_max]|min -%}
{%- endif -%}
{%- endfor -%}
{%- set k = circadian_temperature_kelvin|int -%}
{%- if ns.min_k > 0 -%}{%- set k = [k, ns.min_k]|max -%}{%- endif -%}
{%- if ns.max_k < 1000000 -%}{%- set k = [k, ns.max_k]|min -%}{%- endif -%}
{{ k }}
# Default (on lights to circadian, dim lights to elevation brightness)
default:
- choose:
- conditions: "{{ debug_basic }}"
sequence:
- service: logbook.log
data:
name: "{{ debug_name }}"
message: >-
branch=DEFAULT,
on_b={{ lights_on_brightness | length }},
on_t={{ lights_on_temperature | length }},
dim_b={{ lights_dim_brightness | length }},
dim_t={{ lights_dim_temperature | length }},
bri_circ={{ circadian_brightness_value }},
bri_elev={{ elevation_brightness }},
k={{ circadian_temperature_kelvin }}
entity_id: "{{ sleep_entity }}"
- choose:
- conditions: "{{ lights_on_brightness_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_on_brightness_apply }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
- choose:
- conditions: "{{ lights_on_color_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_on_color_apply }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
hs_color:
- "{{ circadian_hue_value }}"
- "{{ circadian_saturation_default }}"
- choose:
- conditions: "{{ lights_on_temperature_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_on_temperature_apply }}"
brightness: "{{ [ [circadian_brightness_value|int, 1]|max, 255 ]|min }}"
color_temp_kelvin: >-
{%- set tgt = lights_on_temperature_apply -%}
{%- set ns = namespace(min_k=0, max_k=1000000) -%}
{%- for e in tgt -%}
{%- set a_min = state_attr(e,'min_color_temp_kelvin') -%}
{%- set a_max = state_attr(e,'max_color_temp_kelvin') -%}
{%- if a_min is number -%}
{%- set ns.min_k = [ns.min_k, a_min]|max -%}
{%- endif -%}
{%- if a_max is number -%}
{%- set ns.max_k = [ns.max_k, a_max]|min -%}
{%- endif -%}
{%- endfor -%}
{%- set k = circadian_temperature_kelvin|int -%}
{%- if ns.min_k > 0 -%}{%- set k = [k, ns.min_k]|max -%}{%- endif -%}
{%- if ns.max_k < 1000000 -%}{%- set k = [k, ns.max_k]|min -%}{%- endif -%}
{{ k }}
- choose:
- conditions: "{{ lights_dim_brightness_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_dim_brightness_apply }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
- choose:
- conditions: "{{ lights_dim_color_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_dim_color_apply }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
hs_color:
- "{{ circadian_hue_value }}"
- "{{ circadian_saturation_default }}"
- choose:
- conditions: "{{ lights_dim_temperature_apply | length > 0 }}"
sequence:
- service: light.turn_on
data:
entity_id: "{{ lights_dim_temperature_apply }}"
brightness: "{{ [ [elevation_brightness|int, 1]|max, 255 ]|min }}"
color_temp_kelvin: >-
{%- set tgt = lights_dim_temperature_apply -%}
{%- set ns = namespace(min_k=0, max_k=1000000) -%}
{%- for e in tgt -%}
{%- set a_min = state_attr(e,'min_color_temp_kelvin') -%}
{%- set a_max = state_attr(e,'max_color_temp_kelvin') -%}
{%- if a_min is number -%}
{%- set ns.min_k = [ns.min_k, a_min]|max -%}
{%- endif -%}
{%- if a_max is number -%}
{%- set ns.max_k = [ns.max_k, a_max]|min -%}
{%- endif -%}
{%- endfor -%}
{%- set k = circadian_temperature_kelvin|int -%}
{%- if ns.min_k > 0 -%}{%- set k = [k, ns.min_k]|max -%}{%- endif -%}
{%- if ns.max_k < 1000000 -%}{%- set k = [k, ns.max_k]|min -%}{%- endif -%}
{{ k }}
@snowyu
Copy link

snowyu commented Apr 22, 2022

👍 perfect codes.

trigger:

  • platform: time_pattern
    minutes: "/1"

Increase overhead if there are too many bulbs

Maybe separate two blueprints:

  • turn on/off bulb for motion, elevation, illuminance
  • adjust the brightness of the lights

@dimkaram
Copy link
Author

Maybe separate two blueprints:

  • turn on/off bulb for motion, elevation, illuminance
  • adjust the brightness of the lights

Many thanks for your comments. I thought about splitting into two blueprints, but the dependence on time and sun elevation means that there will be some code repetition, so I decided to keep it all in one place.

@haberda
Copy link

haberda commented Oct 15, 2022

One thing to keep in mind, at least in terms of brightness, there are only either 100 or 255 steps per day depending on how you define that. Sure, color temp could have more, but the point is that works out to between 5 and 10 steps per hour. So, I'd say an update every 5 minutes or so is just fine; especially if you add a 30s transition.

I have a similar AppDaemon app that I wrote; I just have it update every 5 min.

@nocelab
Copy link

nocelab commented Oct 8, 2023

I have a lot of Zigbee led driver (https://www.zigbee2mqtt.io/devices/TS0502B.html) and GU10 spots (https://www.zigbee2mqtt.io/devices/RS_227_T.html) all via ZIGBEE2MQTT... I'm trying to use your Blueprint but seems not working, all lamps remains in their brightness and temperature state.
What can I check to better understand my case?

@bernasduro
Copy link

I am sorry for by noobiness... How do I import/install this amazing blueprint automation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment