Help with complex blueprint to sync switch + multiple lights and wait_for_trigger

I’ve been writing a blueprint to keep a given switch & multiple lights in sync. This ends up being somewhat complex because the behavior I want is for whichever changed first - switch (e.g. through a physical tap) or light (e.g. through voice) - to cause the other to change. (There’s clarifying comments about the desired behavior in the full blueprint below.)

One issue I’m trying to resolve from an initial, simpler version of the blueprint is ignoring state updates from the non-initially-triggered entities until the initial change is done. So for example, if the switch changes brightness from 20 to 100, I want to take no action on light state changes until the lights reach 100 brightness. This would fix an occasional bug I observed in the simpler automation, which I believe was due to the light states happening to sync up partway through a switch-initiated change (say at 50 brightness), causing the automation to light.turn_on the switch back to 50.

I’m trying to use the single automation mode plus an action that has a repeat-until loop with wait_for_trigger to keep the automation running until the change is done, but it seems like it’s missing state triggers and getting stuck.

The pseudocode is basically:

action:
  - repeat:
      until: light & switch brightness equal
      sequence:
        - if repeat.first or wait.trigger same as original trigger:
          - send light.turn_on/off to non-triggered entities
        - wait_for_trigger: switch or light state change (same as original triggers)

But the wait_for_trigger seems to wait forever unless I add a timeout, as if it’s missing the state updates caused by the light.turn_on/off above it. With a short timeout the action completes after the timeout, so the condition is true by that point. Is it unreliable to use wait_for_trigger to monitor for the change caused by a service call to finish in this way?

Here is the whole blueprint: sync_switch_and_lights2.yaml · GitHub

In case this helps someone else in the future…

I found a much simpler solution to ignore state changes made by the last run of an automation: use the event’s context values. I’d thought about this originally but the documentation for context doesn’t go into a lot of detail about exactly how the context values are set, so it was unclear exactly how it behaves. What I observed through traces and monitoring events (i.e. this could be subtly wrong or incomplete) was that an automation’s this.context.id value seems to update when the action is triggered. So I added a condition to my automation that trigger.to_state.context.id != this.context.id, i.e. only trigger the action again if the trigger event occurred for a reason other than the last run of the automation.

The full, much simpler blueprint is now this:

blueprint:
  name: Synchronize Inovelli VZM31 Switch & Lights V3
  description: Keep defined switch & lights states' synchronized
  domain: automation
  input:
    switch:
      name: Switch
      selector:
        entity:
          domain: light
          integration: zha
    lights:
      name: Lights
      selector:
        entity:
          domain: light
          multiple: yes

trace:
  stored_traces: 20
mode: restart

trigger:
  - platform: state
    id: switch
    entity_id: !input switch
  - platform: state
    id: lights
    entity_id: !input lights

variables:
  switch: !input switch
  lights: !input lights

condition:
  - '{{ states(trigger.entity_id) in ["on", "off"] }}'
  # Filter out state changes caused by the last run of this automation
  - '{{ trigger.to_state.context.id != this.context.id }}'
  # Action only needed if switch & lights not in same state
  - >
    {{ lights | map('states') |
        select('ne', states(switch)) |
        list | count > 0
        or
        lights | map('state_attr', 'brightness') |
        select('ne', state_attr(switch, 'brightness')) |
        list | count > 0 }}
  - or:
    # Always change lights on switch trigger
    - condition: trigger
      id: switch
    # All lights must have same state & brightness to change switch
    # TODO: Maybe more natural for switch to follow max of lights instead?
    - and:
      - >
        {{ lights |
            reject('eq', trigger.entity_id) |
            map('states') |
            select('ne', states(trigger.entity_id)) |
            list | count == 0 }}
      - >
        {{ lights | 
            reject('eq', trigger.entity_id) |
            map('state_attr', 'brightness') |
            select('ne', state_attr(trigger.entity_id, 'brightness')) |
            list | count == 0 }}

action:
  - service: light.turn_{{ states(trigger.entity_id) }}
    target:
      entity_id: |
        {% if trigger.id == 'lights' %}
          {{ switch }} 
        {% else %}
          {{ lights }}
        {% endif %}
    data: |
      {% if is_state(trigger.entity_id, 'on') %}
        {
          "brightness": {{ state_attr(trigger.entity_id, 'brightness') }},
          "transition": 0.5
        }
      {% else %}
        {}
      {% endif %}
1 Like