Building HA restart resistant Automations

Hi All

I’ve written a couple of automations that use timeouts such as a heating control automation that turns off the heating if all family members are over a certain distance from home and turns it back on when we return that uses an 18 hour timeout to turn it on anyway as a failsafe, and a blind automation that opens the blind just after sunrise and then uses a wait for trigger action to close again at sunset, or if the outside temperature is 21c or above (to help keep the house cooler).

alias: Hall Blind Manager
description: ''
trigger:
  - platform: sun
    event: sunrise
    offset: '00:30:00'
condition: []
action:
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: open
  - wait_for_trigger:
      - platform: sun
        event: sunset
        offset: '00:10:00'
      - platform: numeric_state
        entity_id: weather.home
        attribute: temperature
        above: '21'
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: close
mode: single

The issue is that as I’m learning and experimenting with HA I’m often restarting during the day which interrupts any automation with a wait for trigger currently running and it obviously doesn’t complete. Other than writing individual automations separating out each step (one to open the blind another to close it etc), is there a way to make single multiple stage automations like this more tolerant of a restart?

This post may help

1 Like

Also - look in to Schedy which runs in AppDaemon and is much better for this stuff, especially heating stuff.

https://hass-apps.readthedocs.io/en/stable/apps/schedy/

Here is an example of stuff I use Schedy to do with my heating:

    weather:
      - x: "Add(+0.125) if is_on('binary_sensor.spring_season') else Next()"
      - x: "Add(-0.25) if is_on('binary_sensor.summer_season') else Next()"
      - x: "Add(+0.125) if is_on('binary_sensor.autumn_season') else Next()"
      - x: "Add(+0.250) if is_on('binary_sensor.winter_season') else Next()"
      - x: "Add(-0.1) if is_on('input_boolean.heating_eco_mode') else Next()"
      - x: "Add(-0.1) if is_off('input_boolean.home_state_home') else Next()"
      - x: "Add(+0.1) if float(state('sensor.cc_30_min_gust')) > 15 and (state('sensor.wind_dir_cardinal')[:1] == 'N' or state('sensor.wind_dir_cardinal')[:1] == 'E') else Next()"
#      - x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.cc_rain_rate')) > 0) else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < -9.9 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < -4.9 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 0.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 5.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 10.1 else Next()"
      - x: "Add(+0.025) if float(state('sensor.cc_outside_temperature')) < 14.1 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 30.1 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 24.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 22.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 21.9 else Next()"
      - x: "Add(-0.2) if float(state('sensor.cc_outside_temperature')) > 20.9 else Next()"
      - x: "Add(-0.1) if float(state('sensor.cc_outside_temperature')) > 16.8 else Next()"
      - x: "Add(+0.025) if (float(state('sensor.cc_outside_temperature')) < 15.1 and float(state('sensor.openweathermap_forecast_precipitation_probability')) > 75) else Next()"
      - x: "Postprocess(lambda result: round(result, 1))"

This adjusts the final temperature based on external weather conditions

And then in the actual rules for the house (lower) -

      friendly_name: House
      schedule:
        - v: 18.5
          rules:
            - x: "IncludeSchedule(schedule_snippets['weather'])"
            - x: "Add(-0.2) if is_on('input_boolean.heating_night_mode') else Next()"
            - x: "Add(+0.1) if is_on('binary_sensor.livingroom_presence') else Next()"
            - x: "Add(+0.1) if (float(state('sensor.livingroom_temperature_temperature')) < 17.8 and is_on('binary_sensor.livingroom_presence')) else Next()"
            - { v: 16.5, start: "23:00", end: "07:00" }
            - { v: 17.8, start: "07:00", end: "09:00" }
            - { v: 17.9, start: "09:00", end: "10:00" }
            - { v: 18.0, start: "10:00", end: "10:30" }
            - { v: 18.1, start: "10:30", end: "11:00" }
            - { v: 18.2, start: "11:30", end: "12:00" }
            - { v: 18.3, start: "12:00", end: "12:30" }
            - { v: 18.4, start: "12:30", end: "13:00" }
            - { v: 18.5, start: "13:00", end: "14:00" }
            - { v: 18.6, start: "14:00", end: "15:00" }
            - { v: 18.7, start: "15:00", end: "16:00" }
            - { v: 18.8, start: "16:00", end: "18:00" }
            - { v: 18.9, start: "18:00", end: "20:00" }
            - { v: 18.8, start: "20:00", end: "21:00" }
            - { v: 18.7, start: "21:00", end: "22:00" }
            - { v: 18.6, start: "22:00", end: "23:00" }

I can add and subtract from the final temperature based on whether the house is occupied

