Rotate RGB effect on bulbs with a switch and an automation

I went looking for some type of automation that would rotate through the list of available effects on a bulb without having to create a static list to go through. In the end I created this automation that only needs a text input helper to work. It also uses the list of effects reported back by the bulb to work through it, so it should adapt to any list of effects the entity lists as available (check in the developer panel under states).

Each bulb controlled with this automation will need a text input helper to be created with an initial value of none so the automation can read the first on the list when it starts. In the code below it is called current_wiz_effect

This automation also has two triggered inputs that can be used to move forward or backwards through the list. The forward behavior is the default, but remember to add the trigger ID to your entries if you want to use the backwards functionality.

There are a few odd choices in here like adding a backtick to the end of the effect name to flag it. This is so it doesn’t match anything else in the list, and you can’t always rely on the bulb state when you have a finicky effect. The wiz bulb in question will switch to Rhythm but then a second later switch back to the previous effect. So if you send the trigger within 15 seconds of the last one, it relies on the helper value to determine the current effect. Otherwise it reads it from the bulb to find the next on the list.

Hope this helps someone out there, I’m curious how it does with different effect lists.

alias: Scene Switch 4 - Double / Long Press
description: "Rotate through available effects on RGB bulb"
trigger:
  - device_id: f05af41992bc235838ca1c6d5f078d2f
    domain: zha
    platform: device
    type: remote_button_double_press
    subtype: button_4
    id: trigger_forwards
  - device_id: f05af41992bc235838ca1c6d5f078d2f
    domain: zha
    platform: device
    type: remote_button_long_press
    subtype: button_4
    id: trigger_backwards
condition: []
action:
  - alias: >-
      If over 15 seconds since the last call and there is an effect state on
      bulb, set text helper to current effect. Helps skip over bad effects that 
      revert back to a previous effect.
    if:
      - condition: and
        conditions:
          - condition: template
            value_template: >-
              {% if state_attr('light.wiz_rgbw_tunable_42802c', 'effect') is defined %}
                True
              {% else %}
                False
              {% endif %}
            alias: Is the bulb effect state not null
          - condition: template
            value_template: >-
              {{ now() - this.attributes.last_triggered > timedelta(seconds=15) }}
            alias: >-
              Has it been over 15 seconds since the last trigger of this
              automation
    then:
      - service: input_text.set_value
        metadata: {}
        data:
          value: "{{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect') }}"
        target:
          entity_id: input_text.current_wiz_effect
  - if:
      - condition: state
        entity_id: input_text.current_wiz_effect
        state: none
    then:
      - variables:
          last_list_index: >-
            {{ (state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')|length)-1 }}
        alias: Get index of last item on the list
      - service: input_text.set_value
        metadata: {}
        data:
          value: >-
            {{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')[last_list_index] }}
        target:
          entity_id: input_text.current_wiz_effect
        alias: >-
          Use last item on the list as it will wrap to the first item in the
          repeat loop
    alias: If the effect text helper is set to none, assign it
  - service: input_text.set_value
    metadata: {}
    data:
      value: "{{ states('input_text.current_wiz_effect').split('`')[0] }}"
    target:
      entity_id: input_text.current_wiz_effect
    alias: >-
      Ensure state helper doesn't have a backtick indicating it was found in the
      loop below
  - alias: Loop through effects list to find next effect on the list
    repeat:
      sequence:
        - alias: Find current effect in list
          if:
            - condition: template
              value_template: "{{ repeat.item == states('input_text.current_wiz_effect') }}"
              alias: Current list item equals text input helper value
          then:
            - choose:
                - conditions:
                    - condition: trigger
                      id:
                        - trigger_backwards
                  sequence:
                    - variables:
                        next_effect: |-
                          {% if repeat.index < 1 %}
                            {{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')[(state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')|length)-1] }} 
                          {% else %}
                            {{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')[repeat.index-2] }}
                          {% endif %}
                    - service: input_text.set_value
                      metadata: {}
                      data:
                        value: "{{ next_effect }}`"
                      target:
                        entity_id: input_text.current_wiz_effect
              default:
                - variables:
                    next_effect: >-
                      {% if repeat.index+1 > state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')|length %}
                        {{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')[0] }}
                      {% else %}
                        {{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list')[repeat.index] }}
                      {% endif %}
                - service: input_text.set_value
                  metadata: {}
                  data:
                    value: "{{ next_effect }}`"
                  target:
                    entity_id: input_text.current_wiz_effect
              alias: Pick next effect direction by default
      for_each: "{{ state_attr('light.wiz_rgbw_tunable_42802c', 'effect_list') }}"
  - alias: >-
      If we set a value in the repeat loop, set it in the input helper and send
      to bulb
    if:
      - condition: template
        value_template: |-
          {% if '`' in states('input_text.current_wiz_effect') %}
            True
          {% else %}
            False
          {% endif %}
        alias: Check if there is a backtick in the effect name
    then:
      - service: input_text.set_value
        metadata: {}
        data:
          value: "{{ states('input_text.current_wiz_effect').split('`')[0] }}"
        target:
          entity_id: input_text.current_wiz_effect
        alias: Remove backtick and save new effect to text input helper
      - service: light.turn_on
        alias: Finally set the new effect name on the bulb
        metadata: {}
        data:
          effect: "{{ states('input_text.current_wiz_effect') }}"
        target:
          device_id:
            - a38abf815d0de7157dd768fc842d9274
mode: single