ENHANCED Light Switches - Dynamically control everything in your smart home from your smart light switches

I consider that smart light switches have an amazing untapped potential, but maybe I’m just crazy…

  • Switches are connected to mains electricity, so they are always powered
  • They are ‘strategically’ placed all around our homes (so I’m thinking - access points / control nodes)
  • Most important, everybody has them, so everybody knows how to use them (thinking about family or guests that are not used to control everything with their voice or from a computer/tablet/phone but already have the ‘muscle memory’ to use a light switch)

… So I wanted to do more than just toggle the lights from the light switches…absurd, right?

What I wanted?

Every time I write a script in HomeAssistant (that I consider it should be manually / physically triggered), it will be dynamically (due to a naming convention) added to a list/dropdown linked to a physical button of a light switch anywhere in the house, so pressing the button activates the script (since every appliance or function can be controlled by a script in HA, this gives the light switches the possibility to control anything in the house, software or hardware)

I was looking for a way to control my appliances and to be able to trigger my automations / scripts from physical access points - and this is what I came up with:

You should probably know that I’m using Xiaomi Aqara QBKG12LM, double gang, Zigbee switches running on local network only, with Zigbee2MQTT and CC2531 antennas, but you can use this with any smart switch (it will only have less options)

I chose those switches not just for their aspect, but for the fact they support double press events, both buttons pressed event and I believe they can also send a signal via MQTT for long press, but that also start pairing mode, so I will ignore that for now. They also have power and temperature integrated sensors.

Another great thing about those, is that you can decouple the relay on them so you can run them with smart bulbs without anyone being able to power off the bulb by mistake, but I won’t go into details about that now since this will probably be a huge post anyway.

First thing I did in order to get more from my switches was to install switches with more buttons than needed electrical connections.
For example, if I had a regular 1 gang switch, I replaced it with a 2 gang smart switch, so every one has a free button than can be programmed independently (work as a remote) and connected to homeassistant. So if I press a button (that has no electrical connection in the wall) the switch still sends that event to homeassistant and something happens (if HA is waiting for that certain event)


I’m using LEFT switch to toggle the light (electrical connection) and the RIGHT button to activate a smart plug at DOUBLE click and the jack bottle at Single click…nothing special yet.

This already gives me a way to trigger scripts and automations from physical interaction…but it is not enough; I want to be able to dynamically change what every button from every switch does.


Oonly using the right switch, from a 2 gang switch

There is one more thing I need to solve before I’m satisfied with this implementation; since I have around 10 light switches, I don’t want to manually link scripts to every one of them, every time I make a new script - I want the scripts that can / should be triggered by switches to be automatically exposed and attributed to the certain switch.

So, ideally, when I write a new script, if I decide it should be triggered by a light switch, all I have to do is to make it ‘special’ somehow (start it’s name with “enhanced_”, in my case) and then it will be automatically added to a dropdown of actions / scripts attributed to a certain switch.

Most of the work was done to make the interface clean, while having the option to chose from dozens of scripts for every switch.
I managed to have the main interface simple and clean, with cards that only have 2 buttons plus the homeplan area control, so if this is displayed on a tablet on the wall it looks simple enough, but hidden behind a long press is this absurd implementation, so no one can play with it or change it…i hope…i think…maybe…

Ok, so for those of you interested in this madness, here is what you need to do:

  • First you will need to generate a Long-Lived Access Token. Go to http://your.local.homeassistant.ip:8123/profile, scroll down to the bottom, and under “Long-Lived Access Tokens” you will be able to “CREATE TOKEN”. (copy it somewhere, I keep it in secrets.yaml)
    This will be required, because, in order to populate an input_select, we will need to make an API call from local homeassistant to…local homeassistant, otherwise yaml / jinja will return a string instead of a list and the input_select will only have one element - the list as a string.

In your configuration, you will have:

  • rest_command:
