How to not miss state changes in an automation when waiting for more state triggers?

I’m building an automation for a Ring-to-Open mode for my front door. When RTO is activated, my Nuki Smart Lock unlocks the door, my Ring Chime is snoozed, and then I wait for a ding event on my Ring Doorbell within three minutes. When pressed, my Shelly triggers the door buzzer so I can push the door open.

Waiting for the doorbell ding is a bit complicated, because the “ding state” sometimes remains “on” from a previous ding, even if the doorbell has stopped recording. I solve this by defining a start_ts variable with the current timestamp at the beginning of the script, and then use a template trigger that checks both the ding state being “on” and the lastDing attribute being greater or equal to start_ts.

That all works well in theory, the problem is timing. Sometimes I press the doorbell while the Nuki unlock service call is still ongoing. In that case the template is already true when waiting for the trigger, so I miss it. I could solve this by adding a condition check first. However, that still suffers from a race condition, because two subsequent steps (the condition check and the template trigger) are not executed atomically/point-in-time wise wrt. event bus: the condition could evaluate to false, then the template changes to true, and only then does the automation start waiting for the trigger, so I’d still miss the false->true edge.

I’ve contemplated waiting for the doorbell ring in a parallel branch, but that would make the script orders of magnitude more complicated, because I have to replicate stop conditions (door opened, RTO disabled, home unoccupied), cleanup steps (unsnooze the chime) to those branches.

What would solve this quite elegantly would be a setting for template triggers to also trigger if the template evaluates to true at the beginning. By checking for this after subscribing to state changes, a race condition would be effectively eliminated. PyScript supports this via state_check_now, and right now I’m most likely eyeing rewriting the script in PyScript, but I’d usually like to reserve PyScript for more complex automations.

I understand that triggers in HA are always supposed to be event-based, but then I wonder how one is supposed to use a “wait for trigger/template” action in an automation without exposing oneself to race conditions? This surely can’t be intended.

The RTO script can be found here: RTO · GitHub

I have to say it’s very hard to follow what you are describing without seeing it in action. But I think I might understand.

If I do understand correctly then you might try to use a “choose:” action based on whether the current state of the desired wait_for_trigger entities are on or off at the beginning. It would be a branching logic but sometimes logic is unfortunately complex and there is no other choice. And depending on how the logic flows itmighth be possible to rejoin the branches later in the script based on some condition(s).

Like a Wait for a template?

… a “ding state” that doesn’t remain “on” from a previous ding.


Create a Template Binary Sensor that monitors the doorbell’s state. Use the auto_off option. Have it set the Template Binary Sensor to off after whatever amount of time you think is appropriate.

Reference the Template Binary Sensor in your script as opposed to the doorbell sensor directly.

To echo a bit what @finity said, it sounds like you are in a similar boat as I was for my Zigbee shades (ThirdReality, really slick). The issue was that they would stick in “opening” or “closing” when you don’t open them all the way, so to combat that I did an automation that sounds like what you might need to force the state by stopping the cover:

alias: Side East Shade Force Stop
description: >-
  The shades get stuck on opening or closing, this makes sure it is stopped and
  sets to open or close or partial, whatever the case may be
trigger:
  - platform: state
    entity_id:
      - cover.side_window_east_cover
    to: opening
    for:
      hours: 0
      minutes: 1
      seconds: 30
  - platform: state
    entity_id:
      - cover.side_window_east_cover
    to: closing
    for:
      hours: 0
      minutes: 1
      seconds: 30
condition: []
action:
  - service: cover.stop_cover
    data: {}
    target:
      entity_id: cover.side_window_east_cover
mode: single

Since I know transitioning from open to closed takes far less than 90 seconds, I just set this up that if it’s stuck on “opening” or “closing” to force a stop, which then will stop it and then it reads I want, as “open”, “closed” or “x%”.