State-based automation

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.

1 Like

Hello,

That’s a nice logic you are trying to apply and i would like to try it but i have a question…

Does this cover the scenario where the garage door is open and the sun is setting? For example, i use my garage as a working space for my projects and it can happen that i have the door open and work until late at night…

Will the light turn on 30 min before sunset even if the door is open?

Thanks

I replied to your reddit post also but this isn’t true. You can do one automation with two triggers, name them, and just add a choose action with “triggered by” conditionals. One automation can handle both actions trivially and it will all be in one place. I believe this also negates all your concerns in the next paragraph.

why isn’t its state just a computed result of the other states?

Because you can already do this so there’s no reason to force the behavior as the only way. Triggers can happen on a desired state change and a choose path can drive you toward a desired state. You can also use template actions to drive any state you want from the trigger.from_state or trigger.to_state objects.

Alternatively you can just create a template switch and give it a state template that drives it to your desired state on the light but this does limit you to what a template switch is capable of.

Can you explain what this does?

is_state('binary_sensor.window_7', 'off')
or 
(
 states('binary_sensor.window_7') in ['unavailable', 'unknown']
 and
 is_state('binary_sensor.dry_1', 'off')
)

The entity names (window, dry) aren’t helping me understand what’s being checked.

If Window is off
OR
(
Window is unavailable or unknown 
AND
Dry is off
)

What are the window and dry binary_sensors?

Yes, that’s exactly the goal here. It’s a bunch of logic to resolve “when it’s dark”. In my case, it also needs to be detecting movement, but you can obviously adjust that or remove that part.

Ok, but that still requires me to declare the state dependencies both in the triggers and in the conditionals, right?

Also, for the logic to resolve “when it’s dark” that combines some states, I would still be better off with an helper sensor anyway, so I would end up with logic in both places. That’s why this approach looks a bit cleaner to me, as I really only have the logic to resolve the final wanted state in a single place.

I’m not sure what you’re asking but in order to make decisions based on state inputs in any ladder logic you must declare your inputs and some time-bound moment when the thread should run checks.

as I really only have the logic to resolve the final wanted state in a single place.

Right but how is that different though? This is a thread of ladder logic running through a CPU. It must have some time-bound event and inputs to consider. As far as I can see you’d rebuild the system and just end up with triggers and conditionals and call them something else. How are you considering the states without declaring them as inputs in some way? How are you deciding on when those checks run without some kind of time bound event? If you are declaring them as inputs and there is a time bound event - how is that not just triggers?

It’s explained above:

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)

So I have 2 sensors in my garage door, a normal window/door sensor like all the others I have in my doors/windows (binary_sensor.window_7) and another that is connected by wire to the gate activator which is a dry-contact relay (binary_sensor.dry_1). Neither is 100% reliable, but I found the window_7 one to be “95%” reliable and when it’s unavailable, the dry_1 is “usually” reporting the correct state, so I’m using it as a fallback, thus improving the overall reliability.

It’s just a little tweak, but it’s good for the post to exemplify how clean it is to just maintain and add more logic to it, with zero impact on the automation.

I know the sensor names aren’t very helpful here, sorry about that.

I will also point out that your “wanted state” sensor could be accomplished by a template trigger without the need for a separate sensor at all. Or indeed, three triggers all going to the same choose path with conditionals to check the states of all three.

(…) and some time-bound moment when the thread should run checks.

Well, in my example, you don’t have to worry about “moments” or “transitions” or “when to run checks”. I’m taking advantage of HA’s reactive state engine and abolishing any and all “transition watchers” from my side (apart from the resolved “wanted state”).

I also don’t declare inputs as triggers or anywhere else except from the conditional logic itself. And if it’s declared in only 1 place, it’s certainly more maintainable when you replace/add a new sensor or add more logic.

No worries; that’s why I asked for clarification.

Garage Door → “window contact sensor”
Gate → “dry-contact relay”

OK, that explains the unavailable/unknown test for “window” and fallback to checking on “dry”.

Well, in my example, you don’t have to worry about “moments” or “transitions” or “when to run checks”. I’m taking advantage of HA’s reactive state engine and abolishing any and all “transition watchers” from my side (apart from the resolved “wanted state”).

You’re still “worrying” about a moment/transition/time-bound event. Your “wanted state” sensor is executing another thread the moment the state of any of the inputs changes. That’s the same thing you’ve just moved the time-bound moment to a different logic path (and actually created a second time-bound moment). You could accomplish this with a template trigger and it would do the same thing. The “reactive state engine” is just another trigger logic path under the hood. It is also time bound to the moment a state changes (which you might recognize as a state trigger - because it is). Which is a transition or whatever you wish to call it. Make no mistake, you haven’t removed any of these things, you’ve just shifted them around. At first glance I’m pretty sure it would actually be less efficient too but probably on the order of 1ms so who cares.

I also don’t declare inputs as triggers or anywhere else except from the conditional logic itself.

This is simply not true. You have or conditions within your sensor. Those are conditionals. You have merely bottled the inputs and conditions together in another object and considered that instead. Which is fine, but it’s not “all in one place”. If you’re going to re-use this logic many times then a template sensor does make sense.

certainly more maintainable when you replace/add a new sensor or add more logic.

Debatable and a matter of preference. To me it seems easier to maintain a single automation than it does a template sensor + automation separately. I would only use a template sensor if I intended to use it in many places. At which point I am unlikely to change the template sensor due to its potential impact on secondary scripts.

Doing things in this way also creates some issues with path tracing should you ever need to troubleshoot your automation. Whereas if you have triggers for each of the states you want to consider you can immediately know which entity to look at. So you do need to keep this in mind as well.

1 Like