enhanced_master:
  url: "http://localhost:8123/api/services/input_select/set_options"
  method: POST
  headers:
    authorization: !secret enhanced_authorization
    content-type: application/json    
  payload: >
    {
      "entity_id": "input_select.master_enhanced",
      "options":
      [
        {%- for state in states.script %}
          {%- if state.entity_id.startswith("script.enhanced") %}
            "{{ state.entity_id}}"
            {%- if not loop.last %},{%- endif %}
          {%- endif %}
        {%- endfor %}
      "INACTIVE"
      ]
    }

The enhanced_master (rest command) is going through the entire sensor domain and populates a single input_select with all the scripts that match the naming convention, the scripts that are named enhanced_something.

also under rest command:

enhanced_update:
  url: "http://localhost:8123/api/services/input_select/set_options"
  method: POST
  headers:
    authorization: !secret enhanced_authorization
    content-type: application/json    
  payload: >-
    {
      "entity_id": 
        {%- if is_state("input_select.aqara_office_right_single", "MASTER") %}
          "input_select.aqara_office_right_single",
        {%- elif is_state("input_select.aqara_office_right_double", "MASTER") %}
          "input_select.aqara_office_right_double",
        {%- elif is_state("input_select.aqara_office_left_double", "MASTER") %}
          "input_select.aqara_office_left_double",  
        {%- elif is_state("input_select.aqara_office_both", "MASTER") %}
          "input_select.aqara_office_both",     
        {%- endif %}
      "options": 
      [
        {%- for option in states.input_select.master_enhanced.attributes.options%}
          "{{states.input_select.master_enhanced.attributes.options[(loop.index0)] |replace('script.enhanced_', '') |replace('_', ' ') |title}}"
          {%- if not loop.last %},{%- endif %}
        {%- endfor %}
      ] 
    }

Note that I’m only showcasing a single switch here (the one in the office), so under enhanced_update rest command, you will need to continue the “entity_id” template for EVERY smart switch in your home.

The reason I have 4 different “if_state” conditions is simply because my switches are double gang supporting double click actions plus both buttons pressed at once. I don’t have a “_left_single” in the example because the single press of the left button is connected to the lights (the electrical connection), so that doesn’t need to do anything else.

The bottom part of the command will populate the dropdown of the selected switch with entities that are readable by human eyes, so something that is named “script.enhanced_christmas_tree” will appear as “Christmas Tree” in the dropdown.

The REST commands will only make available the usable scripts for our switches; to get some action we need to move to

  • automations.yaml:
- id: enhanced_aqara_master
  alias: ENHANCED AQARA MASTER
  initial_state: on
  trigger:
  - platform: homeassistant
     event: start
  action:    
  - service: rest_command.enhanced_master

This first automation is only here to populate the main list with usable scripts at the start of homeassistant; it is not crucial.

- id: enhanced_aqara_selector_close
  alias: Aqara Selector Auto-Close
  initial_state: on
  trigger:
  - platform: state
    entity_id: 
    - input_boolean.aqara_office_right
    - input_boolean.aqara_office_left
    to: 'on' 
    for: "00:00:10"   
  action:    
  - service: input_boolean.turn_off
    data_template:
      entity_id: "{{ trigger.entity_id }}"

This second automation is only here as a ‘quality of life’ thingie…it will automatically hide the dropdown 10 seconds after it was opened so the UI returns to it’s clean, simple state. This is not crucial and is only useful if you want to implement this just like me.

