Nested conditions in on/off automation

I’d definitely go the route Muttley mentioned above, anything else is likely to have triggers/conditions that might not be met and cause it not to behave as you expect.

Anything else?

Extraordinary claims require extraordinary proof. Please explain.

Triggers where you are anticipating every possible combination, e.g. some arriving before during and after the condition, as well as leaving during or after are likely to require multiple triggers and multiple conditions to make that work properly. The more you have of both, the more likely there are fringe cases that it misbehaves. Muttley’s suggestion is simple, and will just work when those conditions are met.

The OP wasn’t specific about what the behavior of the off function, but if the light needs to go back off during that time when the person leaves, the one you posted above will not do that. Hence if that is the way the OP expected, it not working ‘exactly as expected’.

What Muttley put above would do that… for example. If the OP wanted the light to stay on when they left during the time, then it wouldnt. What was specified by the OP wasnt technically specific enough.

Your first paragraph presents a theory with no supporting evidence. If you are certain Mutt’s suggestion is “simple”, please compose the template for a binary_sensor that will not have the alleged flaws of “multiple triggers and conditions”.

No, it won’t because the OP has not yet explained when the light should be turned off. When that information becomes available, then it can be addressed.

Thanks for the suggestions guys. The turning off should happen when I’m either not home anymore or when it’s past 23h. The device controlled is a smartplug that is charging an electric toothbrush. Basically I want to charge for a few hours per day but no more than 4h per day and the charging may only happen if I’m at home and not when I’m sleeping.
The easiest way I could think of to solve the max 4h/day + sleeping constraint is to only allow it to be on between 19h and 23h, I never go to bed before 23h anyway. If i’m not home on a particular day between those hours and it misses a charge it’s not a problem anyway because a 4h charge on a previous day should allow for a few days of use.

Okay,
Now both @123 and @callifo have valid points above, and though I don’t agree 100% with either, my opinion is just ‘my opinion’ so doesn’t really count for a lot.

As califo agreed with me I’ll do the counter case for him first : -

  1. Any automation ‘should’ take into account ALL envisaged scenairios regardless of it being encapsulated in a binary sensor or an automation.
  2. Binary sensors are kinda an advanced approach and Taras’s proposal would teach the OP more about how to think about automations, triggers, conditions and the associated actions

Taras’s counter : -

  1. The whole concept of logic, requires breaking things down into smaller, known elements and building it up from there. Taras is doing that but I feel that it’s sometimes easier to adopt a linear approach to such issues and the whole trigger - condition thing gets pretty involved, pretty quickly.
  2. Anything with a time trigger is (‘in my book’) pretty inefficient, as it’s possible to trigger something at 11:14:51 (why you’d need to trigger something at 11 am at 14 minutes and 51 seconds past the hour, is beyond me, but it’s possible) So the watcher for that trigger has to check this trigger every second of every minute of every day. That’s 86,400 evaluations every day. That’s why I use sensor.time templates for my time triggers ( states(‘sensor.time’) == ‘11:15’ ) ( note: - no seconds) - this evaluates only 1440 times a day. Now I ‘could’ put ALL the logic for this in one service template of one automation and call the automation every minute, but I think that’s messy. [Note: these numbers look big and I’m OCD about simplification/efficiency but as you can see (below) such ‘cleansing’ makes sod all difference at the end of the day]
  3. Following on from 2. I have some sensors set up for my speakers (time slots, occupancy, manual control, sleep functions etc.). Doing the same for ALL my regular switches with time/other dependant triggers has halved my CPU usage from 4% and it sometimes even drops to 1% (Pi 4).

So solving this the ‘traditional way’ : -

automation:
  - id: somethingotherthanastupidmadeuprandomnumber
    alias: toothbrush_charging_slot_start
    description: 'Bored with flipping a switch on'
    trigger:
    - platform: time
      at: '19:00:00'
    - platform: state
      entity_id: device_tracker.moto_g_6
      to: 'home'
    condition:
    - condition: state
      entity_id: device_tracker.moto_g_6
      state: 'home'
    - condition: time
      after: '19:00:00'
      before: '23:00:00'
    action:
    - service: switch.turn_on
      entity_id: switch.on_off_plug_in_unit_2
  - id: imthinkingofwrittingabookontheobservedcolourofnavelfluff
    alias: toothbrush_charging_slot_end
    description: 'Bored with flipping a switch off'
    trigger:
    - platform: time
      at: '23:00:00'
    - platform: state
      entity_id: device_tracker.moto_g_6
      from: 'home'
    condition:
      condition: or
      conditions:
        - condition: state
          entity_id: device_tracker.moto_g_6
          state: 'not_home'
        - condition: time
          after: '23:00:00'
    action:
    - service: switch.turn_off
      entity_id: switch.on_off_plug_in_unit_2

