[DEPRECATED] ~~How to make active timers survive a restart~~

so what it is in my case ?
my automation turn sonoff for 30 min.
( you can ser above what i copied. )

Yes, but you use a delay, not a timer. Same effect, but different approach.

o.k
so can you show me what to change in my automation for using it with timer ?

hi,
i think its a little more complicated.
is there other way to acomplished it ?

EDIT

Overlooked to updated both examples with BugFix1.

@allanbell3d Thank you for bringing the oversight to my attention!


Here is how I would handle the situation where I wish to either exclude, or include, a subset of all timers. The choice of which to use depends on how many timers are involved.

Exclusion

Use this version when you only want to EXCLUDE a few timers. For example, you have 10 timers and want all but 3 of them to survive a restart.

  • In the trigger, specify the 7 timers to be preserved.
  • In the template’s timers variable, specify the 3 timers to be excluded.
- alias: 'Active Timers Save'
  trigger:
    platform: state
    entity_id:
    - timer.timer1
    - timer.timer2
    - timer.timer3
    - timer.timer4
    - timer.timer5
    - timer.timer6
    - timer.timer7
  action:
  - service: input_text.set_value
    data_template:
      entity_id: input_text.active_timers
      value: >
        {% set timers = states.timer
                        | rejectattr('object_id', 'in', ['timer8', 'timer9', 'timer10'])
                        | selectattr('state', 'eq', 'active')
                        | list %}
        {% set ns = namespace(timers = '') %}
        {% for t in timers %}
          {% set d = t.attributes.duration.split(':') %}
          {% if 'day' in d[0] %}
            {% set z = d[0].split(', ') %}
            {% set h = ((z[0].split()[0] | int) * 24 + (z[1].split(':')[0] | int)) * 3600 %}
          {% else %}
            {% set h = d[0] | int * 3600 %}
          {% endif %}
          {% set s = h + (d[1]|int*60) + (d[2]|int) %}
          {% set c = ',' if not loop.first else '' %}
          {% set ns.timers = ns.timers ~ '{}{} {}'.format(c, t.object_id, t.last_changed.timestamp()|int + s) %}
        {% endfor %}
        {{ ns.timers }}

Inclusion

Use this version when you only want to INCLUDE a few timers. For example, you have 10 timers but only want 3 of them to survive a restart.

  • In the trigger, specify the 3 timers to be preserved.
  • In the template’s timers variable, specify the same 3 timers.
- alias: 'Active Timers Save'
  trigger:
    platform: state
    entity_id:
    - timer.timer1
    - timer.timer2
    - timer.timer3
  action:
  - service: input_text.set_value
    data_template:
      entity_id: input_text.active_timers
      value: >
        {% set timers = [states.timer.timer1, states.timer.timer2, states.timer.timer3] 
                        | selectattr('state', 'eq', 'active')
                        | list %}
        {% set ns = namespace(timers = '') %}
        {% for t in timers %}
          {% set d = t.attributes.duration.split(':') %}
          {% if 'day' in d[0] %}
            {% set z = d[0].split(', ') %}
            {% set h = ((z[0].split()[0] | int) * 24 + (z[1].split(':')[0] | int)) * 3600 %}
          {% else %}
            {% set h = d[0] | int * 3600 %}
          {% endif %}
          {% set s = h + (d[1]|int*60) + (d[2]|int) %}
          {% set c = ',' if not loop.first else '' %}
          {% set ns.timers = ns.timers ~ '{}{} {}'.format(c, t.object_id, t.last_changed.timestamp()|int + s) %}
        {% endfor %}
        {{ ns.timers }}


So what’s different between these examples and the original one I posted?

The main difference is how the template refers to your timers:

  • In the original example, it simply uses states.timers which represents all of your timers.
  • In the exclusion example, it also uses states.timer but then rejects all the entities you specified in the list: rejectattr('object_id', 'in', ['timer8', 'timer9', 'timer10'])
  • In the inclusion example, it doesn’t use states.timer. It uses the timers you specify in the list: [states.timer.timer1, states.timer.timer2, states.timer.timer3] .
1 Like

Once again thank you very much for the guide and explanation.

As explained by francisp, your automation uses a delay not a timer. I’ve formatted your automation to make it easier to read:

- id: Hot_Water
  alias: 'Hot_Water'
  trigger:
    platform: state
    entity_id: automation.hot_water
    from: 'off'
    to: 'on'
    for: '1'
  action:
    - service: switch.turn_on
      entity_id: switch.sonoff_XXXX
    - delay: '00:30:00'
    - service: switch.turn_off
      entity_id: switch.sonoff_XXXX
    - service: automation.turn_off
      entity_id: automation.hot_water
    - service: automation.turn_off
      entity_id: automation.hot_water

At the very end of the action, it turns off automation.hot_water twice. I don’t understand why the automation is turning itself off and why it attempts to do that twice. However that’s a separate discussion.

If I understand you correctly, you are asking for assistance to convert your automation from using delay to using a timer. If that’s the case then please start a new, separate topic. This one is dedicated to discussing the preservation of timers after a restart.

If you are looking for a replacement for delay but believe timer is too complicated then you don’t have any other options. Those are the two principle ways of waiting for fixed period of time. There is also wait_template but it is designed to wait for a specified condition to become true.

thanks for your help.

I’ve just stumbled upon this post - thanks for sharing, the idea is interesting.

I restore timers individually just because a) there are not many long-duration timers in my system and b) I can control what timers to restore (as some of them just shouldn’t be).

