Emulate a three way switch

I currently have two switches, each one backed by a sonoff switch.

Each switch controls a group of lights. So, we have

Switch A => sonoff A => light group A
Switch B => sonoff B => light group B

I want to group both light groups, such that when I press either one, both light groups are switched on/off.

I tried creating 4 automations that basically do this:

  • Trigger: state sonoff A on => off ==> Action: Sonoff B off
  • Trigger: state sonoff A off => on ==> Action: Sonoff B on
  • Trigger: state sonoff B on => off ==> Action: Sonoff A off
  • Trigger: state sonoff B off => on ==> Action: Sonoff A on

However, when I activated these automations, clicking any button would make my lights go into some sort of loop, with the list flickering constantly.

I did the same kind of setup through the ewelink app and that seems to work fine.
What am I doing wrong with this setup and/or is there any other way to accomplish this?

Thats not really a 3 way switch. A 3 way switch is 2 switches controlling a set of lights with the following logic:

switch 1 on, switch 2 off -> lights on
switch 1 off, switch 2 on -> lights on
switch 1 off, switch 2 off -> lights off
switch 1 on, switch 2 on -> lights off

You just want to pair the switches together.

A dynamic automation that does that would look like this:

trigger:
- platform: state
  entity_id: switch.a
- platform: state
  entity_id: switch.b
condition:
- condition: template
  value_template: >
    {% set switches = ['switch.a', 'switch.b'] %}
    {% set target = trigger.to_state.state %}
    {{ states | selectattr('entity_id', 'in', switches) | selectattr('state','eq', target) | list | length != switches | length }}
action:
- service_template: switch.turn_{{ trigger.to_state.state }}
  data_template:
    entity_id: >
      {% set switches = ['switch.a', 'switch.b'] %}
      {% set target = trigger.to_state.state %}
      {{ states | selectattr('entity_id', 'in', switches) | selectattr('state','!=',target) | map(attribute='entity_id') | list | join(', ') }}

So what this does is it triggers off a or b. Then the condition checks if all the lights are already the target state. If they all are already the target state, the automation does not fire. If they are not, it builds a list of entities to set to the target state.

This will work with any size list. So if you add more lights or switches in the future, all you need to do is add them to the list.

Now, I did notice that we may have to make a map because these switches may not coorelate to your light group.

I can adjust this automation to account for that but I need to see your entity_id’s for all switches & light groups.

2 Likes

The logic you defined is exactly what I’m after, so it really is an (emulated) 3-way switch.
I think the issue in this case is that by switching one of the sonoffs, it directly controls the light it is attached to already.

I’m not sure what you mean by “pair the switches together”.

That just means when you turn the one switch on, the other switch will turn on. Just post all your entity_ids and I can build a replica 3 way switch automation.

I’ve implemented this succesfully, seems to be working much better than my first iteration.
I do notice however that there’s a bit of a delay. If I trigger it through the home assistant app, it’s instant, but triggering it through the physical switch gives me a second of delay, which is a bit jarring.

Do you have any idea on how to remove that delay?

If not, no worries, thanks for your help!

The delay is most likely caused by the delay in updating the switch. Meaning, your hardware switch doesn’t instantly send a i'm this state message. Probably nothing you can do about that other than replacing the hardware.

Hi Petro, Ive been following this feed with interest. I currently have 4 sonoff 3 gang switches which all function the same. One switch operates the porch light, one the hall lights and the last operates the landing light. Ive had these working using a rather rudumental automation which has worked nicely however im trying to consolidate the automation and was trying to use what you have written above however it doesnt seem to work, Could you provide me with any guidance:

The 4 entites im using for the first button are Hallm, Hall2, Hall3 and Hall4.

Thank you

I never saw this post. When you reply, please reply to the person, not the thread. Otherwise your post gets lost.

This can only be done in yaml, outside the automation editor:

trigger:
- platform: state
  entity_id: &entities
  - switch.a
  - switch.b
  - switch.c
  - switch.d
  from: &states
  - 'on'
  - 'off'
  to: *states
variables:
  entities: *entities
  value: > 
    {{ trigger.to_state.state }}
  targets: >
    {{ expand(entities) | rejectattr('entity_id','eq', trigger.entity_id) | selectattr('state','!=', value) | map(attribute='entity_id') | list }}
condition:
- condition: template
  value_template: >
    {{ targets not in ([], none) }}
action:
- service: homeassistant.turn_{{ value }}
  target:
    entity_id: "{{ targets }}"

I’ve got a similar use case and I stumbled upon this and just wanted to say thanks for posting this.

I do see the lag, and it’s about 3 seconds or so. Hasn’t really annoyed me yet and serves its purpose so it’s all good!

This is a little off topic but I’ve been unable to find an answer anywhere else. Petro, you introduced new syntax that I’m unfamiliar with, specifically &entities, &states, *entities, &states. These seem to be provide some kind of list or wildcard capability but I’m unsure if the “states” and “entities” are keywords or just chosen names. A short explanation or link where I can read more would be awesome.

They are not wildcards they are yaml anchors. & sets a variable to contain the yaml below it. * recalls the yaml in the variable and reuses it in this new spot.

Declaring entities for use in another yaml field

Populating entities variable into another yaml field.

Thanks for the quick reply! That is slick. I wonder why I’ve not a seen a single example elsewhere that uses anchors since often the same ‘list’ of entities are found throughout. I’m going to go back and do some cleanup.