Automation to Mirror two Smart Switches

FWIW, I use Tasmota’s device groups to control a dozen smart plugs that are used exclusively for holiday lighting (meaning the rest of the year they sit in a drawer). I use a Template Switch to control them and it’s a fantastic and efficient way to turn them all on/off in unison. If you can leverage this feature then it might prove to be better than using templates, etc. I look forward to hearing about the results of your experiments.

Here’s the automation code, I tested it with 3 of my TX switches to control 1 light, it’s working beautifully, but it’s better if someone else like you, with much more experience than me, could double-check and test it.

The second trigger template is there only because the standard group behaviour for the OFF state can’t be configured, otherwise it wouldn’t be needed.

Group definition:

luce_corridoio:
  name: Luce Corridoio
  entities:
    - light.luce_corridoio
    - switch.luce_corridoio
    - switch.luce_corridoio_2
    - switch.luce_corridoio_3

Automation:

alias: Accensione Luce Corridoio
description: Gestione Luce Corridoio
trigger:
  - platform: state
    id: trigger_luce_corridoio_group_on
    entity_id: group.luce_corridoio
    to: 'on'
    from: 'off'
  - platform: template
    id: trigger_luce_corridoio_group_off
    value_template: |-
      {% if not is_state('group.luce_corridoio', 'unavailable') %}
        {% set ns = expand('group.luce_corridoio') | selectattr('state', '==', 'off') | map(attribute='name') | join(', ') %}
        {{ 'true' if ns|count > 1 else 'false' }}
      {% endif %}
action:
  - choose:
      - conditions:
          - condition: template
            value_template: '{{ trigger.id == 'trigger_luce_corridoio_group_on' }}'
        sequence:
          - service: homeassistant.turn_on
            target:
              entity_id: group.luce_corridoio
      - conditions:
          - condition: template
            value_template: '{{ trigger.id == 'trigger_luce_corridoio_group_off' }}'
        sequence:
          - service: homeassistant.turn_off
            target:
              entity_id: group.luce_corridoio
mode: single
1 Like

I’ll test it tomorrow but a quick read-through suggests it looks good. :+1:

FWIW, even without testing it yet I’ll concede you were right and I was wrong. To spare others the nuisance of wading through my wrong-headed posts, I’ve deleted them so they can get to the good stuff (your example) quickly.


EDIT

My only concern is the Template Trigger. Once it triggers, when the template evaluates to true, it will be ‘set’ and will not trigger again until the template first evaluates to false (which “resets” the trigger). Anyway, maybe my concern is misplaced because you already confirmed it works as desired.

1 Like

You were the last person I wanted to argue with, I’ve learned a lot from your posts, my HA knowledge wouldn’t be what it is now without reading you, Tom, and others helping other people, so no problem at all. :slight_smile:

Let me know if there’s something to be improved…maybe I overlooked some edge cases…

Meh, I’m known to seize on an idea and not let go; persistence isn’t always an asset.

1 Like

When it evaluates to true, it means that the previous state was false, otherwise it wouldn’t be triggered, that means that one of the members transitioned from on to off, then the automation sends the turn_off command to the entire group. Once you turn on a member, the group’s standard state triggers and the turn_on action is sent to the group, so the trigger template would be false. And then you repeat the cycle…

The consistency is guaranteed by the fact that the action is sent to the whole group, so all members should always have the same state, and the group state logic should work…

While writing the code I had the doubt that there could be a race-condition, but then I read the docs once again and the trigger will fire ONLY when there’s a change of state, so when the template evaluation switches state.

I’m exactly like you about that…but I’m always open to admit when I’m wrong about something. Believing in your own idea with perseverance is not a bad thing at all, it helps achieving difficult tasks. The important thing is being open-minded and have the intellectual honesty of admitting when the idea was wrong, like you did.

Without your challenge I wouldn’t have written the automation above, I would have relied solely on Tasmota Device Group functionality. So the discussion led to a positive outcome for other people with the switch mirroring problem.

BTW: I think HA should have this embedded…either by letting the group’s default behaviour for OFF being configurable, or by creating a specific switch_group with configurable logic.

1 Like