Just want to point out that as you may know, input_text as any state object can hold no more than 255 characters as its state so I presume that if someone creative calls their timers like morning_routine_when_I_am_in_the_mood_to_excersise_for_longer and they have say, 3 or more of them, they’ll be in trouble because some timers’ data won’t make it to the input_boolean or (more likely) HA will refuse to accept a new one and they’ll end up with an old dataset (or empty string).

1 Like

Yes, you’re right. That’s a limitation of this design. At the time I was developing it, someone else was submitting a PR to make the restoration of active timers a native capability. Therefore, I considered my solution a stopgap measure until the PR was released. However, the PR was never released.

I can think of alternatives to using an input_text but they’re more complicated. If you know of a simple substitute, I’d like to hear about it. My impression is very few people have adopted what I’ve shared (and none have reported encountering the limitation) so I’m in no hurry to alter the design, especially if it makes it more complicated.

I see.
Just wanted to highlight the limitation for potential users.
Alternative can be using separate input_texts for each timer. It’s a bit more complicated and requires some naming agreements (and it’s just a concept, I haven’t implemented it).

maybe because doing it in bulk is rarely needed?

I think the reason is more comprehensive: I believe few people are even aware that active timers (and delays) aren’t restored. They discover it empirically, when their long duration timer fails to run to completion because it was interrupted by a restart.

1 Like

it’s says about it in the docs. so maybe it’s even more comprehensive - few people need to restore their timers at all as their timers never run for more than few minutes anyway.
just a guess, nevermind

I agree with you. It includes me as well! Plus my server is an old laptop with several hours of battery life. Fact is, I created this solution as an intellectual exercise and actually have no personal need for it. :slight_smile:

1 Like

Just to add to this discussion, some time ago I set up some ‘guest mode’ timers which I needed to survive a restart. My method is I think similar that which @AhmadK is suggesting. It saves the start time and duration when a timer starts and on a restart checks it should still be active and if so, (re)starts it for the necessary remaining time.

It is almost certainly not perfect but it works flawlessly for me. It was however one of my early HA projects and I haven’t been back to revisit it since, so there may well be a better way to do this.

But in case anyone coming here in future is interested, here is the meat of it. Any constructive criticism is welcome, as I said I wrote it a long time ago.

  - alias: Guest mode schedule 1 check at startup
    trigger:
      - platform: homeassistant
        event: start

    condition:
      - condition: state
        entity_id: input_boolean.guest_mode_schedule_1
        state: 'on'

      - condition: or
        conditions:
          - condition: template
            value_template: "{{ states('input_select.guest_mode_schedule_1_day') == now().strftime('%A') }}"

      - condition: template
        value_template: >
          {% set time_now = as_timestamp(now()) %}
          {% set start_time = states('input_datetime.guest_mode_schedule_1_start_time') %}
          {% set start_time = as_timestamp(now()) | timestamp_custom('%Y-%m-%d') + ' ' + start_time %}
          {% set duration = states('input_number.guest_mode_schedule_1_duration') | int * 60 %}
          {% set end_time = as_timestamp(start_time) + duration %}

          {{ time_now > as_timestamp(start_time) and time_now < end_time }}

    action:
      - service: timer.start
        data_template:
          entity_id: timer.guest_mode_schedule_1_duration
          duration: >
            {% set time_now = as_timestamp(now()) %}
            {% set start_time = states('input_datetime.guest_mode_schedule_1_start_time') %}
            {% set start_time = as_timestamp(now()) | timestamp_custom('%Y-%m-%d') + ' ' + start_time %}
            {% set duration = states('input_number.guest_mode_schedule_1_duration') | int * 60 %}
            {% set end_time = as_timestamp(start_time) + duration %}
            {% set seconds_left = end_time - time_now %}
            {% set hours_left = seconds_left // 3600  %}
            {% set minutes_left = (seconds_left - (hours_left * 3600)) // 60 %}

            {{ '%02i' | format(hours_left) }}:{{ '%02i' | format(minutes_left) }}:01

I have a bunch of timers set, but I also constantly mess with HA configuration and have to restart. The one I care about is automatic door lock after 15 minutes. In 15 minutes that I come back from walking the dog I already manage to restart HA 3 times, so the door never auto-locks (because i turned off the asinine 30 second auto-lock). I mean I can add another automation to lock 15 minutes after HA restart, but i would be nice if timers had an option to save state.

Or maybe a supervisor feature. Timer set at 7:45PM to run at 8PM. It is 8PM now and the automation is turned on, but did not fire. Show alert / Execute automation.

I’ve been using this python script since released and works great. All my timers survive.
And if I want to exclude there’s guidance offered by the contributor up above.

Thanks for the info; it works great! Well… a little too great. My timer wont reset if a timer.cancel is initiated.

I’m basically loading this timer from an input_boolean on/off trigger. OFF should cancel the current timer (timer.XXX) — which is does; but upon the boolean ON command again, it loads up the remaining time that was left before it was cancelled. I know it’s because the data was stored (front these automations and python),

How do i clear those values on timer.cancel?

P.S. even timer.start with its 18000s (5Hrs) value in config doiesnt reset it back to 18000s.

Thanks for any help.

What you are experiencing is explained by the Note in the first post.

Because of how the restoration technique works (i.e how it affects duration) you must always specify the timer’s duration when starting it. Otherwise you run the risk of having the timer use a duration that differs from what you originally specified.