- id: enhanced_aqara_office
  alias: "AQARA office ENHANCED"
  initial_state: on
  trigger:
  - platform: mqtt
    topic: 'zigbee2mqtt_ac/aqara_office'
  action:
  - service: script.turn_on
    data_template: 
      entity_id: >- 
        {% if ("right_single" == trigger.payload_json.click) %}
          {{ 'script.enhanced_' + states.input_select.aqara_office_right_single.state | replace (' ', '_') |lower}}
        {% elif ("right_double" == trigger.payload_json.click) %}
          {{ 'script.enhanced_' + states.input_select.aqara_office_right_double.state | replace (' ', '_') |lower}}
        {% elif ("left_double" == trigger.payload_json.click) %}
          {{ 'script.enhanced_' + states.input_select.aqara_office_left_double.state | replace (' ', '_') |lower}}  
        {% elif ("both_single" == trigger.payload_json.click) %}
          {{ 'script.enhanced_' + states.input_select.aqara_office_both.state | replace (' ', '_') |lower}}  
        {% else %}
          script.enhanced_inactive   
        {% endif %}

This (3rd one) is the important one. This will trigger the selected script when you push the light switch button and need to be copy-paste-adapted for EVERY switch in the home.

I apologize for wasting the time of those who don’t need redundant explanation of the code, but I’m trying to make this as clear as I can for everybody and since I’m not a programmer myself I know how big chunks of unexplained code can be discuraging.

I started with the rest commands because this is the logic of this implementation and since probably most people reading this already know where I’m going with it, this should be enough to get it working.
For the rest of us, this goes on…

In order for the previous blocks of code to work we need to declare the (already used above) input_select entities; so we are moving to

  • input_select:
master_enhanced:
  name: MASTER
  options:
    - INACTIVE
  icon: mdi:light-switch

This is the main input_select (dropdown) that will be populated by the rest commands, so you can leave it as it is.

Now, continuing under input_select, this needs to be copy-pasted-adapted for EVERY switch in the house; in this example, I’m only showcasing the office switch:


aqara_office_right_single:                                   # RIGHT SIDE - SINGLE CLICK
  name: SINGLE CLICK - office
  options:
  - INACTIVE
  - MASTER
  icon: mdi:light-switch  

aqara_office_right_double:                                   # RIGHT SIDE - DOUBLE CLICK  
  name: DOUBLE CLICK - office
  options:
  - INACTIVE
  - MASTER
  icon: mdi:light-switch    

# RIGHT SIDE - SINGLE CLICK - POWERED
                              
aqara_office_left_double:                                    # LEFT SIDE - DOUBLE CLICK  
  name: DOUBLE CLICK - office
  options:
  - INACTIVE
  - MASTER
  icon: mdi:light-switch  

aqara_office_both:                                           # BOTH SIDES - SINGLE CLICK
  name: BOTH CLICKED - office
  options:
  - INACTIVE
  - Christmas Tree
  - MASTER
  icon: mdi:light-switch

The options you add here will be selectable without calling the REST command. It’s basically a default mode for every switch, considering that in certain cases you may only want to be able to trigger one or two scripts and not see a full list of dozens of available scripts.
For example the “- Christmas Tree” option presented here will trigger (if selected) and be equivalent to a script called “script.enhanced_christmas_tree”

At this point the implementation is pretty much operational, so the following is mostly related to the way I wanted to build the UI for it; the next parts are required for the visual interface.

  • input_boolean:
aqara_office_right:
  name: office Right
  initial: off
aqara_office_left:
  name: office Left
  initial: off 

This is required for EVERY switch in the house. This is responsible for displaying/hiding the menu for this implementation since it will be linked as a conditional in the lovelace.yaml

  • script:
enhanced_reset:
  sequence:
  - service: input_select.reload              
  - service: rest_command.enhanced_master     

This script first service sill clear every input_select and reset it to default options. The second one will populate enhanced_MASTER after the reset, to have the ‘MASTER’ option working again on all input_select

# office
office_right_selector:
  sequence:
  - service: input_boolean.toggle
    entity_id: input_boolean.aqara_office_right
  - service: input_boolean.turn_off
    entity_id: input_boolean.aqara_office_left

office_left_selector:
  sequence:
  - service: input_boolean.toggle
    entity_id: input_boolean.aqara_office_left
  - service: input_boolean.turn_off
    entity_id: input_boolean.aqara_office_right

