Yes. But you can add a condition in the action section to have it check when the action fires.
Yep, but only the triggers state affects this, not the condition.
Yes. But you can add a condition in the action section to have it check when the action fires.
Yep, but only the triggers state affects this, not the condition.
@jwelter
Can you amend your linked post? Either explain the original theory was incorrect (about conditions allegedly being evaluated before triggers) and/or include a link to Petro’s excellent explanation above.
The reason for my suggestion is because I’ve had this conversation in the past, with finity, and that linked post was supplied as evidence of ‘spooky behavior’. It didn’t take me long to refute it (I even included the results of experiments disproving the alleged behavior) but it would be best if others didn’t continue to use it to support this mistaken theory.
Yes, but the issue is this behavior is not documented anywhere in the docs.
They clearly state that a condition applies after the trigger. But what none of the documents states that if you use a “for” as part of a trigger it is not assumed to be part of the trigger.
I know many people who have spent much time struggling on this…
It may be a foreign concept to you but it’s pretty normal in software. The trigger occurs, the condition is there to basically ask “should i fire this trigger or not”. If the trigger is getting fired… then perform the duration (or action if there is no for loop).
I’ve carried out the following experiment twice with identical results. I encourage others to try it for themselves.
The automation uses both for
and condition
:
Trigger: the input_boolean must be on
for 4 minutes (a 4-minute timer).
Condition: only within a specific 10-minute time span (08:20 - 08:30).
Action: toggle a light.
- alias: 'for and condition tester'
trigger:
platform: state
entity_id: input_boolean.toggler
for: '00:04:00'
condition:
condition: time
after: '08:20:00'
before: '08:30:00'
action:
- service: light.toggle
entity_id: light.family
To understand the interaction between for
and condition
it is instructive to observe the automation’s behavior at the condition’s boundaries (see ‘period A’ and ‘period B’ in the chart below). In other words, how does it behave when we trigger it just before 08:20 and 08:30?
The test begins at 08:18 which means the 4-minute period spans the start of the condition (at 08:20). Four minutes later, the light changes state to on
.
on
on
A minute later, we toggle the input_boolean in order to restart the 4-minute timer. Four minutes later, the light changes state to off
.
off
then on
off
A minute later, we toggle the input_boolean in order to restart the 4-minute timer. Four minutes later, the light remains off
.
off
then on
off
input_boolean: off/on off/on off/on
light: off on | off | off
condition: | 08:20===|=======|===========|=======|===08:30 |
|-----|-----|-------|-----------|-------|-----|-----|
time: 08:18 08:22 08:23 08:27 08:28 08:32
periods: |=====A=====| |=====B=====|
For period A, spanning 08:20, the light is turned on
at 08:22 because the end of period A is within the condition’s time span (08:20 - 08:30).
For period B, spanning 08:30, the light remains off
at 08:32 because the end of period B is not within the condition’s time span (08:20 - 08:30).
The results of this experiment show the condition
is evaluated after for
's time period has expired.
So after fully following the path of code for a trigger, I agree with @123’s findings. I’m not sure how @jwelter’s scenario even occurred.
Anyways if you are interested in the code behind this:
This trigger method
async def async_trigger(self, variables, skip_condition=False,
context=None):
"""Trigger automation.
This method is a coroutine.
"""
if not skip_condition and not self._cond_func(variables):
return
# Create a new context referring to the old context.
parent_id = None if context is None else context.id
trigger_context = Context(parent_id=parent_id)
self.async_set_context(trigger_context)
self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, {
ATTR_NAME: self._name,
ATTR_ENTITY_ID: self.entity_id,
}, context=trigger_context)
await self._async_action(self.entity_id, variables, trigger_context)
self._last_triggered = utcnow()
await self.async_update_ha_state()
gets passed into this method (Through a series of other methods that I’m going to skip)
def async_track_point_in_utc_time(hass, action, point_in_time):
"""Add a listener that fires once after a specific point in UTC time."""
# Ensure point_in_time is UTC
point_in_time = dt_util.as_utc(point_in_time)
@callback
def point_in_time_listener(event):
"""Listen for matching time_changed events."""
now = event.data[ATTR_NOW]
if now < point_in_time or hasattr(point_in_time_listener, 'run'):
return
# Set variable so that we will never run twice.
# Because the event bus might have to wait till a thread comes
# available to execute this listener it might occur that the
# listener gets lined up twice to be executed. This will make
# sure the second time it does nothing.
point_in_time_listener.run = True
async_unsub()
hass.async_run_job(action, now)
async_unsub = hass.bus.async_listen(EVENT_TIME_CHANGED,
point_in_time_listener)
return async_unsub
What should be noted in async_track_point_in_utc_time is that the action
in hass.async_run_job(action, now)
is executed after the point in time listener. And the action contains the conditional check.
Basically, this callback doesn’t return until the time limit has finished. When that occurs the top method is executed and the first thing checked is the condition.
So all in all, my post is not correct. The correct flow is:
non-for trigger flow:
trigger -> condition -> action
for trigger flow:
trigger -> wait for duration -> condition -> action
@petro thanks for digging in; your revised logic is what makes sense to me, that the “for” is part of the trigger itself.
What doesn’t make sense is why my automation needed the condition moved to the action stanza to make it work as desired…
I will poke around with that a bit more to see if it still behaves that way or it was a “issue” with the version I was running at the time.
Well, I looked through just about everything. At one point I thought that only template conditions were resolved at the beginning of the trigger (all else were resolved at the end), but that proved wrong. I honestly have no idea how your situation occurred.
As a system engineer that has been involved in the development of one of the most complex fire-and-forget battle control systems I see it more simplistic in nature.
The yaml defines triggers, conditions, and actions.
Anything in the trigger stanza is that; part of the trigger. If for is included it is part of the trigger. “I want to trigger if the light is on for 5 minutes…” is the intention; not “If the light turns on wait 5 minutes to trigger” as these are significantly different triggers.
As the condition is stated to be considered after the trigger if we have a condition “it’s daylight” and we combined with the above we expect the automation to fire “when the light has been turned on for 5 minutes and it’s daylight”…
I would not expect “if the light is turned on and it’s daylight then trigger after 5 minutes”
These little details are what causes 737MAX’s to be grounded and other fun stuff. We need to be explicit in how these things are interpreted.
Yes. The for
qualifies the triggering event. In the example I provided above, the trigger is not simply the input_boolean turning on
but it must remain that way for
at least 4 minutes.
Effectively, the input_boolean starts a (hidden) 4-minute timer. When the timer expires, the condition is evaluated and, if true, proceeds to execute the action.
FWIW, in the home automation software I use (Premise), to replicate this behavior I would have to define an explicit 4-minute timer. The input_boolean turning to on
would start the timer. Upon the timer’s expiration it would execute a script.
I could do exactly the same thing in Home Assistant, with an explicit timer
, or by just adding for: '00:04:00'
to the trigger section. That’s one helluva nice convenience!
Well this has been in interesting read but I’m not really enjoying the solution to this question. Using the input boolean and 2 automation gets the job done and is easy enough for a single door but when dealing with a bunch, it get a little unwieldy. Currently I have this for a set of doors and it works great, one automation that covers as many doors/windows as I’d like to look at.
- alias: Outside Door Left Opened
trigger:
- platform: state
entity_id: binary_sensor.side_door, binary_sensor.front_door, binary_sensor.patio_slider, binary_sensor.deck_slider, binary_sensor.stair_door
to: 'on'
for:
minutes: 3
action:
service: notify.ios_brads_iphone
data_template:
message: 'The {{ trigger.to_state.name }} was left opened! - {{now().strftime("%H:%M:%S on %m/%d/%y")}}'
Is there a way to cover this using the boolean too with a template for the door shutting? One for open and one for closed is fine if they can work for all.
Also, I realize that the condition/trigger contradiction issue is there but could you use the last changed attribute as a trigger (vs for) and then door closed as a condition or would that be the same thing?
What about an automation that triggers another automation where the door being closed is the condition?
Automation 1:
Trigger: door open for 5 min
Action 1: notify which door was left open
Action 2: trigger automation 2
Automation 2:
Trigger: from automation 1
Condition: Door closed
Action: notify which door was closed
OR what about adding multiple actions with a wait_template and conditions between them?
Trigger: door open for 5 min
Action 1: notify which door was left open
Action 2: wait_template triggering door is closed
Action 3: notify which door was closed
Sorry in advance if any of this is just stupid. I’m trying to learn and lean out my code where I can and avoid repeats of the same things.
Hi everyone , i was trying to set up an automation on some door sensor which activate alexa tts
All is working , apart from it doesn’t recognize i am at home and it shoudn’t start talking
`- alias: Door open
initial_state: 'on'
trigger:
- platform: state
entity_id: binary_sensor.0x00158d00045221bb_contact_2
condition:
condition: template
value_template: '{{states(''person.Umberto'') != ''home''}}'
action:
- service: media_player.volume_set
entity_id: media_player.livingroom , media_player.bedroom
data:
volume_level: '1.0'
- delay:
seconds: 1
- service: notify.alexa_media
data:
target:
- media_player.livingroom
- media_player.bedroom
data:
type: announce
method: tts
message: "Breach , Breach , police has been called"
- service: notify.mobile_app_umby
data:
message: Motion alert
- delay:
seconds: 1
- service: media_player.volume_set
entity_id: media_player.livingroom , media_player.bedroom
data:
volume_level: '0.3' `
My idea is to have a notification on the phone , turn on the lights , alexa saying somethin , and an alarm sound any help?
thanks
In Home Assistant, all entity names are in lowercase (always). Therefore this is invalid:
person.Umberto
whereas this valid:
person.umberto
Thanks so much… i m sorry to bother , there is another point if you can help me , in order to be safe i want it also to work if i am at home but between 00:00 and 06:30 , in order to be covered also during the night.
How can i say If i am not home or if i am at home b ut is beetween the time selected ?
- alias: Window open
initial_state: 'on'
trigger:
- platform: state
entity_id: binary_sensor.0x00158d00045221bb_contact_2
condition:
condition: template
value_template: '{{states(''person.umberto'') != ''home''}}'
action:
i know i can use
condition:
- condition: time
after: 23:30:0
before: '06:30:00'
but i do not know to add the condition for me being home at that time.
Thanks
condition:
- condition: and
conditions:
- condition: time
after: 23:30:00
before: 06:30:00
- condition: state
entity_id: person.umberto
state: home
Ooops… didn’t realize this was 2 yrs old …