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.
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.
Yup, that was basically it… Although i had my config timer: duration set there, i actually had to use a data_template in my automation to force the old duration again… all is well now though… thanks for the script!
I have Problems with the following timer
timer: gartenbewaesserung: duration: '168:00:00'
if i set the timer to ‘00:10:00’ Minutes it works fine but with ‘168:00:00’ hours after a HA reboot the timer reset to idle. I need this timeframe for an lawn irrigation automation that shut start only all 7 days if other conditions like soil humity have the right level and 7 days a long time with regulary needs HA restarts between. Any idea what i make wrong or is it an known limitation that could be solved?
When Home Assistant starts, it’s the python_script’s responsibility to examine the contents of input_text.active_timers
and start any timers that are listed there.
If timer.gartenbewaesserung
is not restarted it means it is either not listed in input_text.active_timers
or the python_script failed to start it.
First, check the log for any warnings or errors related to the automation called “Active Timers Resume” or the python_script " active_timers_resume.py".
Second, perform this experiment:
- Start
timer.gartenbewaesserung
with duration168:00:00
- Go to Developer Tools > States, find
input_text.active_timers
and confirm its state value containsgartenbewaesserung
followed by a large number (which is a timestamp). - Wait a minute or two for the timer to count down.
- Restart Home Assistant.
- Go to Developer Tools > States, find
input_text.active_timers
and see if its state value containsgartenbewaesserung
(followed by a timestamp).
If it contains gartenbewaesserung
but this timer’s state is idle
, and not active
, it suggests that the python_script failed to start it.
Thanks for your fast response. i do what you explain at first i start the timer and the entity input_text.active_timers has the value ‘gartenbewaesserung 1591451673’ After the restart i see no errors in the log and the input_text.active_timers the value ‘gartenbewaesserung 1591451673’ but the timer is idle
now i have additional tested 24:00:00 hours for the timer gartenbewaesserung and an additional timer named test with 168:00:00
input_text.active_timers shows the value gartenbewaesserung 1591452688,test 1591452910
after Restart both timers are idle
next test 00:10:00 minutes for the timer gartenbewaesserung and the additional timer named test with 168:00:00
input_text.active_timers shows the value gartenbewaesserung 1591455593,test 1591455002
after restart the timer gartenbewaesserung runs with the right time but the Timer test (with 168 Hours) is idle
My HA Version is 0.111.0b2 but i’m see this isue also in 0.110.2
it’s look like that the Script works with minutes correct but theare are Problems with Hours
Thank you for performing the suggested experiment and for the additional test data.
I agree with you that the problem appears to be in the python_script. It performs a simple calculation where it subtracts the current timestamp from the timer’s recorded timestamp. If the result exceeds 0
it represents the remaining duration for the timer (and the timer is restarted).
duration = int(z[1]) - int(datetime.datetime.now().timestamp())
if duration > 0:
If the result is zero, that means the timer has no remaining duration and is not restarted. Perhaps the calculation fails for timers with long durations (hours). I will test it later today.
Now i make a last test i set the timer to 09:00:00 and that works so it look like the issue takes place with 10 hours and more.
I think I see the source of the problem and it’s not the python_script. The first automation assumes the duration
attribute is always in this format HH:MM:SS
but look at how the attribute is displayed when I set it to 30 hours like this: 30:00:00
It shows it as 1 day, 6:00:00
which is not in the expected HH:MM:SS
format. That means the first automation calculates the wrong timestamp value. In fact, it’s so terribly wrong that when the python_script performs its calculation using that timestamp it results in a negative value. That falsely implies the timer has already expired so that’s why the python_script does not restart the timer.
I will need to revise the first automation and create a template that is able to handle durations in this format: 1 day, 6:00:00
Make it sense to create an changerequest for the timer integration?
I have enhanced the first automation’s template so that it correctly handles durations expressed in days.
I have updated the first post in this thread. Simply replace the action
portion of your existing automation.active_timers_save
with what is shown in the first post or, for your convenience, what is shown below:
action:
- service: input_text.set_value
data_template:
entity_id: input_text.active_timers
value: >
{% set ns = namespace(timers = '') %}
{% for t in states.timer | selectattr('state', 'eq', 'active') | list %}
{% 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 }}
What’s different is this section:
{% 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) %}
It checks if the duration’s hours portion contains the word day
. If it does then it proceeds to extract the number of days, convert it to hours, combine it to any existing hours, then convert it to seconds.
Great response rate from you! Now it works like a charm
Maximum Thanks !
Thanks very much for this - I’ve just started using long running timers and also have a tendency to restart HA whilst tinkering so this has been very useful.
My use case also has paused timers, so I’ve made changes to include those:
template for setting input_text:
data_template:
entity_id: input_text.active_timers
value: >
{% set ns = namespace(timers = '') %} {% for t in states.timer |
selectattr('state', 'in', ['active','paused']) | list %}
{% 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, t.state) %}
{% endfor %} {{ ns.timers }}
service: input_text.set_value
and python script:
ts = hass.states.get('input_text.active_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())
state = z[2]
if duration > 0:
service_data = {'entity_id': 'timer.{}'.format(z[0]), 'duration':'{}'.format(duration)}
hass.services.call('timer', 'start', service_data, False)
if state == "paused":
hass.services.call('timer', 'pause', {'entity_id' : 'timer.{}'.format(z[0])}, False)
Thank you very much for this, I know this will solve all my timer when I get it working, but I have been scratching my head for a few days. Added input_text, 2x automations and the pythonscript, after enabling a test timer for 30 minutes and restart HA via configuration > Server Controls > Server Management > restart, but the timer came back as idle. I would say out of 10 restart there maybe 1 time the timer will be resume okay, so that proved all scripts and yaml were added correctly.
Does anyone encounter such problem ?
Things I have tried and noticed:
-
after timer enabled and counting down, checked input_timer.active_timers state do indicate “test_timer 1595269963”. the state is registered, so the 1st automation work
-
with HA logger default set to “info”, reboot system and home-assistant.log show
2020-07-20 10:41:52 INFO (SyncWorker_0) [homeassistant.components.python_script] Executing active_timers_resume.py: {}
so the 2nd automation also work and called the pythonscript -
I added couple logger.info line to the pythonscript to see if it read the value back correctly and here are some clue
When timer didn’t resume
2020-07-20 11:13:59 INFO (MainThread) [homeassistant.setup] Setting up python_script
2020-07-20 11:14:01 INFO (MainThread) [homeassistant.setup] Setup of domain python_script took 2.2 seconds.
2020-07-20 11:14:24 INFO (SyncWorker_2) [homeassistant.components.python_script] Executing active_timers_resume.py: {}
2020-07-20 11:14:24 INFO (SyncWorker_2) [homeassistant.components.python_script.active_timers_resume.py] active timers resume script ran
2020-07-20 11:14:24 INFO (SyncWorker_2) [homeassistant.components.python_script.active_timers_resume.py] ts= <state input_text.active_timers=; editable=False, min=0, max=255, pattern=None, mode=text, friendly_name=Active Timers @ 2020-07-20T11:14:02.986849-07:00>
Timer resumed correctly
2020-07-20 11:42:54 INFO (MainThread) [homeassistant.setup] Setting up python_script
2020-07-20 11:42:57 INFO (MainThread) [homeassistant.setup] Setup of domain python_script took 2.6 seconds.
2020-07-20 11:43:19 INFO (SyncWorker_0) [homeassistant.components.python_script] Executing active_timers_resume.py: {}
2020-07-20 11:43:19 INFO (SyncWorker_0) [homeassistant.components.python_script.active_timers_resume.py] active timers resume script ran
2020-07-20 11:43:19 INFO (SyncWorker_0) [homeassistant.components.python_script.active_timers_resume.py] ts= <state input_text.active_timers=test_timer 1595270700; editable=False, min=0, max=255, pattern=None, mode=text, friendly_name=Active Timers @ 2020-07-20T11:42:58.526629-07:00>
2020-07-20 11:43:19 INFO (SyncWorker_0) [homeassistant.components.python_script.active_timers_resume.py] timers= ['test_timer 1595270700']
2020-07-20 11:43:19 INFO (SyncWorker_0) [homeassistant.components.python_script.active_timers_resume.py] duration= 101
so seems like even the input_text.active_timers show the value in the developer tools > states, it is not saved properly. Thus, upon bootup, the script read an empty state, which caused the timer not to resume. And when the state were saved, it will read back properly and resume correctly.
-
Look into HA database noticed input_text.active_timers change and update instantantly whenever the state of the timer is changed
SELECT * FROM "states" WHERE "domain" LIKE 'input_text' ORDER BY "last_changed" DESC
-
Look into file system /.storage/core.restore_state and I believe this is where the pythonscript is getting the states from. And within the input_text.active_timers section, the state was never updated immediately. It update the state in random period.
Base on what I have found, if the input_text.active_timers state wasn’t updated correctly witin the .core.restore_state file, the timer may not resume properly.
Is it my HA configuration problem that causing the core.restore_state file not updating as often ?
Based on what you posted, the active timer was not resumed only when the the input_text was empty, otherwise it worked as expected.
Assuming we have an active timer, why would the input_text be empty?
For starters, the entity_id of an active timer is written to the input_text at the moment the timer starts. I see very little chance of it failing to do that.
As for the input_text itself, Home Assistant will periodically, and just before shutdown, store the input_text’s state to .storage/core.restore_state
. On startup, it initializes the input_text’s state using the value recorded in core.restore_state. The database plays no part in the restoration process and the python_script (as does most everything else) gets the input_text’s value from the state machine (and not directly from core.restore_state).
Under what circumstances, during startup, would the input_text have no state value? This is just a guess but what if, during startup, the python_script executes before the input_text gets its old value restored?
One way to test this theory is to add a delay
to the startup automation to (hopefully) prevent the python_script from executing before the input_text’s state is restored. If that fails to fix it, I’m not sure what’s grieving it nor how to mitigate it.
- alias: 'Active Timers Resume'
trigger:
platform: homeassistant
event: start
action:
- delay: '00:00:05'
- service: python_script.active_timers_resume
Yes, you’re correct, the active timer resume when input_text state value saves correctly, and it was not resumed only when the input_text state value was empty, and in my case, it wasn’t updated in the core.restore_state file for a very long period, not even before HA shutdown which is wierd.
I had my HA installed on Ubuntu, so I have two terminal open, one is monitoring the home-assistant.log file so I can tell when the python script is loading upon startup, another one is monitoring the change of the input_text.active_timer section within the core.restore_state file
When I start a timer, timer registered in the input_text.active_timer’ state value right away, but HA didn’t update the core.restore_state file until really really late, like 20min into a 30minutes timer late for example. so within the first 20min should I restart HA, state value isn’t in core.restore_state file, after reboot script reads an empty value lead to no timer resume.
when it work, the state value look like this :
"state": "test_timer 1595307325"
I don’t know why my HA doesn’t even update the core.restore_state file before shutdown either.