WTH does the delay feature in automations have to reset if HA restarts?

I have a number of automations that include a delay before continuing actions. Some even have a delay for 12+ hours. If HA restarts at any point while that automation is delayed, the automation doesn’t continue.

Doesn’t answer your question, but there are often better ways to write an automation without using delays. Short delays are fine, but IMO long ones should be avoided. If you share one of your automations with a long delay like that, we could see how feasible it would be to rewrite it without a delay.

1 Like

Sure thing! Here’s an example. I’m triggering an action based on a full-day calendar event (no specific time). I want the action to trigger at 3pm.

alias: Robotics club
description: ""
trigger:
  - platform: state
    entity_id: calendar.robotics
    to: "on"
condition: []
action:
  - delay:
      hours: 15
      minutes: 0
      seconds: 0
      milliseconds: 0
  - service: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.robotics_reminder
mode: single

Here’s another example; in this case I’m triggering a reminder to move the laundry over when the washer and/or dryer finishes a cycle. If one finishes, I want to wait up to 1:40 for the other to also finish before sending the reminder:

alias: "Task Due: Laundry"
description: ""
trigger:
  - platform: state
    entity_id: binary_sensor.washer_completed
    to: "on"
    for:
      hours: 0
      minutes: 10
      seconds: 0
  - platform: state
    entity_id: binary_sensor.dryer_completed
    to: "on"
    for:
      hours: 0
      minutes: 10
      seconds: 0
condition: []
action:
  - wait_for_trigger:
      - platform: template
        value_template: "{{ is_state('timer.washer', 'idle') and is_state('timer.dryer', 'idle') }}"
    timeout: "01:40:00"
  - service: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.task_laundry
  - service: notify.notify
    data:
      message: Move the laundry over
  - condition: template
    value_template: "{{ is_state('timer.washer', 'idle') and is_state('timer.dryer', 'idle') }}"
  - if:
      - condition: time
        after: input_datetime.home
        before: input_datetime.sleep
      - condition: state
        entity_id: input_boolean.guests
        state: "off"
      - condition: state
        entity_id: input_boolean.do_not_disturb
        state: "off"
      - condition: state
        entity_id: input_select.mode
        state: Home
    then:
      - service: notify.alexa_media
        data:
          message: Move the laundry over
          target: media_player.primary_alerts
          data:
            type: announce
            method: all
    else: []
mode: single

You could do this:

alias: Robotics club
description: ""
trigger:
  - platform: time
    at: '15:00:00'
condition:
  - condition: state
    entity_id: calendar.robotics
    state: 'on'
action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.robotics_reminder
mode: single

Alternatively, you could ditch the condition and replace the trigger with a calendar trigger:

trigger:
  - platform: calendar
    event: start
    entity_id: calendar.robotics
    offset: +15:00:00

The second option feels weird though, but that might just be because I’ve never used a calendar trigger before.

Here’s an example of a similar automation from my config that might be a good reference.

In short, when either the washer or dryer is done (first trigger), it sends a notification and also turns the respective reminder boolean on. When either reminder boolean is on for 30 minutes (second trigger), it sends a reminder notification and turns off the respective reminder boolean. The reminder booleans can also be turned off when the laundry room is entered (determined by the contact sensor on the door or the motion sensor in the laundry room).

Unfortunately, I don’t think this would survive a restart, so it’s not a solution to the problem. It might be a good reference for other ideas though at least.

I actually tried your solution before coming up with what I have… my reasoning:

  • Full-day calendar events were only considered “on” at midnight
  • I couldn’t get the offset function to work for calendar triggers, but I could get it to trigger at the event time (midnight)

Ah, that is unfortunate…I guess I figured all-day events would be on, well, all day :grinning_face_with_smiling_eyes:

In that case, you could turn a boolean on when the calendar entity goes to on and then use my automation with the boolean instead of the calendar. You would then just need to turn the boolean off with something else.

2 Likes

Brilliant! I’m using booleans all over the place for similar reasons. Don’t know why I didn’t think of that!

You can do this with timer helpers. For example on the robotics reminder automation:

  • Create a timer.robotics_reminder_delay helper set for 15 hours, and check the restore box.
  • Add a 2nd trigger to the automation for when the timer expires, and add a trigger ID called “timer_expired”
  • Add a trigger ID to the existing calendar event trigger called “calendar_event”
  • For the actions, if the trigger ID was the calendar_event then start the timer.
  • if the trigger ID was the timer_expired then fire the reminder
