Script resume after core restart

Hi,

I have several scripts running with long delay time (waiting to a event or running a calculate time of maybe several hours). So if I restart HA because working on the system the scripts are of course canceled.

But how can I resume to get the automation working?
The main point is to switch something on or off after several hours and for this I have a dynamic delay at the script, so maybe there is a better solution to switch something back? For example create a timer to turn on or off a switch from script resuming after restart but how can I do that?

Thank you, Steffen

Post the script. It can be redesigned to work properly even after a restart.

Hi,

it’s for example the modified script from here: https://community.home-assistant.io/t/script-for-sonos-speakers-to-stop-current-alarm-or-temporarily-disable-upcoming-alarm/417610

For example I have an automation running this script to stop the next Sonos alarm if the day is a holiday. So the script tun the alarm off, calculating the time delta to the alarm ending and wait so long to tun on the alarm again. Here is the sequence:

  sequence:
    - choose:
        - conditions: >-
            {{ alarm.found }}
          sequence:
            - service: switch.turn_off
              data:
                entity_id: " {{ alarm.entity_id }} "
            - delay:
                # we now wait to re-enable the alarm by using the delta to when the alarm
                # was suppose to go off, but already adding five minutes, just to be safe
                hours: >-
                  {{ alarm.delta.hours  }}
                minutes: >-
                  {{ alarm.delta.minutes }}
                seconds: >-
                  {{ alarm.delta.seconds }}
            - service: switch.turn_on
              data:
                entity_id: " {{ alarm.entity_id }} "
      default: []

Question is how to get the alarm on again after a reboot?

Thanks, Steffen

Correct me if I am wrong but that’s not a script but a portion of a blueprint for a script.

If it is a script, post an example of an automation that calls this script.

It’s a script and automations, based on the idea of this blueprint. Here is the automation calling the script:

- alias: Sonos - Feiertag, Wecker Off
  id: auto_sonos_feiertag_weckeroff
  description: 'Sonos - Feiertag, schaltet Sonos Wecker Off'
  mode: single
  trigger:
    - platform: time
      at: '01:00:00'
  condition:
    alias: Check Tagestyp
    condition: and
    conditions:
      - condition: state
        entity_id: sensor.tmp_tagestyp
        state: 'Feiertag'
        for:
          minutes: 5
  action:
    - parallel:
        - service: script.script_sonos_stopalarm
          data:
            master_entity_id: media_player.sonos_bad
            time_window: 720
        - service: script.script_sonos_stopalarm
          data:
            master_entity_id: media_player.sonos_schlafzimmer
            time_window: 720

Steffen

Can you explain how the script and automation you posted are related?

The automation calls a script using two variables (master_entity_id, time_window) that aren’t used by the script. The script references variables (like alarm.entity_id) that aren’t passed by the automation.

For this reason I posted the link to the blueprint. But here is my full script (no optimized, but working):

# ***************************************************************************
# *  Copyright 2022 Joseph Molnar mod. by me ;-)
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# ***************************************************************************

# Stops a currently playing or future alarm on a Sonos speaker.

