Animated Light Transitions

This is really just a play project (to see if I can make the lights look pretty - I don’t have a real practical need for it).

But I am trying to do “animated” effects where the lights in the room fade up in a pattern for example where the light starts one side of the room and moves across.

I can do this with a script and a bunch of light.turn_on and delay actions where I typically:

  • Turn the lights on very dimly (to set the start color).
  • Then do a transition (transition attribute of the turn_on) to the first set of colors.
  • Delay the script while the transitions are happening.
  • Do a transition to the next color.
  • … Repeat …
  • Finally activate the final scene that I want the room to end up in.

The approach works and looks nice - it’s just really cumbersome creating the script to do all the transitions.


Some caveats:

  • My lights are all Zigbee bulbs hence I don’t want to send too many commands (max 1 color change per second per light) - this is why I am using the transitions.
  • At the moment I have a mix of lights from different manufactures so the color spaces are not all identical.
  • Additionally some of them handle the transitions differently.
  • I have to be careful when I change colors as some of the bulbs have a tendency to go to white while transitions (doesn’t look good), hence I end up throwing in intermediate states to prevent that.

The net result is that I have to do a lot of intermediate steps and tweak individual bulbs.

I realize I could simplify the problem, if I buy a bunch of identical bulbs - but that’s not my current situation.

Anyway is there an alternative approach for tackling this - it doesn’t have to be a working code solution (happy to code it myself) just looking for a better way of tackling it.

Chated with Claude Code for a bit and came up with the following, its not great, but its a lot better than what I had:

alias: Play Light Animation
fields:
  frames:
    name: Frames
    description: >-
      Ordered list of steps. Each item - entity_id (single id or list), data
      (light.turn_on service data, e.g.
      brightness/rgb_color/color_temp_kelvin/transition), delay (seconds to wait
      after this frame before the next one starts; omit or 0 for no wait).
    required: true
    selector:
      object: {}
  final_scenes:
    name: Final Scenes
    description: >-
      Optional list of scenes to activate once all frames have played, each
      routed through script.scene_selected. Each item - room, scene_name, and an
      optional transition_time (seconds); if omitted, scene_selected uses its
      normal automatic transition behavior.
    required: false
    selector:
      object: {}
sequence:
  - repeat:
      for_each: "{{ frames }}"
      sequence:
        - action: light.turn_on
          target:
            entity_id: "{{ repeat.item.entity_id }}"
          data: "{{ repeat.item.data | default({}) }}"
        - delay: "{{ repeat.item.delay | default(0) }}"
  - if:
      - condition: template
        value_template: "{{ final_scenes is defined and final_scenes | count > 0 }}"
    then:
      - repeat:
          for_each: "{{ final_scenes }}"
          sequence:
            - action: script.turn_on
              target:
                entity_id: script.scene_selected
              data:
                variables:
                  room: "{{ repeat.item.room }}"
                  scene_name: "{{ repeat.item.scene_name }}"
                  transition_time: "{{ repeat.item.transition_time | default(none) }}"

As you can see it’s just a couple of loops, but that cleans up the invocation quite a bit.
I.e. here is a sample script that handles an “animation”:

sequence:
  - action: script.turn_on
    target:
      entity_id: script.play_light_animation
    data:
      variables:
        frames:
          - entity_id:
              - light.dining_ceiling_1
              - light.dining_ceiling_2
              - light.dining_ceiling_3
              - light.kitchen_ceiling_1
              - light.living_side
              - light.living_spotlight
              - light.living_tv_1
              - light.living_tv_2
            data:
              hs_color:
                - 240
                - 100
              brightness: 2
              transition: 0.2
            delay: 0.3
          - entity_id:
              - light.dining_ceiling_1
              - light.dining_ceiling_2
              - light.dining_ceiling_3
              - light.kitchen_ceiling_1
              - light.living_side
              - light.living_spotlight
              - light.living_tv_1
              - light.living_tv_2
            data:
              brightness: 255
              rgb_color:
                - 0
                - 191
                - 255
              transition: 2
            delay: 2
          - entity_id: light.dining_desk
            data:
              color_temp_kelvin: 2000
              brightness_pct: 10
            delay: 0
          - entity_id:
              - light.living_side
              - light.living_tv_1
              - light.living_tv_2
            data:
              transition: 2
              color_temp_kelvin: 2112
              brightness_pct: 50
            delay: 0
          - entity_id: light.living_spotlight
            data:
              rgb_color:
                - 255
                - 36
                - 165
              brightness: 132
              transition: 3
            delay: 2
        final_scenes:
          - room: kitchen
            scene_name: Evening
            transition_time: 4
          - room: living
            scene_name: Evening
            transition_time: 4
          - room: dining
            scene_name: Evening
            transition_time: 4