Scene rotation via button press on smart switch

I have just started installing Inovelli 2-1 switches throughout the house and I wanted to be able to use the config button to rotate between scenes. I realized pretty quickly that activating scenes as you rotate through them could leave the residual effects of prior scenes still active when you get to what you want. I think I’ve come up with a solution that I like, and I figured I’d share in in case:

  1. It’s useful to anyone else
  2. There’s an easier way to do it that I’m not aware of

Automation automation.config_press_rotates_scene:

alias: Config Press Rotates Scene
description: ""
trigger:
  - platform: event
    event_type: zha_event
    event_data:
      command: button_3_press
condition: []
action:
  - service: script.rotate_scene
    data:
      device_id: "{{ trigger.event.data.device_id }}"
mode: single

Script script.rotate_scene:

alias: Rotate Scene
sequence:
  - alias: Define variables
    variables:
      room: "{{ area_id(device_id) }}"
      snapshot_scene: "{{ area_id(device_id) ~ '_snapshot' }}"
      scenes: |
        {{ area_entities(area_id(device_id))
            |select('search', '^scene\.')
            |sort
            |list }}
  - alias: Abort if there are no scenes in this area
    if:
      - condition: template
        value_template: "{{ scenes|length == 0 }}"
    then:
      - stop: There are no scenes in this area
  - alias: Define snapshot entities
    variables:
      snapshot_entities: |
        {{ scenes|map('state_attr', 'entity_id')|sum(start=[]) }}
  - alias: >-
      Create individualized snapshot scenes (to restore entities affected by
      other scenes in rotation)
    repeat:
      for_each: "{{ scenes }}"
      sequence:
        - alias: Create a snapshot using entities from other scenes in rotation
          service: scene.create
          data:
            scene_id: >-
              {{ snapshot_scene ~ '_' ~ repeat.item|regex_replace('^scene\.', '')|regex_replace('^' ~ room ~ '_', '') }}
            snapshot_entities: >
              {% set scene = repeat.item %}
              {% set entities = state_attr(scene, 'entity_id') %}
              {{ snapshot_entities|reject('in', entities)|list }}
  - alias: Create an overall snapshot scene
    service: scene.create
    data:
      scene_id: "{{ snapshot_scene }}"
      snapshot_entities: "{{ snapshot_entities }}"
  - alias: Continue allowing scene rotation until a timeout
    repeat:
      sequence:
        - parallel:
            - alias: Restore snapshot state
              service: scene.turn_on
              target:
                entity_id: >-
                  scene.{{ snapshot_scene ~ '_' ~ (scenes[(repeat.index - 1) % scenes|length])|regex_replace('^scene\.', '')|regex_replace('^' ~ room ~ '_', '') }}
              data:
                transition: 0.25
            - alias: Apply the scene
              service: scene.turn_on
              target:
                entity_id: "{{ scenes[(repeat.index - 1) % scenes|length] }}"
              data:
                transition: 0.25
        - wait_for_trigger:
            - platform: event
              event_type: zha_event
              event_data:
                command: button_3_press
              id: rotate
            - platform: event
              event_type: zha_event
              event_data:
                command: button_1_press
                device_id: "{{ device_id }}"
              id: cancel
          timeout: "0:00:10"
      until:
        - condition: template
          value_template: |
            {{ (wait.trigger is none) or
               (wait.trigger.id == 'cancel') or
               (wait.trigger.id == 'rotate' and wait.trigger.event.data.device_id != device_id) }}
  - alias: >-
      Restore the original snapshot if the paddle was pressed down or a
      different config button was pressed
    if:
      - condition: template
        value_template: |
          {{ (wait.trigger.id == 'cancel') or
             (wait.trigger.id == 'rotate' and wait.trigger.event.data.device_id != device_id) }}
    then:
      - alias: Restore starting state
        service: scene.turn_on
        target:
          entity_id: scene.{{ snapshot_scene }}
        data:
          transition: 0.25
  - alias: Re-invoke with the new device if a different config button was pressed
    if:
      - condition: template
        value_template: >
          {{ (wait.trigger.id == 'rotate' and wait.trigger.event.data.device_id
          != device_id) }}
    then:
      - alias: >-
          Start the new rotation (synchronously which prevents a new automation
          starting this script)
        service: script.rotate_scene
        data:
          device_id: "{{ wait.trigger.event.data.device_id }}"
mode: parallel
icon: mdi:palette
max: 10

This basically does the following:

  1. Find all scenes in the area.
  2. Create an individualized scene snapshot that will be applied alongside each scene in rotation. This contains any entities from other scenes that will be rotated through.
  3. Create an overall scene snapshot of the state of entities used by any scene in rotation.
  4. Apply the next scene each time the config button is pressed & at the same time restore using the snapshot that’s specifically associated with this scene.
  5. Allow a quick abort of pressing down on the paddle to restore the original state or to use a different switch (opps, wrong one—let me try the other one).

This also leaves the residual scenes that are created, but it could add a scene.reload service call at the end to clean up if one so desired.

I’ve just finished it a little while ago, so it hasn’t been used extensively. There may be issues, but it’s working in my limited testing. Comments & criticism welcome!

1 Like

That’s great! I am finishing up an addition now and will be installing Innovelli switches in a few weeks, I’ll bookmark and try this when it’s all active.