script_sonos_stopalarm:
  alias: Sonos - Stop Alarm
  mode: parallel
  max: 50
  max_exceeded: warning
  icon: mdi:alarm
  fields:
    master_entity_id:
      description: The entity id of the device to stop the alarm on.
      name: Entity
      required: true
      selector:
        entity:
          domain: media_player
          integration: sonos
    time_window:
      name: Time Window
      description:
        If music isn't playing this is the number of minutes (up to 12 hours) the script will look forward
        to find an active alarm to disable. The alarm is re-enabled about 5 minutes after the time it
        would have gone off.
      required: false
      default: 720
      selector:
        number:
          min: 0
          max: 1440
          step: 60
          unit_of_measurement: minutes
          mode: slider

  variables:
    valid_time_window: >-
      {# make sure we have a valid time window to use #}
      {{ 720 if (time_window is undefined or time_window is not number or time_window < 0 or time_window > 1440) else time_window }}
    alarm: >-
      {% set entities = device_entities(device_id(master_entity_id)) %}
      {# since the current day can flip while running this script and we want this as accurate as #}
      {# possible, we capture the current time right away and base all calculations on it so there #}
      {# are no oddities but does mean up front math and more variables to use later #}
      {% set current_time = now() %}
      {# set for current midnight to help with calculating alarms for today below #}
      {# again, doesn't use today_at() to ensure no cross-over in the day during execution #}
      {% set current_midnight = current_time.replace(hour=0,minute=0,second=0,microsecond=0) %}
      {# we track tomorrow to help with cross-overs at midnight #}
      {% set tomorrow_midnight = current_midnight + timedelta(days=1) %}
      {# the Sonos alarms start the week on Sunday but start at 0 and the ISO week starts on Monday #}
      {# but starts at 1, so to correct the math we just modulo against 7 #}
      {% set current_weekday = current_midnight.isoweekday() % 7 %}
      {% set tomorrow_weekday = ( current_weekday + 1 ) % 7 %}
      {# we use this namespace to track the alarm time info we find #}
      {% set alarm_timing = namespace(days = "", time={}) %}
      {# we set initial delta to help processing later #}
      {% set found_alarm = namespace(found = false, master_entity_id = '', entity_id = '', alarm_id = '', time = '', delta = timedelta(minutes=valid_time_window)) %}
      {# we iterate through all the entities on the specified entity looking for alarms #}
      {% for entity_id in entities %}
        {# we see if we found a switch that has an alarm #}
        {% if states[entity_id].domain == "switch" and is_state(entity_id,'on') and not(state_attr(entity_id,'alarm_id') is none) %}
          {# we need to figure out which days the alarm is on #}
          {% set recurrence = state_attr( entity_id, 'recurrence') %}
          {% if recurrence == 'DAILY' %}
            {# if a daily alarm, we just indicate we will look across all the days #}
            {% set alarm_timing.days = '0123456' %}
          {% elif recurrence == 'WEEKDAYS' %}
            {# if weekdays we just indicate we will look across Monday through Friday #}
            {% set alarm_timing.days = '12345' %}
          {% elif recurrence == 'WEEKENDS' %}
            {# if a daily alarm, we just indicate we will look across Saturday and Sunday #}
            {% set alarm_timing.days = '06' %}
          {% else %}
            {# if not a daily alarm, the format is ON_#### (e.g. ON_1236), where ### are numbers of the weekdays #}
            {% set alarm_timing.days = recurrence.split('_')[1] %}
          {% endif %}
          {# we need the time as a delta so we can add later, yes this is hefty work #}
          {# but I don't know another way to take a string and turn into a timedelta #}
          {% set alarm_time = state_attr( entity_id, 'time') %}
          {% set alarm_time_parts = alarm_time.split(':') %}
          {% set alarm_timing.time = timedelta( hours=alarm_time_parts[0] | int, minutes=alarm_time_parts[1] | int ) %}
          {# so now we loop through each day to see if we have a matching alarm #}
          {# ideally we'd shortcircuit once we find it or go too far #}
          {% for day_string in alarm_timing.days %}
            {% set day = day_string | int %}
            {% if day == current_weekday %}
              {# if the alarm is for today then we use current_midnight as a basis for time comparison #}
              {% set alarm_timestamp = current_midnight + alarm_timing.time-%}
              {% set delta = alarm_timestamp - current_time %}
              {# delta can't be negative, and replace alarm if delta is less than the last found (which includes never found) #}
              {% if delta >= timedelta() and found_alarm.delta >= delta %}
                {% set found_alarm.found = true %}
                {% set found_alarm.entity_id = entity_id %}
                {% set found_alarm.alarm_id = state_attr(entity_id,'alarm_id') %}
                {% set found_alarm.time = alarm_time %}
                {% set found_alarm.call_timestamp = current_time %}
                {% set found_alarm.delta = delta %}
              {% endif %}
            {% elif day == tomorrow_weekday %}
              {# if the alarm is for tomorrow then we use tomorrow_midnight as a basis for time comparison #}
              {% set alarm_timestamp = tomorrow_midnight + alarm_timing.time-%}
              {% set delta = alarm_timestamp - current_time %}
              {# delta can't be negative, and replace alarm if delta is less than the last found (which includes never found) #}
              {% if delta >= timedelta() and found_alarm.delta >= delta %}
                {% set found_alarm.found = true %}
                {% set found_alarm.entity_id = entity_id %}
                {% set found_alarm.alarm_id = state_attr(entity_id,'alarm_id') %}
                {% set found_alarm.time = alarm_time %}
                {% set found_alarm.call_timestamp = current_time %}
                {% set found_alarm.delta = delta %}
              {% endif %}
            {% endif %}
          {% endfor %}
        {% endif %}
      {% endfor %}
      {# if we find the alarm then we can stop / pause it #}
      {% if found_alarm.found == true %}
        {# return back the delta to help calculate when to re-enable the alarm, yes missing microseconds, but close enough #}
        {% set delta_hours = ( found_alarm.delta.seconds / ( 3600 ) ) | int %}
        {% set remaining_seconds = found_alarm.delta.seconds - ( delta_hours * 3600 ) | int %}
        {% set delta_minutes = ( remaining_seconds / 60 ) | int  %}
        {% set remaining_seconds = remaining_seconds - ( delta_minutes * 60 ) %}
        {{ { 'found': true, 'master_entity_id': master_entity_id,
              'entity_id': found_alarm.entity_id, 'alarm_id': found_alarm.alarm_id,
              'time': found_alarm.time, 'call_timestamp' : found_alarm.call_timestamp | string,
              'delta': { 'hours': delta_hours, 'minutes': (delta_minutes + 5), 'seconds' : remaining_seconds } } }}
      {% else %}
        {{ { 'found': false } }}
      {% endif %}

  sequence:
    - choose:
        - conditions: >-
            {{ alarm.found }}
          sequence:
            - service: switch.turn_off
              data:
                entity_id: " {{ alarm.entity_id }} "
            - delay:
                # we now wait to re-enable the alarm by using the delta to when the alarm
                # was suppose to go off, but already adding five minutes, just to be safe
                hours: >-
                  {{ alarm.delta.hours  }}
                minutes: >-
                  {{ alarm.delta.minutes }}
                seconds: >-
                  {{ alarm.delta.seconds }}
            - service: switch.turn_on
              data:
                entity_id: " {{ alarm.entity_id }} "
      default: []

Output is something like that for the alarm variable:

{
  "found": true,
  "master_entity_id": "media_player.sonos_bad",
  "entity_id": "switch.sonos_alarm_1197",
  "alarm_id": "1197",
  "time": "04:58:00",
  "call_timestamp": "2023-03-22 15:00:00.331295+01:00",
  "delta": {
    "hours": 13,
    "minutes": 62,
    "seconds": 59
  }
}

To be clear, that’s not a script, it’s a blueprint for producing a script.

It appears you want to modify the blueprint so that the script it produces can survive a restart. In other words, the blueprint can’t employ a delay statement (which is cancelled by a restart) but must be replaced by something that can survive a restart such as a timer.

If that’s what you want then it will require a significant redesign of the entire application. It can no longer be a blueprint for a script but a blueprint for an automation because a timer produces events and only an automation can listen for the timer’s events.

OK, so I’m a newby, I think it’s a blueprint only if there is a blueprint keyword and showing at blueprint. My version showed at scripts and I using at service tab as script after setting the parameters. So I to not understood and must read again the docu.

But also how I can create a timer from a script to survive a restart. So if I do that I can stop the alarm and create a timer for start again from an automation if timer is ready or I’m wrong?

Thanks, Steffen

A script can start a timer. However, only an automation can detect when the timer is finished. That’s why I said the application would require a complete redesign because you can’t use a script alone to manage a timer.

Can I create a timer and the duration from a script or must the timer already defined in config before (because there is a entity_id as required to start a timer)? Do you have a example code?

If yes can I give back the name of the timer to the automation, so I can build a wait condition in the action part of the automation looking on the timer going back to idle?

Is there a possibility, am I on the right path?
Steffen

As you can see from the timer's documentation, there’s no service call to create a timer (therefore a timer can’t be created by a script or automation).

Pity, so do you have a idea how I can redesign such a automation?

Preparation:

  • Define several timers without a fixed duration but with restore true.
  1. Trigger a Automation if the day is a holiday?
  2. Find all alarm clocks on my Sonos devices for these day (that’s in my posting)?
  3. Turn off these Alarm entity’s?
  4. ???

Maybe you can bump me in the right direction for a better and suviveral solution?
Steffen

It seems that to meet your goal, you will have to use the script_sonos_stopalarm blueprint or, at the very least, copy and use all of the blueprint’s alarm-handling code.

In either case, I don’t have the time to devote to such a project. I suggest you contact the blueprint’s author and see if they are interested in doing it.

Ok thank you.

I using the use all of the blueprint’s code but there is the problem because it’s only a script domain and after restart it was cancelt. So I try to find some way and maybe somebody else have a idea and could help …