Proposing HA features for better configuration design

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_booleans, binary_sensors and regular switches. 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 :P

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.

  1. Only state changes trigger automations. Turning on a binary_sensor that is already on has no effect. ↩︎

  2. Don’t Repeat Yourself - one of many guiding principles of programming and software design. ↩︎