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: []