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

Good detective work!

I’ve never timed the interval used for storing state values but 20 minutes sounds about right.

That makes two of us. It’s supposed to do a final commit to core.restore_state during the shutdown process.

Let’s say that there’s no final commit operation. That means the only active timers that will be affected are the ones that started during the period between the last periodic commit and shutdown. In other words, worst case, during the last 20 minutes of operation before shutdown.

Found an open issue that at HA github https://github.com/home-assistant/core/issues/36814#issuecomment-646804744. At least now I know I’m not the only one =) and at the same time Frederic look into the code confirm 15 min interval between updates for the core.restore_state file

1 Like

Well, that confirms my theory about the lack of a final commit. That’s a bad bug. I’m surprised it hasn’t been prioritized for correction (now over one month old). No one has even been assigned to investigate it yet.

First of all thanks Taras for the great work!!

I implemented this after someone suggested it in Discord and was very happy with the solution but I didn’t read this post and did not realize the limitation. I just found out from seeing some errors in the log file:

 [homeassistant.components.input_text] Invalid value: (length range 0 - 100)

My use case is 40 timers that need to run for 3 days each.

Any suggestions about how this could be implemented?

Thanks in advance!

Ok, so for anybody that might have the same problem, this is the work around I have implemented:

  • Create a bunch of input_text to store the different timers.
  • Using the include variant of the Active timers Save automation I repeat the service call as many times as needed referencing each time the different input_text we just created and its assigned timers in the include section.
  • In the python script I do the same, repeat the whole code for each input_text.

This seems to work, its not a very elegant solution but its the best I could come up with.

Update: Actually I have found a problem using the inclusion version of the automation, it was not updated to work with timers over 24 hrs, might want to add this to the first post:

- 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 }}
1 Like

Yes, I’m currently exploring the use of an input_select to store timer information. Each option in the input_select stores one timer’s information. An input_select has a set_options service but, for this application, an automation cannot be used and it will have to be a python_script.

The reason is because we have to dynamically build a list for set_options. A Jinja2 template cannot generate a list, it always generates a string. A python_script doesn’t have this limitation.

Unfortunately, there is no service call to retrieve an input_select’s options as a list UPDATE:
There’s no service call but all the options are indeed available as a list in the input_select’s attributes! :man_facepalming: That makes it very easy to iterate through all the options and requires none of the techniques I mentioned below.
(which would make it easy to iterate through its options). In addition, there’s no way to request the number of options. The best that it offers is a next_option service to step through the options. However, without knowing how many options exist, we can step through all of them over and over.

The simplest trick is to have the last option serve as a marker. In other words, the last option is never timer information but something like '###'. If one encounters it while stepping through the options, it indicates we have finished iterating through all available options.

I’m currently experimenting with this approach. I can easily create an input_select with 50 options. That means it is capable of storing the information for 50 timers (and up to 255 characters for each one). There may be an upper limit on the number of options supported by an input_select but I doubt anyone will ever encounter it.

Ha! That was a giant of waste of time!

I created the means to store time information in an input_select, and to retrieve it, only to discover something I should have known all along: an input_select’s options are initialized at startup.

No matter what options you set, on startup they revert to whatever is specified in the input_select’s configuration. Plus, you cannot configure an input_select without specifying options.

It appears the workaround you implemented (using multiple input_text entities) will have to suffice. I don’t intend to change the original code (you’re the first to encounter the 255 character limitation). Should anyone else be in the same position they have your workaround available.

Anyway, the exercise didn’t provide the result I wanted but it did serve to improve by knowledge of python.

Are you referring to what is listed in the first post under BugFix 1, 2020-0607?

Thanks for looking in to it Taras, apreciate your work!

Regarding your question: yes, I´m referring to that. I copied the code for the include version of the automation as its posted and that doesn’t have the bug fix implemented. Took some testing until I figured out why it was not working and included your modifications, now its working fine.

As a suggestion might be worth to add the 3 versions of the code with the bug fix implemented on the first post so people who use the exclude/include versions don´t need to figure out that the code for those needs to have the bug fixed added!

Anyway, thanks again, really useful script :smiley:

1 Like

Not following you. The correction reported in this post:

Is present in the topic’s first post.

These 2 variants are still the old code, took me some time to realize I needed to modify them.

I’m still confused. If both of those posts contain the old code, which post contains the new code?

Got your PM; thank you for pointing out the two examples where I forgot to apply the BugFix!

1 Like

Please vote for this!

Hi Taras,

Thank you for this awesome script.

I have a question. Is there a way to make paused timers also survive a restart?
Same as with the active timers, so put it back to previous paused time but in paused state.

Yes, it would require modifying both the automation and the python_script.

