Door sensor notification help

Finally got around to trying this out. I’m getting an error stating “Error rendering data template: UndefinedError: ‘triggered’ is undefined”. I’m pretty sure it’s referring to the trigger on the door1_notifier automation but I can’t figure out the issue.

FYI, no difference if I manually toggle the boolean or let the door sensor toggle it.

- id: door1_open
  alias: 'Door 1 is open'
  trigger:
    platform: state
    entity_id: sensor.door1
    to: 'Open'
    for:
      minutes: 1
  condition: []
  action:
  - service: input_boolean.turn_on
    entity_id: input_boolean.door1_left_open
        
- id: door1_closed
  alias: 'Door 1 is closed'
  trigger:
    platform: state
    entity_id: sensor.door1
    to: 'Closed'
  condition:
    condition: state
    entity_id: input_boolean.door1_left_open
    state: 'on'
  action:
  - service: input_boolean.turn_off
    entity_id: input_boolean.door1_left_open
    
- id: door1_notifier
  alias: 'Door 1 Notifier'
  trigger:
    platform: state
    entity_id: input_boolean.door1_left_open
  condition: []
  action:
  - service: notify.door_notify
    data_template:
      title: "{{ 'Door1 left open!' if triggered.to_state.state == 'on' else 'Door1 closed' }}"
      message: >-
        {% if triggered.to_state.state == 'on' %}
          Door1 has been open since {{states.zwave.ecolink_door_window_sensor.attributes.receivedTS}}.
        {% else %}
          Door1 was closed at {{states.zwave.ecolink_door_window_sensor.attributes.receivedTS}}.
        {% endif %}

That’s my fault. A little too fast on the keyboard …

In data_template, there are two instance of the word triggered. Replace them with trigger.

I’ve corrected the example in my original post (above).

Thank you! All is working normally now. You’d think the “undefined” would have given me a clue. :roll_eyes:

Hi Petro,

I think that if you use a trigger with a “for” to specify a time the condition is only evaluated at the start; and not re-evaluated at the end of the for wait time. So if the condition changes during the “for” it does not impact the trigger.

I have proved this with several automations; and the fix is to duplicate the condition at the start of the action section as this will get evaluated once the trigger is triggered.

I’ve not dug into the code to find this; but presume a “for” sets up a callback timer and the condition is checked then that occurs; and not when the timer expires.

Yes but the trigger still occurs before the condition is checked. Actions occur after the time is hit, the action is in the callback.

My experience is a bit different, please see this thread for an example:

Let me lay it out to you so you understand better:

non-for trigger flow:

trigger -> condition -> action

for trigger flow:

trigger -> condition -> wait for duration -> action

Your experience is exactly what I’m describing. Conditions are always after triggers. No matter what. You for some reason are lumping the for duration into the trigger, that’s not the case.

Thanks, that clarifies it for me.

I was assuming the “wait” was part of the trigger and not evaluated after the condition.

So this means as long as the condition is valid when the trigger is valid we pass to the wait; and if the condition changes during that wait it is irrelevant to the action being performed once wait is passed.

I do assume the trigger continues to be evaluated during the wait so that if it state changes so the wait duration is not met; then it exits and the entire cycle starts again.

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.

  • 08:18 input_boolean set to on
  • 08:22 light turns 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.

  • 08:23 input_boolean set to off then on
  • 08:27 light turns off

A minute later, we toggle the input_boolean in order to restart the 4-minute timer. Four minutes later, the light remains off.

  • 08:28 input_boolean set to off then on
  • 08:32 light remains 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.

1 Like

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 :slight_smile: any help?

thanks