And not a template in sight.
This should work (not tested it) - BUT the major reason I dislike this is that the logic for turning on and turning off is closely related but not in one location (hence why sometimes going for the messy solution of putting it all in one service template)

My preferred solution is the binary sensor : -

binary_sensor:
  - platform: template
    sensors:
      ## toothbrush
      bs_toothbrush_charging_slot:
        entity_id: sensor.time, device_tracker.moto_g_6, binary_sensor.bs_toothbrush_charging_slot
        value_template: >
          {% set start = '19:00' %}
          {% set end = '23:00' %}
          {% set home = states('device_tracker.moto_g_6') == 'home' %}
          {% set tbok = home and start <= states('sensor.time') < end %}
          {{ tbok }}
        friendly_name: Toothbrush Charging OK
        icon_template: "{{ 'mdi:power-plug' if is_state('bs_toothbrush_charging_slot', 'on') else 'mdi:power-plug-off' }}"

automation:
  - id: somethingelsereallybanal
    alias: toothbrush_charging_slot
    description: 'Am I Just Dreaming This ?'
    trigger:
    - platform: state
      entity_id: binary_sensor.bs_toothbrush_charging_slot
    action:
    - service_template: switch.turn_{{ states('binary_sensor.bs_toothbrush_charging_slot') }}
      entity_id: switch.on_off_plug_in_unit_2

Note: you could compress the sensor template a bit more but this is written for easy reading and maintenance. There’s nothing I hate worse than having to comeback to modify something and stand there scratching my head, “What the hell does this do ?

Note: The reason why the sensor is self referencing is that rather than repeat the whole template in the icon you just reference it’self, so ‘IT’ changes, updates the output, then goes ‘Oh ! I’ve changed too’ and changes the icon.
The sensor updates 1/min but does not class as ‘messy’ because it does more than one thing (yeah. I’m splitting hairs ! :smiley: )
Oooh ! nearly forgot, you NEED sensor.time to be configured for this to work
Next Thought: the two automations at the top should survive the sequence mangling of the gui editor. The last (binary_sensor version) ‘should’ also survive but the results will be more than fugly !
Edit: Taras has pointed out on another thread how to stop the automations editor mangling your automations (I’ll look for a reference) but another way is to get rid of " - id: " and just use " - alias: " instead. See Below : -

This is the below bit : -
Edit: DON’T do this. See : -

Agreed. I also agree with this statement:

The “advanced” aspect is simply because one must employ templating. It’s a technique I employ frequently in my system.

What I disagree with is callifo’s statement that " anything else is likely to have triggers/conditions that might not be met". It’s equally possible to create a Template Binary Sensor whose template fails to handle all requirements, namely not triggering when it should or overlooking to check a particular condition. After all, the template must achieve the same functionality as explicitly listing triggers and conditions in an automation.

Home Assistant’s ‘clock tick’ is 1 second. That means it has a minimum resolution of 1 second and each time it advances by a second it checks if there’s ‘stuff to do’. So if you use a Time Trigger set for 08:04:30 or 08:00 or a Template Trigger with sensor.time checking for 08:05, the internal clock still checks for ‘stuff to do’ every second.


NOTE

In the example employing explicitly listed triggers and conditions, we both know that the two automations can also employ Template Triggers and Template Conditions. This blurs the line between the two techniques. However, the end-user must know how to create templates (knowledge of Jinja2, Trigger State Object, etc).

Absolutely agree !
You might as well say “do stuff here” garbage is garbage - make it ‘not’ garbage

Yep ! that to is unavoidable AND is actually beneficial for ALL sorts of ‘other’ automations.
My point is : - “lets give HA as little to do at that ‘one second’ point, as is feasibly possible”