Those 2 scripts will be used by lovelace.yaml to display the dropdown menus and you will need to copy-pase-adapt them for EVERY switch in the house.

Bonus (which means completely useless) scripts to test your integration:

# TEST
enhanced_test_one:
  sequence:
  - service: tts.cloud_say
    entity_id: media_player.office_assistant
    data:
      message: 'Test 1'
# TEST
enhanced_test_two:
  sequence:
  - service: tts.cloud_say
    entity_id: media_player.office_assistant
    data:
      message: 'Test 2'
# TEST
enhanced_test_three:
  sequence:
  - service: tts.cloud_say
    entity_id: media_player.office_assistant
    data:
      message: 'Test 3'
# TEST
enhanced_test_four:
  sequence:
  - service: tts.cloud_say
    entity_id: media_player.office_assistant
    data:
      message: 'Test 4'

And if for some reason you are still here and want to check the lovelace…here you go

  • lovelace.yaml:

# ________________________________ AQARA office
    - type: horizontal-stack
      cards:
      - type: conditional                                 # LEFT SIDE SELECTOR                             
        conditions:
        - entity: input_boolean.aqara_office_left
          state: "on"         
        card:         
          type: entities
          show_header_toggle: false
          entities:
          - entity: input_select.aqara_office_left_double
          - entity: input_select.aqara_office_both        
              
      - type: glance                                      ###    AQARA office SWITCH    ###
        show_state: false
        show_name: true
        theme: shadesofgrey_dimmed
        entities:
        - entity: switch.aqara_office_left                # LEFT Switch
          icon: mdi:light-switch
          tap_action:
            action: toggle   
          double_tap_action:      
            action: call-service
            service: rest_command.enhanced_update            
          hold_action:
            action: call-service
            service: script.office_left_selector   
          name: office 
        - entity: switch.aqara_office_right              # RIGHT Switch
          icon: mdi:light-switch
          tap_action:
            action: toggle      
          hold_action:
            action: call-service
            service: script.office_right_selector 
          double_tap_action:      
            action: call-service
            service: rest_command.enhanced_update
          name:  

      - type: conditional                                 # RIGHT SIDE SELECTOR         
        conditions:
        - entity: input_boolean.aqara_office_right
          state: "on"         
        card:         
          type: entities
          show_header_toggle: false
          entities:
          - entity: input_select.aqara_office_right_single 
          - entity: input_select.aqara_office_right_double     
                          
    - type: conditional                                   # AQARA office STATS                                 
      conditions:
      - entity: sensor.aqara_office_power
        state_not: "unknown"        
      - entity: sensor.aqara_office_power
        state_not: "0"
      # - entity: switch.aqara_office_left
      #   state_not: "off"          
      card:                      
        type: horizontal-stack                   
        cards:
        - type: horizontal-stack                   
          cards:              
          - type: gauge
            name: " "
            theme: split_theme
            entity: sensor.aqara_office_temperature 
            severity:
              green: 0
              yellow: 45
              red: 60
          - type: gauge
            name: " "
            theme: split_theme
            entity: sensor.aqara_office_power  
            severity:
              green: 0
              yellow: 60
              red: 100
        - type: sensor
          entity: sensor.aqara_office_consumption 
          name: Energy consumption
          theme: split_theme
          icon: mdi:power-plug
          graph: line

Needless to say, this needs to be adapted and used for EVERY switch in the house.

I implemented this in my home almost 1 year ago and I wanted to share it ever since but I knew it will be a very long post and I never had the time or will do start doing it, but I used this since then and it actually makes the entire home feel a lot smarter (…and it gives a purpose to smart switches).

I hope you can enjoy this at least as much as I do. Have fun!

8 Likes

I don’t know why this thread has no replies. It is great thanks.

When I buy a smart switch, I always buy one with more buttons than I immediately need, for the same reason. Your integration is better than mine though.

2 Likes

This completely flew under the radar, but it’s good you found it.

Glad you appreciate it. Thanks!