Yet another light fader

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 }}"

Maybe some more info on what it does? What makes it different than others?

1 Like