The automation currently selects only active times so it would need to be extended to also select paused timers. In addition, it must be enhanced to also store the timer’s type (active or paused) in the input_text.

The python_script must be modified to restore the timer according to its type (active or paused).


EDIT 1

On second thought, I would use two input_text entities, one for storing data for active timers and the other for paused timers. It provides more storage space (an input_text is limited to storing 255 characters) and simplifies the code for storing/restoring the timers.

EDIT 2

Upon further investigation, restoring a paused timer isn’t as straightforward as restoring an active timer.

The timer.pause service doesn’t support setting the timer’s duration (for obvious reasons). That means in order to set the paused timer’s new duration you first have to call timer.start because it’s the only service that supports setting duration. Then, perhaps after delay of one second to allow the service to execute, immediately call timer.pause.

Hi,

thanks for the insight. I got it working without a delay between start and pause.

  • created extra input (this definitely made this easier)
  • My active timers resume automation now have 2 actions, 1 for each input to update
  • The script I just have it duplicated code and slightly modified for the paused tiers.

After restarting HA twice, starting and pausing a timer before the 2nd restart, it seems to have now survived the restart.

ts = hass.states.get('input_text.paused_timers')
if ts is not None and ts.state != 'unknown' and ts.state != '':
  timers = ts.state.split(',')
  for t in timers:
    z = t.split()
    duration = int(z[1]) - int(datetime.datetime.now().timestamp())
    if duration > 0:
      service_data = {'entity_id': 'timer.{}'.format(z[0]), 'duration':'{}'.format(duration)}
      hass.services.call('timer', 'start', service_data, False)
      service_data = {'entity_id': 'timer.{}'.format(z[0])}
      hass.services.call('timer', 'pause', service_data, False)

So about this delay, I could add time.sleep(0.1) - but the question for me is, is hass.services.call() async or sync? of async it will be needed, if sync, then I will leave it out.

Edit:
Reading thread again, I notice @ Alfiegerner actually did paused timers in the same input in an even shorter way.

Alfiegerner’s version uses a single input_text to store active and paused timers. That means the timer’s state must also be included in the input_text. That technique consumes more storage space which is limited to 255 characters. It will work fine if there are never more than just a few active and paused timers to store/restore and their names aren’t very long.

Given that storage space is limited, and some users have reported they use long timer names, my preference is to use two separate input_text entities.

Regarding the 1-second delay (between calling timer.start and timer.pause) there may be no need for it. I’m in the process of revising the entire solution. Because of many recent scripting enhancements, the python_script in the original solution, which restores the timers, is replaced by an automation.

I speculated there may be a need for a minor delay between the two service calls within this new automation. However, I haven’t reached the testing stage yet and that 1-second delay might not be necessary. If your python_script works reliably without a delay between the two calls, don’t bother adding a delay.

Are you satisfied with the way your paused timers are being restored? Specifically, are paused timers being restored with the correct duration?

I’m asking because I don’t think paused timers should be restored precisely like active timers.

For example, imagine a 10-minute timer has been paused with 8 minutes remaining. I believe whenever it is started again, it should have a duration of 8 minutes. In other words, even if Home Assistant is turned off for an hour, when it is started the paused timer will be restored with 8 minutes remaining. However, it won’t if you compute its duration using the same method as for active timers (the paused timer will be treated as finished if Home Assistant is restarted more than 8 minutes later).

Your thoughts?

I haven’t tested taking home assistant offline for longer than any of my paused timers to be honest.

I am however happy with how the paused timers are restarted. My use case is, I have timers, that might need to be paused for 2-3 hours, then resumed. I only pause timers currently for power outages/rolling-blackouts. My home assistant is on backup power, so it won’t be offline for as long. When I restart it, it is usually a config update and is thus relatively short period.

I need the paused timers restarted mostly because I tinker too much with updating and tweaking automations. The timers are important to me to keep certain things running for specific amount of times.

So yes, happy with it as is currently, but I see your point and how it could affect different use cases.

A paused timer may not be restarted because it’s handled like an active timer.

Example:

  • At 16:00 you pause a timer.
  • The paused timer has 15 minutes left.
  • At 16:20 Home Assistant is restarted.
  • The paused timer is not restored.

It’s not restored because, if it were an active timer, it would have expired at 16:15 and the current time is past that (it’s 16:20). The calculation makes no distinction between paused and active timers so the paused timer is not restored.

I believe the paused timer should be restored at 16:20 with a duration of 15 minutes. This is the behavior that will be adopted by a completely new version I have developed (for saving/restoring active and paused timers) that I will post in a separate thread (very soon).

aah ok I haven’t tested it like that.

Cant wait for your new version! Please add a link to the new thread on this thread when you do.