How to: Get 3 switches for the price of two

I’m posting this howto is kind of a breadcrumb for folks looking to learn and solve their own issues.

Scenario: you have two switches, but you want three. Luckily there’s a way to game it out of the system. Basically you can treat both switches updating at the same time as a 3rd switch.

What you do is:

  1. when either switch updates set a boolean to true,
  2. use a nonblocking delay to give the other switch a chance to set it’s boolean
  3. if the other switches boolean isn’t set to true, execute the single switch action
  4. if both are true execute a double switch action
  5. each switch then sets it’s boolean to false

In the example below, one switch cycles a template number that sets a scene for the bedroom. The other switch turns a fan on or off. Both together cycles the bedroom input number, then applies it to the kitchen, livingroom, and hallway. Basically any switch I can’t get to at night without potentially stubbing my toe in a darkened room. I’m still learning esphome so don’t be afraid to suggest improvements.

Relevant example code

   - id: too_hot_boolean
     type: bool
     restore_value: false
     initial_value: 'false'
   - id: switch_1_change
     type: bool
     restore_value: false
     initial_value: 'false'
   - id: switch_2_change
     type: bool
     restore_value: false
     initial_value: 'false'
   - id: bedroom_scene
     type: int
     restore_value: false
     initial_value: '0'

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO13
    name: "${switch_1_friendly_name} Switch Now"
    on_state:
      then:
      - lambda: id(switch_1_change) = true;
      - delay: 250ms
## only turn on fan if not controlling the rest of the house      
      - if:
           condition:
            - lambda: return !id(switch_2_change);
           then:
              - homeassistant.service:
                  service: switch.toggle
                  data_template: 
                    entity_id: switch.fan_outlet_switch 
           else:    
      - delay: 500ms
      - lambda: id(switch_1_change) = false;
            
  - platform: gpio
    pin:
      number: GPIO5
    name: "${switch_2_friendly_name} Switch Now"
    on_state:
      then:
      - lambda: id(switch_2_change) = true;
      - delay: 250ms
## get the current bedroom scene number so we can increment it
      - http_request.get:
          url: http://bedroom-switch.lan/number/switch_scene
          on_response:
            then:
            - lambda: |-
                  json::parse_json(id(http_request_data).get_string(), [](JsonObject root) {
                      id(bedroom_scene) = root["state"];
                      //bedroom has 4 possible scenes includeing off, but most only have 3
                      //so we need to change it accourdingly
                      if(++id(bedroom_scene) > ((id(switch_1_change) && id(switch_2_change))?2:3)) {
                        id(bedroom_scene) = 0;
                      }
                  });
      - http_request.post: !lambda |-
                  return "http://bedroom-switch.lan/number/bedroom_switch_scene/set?value=" + std::to_string(id(bedroom_scene));
      - if:
## if both switches were hit, apply it to the rest of the house
## the kitchen switch is by the front door so sets all the  switches to the bedroom.
           condition:
            - lambda: return (id(switch_1_change) && id(switch_2_change));
           then:
            - http_request.post: !lambda |-
                        return "http://kitchen-switch.lan/number/scene/set?value=" + std::to_string(id(bedroom_scene)); 
      - delay: 500ms
      - lambda: id(switch_2_change) = false;