2 Likes

Nice! Just noticed that, as of 6 months ago, timers can survive HA restarts by setting restore to True.

2 Likes

So a better WTH would be why doesn’t a delay (or any “wait_x” function in general) have the same “restore” functionality as a timer?

2 Likes

I believe it’s because a timer is an entity, that only gained the ability to be restored this year, whereas delay, wait_template, etc are scripting statements within an automation entity and automations that are in-progress are not resumed after a restart/reload.

Restoring a timer simply involves computing the remaining duration. In contrast, restoring an automation would require keeping track of which statement was being executed when the automation was stopped. Then it must determine what remains for that statement to do. For example, if it’s a repeat - count how many more iterations remain. If it’s a delay then how much longer to wait. It gets more complicated for other statements that are monitoring other entities (wait_template) because, after a reload/restart, those other entities may have already changed state.

The situation would be mitigated by the introduction of incremental reloading. Only a new/modified automation would be reloaded and all other in-progress automations would remain undisturbed. Only a full reload, or a restart, would stop all in-progress automations and reload them. There’s an existing Feature Request for this and at least one other WTH.

yeah, I guess adding “wait_x” was incorrect tho I think there might be some way to increase reliability of some of those, too.

However, it is still valid for delays and “for:”.

I even offered a possible solution in one thread a while back (not that I can find it now). It was basically set a datetime instance for the delay, etc in the background (I foresee it being similar to how “for:” works but it will be retained thru restarts) and then use that datetime as a trigger to resume the automation. Of course the automation step would have to be retained thru restarts as well so it would know which automations to auto restart and to know where it left off.

Then you could use that same retained step data to allow for a “wait_x” step to complete the remainder of it’s automation.

it may not be easy but at least it’s starting the discussion (again) on a possible solution to this long running issue.

Currently, a reload doesn’t concern itself with any aspect of an in-progress automation. It effectively “wipes the slate clean” and starts over from scratch.

Only when it’s capable of handling in-progress automations differently, such as with incremental reloading, will it be able to preserve for, delay, etc.

Right.

That’s what I’m suggesting changing.

I’m not even sure it would have to rely on incremental reloading either.

we already have automation traces so there is already some means to keep track of where the automation currently is in it’s step. Just save that data to a non-volatile location in .storage (it might already be since we can look at traces after restarts) and then use that data after automation reloads etc to start the automation back up at the last recorded step was at reload.

Then once the automation completes flush the automation in-progress data (including any datetime instance data for delays etc) for that automation until the automation is triggered next.

The timer solution posted by @mekaneck works really well. I used to use a delay to make lights turn off at a random time. I switched to the timer solution and now my lights don’t stay on if HA is restarted in the delay window. So the automation contains

trigger:
  - platform: time
    at: "22:30:00"
    id: time-off
  - platform: event
    event_type: timer.finished
    event_data:
      entity_id: timer.light_timer
    id: timer-finished
  .
  .
  .
action:
  - choose:
      - conditions:
          - condition: trigger
      - conditions:
          - condition: trigger
            id: time-off
        sequence:
          - service: timer.start
            data:
              duration: "{{ range(60, 1800) | random }}"
            target:
              entity_id: timer.light_timer
      - conditions:
          - condition: trigger
            id: timer-finished
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.bedroom
  .
  .
  .
1 Like

I may be wrong but I believe the reason we can see traces after a restart/reload is because they are created for completed automations. I don’t recall seeing partial traces for interrupted automations.

Nevertheless, even if partial traces are available, to help resume interrupted automations, it may work for resuming delay and for (although I don’t think their remaining duration is currently recorded in a trace but could be added) it still presents a “logic problem” for statements that monitor other entities. A restart (not a reload) is likely to change entity states (so a wait_template or wait_for_trigger will miss that state-change because automations are loaded after entities).

Having said that, I think your idea of using the trace, for resuming the interrupted automation, stands a far better chance of implementation than inventing incremental reloading.

2 Likes

Is a negative chance worse than a zero chance? :laughing:

That’s how much traction my last attempt at a similar suggestion to fix this got.

I think the challenge is to convince the development team (or a volunteer developer) that the current behavior is a significant weakness in the product.

All it takes to abort an in-progress automation is a single Reload Automations … which happens automatically, and invisibly, the instant a user saves an automation with the Automation Editor. Effectively, the “Save” button is a “Save … and kill all running automations”.

1 Like