I have a wireless wall panel switch with 3 buttons. I want to use this to control lights in Home Assistant. Each button toggles a light through an automation.
Normal light
instances work fine out of the box, because you can use light.toggle
service on them. This allows you to use a single button to both turn on and turn off the light. Unfortunately, mqtt_json
lights do not support the toggle feature. The solution is to either use two buttons (one for “turn on” one for “turn off”), which would waste a button mapping, or we can write automations that handle the state of the mqtt_json
light and imitate a toggle
service.
I had considered this, however, it would create a set of really bad automations whose sole purpose is to imitate a toggle service.
Toggle LED strip (MQTT JSON light)
So I did just that… for sake of simplicity and just getting it to do what I want it to do. The following are all scripts related to tv_led
which is the mqtt_json
light associated with the LED strip behind my TV unit.
There are 5 automations:
1. Dim light
2. undim light
3. quick fade in
4. quick fade out
5. toggle between 3 and 4
fade_out_strip:
alias: fade out strip
sequence:
- service: light.turn_on
data:
entity_id: light.tv_led
white_value: 255
brightness: 55
rgb_color: [0,0,0]
transition: 5
fade_in_strip:
alias: fade in strip
sequence:
- service: light.turn_on
data:
entity_id: light.tv_led
white_value: 255
brightness: 200
rgb_color: [0,255,0]
transition: 2
tv_fade_in_quick:
alias: tv fade in quick
sequence:
- service: light.turn_on
data_template:
entity_id: light.tv_led
white_value: 255
brightness: 255
rgb_color: [255,255,255]
transition: 1
tv_fade_out_quick:
alias: tv fade out quick
sequence:
- service: light.turn_on
data:
entity_id: light.tv_led
white_value: 0
brightness: 255
rgb_color: [0,0,0]
transition: 1
- delay: "00:00:05"
- service: light.turn_off
entity_id: light.tv_led
tv_toggle_quick:
sequence:
- service_template: >
script.tv_fade_in_quick
Why I dislike this solution
I have a few problems with this setup:
* the solution does not scale, add a second mqtt_json
light (like I have on my bed’s headboard) and you have to copy all 5 automations (or come up with ludacrous templates that take parameters, and add that same parameter filth to all automations that use them)
* the service parameters (and yaml keys) are duplicated across all 4 automations. The YAML format, by its key-value nature, can cause a lot of overhead in the way automations must be defined. Each automation above duplicates the light.turn_on
service call. The only thing that diffes is the actual values passed in.
I wish there was a way to define a function that takes these values such as brightness and entity_id as a parameter and calls the light.turn_on
service using the supplied parameters. We could then define light-weight wrapper functions for automations number 1, 2, 3 and 4 and pass in the entity_id to be controlled. Similarly, automation 5 could control any light using one of the afforementioned functions in its if-else blocks and taking a parameter itself to pass along.
That would be infinitely scalable as those functions can be reused for all led strips, supplying a different parameter each time.
I suppose this is the tradeoff we have to live with given the relative of ease of use of the YAML format compared to typed programming languages.
Define Programming Interfaces for components
We have a number of components that act like “switches” such as input_boolean
s, binary_sensors
and regular switch
es. The problem with those is that they contain state, and need to be reset to off in order to trigger automations1 This creates a problem: When are they to be reset to off? You might say at the end of your automation that uses them.
I think that is really bad design because it means every single automation that uses this switch has to remember to set it back to off. It should not be the automations responsibility to main the state of the switch.
The feature I am proposing is an “interface
” component (or whatever the final name may be). I am talking about programming interfaces and, for the love of all that compiles, not GUI interfaces.
The interface
does not have a state like on
or off
. All it represents is an abstraction of another component. Think of it like a variable that stores a more complex entity.
For example Say you have a complex MQTT trigger for motion sensors, which contains a specific topic, some more configuration and lots of detail specific to how the trigger works. Now let’s say you need to use that trigger in 2 or 3 different automations. This means you have to copy and paste the trigger into all automations, meaning you now have the same trigger defined in 3 different locations.
This is not good. Any time you find yourself copy and pasting in programming means you are probably violating the DRY principle.2
Rather than copying that trigger to X different automations (and duplicating information such as MQTT topic, payload etc), this proposed interface
component would enable you to create a single interface
containing the specific trigger information. And then you may duplicate the relatively simple interface into multiple locations.
You would then be able to use a reference to this interface
as a trigger in other automations. In other words, all your X automations refer to the same interface
which in turn contains the trigger.
We can extend this idea to automation actions and conditions as well. Have a set of really complex conditions that is better maintained in a central location? Create an interface
for each and refer to that named interface
in your automations.
Or consider the interface
below, which abstracts the light.turn_on
srvice from the previous section.
interfaces:
set_strip_color:
parameters:
- name: colour
default: [150,0,0]
- name: transition_time
default: 2
- name: entity
required: true
reference:
service: light.turn_on
data:
entity_id:
white_value: 255
brightness: 200
rgb_color:
transition:
We would then be able to call this interface
in other automations, passing in the parameters.
# left out other automation parts.
actions:
- interface: set_strip_color
paramaters:
- entity: light.tv_led
- colour: [100,100,0]
Note that this is not a Home Assistant feature. I am proposing this functinality. Don’t break your configuration by attempting to try this out 😛 {: .notice–warning}
In summary, an interface
is a way to store a complex trigger
, service
, condition
, etc and reuse it easily. It might help to rename interface
to function
depending on the use case.
I learned a few months after wwriting this post about AppDaemon. I highly recommend you check it out if you find yourself restricted by HA’s YAML format. It provides a Python based way to define automations.