Fully done in home assistant.
alias: Light Fader v2
description: Fade brightness %, color temperature (Kelvin), and RGB color over time.
mode: parallel
fields:
light:
name: Light
description: Light entity to fade.
selector:
entity:
domain: light
end_pct:
name: End Brightness (%)
description: Brightness target percentage (0-100).
selector:
number:
min: 0
max: 100
step: 1
mode: slider
default: 100
color_temp:
name: End Color Temp (Kelvin)
description: Target color temperature in Kelvin (optional).
selector:
number:
min: 2000
max: 6500
step: 100
mode: slider
default: null
rgb_color:
name: End RGB Color
description: >-
RGB color as comma-separated string (e.g., "255,255,255"). Leave empty to
skip.
selector:
text: {}
default: ""
transition:
name: Transition Time (seconds)
description: Time over which to transition, in seconds.
selector:
number:
min: 0
max: 3600
step: 1
default: 3
sequence:
- variables:
total_ms: "{{ (transition | int(3)) * 1000 }}"
rgb_color_list: |-
{% if rgb_color | trim %}
{% set parts = rgb_color.split(',') %}
{% if parts | length == 3 %}
{{ parts | map('int') | list }}
{% else %}
[]
{% endif %}
{% else %}
[]
{% endif %}
brightness_start_pct: >-
{% set b = state_attr(light, 'brightness') %} {{ ((b | int(0)) / 255 *
100) | round(0) if b is not none else 0 }}
brightness_end_pct: "{{ end_pct | int(100) }}"
brightness_diff: "{{ (brightness_end_pct - brightness_start_pct) | abs }}"
brightness_sign: "{{ 1 if brightness_end_pct > brightness_start_pct else -1 }}"
has_color_temp_input: "{{ color_temp is not none and color_temp | string | trim != '' }}"
light_supports_color_temp: >-
{{ state_attr(light, 'supported_color_modes') | join(',') is
search('color_temp') }}
do_color_temp_fade: "{{ has_color_temp_input and light_supports_color_temp }}"
color_temp_start_mired: |-
{% if do_color_temp_fade %}
{{ state_attr(light, 'color_temp') | int(0) }}
{% else %}
0
{% endif %}
color_temp_end_mired: |-
{% if do_color_temp_fade and color_temp | int(0) > 0 %}
{{ (1000000 / (color_temp | int)) | round(0) }}
{% else %}
0
{% endif %}
color_temp_min_mireds: "{{ state_attr(light, 'min_mireds') or 153 }}"
color_temp_max_mireds: "{{ state_attr(light, 'max_mireds') or 500 }}"
color_temp_end_clamped: |-
{% if do_color_temp_fade %}
{% set vals = [color_temp_min_mireds, color_temp_end_mired, color_temp_max_mireds] %}
{% set sorted_vals = vals | sort %}
{{ sorted_vals[1] }}
{% else %}
0
{% endif %}
color_temp_diff: |-
{% if do_color_temp_fade %}
{{ (color_temp_end_clamped - color_temp_start_mired) | abs }}
{% else %}
0
{% endif %}
color_temp_sign: |-
{% if do_color_temp_fade %}
{{ 1 if color_temp_end_clamped > color_temp_start_mired else -1 }}
{% else %}
0
{% endif %}
do_rgb_fade: "{{ not do_color_temp_fade and rgb_color_list | length == 3 }}"
rgb_raw: "{{ state_attr(light, 'rgb_color') }}"
rgb_start: >-
{% if do_rgb_fade and rgb_raw is iterable and rgb_raw is not string and
rgb_raw | length == 3 %}
{{ rgb_raw }}
{% else %}
[0, 0, 0]
{% endif %}
rgb_end: "{{ rgb_color_list if do_rgb_fade else [0, 0, 0] }}"
r_diff: "{{ (rgb_end[0] - rgb_start[0]) | abs if do_rgb_fade else 0 }}"
g_diff: "{{ (rgb_end[1] - rgb_start[1]) | abs if do_rgb_fade else 0 }}"
b_diff: "{{ (rgb_end[2] - rgb_start[2]) | abs if do_rgb_fade else 0 }}"
r_sign: "{{ 1 if rgb_end[0] > rgb_start[0] else -1 }}"
g_sign: "{{ 1 if rgb_end[1] > rgb_start[1] else -1 }}"
b_sign: "{{ 1 if rgb_end[2] > rgb_start[2] else -1 }}"
max_steps: "{{ [brightness_diff, color_temp_diff, r_diff, g_diff, b_diff] | max }}"
delay_ms: "{{ (total_ms / max_steps) | round(1) if max_steps > 0 else 1000 }}"
- repeat:
while:
- condition: template
value_template: "{{ repeat.index <= max_steps }}"
sequence:
- variables:
current_brightness_pct: >-
{{ brightness_start_pct + repeat.index * brightness_sign *
(brightness_diff / max_steps) }}
current_color_temp: |-
{% if do_color_temp_fade %}
{{ (color_temp_start_mired + repeat.index * color_temp_sign * (color_temp_diff / max_steps)) | round(0) }}
{% else %}
0
{% endif %}
current_rgb:
- >-
{{ (rgb_start[0] + repeat.index * r_sign * (r_diff / max_steps))
| round(0) }}
- >-
{{ (rgb_start[1] + repeat.index * g_sign * (g_diff / max_steps))
| round(0) }}
- >-
{{ (rgb_start[2] + repeat.index * b_sign * (b_diff / max_steps))
| round(0) }}
light_data: >-
{% set data = {'brightness_pct': [[current_brightness_pct, 100] |
min, 0] | max} %} {% if do_color_temp_fade %}
{% set data = data | combine({'color_temp': current_color_temp}) %}
{% elif do_rgb_fade %}
{% set data = data | combine({'rgb_color': current_rgb}) %}
{% endif %} {{ data }}
- target:
entity_id: "{{ light }}"
data: "{{ light_data }}"
action: light.turn_on
- delay:
milliseconds: "{{ delay_ms }}"