Here is a script version of mrsnyds original work. Be advised this script is not identical to the original automation version. It uses the same algorithm but with several differences:
-
Uses
light
notentity_id
The option to specify the light(s) to be dimmed is namedlight
instead ofentity_id
. The reason is because when you call a script using thescript.turn_on
service, you specify which script to turn on by setting the service’sentity_id
option. This is the same name, so the script’s variable name was changed tolight
. -
No need to specify
branch
When calling the script, you do not need to includebranch: 'calculate'
. The script defaults to ‘calculate’ mode when thebranch
option is not included. -
Streamlined templates
The templates are different. Wherever possible, I reduced code-size. Based on the tests I’ve done, everything continues to work like the original, automation version (but YMMV). -
Includes
fields
option
Each variable is documented so that it appears in Developer Tools > Services.
Here is the script version with all of mrsnyds comments.
Click to reveal commented version
light_fader:
description: 'Fades lights to a desired level over a specified transition period.'
fields:
light:
description: Entity_id of light(s) as a list or comma-separated.
example: light.kitchen, light.bathroom, light.garage
end_level_pct:
description: Integer value from 0 to 100 representing the desired final brightness level.
example: '80'
transition:
description: Transition time for fading indicated in HH:MM:SS format (Hours:Minutes:Seconds).
example: '00:05:30'
branch:
description: "OPTIONAL. If unspecified, default is 'calculate'. 'execute_fade' is reserved for use by the script itself."
example: calculate
mode: parallel
sequence:
# SUMMARY: (1) The first branch is selected when the triggering event data omits the "branch" option or sets it to "branch == 'calculate'.
# The additional data that is included with the triggering event is used to calculate all the parameters needed
# to execute a repeat loop that transitions the light brightness from one level, to another, in a given amount of time.
# (2) The first branch is essentially just the initialization phase which supplies all of the parameters needed to execute the
# light transition (fade in or fade out). Some of the data is a pass through from the original triggering event,
# but three values - "start_level_pct", "delay_milli_sec", and "step_pct" - are calculated with templates.
# By including branch == 'execute_fade' in the event data, the second branch is selected in this automation.
# (3) The repeat loop in the second branch executes until the desired "end_level_pct" is reached.
- choose:
#FIRST BRANCH "calculate" ... derive values from the data in original trigger and pass them to second branch, "execute_fade"
- conditions:
- condition: template
value_template: "{{ branch is not defined or branch == 'calculate' }}"
sequence:
- service: script.turn_on
entity_id: script.light_fader
data_template:
variables:
branch: 'execute_fade'
light: "{{ light }}"
start_level_pct: >-
{% set b = state_attr(light, 'brightness') %}
{{ 0 if b == none else (b/255*100)|round }}
end_level_pct: "{{ end_level_pct|int }}"
# ======= "delay_milli_sec" calculation notes ==============
# The basic calculation for delay_milli_sec could result in either a positive
# or negative value, but the delay must always be a positve value - no matter
# which direction the fade is going - so last step uses "abs" for absolute value.
delay_milli_sec: >-
{# ============================================================ #}
{# start_level_pct MUST BE "0" IF BRIGHTNESS ATTRIBUTE IS "none" #}
{# ============================================================ #}
{##}
{% set b = state_attr(light, 'brightness') %}
{% set start_level_pct = 0 if b == none else (b/255*100)|round %}
{# ============================================================ #}
{# End_level_pct IS PROVIDED BY VARIABLE #}
{# ============================================================ #}
{##}
{% set end_level_pct = end_level_pct|int %}
{##}
{# ============================================================ #}
{# DERIVE transition_secs FROM VARIABLE hh:mm:ss #}
{# ============================================================ #}
{##}
{% set t = transition.split(':') | map('int') | list %}
{% set transition_secs = t[0]*3600 + t[1]*60 + t[2] %}
{##}
{# ============================================================ #}
{# CALCULATE & RETURN delay_milli_sec ... min set to 100ms #}
{# ============================================================ #}
{% set delay_milli_sec = (((transition_secs/(end_level_pct - start_level_pct))|abs)|round(precision=3)*1000)|int %}
{{ 100 if delay_milli_sec <= 99 else delay_milli_sec }}
# ======= "step_pct" calculation notes ==============
# Compare start level to end level to see if we increasing or decreasing the brightness
# by 1% with each loop. Must repeat the same "if" templating as in delay_milli_sec for start_level_pct
step_pct: >
{% set b = state_attr(light, 'brightness') %}
{% set start_level_pct = 0 if b == none else (b/255*100)|round %}
{{ 1 if start_level_pct < end_level_pct|int else -1 }}
#SECOND BRANCH ... accepts values derived in the first branch, as well as "pass throughs" from
# the original trigger, and then actually does the light transition.
- conditions:
- condition: template
value_template: "{{ branch is defined and branch == 'execute_fade' }}"
sequence:
- repeat:
while:
# Terminates at set # of iterations ... just in case
- condition: template
value_template: "{{ repeat.index <= 102 }}"
# Check to confirm that current brightness has not yet reached end_level_pct
- condition: template
value_template: >-
{% set b = state_attr(light, 'brightness') %}
{% set b = (b/255*100)|round if b != none else b %}
{% if b == none -%}
{{ step_pct|int != -1 }}
{%- elif b > end_level_pct|int -%}
{{ step_pct|int == -1 }}
{%- else -%}
{{ b < end_level_pct|int }}
{%- endif %}
# Check to confirm that current brightness is at the expected level for this iteration.
# If during a long fade (e.g. 10 minutes) someone manually adjusted the brightness,
# the current brightness will not match what the loop expected, and it will stop,
# respecting the manual intervention of a person who is setting their preference.
- condition: template
value_template: >-
{% set b = state_attr(light, 'brightness') %}
{% set b = (b/255*100)|round if b != none else 0 %}
{{ b == start_level_pct|int + ((repeat.index - 1) * (step_pct|int)) }}
sequence:
- service: light.turn_on
data_template:
entity_id: "{{ light }} "
# brightness_step_pct: "{{step_pct}}"
brightness_pct: >-
{% set x = start_level_pct|int + (repeat.index|int * step_pct|int) %}
{{ ([0, x, 100]|sort)[1] }}
- delay:
milliseconds: "{{ delay_milli_sec|int }}"
Here is the script version without comments.
Click to reveal comment-free version
light_fader:
description: 'Fades lights to a desired level over a specified transition period.'
fields:
light:
description: Entity_id of light(s) as a list or comma-separated.
example: light.kitchen, light.bathroom, light.garage
end_level_pct:
description: Integer value from 0 to 100 representing the desired final brightness level.
example: '80'
transition:
description: Transition time for fading indicated in HH:MM:SS format (Hours:Minutes:Seconds).
example: '00:05:30'
branch:
description: "OPTIONAL. If unspecified, default is 'calculate'. 'execute_fade' is reserved for use by the script itself."
example: calculate
mode: parallel
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ branch is not defined or branch == 'calculate' }}"
sequence:
- service: script.turn_on
entity_id: script.light_fader
data_template:
variables:
branch: 'execute_fade'
light: "{{ light }}"
start_level_pct: >-
{% set b = state_attr(light, 'brightness') %}
{{ 0 if b == none else (b/255*100)|round }}
end_level_pct: "{{ end_level_pct|int }}"
delay_milli_sec: >-
{% set b = state_attr(light, 'brightness') %}
{% set start_level_pct = 0 if b == none else (b/255*100)|round %}
{% set end_level_pct = end_level_pct|int %}
{% set t = transition.split(':') | map('int') | list %}
{% set transition_secs = t[0]*3600 + t[1]*60 + t[2] %}
{% set delay_milli_sec = (((transition_secs/(end_level_pct - start_level_pct))|abs)|round(precision=3)*1000)|int %}
{{ 100 if delay_milli_sec <= 99 else delay_milli_sec }}
step_pct: >
{% set b = state_attr(light, 'brightness') %}
{% set start_level_pct = 0 if b == none else (b/255*100)|round %}
{{ 1 if start_level_pct < end_level_pct|int else -1 }}
- conditions:
- condition: template
value_template: "{{ branch is defined and branch == 'execute_fade' }}"
sequence:
- repeat:
while:
- condition: template
value_template: "{{ repeat.index <= 102 }}"
- condition: template
value_template: >-
{% set b = state_attr(light, 'brightness') %}
{% set b = (b/255*100)|round if b != none else b %}
{% if b == none -%}
{{ step_pct|int != -1 }}
{%- elif b > end_level_pct|int -%}
{{ step_pct|int == -1 }}
{%- else -%}
{{ b < end_level_pct|int }}
{%- endif %}
- condition: template
value_template: >-
{% set b = state_attr(light, 'brightness') %}
{% set b = (b/255*100)|round if b != none else 0 %}
{{ b == start_level_pct|int + ((repeat.index - 1) * (step_pct|int)) }}
sequence:
- service: light.turn_on
data_template:
entity_id: "{{ light }} "
# brightness_step_pct: "{{step_pct}}"
brightness_pct: >-
{% set x = start_level_pct|int + (repeat.index|int * step_pct|int) %}
{{ ([0, x, 100]|sort)[1] }}
- delay:
milliseconds: "{{ delay_milli_sec|int }}"