A question on programming logic more than anythign HA specific:
I keep finding that many of my automations constantly try to deal with the same structure: I typically want to trigger off something, (e.g. A – temp above 25), but then only act on it if some condition is satisfied (e.g. B – someone is home).
But then immediately this leads to the complement of this pair: I would like to execute the automation sequence if B is triggered while A is true (someone gets back home and it’s already hot).
So I write this as two separate triggers and then repeat both the triggers as conditions. A bit of repetition, but simple enough.
Once you begin to add multiple triggers this rapidly escalates to a nightmare. But it feels like this (trigger A, condition B, trigger B condition A) pair is completementary in a well defined way, so there should be a better way of writing this set of logical requirements.
Indeed I’ve ended up with some binary sensors for conditions which can be interpreted as schedules and the like which has simplified some of this. I should use this more!
Using template triggers is something I hadn’t quite thought through before. It indeed achieves the trigger and condition in one and you can package such triggers with their conditions nicely so that you don’t need to pollute the choose statements below too much.
They do become a bit hard to read I guess so a bit more commenting is called for.
Can’t you just list all the trigger:s that you would want to consider; then or your list of condition:s to exactly specify every situation when the automation should trigger?
Alternatively you can achieve the same thing (more neatly in complex situations) with a template sensor that:
you define the set of triggers as above as to when to re-evaluate the sensor value.
duplicate the logic of the list of conditions above in the template calculation.
return a binary value (that provides a simple platform: state … to: True trigger for your automation).
If you want the automation to trigger again when the trigger conditions are already met, but then are met again, then your template would have to include a timed reset (to force a state change).
Well, in case anyone would prefer to recommend something specific for a non-general example, here is a somewhat extreme automation that is typical in that I wanted to add a condition to the final choose clause that the automation shouldn’t trigger when the sensor.home_weather_dni is below 400, but this then requires me to add a whole new trigger for when this sensor goes above 400 and a bunch of other conditions are true.
I’ve ended up combining everything into one automation and maybe this is not the most sensible, but it keeps my automations somewhat orderly in the UI.
It seems like rossk’s suggestion of template triggers might indeed be the best at shrinking the logic here.
- alias: "Blind: Sunscreener -- Balcony"
description: "Move blacony blinds according to sun_screener, but take into account rain!"
id: "urergdffdbddpteirytw3gtg"
trigger:
- id: sun_in_window
platform: state
entity_id:
- binary_sensor.blind_balcony_right_optimal_state
- binary_sensor.blind_balcony_left_optimal_state
to: "off"
variables:
blind: "{{trigger.to_state.object_id.removesuffix('_optimal_state')}}"
action: "close"
- id: sun_off_window
platform: state
entity_id:
- binary_sensor.blind_balcony_right_optimal_state
- binary_sensor.blind_balcony_left_optimal_state
to: "on"
variables:
blind: "{{trigger.to_state.object_id.removesuffix('_optimal_state')}}"
action: "open"
- id: sun_on_balcony
platform: state
entity_id:
- binary_sensor.blind_balcony_right_copy_optimal_state
- binary_sensor.blind_balcony_left_copy_optimal_state
from: "on"
to: "off"
variables:
blind: "{{trigger.to_state.object_id.removesuffix('_copy_optimal_state')}}"
action: "close"
- id: sun_off_balcony
platform: state
entity_id:
- binary_sensor.blind_balcony_right_copy_optimal_state
- binary_sensor.blind_balcony_left_copy_optimal_state
from: "off"
to: "on"
variables:
blind: "{{trigger.to_state.object_id.removesuffix('_copy_optimal_state')}}"
action: "open"
- id: sun_bright
platform: numeric_state
entity_id: sensor.home_weather_dni
above: 400
- id: balcony_door_opened
platform: state
entity_id:
- binary_sensor.aqara_bedroom_balcony_door
- binary_sensor.aqara_salon_balcony_door_2
from: "off"
to: "on"
- id: TimerIdle
platform: state
entity_id:
- timer.blind_balcony_right
- timer.blind_balcony_left
from: "active"
to: "idle"
variables:
blind: "{{trigger.to_state.object_id}}"
action:
"{{'close' if (is_state('binary_sensor.' ~ blind ~ '_optimal_state','off') or
(is_state('binary_sensor.' ~ blind ~'_copy_optimal_state','off') and (is_state('binary_sensor.aqara_salon_balcony_door_2','off') or is_state('binary_sensor.aqara_bedroom_balcony_door','off') )))
else 'open'}}"
- id: "rain"
platform: numeric_state
entity_id: sensor.wu_holesovice_precipitation_rate
above: "0.09"
- id: "rain_stop"
platform: numeric_state
entity_id: sensor.wu_holesovice_precipitation_rate
below: "0.09"
for: "0:05:00"
condition:
- or:
- condition: trigger
id: "rain"
- and:
- condition: state
entity_id: input_boolean.blind_sunscreener_enable
state: "on"
action:
- choose:
- alias: "It started raining: retract covers"
conditions:
- condition: trigger
id: rain
sequence:
- service: cover.open_cover
target:
entity_id:
- cover.blind_balcony_left
- cover.blind_balcony_right
- service: timer.start
target:
entity_id:
- timer.blind_balcony_right
- timer.blind_balcony_left
data:
duration: "00:04:55"
- alias: "It stopped raining: close covers if sun in house, or on balcony and doors open"
conditions:
- condition: trigger
id: "rain_stop"
sequence:
- repeat:
for_each:
- "blind_balcony_right"
- "blind_balcony_left"
sequence:
- if:
- "{{ is_state('timer.'~repeat.item,'idle')}}"
- or:
- "{{ is_state('binary_sensor.' ~ repeat.item ~ '_optimal_state','off')}}"
- and:
- "{{ is_state('binary_sensor.' ~ repeat.item ~ '_copy_optimal_state','off')}}"
- "{{ is_state('binary_sensor.aqara_bedroom_balcony_door', 'on') or is_state('binary_sensor.aqara_salon_balcony_door_2','on')}}"
then:
- service: cover.close_cover
target:
entity_id: "cover.{{repeat.item}}"
- service: timer.start
target:
entity_id: "time.{{repeat.item}}"
data:
duration: "00:30"
- alias: Close covers if strong sun on balcony and balcony door has been opened
conditions:
- or:
- and:
- condition: trigger
id: balcony_door_opened
- condition: numeric_state
entity_id: sensor.wu_home_weather_dni
above: 400
- and:
- condition: trigger
id: sun_bright
- condition: state
match: any
entity_id:
- binary_sensor.aqara_bedroom_balcony_door
- binary_sensor.aqara_salon_balcony_door_2
state: "off"
sequence:
- repeat:
for_each:
- "blind_balcony_right"
- "blind_balcony_left"
sequence:
- if:
- "{{ is_state('binary_sensor.' ~ repeat.item ~ '_copy_optimal_state','off')}}"
- "{{ is_state('timer.' ~ repeat.item, 'idle')}}"
then:
- service: cover.close_cover
target:
entity_id: "cover.{{repeat.item}}"
- service: timer.start
target:
entity_id: "time.{{repeat.item}}"
data:
duration: "00:30"
- alias: Otherwise, operate covers according to optimal state
conditions:
- "{{is_state('timer.' ~ blind, 'off')}}"
- not:
- or:
- alias: "But -- do not close covers if triggered by sun on balcony but doors closed or sun weak"
and:
- condition: trigger
id: sun_on_balcony
- or:
- "{{ is_state('binary_sensor.aqara_bedroom_balcony_door', 'off') and is_state('binary_sensor.aqara_salon_balcony_door_2','off')}}"
- condition: numeric_state
entity_id: sensor.home_weather_dni
below: 400
- alias: "Do not open covers if sun_off_window but still shining on balcony"
and:
- condition: trigger
id: sun_off_window
- "{{ is_state('binary_sensor.aqara_bedroom_balcony_door', 'off') or is_state('binary_sensor.aqara_salon_balcony_door_2','off')}}"
- "{{states('sensor.weather_home_dni')|float('not number')>400}}"
- "{{is_state('cover.'~blind ~ '_copy_optimal_state','off')}}"
sequence:
- service: "cover.{{action}}_cover"
target:
entity_id: "cover.{{blind}}"
- service: timer.start
target:
entity_id: "timer.{{ blind }}"
data:
duration: "00:30"
mode: parallel
Sorry to abuse the goodwill of the forums, but I keep getting myself into the same level of mess with any complicated automation. Following you advice above, I’ve managed to rewrite the complicated condition-sequence part using template triggers, leaving me with a simple sequence but very convoluted trigger conditions.
This is ok if I do it once, but now the price is that I cannot think how to iterate over the covers – in this example there are two covers, left and right. Then, as far as I can see, I need to have separate triggers for sun on and sun off (they are the same condition with a not pushed through, apart from the timer requirement to be idle which is the same for both).
Seems like there should be something smarter one could do?
- alias: "Blind: Sunscreener -- Balcony"
description: "Move balcony blinds according to sun_screener, but take into account rain!"
id: "urergdffdbddpteirytw3gtg"
trigger:
- id: sun_on_right
platform: template
value_template: >
{{(
(is_state('binary_sensor.blind_balcony_right_optimal_state','off')
and states('sensor.home_weather_dni')|float(0) > 200
)
or
( (states('binary_sensor.aqara_bedroom_balcony_door','on') or states('binary_sensor.aqara_salon_balcony_door_2','on') )
and is_state('binary_sensor.blind_balcony_right_copy_optimal_state','off')
and states('sensor.home_weather_dni')|float(0) > 400
)
)
and states('sensor.wu_holesovice_precipitation_rate')|float(0) < 0.09
and is_state('timer.blind_balcony_right','idle') }}
variables:
blind: "blind_balcony_right"
action: "close"
- id: sun_off_right
platform: template
value_template: >
{{(
(is_state('binary_sensor.blind_balcony_right_optimal_state','on')
or states('sensor.home_weather_dni')|float(300) < 200
)
and
( (states('binary_sensor.aqara_bedroom_balcony_door','off') and states('binary_sensor.aqara_salon_balcony_door_2','off') )
or is_state('binary_sensor.blind_balcony_right_copy_optimal_state','on')
and states('sensor.home_weather_dni')|float(500) <= 400
)
)
and is_state('timer.blind_balcony_right','idle') }}
variables:
blind: "blind_balcony_right"
action: "open"
- id: sun_on_left
platform: template
value_template: >
{{(
(is_state('binary_sensor.blind_balcony_left_optimal_state','off')
and states('sensor.home_weather_dni')|float(0) > 200
)
or
( (states('binary_sensor.aqara_bedroom_balcony_door','on') or states('binary_sensor.aqara_salon_balcony_door_2','on') )
and is_state('binary_sensor.blind_balcony_left_copy_optimal_state','off')
and states('sensor.home_weather_dni')|float(0) > 400
)
)
and states('sensor.wu_holesovice_precipitation_rate')|float(0) < 0.09
and is_state('timer.blind_balcony_left','idle') }}
variables:
blind: "blind_balcony_left"
action: "close"
- id: sun_off_left
platform: template
value_template: >
{{(
(is_state('binary_sensor.blind_balcony_left_optimal_state','on')
or states('sensor.home_weather_dni')|float(300) < 200
)
and
( (states('binary_sensor.aqara_bedroom_balcony_door','off') and states('binary_sensor.aqara_salon_balcony_door_2','off') )
or is_state('binary_sensor.blind_balcony_left_copy_optimal_state','on')
and states('sensor.home_weather_dni')|float(500) <= 400
)
)
and is_state('timer.blind_balcony_left','idle') }}
variables:
blind: "blind_balcony_left"
action: "open"
- id: "rain"
platform: numeric_state
entity_id: sensor.wu_holesovice_precipitation_rate
above: "0.09"
condition:
- or:
- condition: trigger
id: "rain"
- condition: state
entity_id: input_boolean.blind_sunscreener_enable
state: "on"
action:
- choose:
- alias: "It started raining: retract covers"
conditions:
- condition: trigger
id: rain
sequence:
- service: cover.open_cover
target:
entity_id:
- cover.blind_balcony_left
- cover.blind_balcony_right
- service: timer.start
target:
entity_id:
- timer.blind_balcony_right
- timer.blind_balcony_left
data:
duration: "00:05"
default:
- alias: Sun status changed, move covers
service: "cover.{{action}}_cover"
target:
entity_id: "cover.{{blind}}"
- alias: start timers
service: timer.start
target:
entity_id: "timer.{{ blind }}"
data:
duration: "00:30"
mode: parallel