1 Like

I’m aware you asked for a single automation, but these two would do it. I always do this. There might be errors.

In the next post there’s an example in one automation. Not sure it’ll work though.

alias: Hall Blind Manager Open
trigger:
  - platform: sun
    event: sunrise
    offset: '00:30:00'
action:
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: open
  - service: homeassistant.turn_on
    entity_id: automation.hall_blind_manager_close

alias: Hall Blind Manager Close
trigger:
  - platform: sun
    event: sunset
    offset: '00:10:00'
  - platform: numeric_state
    entity_id: weather.home
    attribute: temperature
    above: '21'
action:
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: close
  - service: homeassistant.turn_off
    entity_id: automation.hall_blind_manager_close
1 Like

This would do it in a single automation I believe. I can’t test it.

alias: Hall Blind Manager Open
trigger:
- platform: sun
  event: sunrise
  offset: '00:30:00'
- platform: sun
  event: sunset
  offset: '00:10:00'
- platform: numeric_state
  entity_id: weather.home
  attribute: temperature
  above: '21'
action:
- service: "cover.{{ 'open_cover' if is_state('sun.sun', 'above_horizon') and trigger.entity_id != 'weather.home' else 'close_cover'}}"
  entity_id: cover.hall_blind
1 Like

You could use the datetime helper as a trigger. Just timestamp the time with your 18hr offset and it will be unaffected by home assistant restart.

I’ve written a blueprint for this called ‘Persistent Timer’ It’s not very elegant but you could take a look at the code and alter it for your needs. :+1:

1 Like

Thanks guys, I’ll definitely use some of the suggestions to help improve the blind control automation although it was as much a philosophical question on general best practices dealing with restarts as how to resolve it for this automation in particular. Schedy looks interesting so will have a look into that. I did find the persistent timer threads on here which look good but I wasn’t sure how you’d use a timer when you want a failsafe to timeout an action whilst waiting for another trigger action (eg people returning home) as opposed to waiting a set time then performing an action, maybe via a choice action with one following the timer and the other the trigger?

Put an example in YAML about some so we can work it out.

As far as I understand, I’d do the two automations, so you avoid crapping it through a restart. Second automation should have the timeout as a trigger and the other trigger. In the action section, you will usually want either to make the automation turn itself off or set some strong condition, so it either triggers or “timeouts”. I’d personally make the automation turn itself off, it’s flawless.

It’s the best way I found. Hopefully somebody can help us fit it in one single automation. But both in NR and YAML, this is the only way I found to avoid it.

1 Like

Cheers yep, here’s my “heating off when everyone is away” automation. I’ve added a couple of comments to help it make sense

alias: Geniushub - Heating off if everyone is away
description: ''
trigger:
  - platform: numeric_state
    entity_id: proximity.proximity_cr  #proximity sensor reporting minimum distance from home of me and wife
    above: '13000'
    for:
      hours: 0
      minutes: 2
      seconds: 0
      mliseconds: 0
condition:        # Checks my daughter is not at home (likely at school a few miles away so set to a lower distance)
  - condition: numeric_state
    entity_id: proximity.j_home
    above: '3000'
action:
  - service: climate.turn_off
    target:
      entity_id:
        - climate.kitchen
        - climate.living_room
        - climate.front_room
        - climate.hall
        - climate.study
        - climate.master_bedroom
        - climate.j_s_bedroom_2
  - wait_for_trigger:
      - platform: numeric_state
        entity_id: proximity.proximity_cr
        below: '13000'
      - platform: numeric_state
        entity_id: proximity.j_home
        below: '1500'
    timeout: '18:00:00'    # Timeout defaults to turning heating back on if no other triggers occur
  - service: climate.turn_on
    target:
      entity_id:
        - climate.kitchen
        - climate.living_room
        - climate.front_room
        - climate.hall
        - climate.study
        - climate.master_bedroom
        - climate.j_s_bedroom_2
mode: single

By the way, I think I worked a simple way to modify my own blind automation routine to make it robust and that was to simply set a second trigger for an HA start event, and then put a condition in the automation so it only runs between sunrise and sunset.

alias: Hallway - Blind Manager
description: ''
trigger:
  - platform: sun
    event: sunrise
    offset: '00:05:00'
  - platform: homeassistant
    event: start
condition:
  - condition: sun
    before: sunset
    after: sunrise
action:
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: open
  - wait_for_trigger:
      - platform: sun
        event: sunset
        offset: '-00:05:00'
      - platform: numeric_state
        entity_id: weather.home
        attribute: temperature
        above: '21'
  - device_id: <blind>
    domain: cover
    entity_id: cover.hall_blind
    type: close