Taras, I completed the installation (tasmotization included) and configuration of all 15 switches, and also created 9 groups to mirror some of the switches. Everything’s working fine, the automation seems to work ok. I decided not to use Tasmota’s DeviceGroup because the automation is very fast and through groups it is far more manageable vs sending commands to single switches.

The final test will be the feedback of my wife and the kids. :slight_smile:

1 Like

Unfortunately, the results of my tests have not been as comprehensively positive as yours. Using a group of 5 lights (UPB) and one switch (homekit_controller), usually one or two of them failed to either turn on or off. However, further testing revealed that it might be due to the lighting integration (UPB) that I am using.

Using Developer Tools > Services, I can control the group reliably using the homeassistant.turn_on and homeassistant.turn_off service calls (i.e. all entities in the group are turned on/off). It’s the same service calls used in the automation. However, when the automation operates, the number of entities that get turned on (or off) is rarely all of them.

I repeated the test using a different group of entities containing seven lights (5 Philips Hue and 2 UPB) and one switch (homekit_controller). That combination was controlled reliably by the automation (except for one time when I used a Hue Dimmer Switch to turn off a Hue light).

Given this difference, I can only assume that there’s something amiss with UPB communications which causes at least one of the original group’s 5 lights to fail to receive the message.

To recap:

  1. Controlling the group of 5 UPB lights and 1 Wi-Fi switch directly always results in success; the 5 UPB lights always receive the transmitted message.

  2. Turning off one of the 5 UPB light-switches manually will trigger the automation, but the command the automation sends is not always received by all 5 UPB lights.

I’m led to assume that there’s some kind of ‘communication storm’ that occurs when Home Assistant receives the “I just got turned off manually” command, from the UPB light, and then sends “turn_off” commands to the other 4 UPB lights. Weird because sending commands to all 5 UPB lights works just fine but just not in the aforementioned scenario. :thinking: :man_shrugging:

It’s possible that this quirk is the same one that negatively impacted my previous testing (that I ultimately abandoned). The majority of my lighting is based on UPB and it works reliably. However, this seems to be an edge-case that causes unreliable performance.

I don’t intend to investigate this any further, given that the automation works well for you and probably for others (who are using lighting based on Zigbee, zwave, Wi-Fi, etc). FWIW, I currently have the means to reliably control large groups of UPB devices via its native groups (equivalent to Zigbee groups) so there’s little incentive for me to continue debugging.

Lastly, I also tested a reduced version of your automation and it worked equally well (which in my test environment still means it only controlled ~ 95% of the lights reliably). Perhaps it works 100% for you.

alias: Grouped Light Control
trigger:
  - platform: state
    entity_id: group.my_lights
    to: 'on'
    from: 'off'
  - platform: template
    value_template: "{{ expand('group.my_lights') | selectattr('state', '==', 'off') | list | count > 1 }}"
action:
  - service: "homeassistant.turn_{{ iif(trigger.platform == 'state', 'on', 'off') }}"
    target:
      entity_id: group.my_lights
mode: single

I would have asked you to test only with the 5 philips hue lights and one/two switches, excluding UPB, just to effectively prove it’s the UPB integration that creates issues. After all, for the test, it’s enough to send the homeassistant.action command to a test group, the lights should all receive and process it, otherwise there’s a problem.

Today my family didn’t complain about anything…but I’ll wait 24h more to declare it’s working perfectly for me.

Your optimized version of the automation is great, I will use it. I was also thinking about making a blueprint once I’m confident it’s working ok.

Thanks.

I tested your code, and unfortunately it doesn’t work reliably…often some lights won’t turn on/off. I will investigate and report back updating this post.

UPDATE: nailed it, it was the one-line value_template.

This doesn’t work, I guess because it seems it doesn’t produce a list:

    value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | list | count > 1 }}"

This works, and it evaluates correctly:

    value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | map(attribute='name') | join(', ') | list | count > 1 }}"

So the final optimized and working automation (at least here with 15 switches and 9 mirrored groups) is this:

automation:
  - id: gruppo_luce_corridoio
    alias: Accensione Luce Corridoio
    description: Gestione Luce Corridoio
    trigger:
      - platform: state
        entity_id: group.luce_corridoio
        from: 'off'
        to: 'on'
      - platform: template
        value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | map(attribute='name') | join(', ') | list | count > 1 }}"
    action:
      - service: "homeassistant.turn_{{ iif(trigger.platform == 'state', 'on', 'off') }}"
        target:
          entity_id: group.luce_corridoio
    mode: single

It definitely produces a list. Copy-paste it into the Template Editor, remove count > 1 and you’ll see it’s a list. It converts the generator object to a list.

Extracting name then using join to convert the generator object to a comma-delimited string, then converting the string to a list is just needless voodoo; the extra data-conversion isn’t needed to compute the number of items in the originating generator object.

The only real difference is that the extra conversion steps add a few extra microseconds of computational time. Beyond that, there’s no difference in the integer value reported by both templates. Put both in the Template Editor and you’ll see they produce the same boolean result.

If you’re seeing that the longer template works more reliably, that’s the one that takes a few more microseconds to compute and, sadly, implies a timing issue is in the mix.

You were perfectly right, the problem lied elsewhere.

Nope, luckily it was an even more basic problem, a stupid logical error: the counter was wrong, it was supposed to be > 0 and not > 1, that means “if the list contains one or more elements (> 0), then at least one switch/light is off” so the trigger template is true. With >1 you needed at least two elements in “off” state.

Is there a function for lists to check if it’s empty without using a counter? I’d prefer using that vs counting.

Updated code:

    alias: Grouped Light Control
    trigger:
      - platform: state
        entity_id: group.my_lights
        from: 'off'
        to: 'on'
      - platform: template
        value_template: "{{ expand('group.my_lights') | selectattr('state', '==', 'off') | list | count > 0 }}"
    action:
      - service: "homeassistant.turn_{{ iif(trigger.platform == 'state', 'on', 'off') }}"
        target:
          entity_id: group.my_lights
    mode: single

Well that’s a relief; glad to hear it’s not related to a few extra microseconds.

Seeing that the original example used > 1, instead of > 0 , it’s a wonder how it worked without any glitches in your original tests.

To my knowledge, it results to the same thing but if you really want to dispense with the count filter, just compare the computed list to an empty list.

    alias: Grouped Light Control
    trigger:
      - platform: state
        entity_id: group.my_lights
        from: 'off'
        to: 'on'
      - platform: template
        value_template: "{{ expand('group.my_lights') | selectattr('state', '==', 'off') | list != [] }}"
    action:
      - service: "homeassistant.turn_{{ iif(trigger.platform == 'state', 'on', 'off') }}"
        target:
          entity_id: group.my_lights
    mode: single

I’ll be testing this version, where the Template Condition now checks for one or more lights off, later today and report back with my results. Hopefully the change in quantity checked will produce better results in my lighting environment.

Yes, that’s exactly the first thought when I noticed the problem, I think it’s because it depends on the # of group members and how you do the test.

I’m thinking it takes less to compare vs an empty list respect to counting the members of the list. I’ll try your suggestion.

I really hope it works also in your case. But the first test I would do with your setup, having a light controller integration in between, is simply running a test sending the homeassistant_turn_X command to a test group and make sure all members receive it and process it. Once you’re confident about that, I see no reason why the automation wouldn’t work. Let me know…thanks.

Done some tests: these tests are all equivalent in terms of defining the template trigger and I tested all three in the automation, they all seem to work ok.

value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | list | count > 0 }}"
value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | list | count }}"
value_template: "{{ expand('group.luce_corridoio') | selectattr('state', '==', 'off') | list != [] }}"

So it’s just a matter of deciding which of the 3 is more efficient. I think the 3rd one, that is also more easy to understand from a code perspective.

Let me know your thoughts…

1 Like

What are you using as a metric for defining efficiency in this comparison?

For example, if it’s speed of execution, I’m curious to know what method you will use to benchmark it.

No benchmarks, I simply believe that comparing to [] is faster than counting the items of a list. In practice, I think the difference will be minimal. In addition to that, code readability is better.

1 Like

When I use this in the template developer tool I get a UndefinedError: ‘trigger’ is undefined

The developer tools template editor knows nothing about your automation and hence nothing about the available trigger variables.