Introduction
First of all, let me explain what I was trying to do.
I have a LED strip in the garage which I wanted to be turned on based on movement but only when it’s dark. And turned off otherwise.
These conditions themselves presented some challenges, such as:
- I had to use 2 PIR sensors to cover the entire area
- The PIR sensors’ “lux” attribute is extremely unreliable, takes too long to update, the values don’t seem right or consistent at all and there is also the problem that it would stop being “dark” with the light turning on (so that would make it turn off, as it would “stop being dark”)
The solution for these conditions:
- Combine both PIR sensors’ states (when either one detects movement).
- To know when it’s dark, combine of the sun sensor with the garage door sensor. - It’s dark if the sun has set/will set soon or the garage door is closed (as I don’t have any windows there).
The Problem with Traditional Trigger-Based Automations
Now, for the main purpose of this post, here’s my problem (or existential crisis, really) with the traditional way of doing automations.
I would have to create two automations:
- One to turn on the light when all the conditions are met.
- Another one to turn off the light when the inverse of those conditions are met.
This works but quickly becomes hard to manage as conditions grow more complex. I’d have to:
- List every relevant state in the triggers of both automations.
- Carefully negate conditions to ensure the light turns off at the right time.
- Maintaining two automations that essentially do the same thing, just inverted.
The solution - state-based automations
This idea intuitively came to me as I’m coding daily in a state-based paradigm. If we just want a light state to be based on other states, why isn’t its state just a computed result of the other states? I wanted to try that.
The idea is to have a reactive, single source of truth for the “wanted state” of the LED strip and a minimal, traditional automation that “interfaces” with it, syncing the “wanted state” to the LED strip.
So in the end I have only these 2 components:
- The “wanted state” sensor
- The automation to sync it to the physical light
The “wanted state” sensor
I’m using a template sensor to write the complex logic.
Since I only want to resolve an on/off state for the LED strip, it’s of binary type.
ID: binary_sensor.c_garage_led_wanted_state
(I prefix these sensors with a “c”, as in “computed”, just for easier identification.)
Template:
{{
(
is_state('binary_sensor.pir_4_presence', 'on') or
is_state('binary_sensor.pir_5', 'on')
) and
(
(
is_state('binary_sensor.window_7', 'off') or
(
states('binary_sensor.window_7') in ['unavailable', 'unknown'] and
is_state('binary_sensor.dry_1', 'off')
)
) or
is_state('sun.sun', 'below_horizon') or
(state_attr('sun.sun', 'next_setting') - now()).total_seconds() / 60 < 30
)
}}
This includes the logic I described above and some extra details. The binary result is “true” (“on”) if:
- Either PIR sensor is detecting movement
- And is dark inside the garage, which is true if:
- The garage door is closed (I’m also evaluating a backup sensor to rely on in case the main one reports “unavailable” or “unknown” states)
- Or the sun has set (or is setting in the next 30 minutes)
The automation
The automation here simply maps the computed result into the action to perform on the device.
It triggers when the wanted state changes and maps that wanted state into an actual action:
- “on” → turn on
- “off” → turn off
alias: Control Garage LED Strip
description: ""
triggers:
- entity_id: binary_sensor.c_garage_led_wanted_state
trigger: state
actions:
- choose:
- conditions:
- condition: state
entity_id: binary_sensor.c_garage_led_wanted_state
state: "on"
sequence:
- data: {}
target:
entity_id: light.led_3
action: light.turn_on
- conditions:
- condition: state
entity_id: binary_sensor.c_garage_led_wanted_state
state: "off"
sequence:
- data: {}
target:
entity_id: light.led_3
action: light.turn_off
default:
- metadata: {}
data: {}
target:
entity_id: light.led_3
action: light.turn_off
(It also provides a default action of “turn off”, in case something’s wrong.)
Final touches
The “wanted state” sensor can be set to “show as light” so that it visually reflects the kind of the device the state is for.
Benefits
- Simpler and more maintainable (single source of truth for the wanted state + single minimal automation that only interfaces with it)
- Allows for changing and growing logic and changing sensors in a single place
- Bonus: If the light controller itself proves to be unreliable, you can run the automation whenever or for example add a trigger to run every minute or so and it will work as a retry mechanism, always resolving the correct action to run for the LED strip.
Please let me know whay do you think about this.