Well, in my example, you don’t have to worry about “moments” or “transitions” or “when to run checks”. I’m taking advantage of HA’s reactive state engine and abolishing any and all “transition watchers” from my side (apart from the resolved “wanted state”).

How do you think your template sensor updates its state? By using the exact same “transition watchers” automations use.

Every single sensor you have is effectively a separate automation: the first half is a trigger, constantly “watching” for some new input, and the second half an action, telling it to update its state.

The only difference with your approach and the more traditional way of building automations is where you want to see and maintain your ever-changing and ever-growing list of different possible trigger combinations.

If you want to do that from the template sensor config, good for you, but that’s just a personal choice. Many people have done it, and they don’t consider it some groundbreaking hack, because it isn’t.

Hell, your current example with only two choices (on and off) seems like overkill for this concept, I personally don’t like adding extra entities unless I absolutely have to. And as 123 Taras above me said, you lose the ability to trace and debug what exactly caused your automation to run (or not run).

1 Like

Without digging too deep in the exact implementation of your problem and solution: I think this is a good approach and I use it more often. Maybe not so much to avoid multiple triggers and conditions (motion and time constraint) as long as the automation remains relatively simple.

So while the approach in itself has merit, I’d go about it in a slightly different way.

The reason the automation gets complicated is not the three things that need to coincide, but the fact that the inputs are ill defined. There is no clear way to tell if it is dark. Solution: define a template sensor with a trustworthy dark state.

Multiple sensors needed to determine movement in one area? Create a group so you have one simple sensor about movement in that area (or use labels).

The time range, is it really the time that is relevant to you? or is it just another way to say it is dark outside? Make sure you have a sensor saying that in a simple way. If you have a reliable outside lux sensor one day it will probably be more accurate than senset/sunrise. So you can swap that out later in the “It is dark outside” sensor. Plus it seems you have two ways to say it is dark?

Once you have clear, well named inputs, then chances are the automation is simple enough that you do not need a “does the automation need to run” sensor too. The automation itself can have an easy to understand combination of things.

And last but not least: I am not a fan of putting the on and off automation in one automation. The trigger is different, what you do is different, the conditions are not the same. By keeping them separate you can often avoid conditions or ifs all together, and/or tweak each behavior so they might not be exactly opposite. Often for me, off is less complex, or slightly different.

Why not use a bayesian sensor for the door (merging the multiple sensor sources) and a group for PIR?
this way you check on 2 sensors without so many ifs.

Well, I don’t know why are you discussing internals. The whole point of this proposal is to keep automation creation as DRY and maintainable as possible (a long as there are no performance hits).
A state-based approach is just an approach to organize things. Traditionally we do an event-based approach, the automations are designed towards that, this is just an alternative approach by abolishing the transitions on our side (whomever creates and maintains the automations) and leveraging HA’s reactive system, regardless of what it does internally.

My goal is to get feedback on this approach, conceptually, rather than the concrete example.
As a developer, I tend to default to code too quickly.
In any case, yes, I’m all for extracting things into their own sensor to separate concerns. It can become even cleaner.

How do you think your template sensor updates its state? By using the exact same “transition watchers” automations use.
Every single sensor you have is effectively a separate automation: the first half is a trigger, constantly “watching” for some new input, and the second half an action, telling it to update its state.

Why do you guys keep bringing up internals to the discussion? This is not about internals, it’s about creating and maintaining automations.

The only difference with your approach and the more traditional way of building automations is where you want to see and maintain your ever-changing and ever-growing list of different possible trigger combinations.

Exactly. I’ve given a couple extra reasons but that’s probably the main one. If I know that X device should be in “on” state from a set of conditions, with this approach I only need to care about those conditions. I don’t need to carefully add each used dependency as a trigger nor even care which dependency triggered the re-evaluation. I’m just applying the state-based approach used in software development to the automations.

If you want to do that from the template sensor config, good for you, but that’s just a personal choice. Many people have done it, and they don’t consider it some groundbreaking hack, because it isn’t.

I never said this is ground-breaking. I just thought this makes a lot of sense to me and has some advantages (which I presented in the post) and just wanted to ask for personal opinions from you guys. I also didn’t find anything like this after some search.

Hell, your current example with only two choices (on and off) seems like overkill for this concept, I personally don’t like adding extra entities unless I absolutely have to. And as 123 Taras above me said, you lose the ability to trace and debug what exactly caused your automation to run (or not run).

I don’t see any problem in adding extra entities if that results in a better separation of concerns and DRY-er automations.
In my example it’s just an on/off result, but it could be anything, really. I could have more complex logic and more possible results, such as “on”, “off”, “alert”, “bright”, etc, or even more complex structures, and using this approach it would easily scale without changing the approach.
It might seem overkill for this specific example, but I’m talking about the approach in general, rather than the specific case.

Thank you very much for your constructive comment.
I wanted to focus on the approach itself, not on the details, but I completely agree on extracting those concerns into their own sensors, I’m all for SOLID principles.
This is the best way that I have so far to tell if it’s dark outside and I’d say it’s working pretty well. A reliable lux sensor would be better, but until I need that extra accuracy, this is perfectly fine.
The sensor group might be worth exploring.

And last but not least: I am not a fan of putting the on and off automation in one automation. The trigger is different, what you do is different, the conditions are not the same. By keeping them separate you can often avoid conditions or ifs all together, and/or tweak each behavior so they might not be exactly opposite. Often for me, off is less complex, or slightly different.

The thing with this approach is, if the “wanted state” already gives you the final state that the target device should be in, then the automation itself just needs to sync that result into the device. That’s all it does. It’s like a “driver” that converts the “wanted state” (could be an abstract identifier, like “danger” instead of “on” or “off”) into the actual signal that will be sent to the physical device.