Again, I absolutely agree, Templates are like the Superman of HA, they (and the template editor) are your best friends, you can pretty much do anything with a decent template. (Taras is one of those to whom I go, for advice).

The above was not written for Taras’s education: he’s forgotten more than I know about Home Automation. I always try to write my posts to be as informative (and give as little misinformation) as is possible to anyone who comes across the thread. My apologies if I implied anything else.
Similarly Califo has been on the forum a long time and knows this stuff too, I was just showing that there are pro’s and cons to any argument (but ultimately I agreed with his preference :rofl: )

I agree with the same preference, but not with the premise that it is implicitly superior to “anything else”. It has to achieve the same requirements and simply goes about it another way (via templating).

For example, the template you created can be distilled and used in the first example with Template Triggers and Conditions.

automation:
  - id: wadever
    alias: toothbrush_charging_slot_start
    trigger:
      platform: template
      value_template: >
        {{ states('sensor.time') == '19:00' or states('device_tracker.moto_g_6') == 'home' }}
    condition:
      condition: template
      value_template: >
        {{ '19:00' <= states('sensor.time') <= '23:00' and states('device_tracker.moto_g_6') == 'home' }}
    action:
      service: switch.turn_on
      entity_id: switch.on_off_plug_in_unit_2

One advantage of using a Template Binary Sensor is that it can be used with a single automation, to turn the switch on/off, whereas in the first technique it’s simpler to use two separate automations (could be bludgeoned into one with some loss of legibility). However, the second technique requires a Template Binary Sensor, an automation, and sensor.time; six of one, half a dozen of the other.

Generally agree, but it was deliberate to keep the two methods (conventional vs template) separate
Otherwise you could reduce the whole thing to : -

automation:
  - id: iliketorideabicycle
    alias: toothbrush_charging_slotification
    trigger:
      - platform: time_pattern
        minutes: '/1'
    action:
      - service_template: >
          {% set val = states('device_tracker.moto_g_6') == 'home' and '19:00' <= states('sensor.time') < '23:00' %}
          switch.turn_{{ 'on' if val else 'off' }}
        entity_id: switch.on_off_plug_in_unit_2

Which would be fine for template literates but not as a first introduction

Messy !

Well, not really a good introduction for anyone:

  • When that automation’s condition is met, it will call the switch.turn_on service every minute.
  • When the condition is not met, it calls switch.turn_off every minute.
1 Like

I absolutely agree !
I warned it was messy, it’s difficult to follow the logic and to maintain.
It also floods the ‘network’ with instructions every minute (I could enhance the template further * to stop that) but we are then just adding layers of paint to hide a hideous automation. ( * perhaps with scripts or the soon to be released ‘choose’ but … )
BUT it would work !

You must agree that ‘some’ people try to minimise the ‘number’ of automations on their system, sometimes at the expense of clarity, maintenance and actual efficiency.

@jonas87, please don’t use the last, pretend that you never saw it, but it does illustrate how powerful templates can be AND with a little reading and a few examples there’s not much that you can’t do with them. You don’t need Appdaemon or NodeRed (Taras does use NodeRed though, no idea why as he has the skills to not bother)
Ultimately it depends on what you want out of HA, to some it’s either ‘off the shelf’ or they are not interested.

I’ve experienced this when trying to turn something on and off using a single automation. Often, you can tell by the complexity of the service_template if you’ve been clever and efficient or gone to extraordinary lengths to jam a square peg into a round hole!

Whilst taking into account ‘some’ of Taras’s comments, a possible improvement is : -

automation:
  - id: busterbloodvessel
    alias: toothbrush_charging_slotification_the_sequel
    trigger:
      - platform: state
        entity_id: device_tracker.moto_g_6
      - platform: time
        at: '19:00:00'
      - platform: time
        at: '23:00:00'
    action:
      - service_template: >
          {% set val = states('device_tracker.moto_g_6') == 'home' and '19:00' <= states('sensor.time') < '23:00' %}
          switch.turn_{{ 'on' if val else 'off' }}
        entity_id: switch.on_off_plug_in_unit_2

This gets rid of the minute by minute checking and the consequent command on the network (there is nothing wrong with turning a switch that is on to on, but it’s just consuming bandwidth)
But ALL the other comments still stand, hard to read, maintain etc.