mode: single

Sounds like a good idea :smiley:

Instead of the timeout, could you use a certain time of the day you know it should’ve already timed out, like 5 am or something like that? if you really need an exactly 18 hours long timeout, the datetime helper @AJStubbsy suggested wouldn’t be a bad idea.

This would be very easy in NR btw. You later YAML split into two automations.

alias: Heating off if everyone is away
trigger:
  - platform: numeric_state
    entity_id: proximity.proximity_cr 
    above: '13000'
    for: '00:02:00'
condition:
  - condition: numeric_state
    entity_id: proximity.j_home
    above: '3000'
action:
  - service: climate.turn_off
    target:
      entity_id:
        - climate.kitchen
        - climate.living_room
        - climate.front_room
        - climate.hall
        - climate.study
        - climate.master_bedroom
        - climate.j_s_bedroom_2
  - service: homeassistant.turn_on
    entity_id: automation.heating_on_if_everyone_is_home

alias: Heating on if everyone is home
trigger:
  - platform: numeric_state
    entity_id: proximity.proximity_cr 
    below: '13000'
  - platform: numeric_state
    entity_id: proximity.j_home
    below: '1500'
  - platform: time
    at: '05:00'
action:
  - service: climate.turn_on
    target:
      entity_id:
        - climate.kitchen
        - climate.living_room
        - climate.front_room
        - climate.hall
        - climate.study
        - climate.master_bedroom
        - climate.j_s_bedroom_2
  - service: homeassistant.turn_off
    entity_id: automation.heating_on_if_everyone_is_home
1 Like

This probably would do it in one automation. The template checks which was the last time the automation triggered. If 18 hours have passed since it last triggered, then it should turn on the climates again. :crossed_fingers:

alias: Heating off if everyone is away
trigger:
  - platform: numeric_state
    entity_id: proximity.proximity_cr 
    above: '13000'
    for: '00:02:00'
  - platform: numeric_state
    entity_id: proximity.proximity_cr 
    below: '13000'
  - platform: numeric_state
    entity_id: proximity.j_home
    above: '3000'
  - platform: numeric_state
    entity_id: proximity.j_home
    below: '1500'
  - platform: template
    value_template: "{{(as_timestamp(now())|int - as_timestamp(state_attr('automation.NAME_OF_THIS_AUTOMATION', 'last_triggered'))|int) >= 64800 }}"
action:
  - service: >
      {% set timeout = (as_timestamp(now())|int - as_timestamp(state_attr('automation.NAME_OF_THIS_AUTOMATION', 'last_triggered'))|int) %}
      {% if states('proximity.proximity_cr')|int > 13000 and states('proximity.j_home')|int > 3000%}
        homeassistant.turn_off
      {% elif timeout >= 64800 or states('proximity.proximity_cr')|int < 13000 or states('proximity.j_home')|int < 1500%}
        homeassistant.turn_on
      {% endif %}
    target:
      entity_id:
        - climate.kitchen
        - climate.living_room
        - climate.front_room
        - climate.hall
        - climate.study
        - climate.master_bedroom
        - climate.j_s_bedroom_2
1 Like

For future reference:

It starts with the simplest way to achieve the goal and progressively improves it until it’s able to handle a restart correctly.

2 Likes

Thanks both and especially obaldius for taking the time to rewrite my automations, I’m going to have a proper read through tomorrow when I’m a little less tired to understand exaxtly what they’re doing, HA is certainly a steep learning curve haha.

One thing I’m taking from this already though, is a need to sometimes stop thinking so linearly with my automations. I’m in the habit of structuring automations where the first event starts things off then the rest of the automation waits for subsequent events to react to. Some of the suggestions highlight that sometimes its better to trigger on all the events (sunrise, sunset, away from home, near home etc) and then structure the actions to be intelligent enough to work out what action should occur. Thanks again.

1 Like

If you wish, you can perform arithmetic directly with datetime objects as opposed to converting them to timestamps first.

In this case, it reduces this:

{{(as_timestamp(now())|int - as_timestamp(state_attr('automation.NAME_OF_THIS_AUTOMATION', 'last_triggered'))|int) >= 64800 }}

to this:

{{ now() - state_attr('automation.NAME_OF_THIS_AUTOMATION', 'last_triggered') >= timedelta(hours=18) }}
2 Likes

always check @123 suggestions. You won’t find better advice than his.

1 Like

Yep, sorry @123 my “and especially obaldius” comment wasn’t intended as a slight on you as I certainly appreciate everyone’s contributions, just acknowledging the time that obaldius had obviously spent rejigging my automations :+1:

1 Like