Note: the amount of time and effort gone into this discussing options and suggestions etc. (by @123 , @callifo and myself) makes this one of the most expensive solutions of any automation I’ve come across. Use it as a lesson, most aren’t worth this !
:rofl:

1 Like

Um… Going back to the original post… If you need nested conditions you can create them fairly simply with nested scripts. You don’t have to have all your code in one place - it can be broken up into small scripts like subroutines in the bad old days. Particularly useful if different automations perform similar actions.

Just wait for ‘choose’ it won’t be long now.
(Maybe as soon as 0.114.0 ???)
Edited: I was wrong, it’s going into 0113.0
:partying_face:

@Stiltjack (tagged you to ensure you saw this !)

1 Like

I like your most recent design iteration best. It incorporates the best of both worlds (and proves “anything else” can indeed be as good or better).

In this situation, if sensor.time isn’t already handy, it can be replaced with now() like this:

      - service_template: >
          {% set val = states('device_tracker.moto_g_6') == 'home' and
                       '19:00' <= now().strftime('%H:%M') < '23:00' %}
          switch.turn_{{ 'on' if val else 'off' }}
        entity_id: switch.on_off_plug_in_unit_2

NOTE

This version of the automation will still call switch.turn_off whenever the device_tracker changes to home but the time is outside of 19:00-23:00. It’s not a showstopper but if there was a desire to eliminate that behavior, all it would take is the inclusion of a condition.

1 Like

Eh ?
The switch ‘should’ already be ‘off’ as triggered at ‘23:00’ ??

Note to others: As Taras says above (arriving home, outside the 19:00-23:00 will cause a trigger to fire causing either an ‘off’ command to a device that is already ‘off’ - Unless the user is hopping across the boundary line every minute, this is not a problem (and even then, as you were ‘away’, not a lot should be happening at home to require a couple of bytes of your precious bandwidth :rofl: )

I tend to agree with the binary sensor because it makes the automation simple. But it requires some advanced templating. Either way, people can help you with that. To hit this approach, start by integrating sensor.time.

sensor:
  - platform: time_date
    display_options:
      - 'time'

This sensor updates every minute of the day and has an entity_id of sensor.time.

Next, create a binary_sensor.

binary_sensor:
- platform: template
  sensors:
    unit_2_helper:
      friendly_name: Unit 2 Automation Helper
      entity_id: sensor.time
      value_template: >
        {% set is_home = is_state('device_tracker.moto_g_6', 'home') %}
        {{ is_home and 19 <= now().hour < 23 }}

Now this will create a binary_sensor that is on when you are home and between the hours of 19 and 23. Lets break down the template:

This line simply sets is_home to true or false based on device_tracker.moto_g_6 being home or not.

        {% set is_home = is_state('device_tracker.moto_g_6', 'home') %}

This line returns 'on' or 'off' to the binary_sensor. It will be 'on' when is_home is True and when the current hour is between 19 and 23 (including 19 and 23).

        {{ is_home and 19 <= now().hour < 23 }}

Now that we have our binary_sensor.unit_2_helper, we can make the automations.

- alias: Turn On Unit 2
  trigger:
  - platform: state
    entity_id: binary_sensor.unit_2_helper
    to: 'on'
  action:
  - service: switch.turn_on
    entity_id: switch.on_off_plug_in_unit_2
- alias: Turn Off Unit 2
  trigger:
  - platform: state
    entity_id: binary_sensor.unit_2_helper
    to: 'off'
  action:
  - service: switch.turn_off
    entity_id: switch.on_off_plug_in_unit_2

And if you want to get your toes wet, here’s 1 automation that does it all (but uses templates).

- alias: Turn On/Off Unit 2
  trigger:
  - platform: state
    entity_id: binary_sensor.unit_2_helper
  action:
  - service_template: switch.turn_{{ trigger.to_state.state }}
    entity_id: switch.on_off_plug_in_unit_2

Look at what happens when the device_tracker changes state to home but the current time is not within the desired range. Your service_template calls either turn_on or turn_off and, this situation, will choose to call turn_off. So, as per my original point, it will call the turn_off service outside of the time range (even though the switch is already off). A condition can suppress that.

Long story short, there’s more than one way to achieve the desired goal. No matter which one you use, you have to ensure it covers all bases. All techniques discussed provide the user with sufficient latitude to screw up.

1 Like