Skip to content

Instantly share code, notes, and snippets.

@Ltek
Last active March 31, 2026 01:15
Show Gist options
  • Select an option

  • Save Ltek/0d88ed720f635a7579849aac93b9ff93 to your computer and use it in GitHub Desktop.

Select an option

Save Ltek/0d88ed720f635a7579849aac93b9ff93 to your computer and use it in GitHub Desktop.
Home Assistant Blueprint : Repeating Alert Notifications by LTek
blueprint:
name: ⚠️ Repeating Alert Notifications by LTek
description: |
**Repeating Alert Notifications ideal for doors, windows, locks, etc**
🚀 Version 2026.02.27.26c
- ADDED: Final Notification and Actions
- IMPORTANT: Automations prior to 18c need to be recreated.
Author: LTek
📖 Community Discussion, Help, and Details... https://community.home-assistant.io/t/repeating-alert-notifications/888294
**FEATURES**
⚡ Advanced Triggering — Any trigger type (state, numeric, template, event) with optional conditions and restart mode
🔄 Restart Survival — Optional Timer + Text helpers preserve the repeat countdown and count across HA restarts
❌ Notification Clearing — Dedicated clear trigger, auto-clear delay, and user-defined Notification ID to replace or clear alerts across automations
♻️ Repeating Alerts — Configurable repeat limits, intervals, and optional repeat counter appended to title
⏱️ Timing Controls — Initial delay before first alert, repeat intervals, and auto-clear delay after last repeat
🎬 Actionable Buttons — Up to 3 buttons + optional cancel, button timeout, and failsafe auto-action if all repeats go unanswered
📲 Multi-Device Delivery — Send to multiple mobile devices simultaneously
💬 Customizable Notifications — Templated titles and messages with built-in variables (see below)
👇 Tap Action — Open dashboards, URLs, or specific app views on notification tap
✅ Android and iOS Specific Options — Android icon, color, persistence, and channel; iOS interruption levels and sounds
💥️ Custom Actions — Run your own actions on first trigger, on each notification, and on clear
**📖 Available Variables (use in Notification ID, title, and message)**
- `{{ now().strftime('%I:%M %p') }}` — Local time (e.g. 3:45 PM)
- `{{ now().strftime('%A') }}` — Day of week (e.g. Tuesday)
- `{{ now().strftime('%Y%m%d') }}` — Date stamp (e.g. 20260219)
- `{{ trigger.entity_id }}` — Entity that fired the trigger (if state-based)
- `{{ states('sensor.my_sensor') }}` — Current state of any entity
- `{{ trigger_entity_state }}` — State of the triggering entity at the time of trigger (e.g. on, off, open)
- `{{ time_since_trigger }}` — Time elapsed since trigger entity last changed (e.g. 4 min 32 sec ago), updates on each repeat
- `{{ friendly_name }}` - Friendly Name of the triggering entity or the user custom version
- `{{ trigger_entity_state }}` - Display the actual state that triggered the automation
- `{{ trigger_fired_at | timestamp_custom('%I:%M %p') }}` — time the trigger occurred (e.g., 3:45 PM)
- `{{ trigger_fired_at | timestamp_custom('%Y-%m-%d %H:%M:%S') }}` — full date and time
**⚠️ Notification ID Note**
The Notification ID (tag) is used to replace or clear notifications on devices.
Sending a new notification with the same ID replaces the existing one.
A companion automation sharing the same Notification ID can send clear_notification to dismiss it.
If left blank, notifications cannot be replaced or cleared by this automation.
**⚠️ Action Buttons — How It Works**
Buttons appear on every notification including repeats. After each send the automation
waits up to the Button Timeout for a press before continuing.
- **Button pressed** → that button's action fires immediately, all remaining repeats are cancelled
- **Cancel pressed** → automation stops immediately, no actions fire
- **Timeout (mid-repeat)** → automation continues to the next repeat, no action fires yet
- **Timeout (final repeat)** → Auto Actions fire as a one-time failsafe, then the automation ends
The Button Timeout defaults to the Repeat Interval. For long repeat intervals, set a shorter
Button Timeout so the user gets a prompt response window without waiting the full interval.
Auto Actions are distinct from Custom Actions — Custom Actions fire on every notification,
Auto Actions fire only once after all repeats are exhausted with no button ever pressed.
source_url: https://gist.github.com/Ltek/0d88ed720f635a7579849aac93b9ff93
domain: automation
input:
# ── SECTION: Alert Trigger ────────────────────────────────────────────────
section_trigger:
name: "Alert Trigger"
icon: mdi:lightning-bolt
collapsed: false
input:
alert_trigger:
name: "Alert Trigger"
description: >
Define what triggers the alert notification. Supports any trigger
type — state changes, numeric thresholds, templates, time patterns, etc.
default: []
selector:
trigger: {}
duration_issue_state:
name: "Time Delay before Alert is Sent"
description: >
How long to wait after the trigger fires before sending the alert.
Useful to avoid spurious alerts (e.g. only notify if a door is open for 10 minutes).
For state-based triggers, prefer setting the 'for' duration on the trigger itself
for more reliable behaviour.
default:
minutes: 0
selector:
duration:
enable_day: true
condition_send_notification:
name: "Optional Trigger Conditions"
description: >
Conditions checked after the alert trigger fires. All conditions must be true
for the notification to send. Useful for restricting alerts to specific times,
modes, or entity states.
default: []
selector:
condition: {}
# ── SECTION: Custom Actions ───────────────────────────────────────────────
section_actions:
name: "Custom Actions"
icon: mdi:cog
collapsed: true
input:
custom_action_first_trigger:
name: "Custom Action - On First Trigger"
description: >
Actions run once when the alert first triggers, before the first notification is sent.
Runs only on the initial trigger, not on repeats.
default: []
selector:
action: {}
custom_action_issue_state:
name: "Custom Action - On Each Notification"
description: Actions run each time a notification is sent (including repeats).
default: []
selector:
action: {}
custom_action_on_clear:
name: "Custom Action - On Clear"
description: Actions run after the notification is cleared (via clear trigger or auto-clear delay).
default: []
selector:
action: {}
# ── SECTION: Notification Settings ───────────────────────────────────────
section_content:
name: "Notification Settings"
icon: mdi:message-text
collapsed: true
input:
notify_device:
name: "Devices to Notify"
description: Select mobile devices to receive notifications (Home Assistant companion app).
default: []
selector:
device:
integration: mobile_app
multiple: true
notification_id:
name: "Notification ID"
description: >
A unique tag used to replace or clear this notification on devices.
Supports template variables (e.g. front-door-alert or alert-{{ now().strftime('%Y%m%d') }}).
⚠️ If left blank, notifications cannot be replaced or cleared by this automation.
Multiple automations sharing the same Notification ID can replace/clear each other's alerts.
default: ''
selector:
text:
multiple: false
multiline: false
friendly_name:
name: "Custom Friendly Name"
description: Override the friendly name used in notification templates (e.g. Fridge, Front Door).
default: ''
selector:
text:
multiple: false
multiline: false
notification_title:
name: "Notification Title"
description: "Supports template variables. See descritpion above."
default: '{{ friendly_name }} changed to {{ trigger_entity_state }}'
selector:
text:
multiple: false
multiline: false
notification_message:
name: "Notification Message"
description: "Supports template variables. See descritpion above."
default: "Trigger started {{ time_since_trigger }} ago"
selector:
text:
multiple: false
multiline: false
notification_click_url:
name: "Open URL with tap"
description: URL that opens when the notification is tapped.
default: ''
selector:
text:
multiple: false
multiline: false
# ── SECTION: Auto-Clear ───────────────────────────────────────────────────
section_clear:
name: "Auto-Clear"
icon: mdi:bell-off
collapsed: true
input:
clear_trigger:
name: "Trigger to Clear Notification"
description: >
When this trigger fires, the notification is immediately cleared on all devices
using the Notification ID. Leave empty if you only want auto-clear by delay,
or if clearing is handled externally.
⚠️ This trigger must be different from the Alert Trigger above. If both
triggers fire at the same time or on the same condition, the automation
will conflict — sending and clearing the notification simultaneously.
default: []
selector:
trigger: {}
auto_clear_delay:
name: "Auto-Clear Delay"
description: >
After all repeats have completed (or after the first notification if repeats
are disabled), wait this duration then automatically clear the notification
on all devices. Set to 0 to disable — clearing will only happen via the
Trigger to Clear Notification above, or a companion automation sharing the same Notification ID.
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration:
enable_day: false
# ── SECTION: Repeat ───────────────────────────────────────────────────────
section_repeat:
name: "Repeat Alerts"
icon: mdi:repeat
collapsed: true
input:
repeat_notification:
name: "Repeat Alerts"
description: >
Repeat the notification at a set interval until auto-clear fires or the repeat limit is reached.
If Action Buttons are configured, pressing a button on any repeat will cancel all remaining repeats.
When enabled, the Delay Between Repeats controls the full repeat cycle — it is used as both
the interval between notifications and the button press timeout, so the cycle is always exactly
the interval you set.
default: false
selector:
boolean: {}
max_repeat_count:
name: "Maximum Repeat Count"
description: Maximum number of repeats. Set to 0 for unlimited.
default: 0
selector:
number:
min: 0.0
max: 100.0
step: 1.0
mode: box
time_between_repeat_notification:
name: "Delay Between Repeats"
description: >
Time between repeat notifications. When Action Buttons are enabled, this value also
serves as the button press timeout — meaning the full repeat cycle is exactly this
duration (not this plus a separate timeout). If no button is pressed within this
window, the next repeat fires automatically.
default:
minutes: 1
selector:
duration:
enable_day: true
show_repeat_count:
name: "Show Repeat Count in Title"
description: >
When enabled, appends the current repeat number to the notification title.
Format: "Your Title (1)", "Your Title (2)", "Your Title (3)", etc.
The first notification shows (1), each repeat increments by one.
default: false
selector:
boolean: {}
# ── SECTION: Action Buttons ───────────────────────────────────────────────
section_buttons:
name: "Action Buttons"
icon: mdi:gesture-tap-button
collapsed: true
input:
include_action_buttons:
name: "Enable Action Buttons"
description: >
Select which buttons to show on the first notification.
Buttons only appear on the first notification, not on repeats.
If a button is pressed, the repeat loop is skipped and the button's action fires instead.
default: []
selector:
select:
options:
- label: "Button 1"
value: enable_action_button_1
- label: "Button 2"
value: enable_action_button_2
- label: "Button 3"
value: enable_action_button_3
multiple: true
custom_value: false
sort: false
include_auto_actions:
name: "Auto Actions on Timeout (Failsafe)"
description: >
Select which button actions should fire automatically as a failsafe if all repeats
complete and no button was ever pressed. Fires once only, after the final repeat times out.
If no repeats are configured, fires after the first (and only) notification times out.
Leave empty to take no automatic action.
default: []
selector:
select:
options:
- label: "Button 1 Action"
value: enable_auto_action_1
- label: "Button 2 Action"
value: enable_auto_action_2
- label: "Button 3 Action"
value: enable_auto_action_3
multiple: true
custom_value: false
sort: false
action_button_timeout:
name: "Button Timeout"
description: >
How long to wait for a button press before the automation continues.
Only applies when Repeating is disabled. When repeating is enabled, the
Delay Between Repeats is used as the timeout instead — so the repeat cycle
stays exactly the interval you set with no extra wait stacked on top.
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration: {}
action_button_1_label:
name: "Button 1 Label"
description: Text displayed on button 1.
default: 'Acknowledge'
selector:
text: {}
action_button_1_action:
name: "Button 1 Action"
description: Actions to run when Button 1 is pressed.
default: []
selector:
action: {}
action_button_2_label:
name: "Button 2 Label"
description: Text displayed on button 2.
default: 'Snooze'
selector:
text: {}
action_button_2_action:
name: "Button 2 Action"
description: Actions to run when Button 2 is pressed.
default: []
selector:
action: {}
action_button_3_label:
name: "Button 3 Label"
description: Text displayed on button 3.
default: 'Ignore'
selector:
text: {}
action_button_3_action:
name: "Button 3 Action"
description: Actions to run when Button 3 is pressed.
default: []
selector:
action: {}
action_button_stop_label:
name: "Cancel Button Label"
description: >
Text for an optional cancel button. Shown when any button above is enabled.
Pressing cancel stops the automation — no actions fire and repeats are cancelled.
default: 'Cancel'
selector:
text: {}
# ── SECTION: Final Notification ─────────────────────────────────────────
section_final_notification:
name: "Final Notification"
icon: mdi:bell-alert
collapsed: true
input:
enable_final_notification:
name: "Enable Final Notification"
description: >
When Repeating is enabled, send one last notification after all repeats are
exhausted and the final button timeout expires. Useful to inform the user
that the alert is ending or that an auto-action has been triggered.
Has no effect when Repeating is disabled.
default: false
selector:
boolean: {}
final_notification_title:
name: "Final Notification Title"
description: "Supports the same template variables as the main notification title."
default: '{{ friendly_name }} — alert ended'
selector:
text:
multiple: false
multiline: false
final_notification_message:
name: "Final Notification Message"
description: "Supports the same template variables as the main notification message."
default: 'No response after {{ time_since_trigger }}'
selector:
text:
multiple: false
multiline: false
custom_action_final_notification:
name: "Custom Action - On Final Notification"
description: >
Actions run when the final notification is sent.
Only fires when Final Notification is enabled and all repeats are exhausted.
default: []
selector:
action: {}
# ── SECTION: Restart Persistence ─────────────────────────────────────────────
section_persistence:
name: "Restart Persistence"
description: Reccomended to enable this when using long timeframes
icon: mdi:restart
collapsed: true
input:
include_persistence:
name: "Enable Restart Persistence"
description: >
When enabled, the repeat countdown and count survive HA restarts.
Requires a Timer helper and a Text helper selected below.
When creating the Timer helper, tick the "Restore" box so it resumes
after a restart. Each automation instance needs its own dedicated pair
of helpers.
default: false
selector:
boolean: {}
persistence_timer:
name: "Timer Helper"
description: >
Select the Timer helper. Only active when restart persistence is enabled above.
Create via Settings -> Helpers -> Add Helper -> Timer.
Tick the "Restore" box. Do not set a duration in the helper.
default: []
selector:
entity:
filter:
- domain:
- timer
multiple: false
persistence_text:
name: "Text Helper"
description: >
Select the Text (input_text) helper. Only active when restart persistence is enabled above.
Create via Settings -> Helpers -> Add Helper -> Text.
default: []
selector:
entity:
filter:
- domain:
- input_text
multiple: false
# ── SECTION: Android Options ──────────────────────────────────────────────
section_android:
name: "Android Options"
icon: mdi:robot
collapsed: true
input:
notification_icon_warning:
name: "Notification Icon (Android Only)"
description: The icon shown in the alert notification.
default: alert
selector:
select:
options:
- alert
- alert-circle
- door
- door-open
- motion-sensor
- fridge
- fridge-alert
- home
- home-alert
- home-assistant
- window-closed
- window-open
- window-open-variant
custom_value: false
multiple: false
sort: false
notification_color:
name: "Notification Color (Android Only)"
description: The color of the notification.
default: red
selector:
select:
options:
- red
- orange
- yellow
- green
- blue
- purple
custom_value: false
multiple: false
sort: false
notification_persistent:
name: "Persistent Notification (Android Only)"
description: >
Notification cannot be closed manually — stays until auto-clear fires
or a companion automation clears it. Works well combined with Repeat Alerts.
default: false
selector:
boolean: {}
# ── SECTION: iOS Options ──────────────────────────────────────────────────
section_ios:
name: "iOS Options"
icon: mdi:apple
collapsed: true
input:
notification_interruption_level:
name: "Interruption Level (iOS Only)"
description: Controls the intrusiveness of the notification on iOS devices.
default: active
selector:
select:
options:
- passive
- active
- time-sensitive
- critical
custom_value: false
multiple: false
sort: false
mode: restart
max_exceeded: silent
trigger_variables:
_alert_triggers: !input alert_trigger
# ── Top-level variables (all declared at top level — cannot be nested) ────────
variables:
custom_friendly_name: !input friendly_name
notification_id_template: !input notification_id
duration_issue_state: !input duration_issue_state
condition_send_notification: !input condition_send_notification
notify_device: !input notify_device
notification_click_url: !input notification_click_url
repeat_notification: !input repeat_notification
show_repeat_count: !input show_repeat_count
max_repeat_count: !input max_repeat_count
time_between_repeat_notification: !input time_between_repeat_notification
notification_icon_warning: !input notification_icon_warning
notification_color: !input notification_color
notification_persistent: !input notification_persistent
notification_interruption_level: !input notification_interruption_level
include_action_buttons: !input include_action_buttons
action_button_1_label: !input action_button_1_label
action_button_2_label: !input action_button_2_label
action_button_3_label: !input action_button_3_label
action_button_stop_label: !input action_button_stop_label
action_button_timeout: !input action_button_timeout
include_auto_actions: !input include_auto_actions
enable_final_notification: !input enable_final_notification
final_notification_title: !input final_notification_title
final_notification_message: !input final_notification_message
custom_action_first_trigger: !input custom_action_first_trigger
custom_action_issue_state: !input custom_action_issue_state
custom_action_on_clear: !input custom_action_on_clear
auto_clear_delay: !input auto_clear_delay
include_persistence: !input include_persistence
persistence_timer: !input persistence_timer
persistence_text: !input persistence_text
repeat_count: 0
triggers:
- triggers: !input alert_trigger
- triggers: !input clear_trigger
- platform: homeassistant
event: start
id: ha_restart
action:
- variables:
notify_services_list: >
{% set ns = namespace(services=[]) %}
{% for device_id in notify_device %}
{% set matched = device_entities(device_id)
| select('match', 'notify\.mobile_app_')
| list %}
{% if matched | length > 0 %}
{% set ns.services = ns.services + [matched[0]] %}
{% else %}
{% set device_name = device_attr(device_id, 'name') | slugify %}
{% set ns.services = ns.services + ['notify.mobile_app_' ~ device_name] %}
{% endif %}
{% endfor %}
{{ ns.services }}
number_of_notify_services: '{{ notify_services_list | count }}'
notification_tag: '{{ notification_id_template }}'
trigger_fired_at: >
{% if trigger.to_state is defined and trigger.to_state is not none %}
{{ trigger.to_state.last_changed | as_timestamp }}
{% else %}
{{ as_timestamp(now()) }}
{% endif %}
trigger_entity_state: '{{ trigger.to_state.state if trigger.to_state is defined else states(trigger.entity_id) }}'
friendly_name: >
{% if custom_friendly_name != '' %}
{{ custom_friendly_name }}
{% else %}
{{ state_attr(trigger.entity_id, 'friendly_name') | default(trigger.entity_id) | default('Alert') }}
{% endif %}
rendered_title: !input notification_title
- condition: template
value_template: '{{ trigger.idx in ["0", "1"] or trigger.platform == "homeassistant" }}'
- choose:
# ── HA RESTART RECOVERY ───────────────────────────────────────────────
- conditions:
- condition: template
value_template: '{{ trigger.platform == "homeassistant" }}'
sequence:
- condition: template
value_template: >
{{ include_persistence == true and
is_state(persistence_timer, 'active') }}
- variables:
_raw: '{{ states(persistence_text) }}'
_data: '{{ _raw | from_json if _raw not in ["", "{}"] else {} }}'
trigger_fired_at: '{{ _data.get("fired_at", as_timestamp(now())) | float }}'
repeat_count: '{{ _data.get("count", 0) | int }}'
notify_services_list: >
{% set ns = namespace(services=[]) %}
{% for device_id in notify_device %}
{% set matched = device_entities(device_id)
| select('match', 'notify\.mobile_app_')
| list %}
{% if matched | length > 0 %}
{% set ns.services = ns.services + [matched[0]] %}
{% else %}
{% set device_name = device_attr(device_id, 'name') | slugify %}
{% set ns.services = ns.services + ['notify.mobile_app_' ~ device_name] %}
{% endif %}
{% endfor %}
{{ ns.services }}
number_of_notify_services: '{{ notify_services_list | count }}'
notification_tag: '{{ notification_id_template }}'
trigger_entity_state: ''
friendly_name: >
{% if custom_friendly_name != '' %}
{{ custom_friendly_name }}
{% else %}
Alert
{% endif %}
rendered_title: !input notification_title
action_btn_1: '{{ "BTN1_" ~ context.id }}'
action_btn_2: '{{ "BTN2_" ~ context.id }}'
action_btn_3: '{{ "BTN3_" ~ context.id }}'
action_btn_stop: '{{ "STOP_" ~ context.id }}'
actions_list: >
{% set actions = [] %}
{% if 'enable_action_button_1' in include_action_buttons and action_button_1_label != '' %}
{% set actions = actions + [{'action': 'BTN1_' ~ context.id, 'title': action_button_1_label}] %}
{% endif %}
{% if 'enable_action_button_2' in include_action_buttons and action_button_2_label != '' %}
{% set actions = actions + [{'action': 'BTN2_' ~ context.id, 'title': action_button_2_label}] %}
{% endif %}
{% if 'enable_action_button_3' in include_action_buttons and action_button_3_label != '' %}
{% set actions = actions + [{'action': 'BTN3_' ~ context.id, 'title': action_button_3_label}] %}
{% endif %}
{% if include_action_buttons | length > 0 and action_button_stop_label != '' %}
{% set actions = actions + [{'action': 'STOP_' ~ context.id, 'title': action_button_stop_label}] %}
{% endif %}
{{ actions }}
last_wait_timed_out: false
- wait_for_trigger:
- platform: state
entity_id: !input persistence_timer
to: idle
continue_on_timeout: false
- variables:
repeat_count: '{{ repeat_count + 1 }}'
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{{ {"fired_at": trigger_fired_at, "count": repeat_count} | to_json }}'
- service: timer.start
entity_id: !input persistence_timer
data:
duration: !input time_between_repeat_notification
- delay: !input time_between_repeat_notification
# ── CLEAR TRIGGER FIRED ───────────────────────────────────────────────
- conditions:
- condition: template
value_template: '{{ trigger.idx == "1" }}'
sequence:
- repeat:
count: '{{ number_of_notify_services }}'
sequence:
- service: '{{ notify_services_list[repeat.index-1] }}'
data:
message: clear_notification
data:
tag: '{{ notification_tag }}'
- if:
- condition: template
value_template: '{{ include_persistence == true }}'
then:
- service: timer.cancel
entity_id: !input persistence_timer
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{}'
- choose: []
default: !input custom_action_on_clear
# ── ALERT TRIGGER FIRED ───────────────────────────────────────────────────
default:
# ── OPTIONAL CONDITIONS ───────────────────────────────────────────────────
- condition: !input condition_send_notification
# ── PERSIST STATE ON FRESH TRIGGER (before initial delay) ───────────────
- if:
- condition: template
value_template: '{{ include_persistence == true }}'
then:
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{{ {"fired_at": trigger_fired_at, "count": 0} | to_json }}'
- if:
- condition: template
value_template: >
{{ (duration_issue_state.hours | default(0) | int) +
(duration_issue_state.minutes | default(0) | int) +
(duration_issue_state.seconds | default(0) | int) > 0 }}
then:
- service: timer.start
entity_id: !input persistence_timer
data:
duration: !input duration_issue_state
else:
- service: timer.cancel
entity_id: !input persistence_timer
# ── INITIAL DELAY ─────────────────────────────────────────────────────────
- if:
- condition: template
value_template: >
{{ (duration_issue_state.hours | default(0) | int) +
(duration_issue_state.minutes | default(0) | int) +
(duration_issue_state.seconds | default(0) | int) > 0 }}
then:
- delay: !input duration_issue_state
# ── CUSTOM ACTION: ON FIRST TRIGGER ───────────────────────────────────────
- choose: []
default: !input custom_action_first_trigger
# ── BUILD ACTION BUTTONS ──────────────────────────────────────────────────
- variables:
action_btn_1: '{{ "BTN1_" ~ context.id }}'
action_btn_2: '{{ "BTN2_" ~ context.id }}'
action_btn_3: '{{ "BTN3_" ~ context.id }}'
action_btn_stop: '{{ "STOP_" ~ context.id }}'
actions_list: >
{% set actions = [] %}
{% if 'enable_action_button_1' in include_action_buttons and action_button_1_label != '' %}
{% set actions = actions + [{'action': 'BTN1_' ~ context.id, 'title': action_button_1_label}] %}
{% endif %}
{% if 'enable_action_button_2' in include_action_buttons and action_button_2_label != '' %}
{% set actions = actions + [{'action': 'BTN2_' ~ context.id, 'title': action_button_2_label}] %}
{% endif %}
{% if 'enable_action_button_3' in include_action_buttons and action_button_3_label != '' %}
{% set actions = actions + [{'action': 'BTN3_' ~ context.id, 'title': action_button_3_label}] %}
{% endif %}
{% if include_action_buttons | length > 0 and action_button_stop_label != '' %}
{% set actions = actions + [{'action': 'STOP_' ~ context.id, 'title': action_button_stop_label}] %}
{% endif %}
{{ actions }}
# ── FIRST NOTIFICATION ────────────────────────────────────────────────────
- parallel:
- repeat:
count: '{{ number_of_notify_services }}'
sequence:
- variables:
time_since_trigger: >
{% set diff = (as_timestamp(now()) - trigger_fired_at) | int %}
{% if diff < 1 %}
just now
{% else %}
{% set h = diff // 3600 %}
{% set m = (diff % 3600) // 60 %}
{% set s = diff % 60 %}
{% if h > 0 %}{{ h }}h {% endif %}{% if m > 0 %}{{ m }}m {% endif %}{% if h == 0 and s > 0 %}{{ s }}s{% endif %}
{% endif %}
- variables:
current_message: !input notification_message
- variables:
current_title: !input notification_title
- service: '{{ notify_services_list[repeat.index-1] }}'
data:
message: '{{ current_message }}'
title: >
{{ current_title }}{% if show_repeat_count %} ({{ repeat_count + 1 }}){% endif %}
data:
clickAction: !input notification_click_url
url: !input notification_click_url
tag: '{{ notification_tag }}'
color: !input notification_color
notification_icon: 'mdi:{{ notification_icon_warning }}'
push:
interruption-level: !input notification_interruption_level
persistent: !input notification_persistent
sticky: !input notification_persistent
actions: '{{ actions_list }}'
- choose: []
default: !input custom_action_issue_state
# ── WAIT FOR BUTTON PRESS (first notification) ────────────────────────────
- variables:
last_wait_timed_out: false
- if:
- condition: template
value_template: '{{ actions_list | length > 0 }}'
then:
- if:
- condition: template
value_template: '{{ repeat_notification }}'
then:
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_1 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_2 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_3 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_stop }}'
timeout: !input time_between_repeat_notification
continue_on_timeout: true
else:
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_1 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_2 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_3 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_stop }}'
timeout: !input action_button_timeout
continue_on_timeout: true
# ── BUTTON PRESSED → fire action and stop ─────────────────────────────
- choose:
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_1 }}'
sequence:
- sequence: !input action_button_1_action
- stop: "Button 1 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_2 }}'
sequence:
- sequence: !input action_button_2_action
- stop: "Button 2 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_3 }}'
sequence:
- sequence: !input action_button_3_action
- stop: "Button 3 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_stop }}'
sequence:
- stop: "Alert cancelled by user"
# ── TIMEOUT → track it, fall through to repeat loop ──────────────────
- variables:
last_wait_timed_out: '{{ wait.trigger is none }}'
# ── REPEAT LOOP ───────────────────────────────────────────────────────────
- if:
- condition: template
value_template: '{{ repeat_notification and (max_repeat_count == 0 or repeat_count < max_repeat_count) }}'
then:
- variables:
repeat_count: '{{ repeat_count + 1 }}'
- if:
- condition: template
value_template: '{{ include_persistence == true }}'
then:
- service: timer.start
entity_id: !input persistence_timer
data:
duration: !input time_between_repeat_notification
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{{ {"fired_at": trigger_fired_at, "count": repeat_count} | to_json }}'
- if:
- condition: template
value_template: '{{ actions_list | length == 0 }}'
then:
- delay: !input time_between_repeat_notification
- repeat:
while:
- condition: template
value_template: '{{ repeat_notification and (max_repeat_count == 0 or repeat_count < max_repeat_count) }}'
sequence:
- parallel:
- repeat:
count: '{{ number_of_notify_services }}'
sequence:
- variables:
time_since_trigger: >
{% set diff = (as_timestamp(now()) - trigger_fired_at) | int %}
{% set h = diff // 3600 %}
{% set m = (diff % 3600) // 60 %}
{% set s = diff % 60 %}
{% if h > 0 %}{{ h }}h {% endif %}{% if m > 0 %}{{ m }}m {% endif %}{% if h == 0 and s > 0 %}{{ s }}s{% endif %}
- variables:
current_message: !input notification_message
- variables:
current_title: !input notification_title
- service: '{{ notify_services_list[repeat.index-1] }}'
data:
message: '{{ current_message }}'
title: >
{{ current_title }}{% if show_repeat_count %} ({{ repeat_count + 1 }}){% endif %}
data:
clickAction: !input notification_click_url
url: !input notification_click_url
tag: '{{ notification_tag }}'
color: !input notification_color
notification_icon: 'mdi:{{ notification_icon_warning }}'
push:
interruption-level: !input notification_interruption_level
persistent: !input notification_persistent
sticky: !input notification_persistent
actions: '{{ actions_list }}'
- choose: []
default: !input custom_action_issue_state
# ── WAIT FOR BUTTON PRESS ON REPEAT ───────────────────────────
- if:
- condition: template
value_template: '{{ actions_list | length > 0 }}'
then:
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_1 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_2 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_3 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_stop }}'
timeout: !input time_between_repeat_notification
continue_on_timeout: true
# ── BUTTON PRESSED → fire action and stop repeats ──────────
- choose:
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_1 }}'
sequence:
- sequence: !input action_button_1_action
- stop: "Button 1 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_2 }}'
sequence:
- sequence: !input action_button_2_action
- stop: "Button 2 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_3 }}'
sequence:
- sequence: !input action_button_3_action
- stop: "Button 3 pressed"
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_stop }}'
sequence:
- stop: "Alert cancelled by user"
# ── TIMEOUT → track it, continue loop ─────────────────────
- variables:
last_wait_timed_out: '{{ wait.trigger is none }}'
- variables:
repeat_count: '{{ repeat_count + 1 }}'
- if:
- condition: template
value_template: '{{ include_persistence == true }}'
then:
- service: timer.start
entity_id: !input persistence_timer
data:
duration: !input time_between_repeat_notification
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{{ {"fired_at": trigger_fired_at, "count": repeat_count} | to_json }}'
- if:
- condition: template
value_template: '{{ actions_list | length == 0 }}'
then:
- delay: !input time_between_repeat_notification
# ── AUTO-ACTIONS (failsafe — fires only if all repeats exhausted unanswered)
- if:
- condition: template
value_template: '{{ actions_list | length > 0 and last_wait_timed_out }}'
then:
- if:
- condition: template
value_template: '{{ "enable_auto_action_1" in include_auto_actions }}'
then: !input action_button_1_action
- if:
- condition: template
value_template: '{{ "enable_auto_action_2" in include_auto_actions }}'
then: !input action_button_2_action
- if:
- condition: template
value_template: '{{ "enable_auto_action_3" in include_auto_actions }}'
then: !input action_button_3_action
# ── FINAL NOTIFICATION ───────────────────────────────────────────────────
- if:
- condition: template
value_template: '{{ repeat_notification and enable_final_notification and last_wait_timed_out }}'
then:
- parallel:
- repeat:
count: '{{ number_of_notify_services }}'
sequence:
- variables:
time_since_trigger: >
{% set diff = (as_timestamp(now()) - trigger_fired_at) | int %}
{% if diff < 1 %}
just now
{% else %}
{% set h = diff // 3600 %}
{% set m = (diff % 3600) // 60 %}
{% set s = diff % 60 %}
{% if h > 0 %}{{ h }}h {% endif %}{% if m > 0 %}{{ m }}m {% endif %}{% if h == 0 and s > 0 %}{{ s }}s{% endif %}
{% endif %}
- variables:
final_title: !input final_notification_title
- variables:
final_message: !input final_notification_message
- service: '{{ notify_services_list[repeat.index-1] }}'
data:
message: '{{ final_message }}'
title: '{{ final_title }}'
data:
clickAction: !input notification_click_url
url: !input notification_click_url
tag: '{{ notification_tag }}'
color: !input notification_color
notification_icon: 'mdi:{{ notification_icon_warning }}'
push:
interruption-level: !input notification_interruption_level
persistent: !input notification_persistent
sticky: !input notification_persistent
- choose: []
default: !input custom_action_final_notification
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_1 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_2 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_3 }}'
- platform: event
event_type: mobile_app_notification_action
event_data:
action: '{{ action_btn_stop }}'
timeout: !input action_button_timeout
continue_on_timeout: true
- choose:
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_1 }}'
sequence:
- sequence: !input action_button_1_action
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_2 }}'
sequence:
- sequence: !input action_button_2_action
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_3 }}'
sequence:
- sequence: !input action_button_3_action
- conditions:
- condition: template
value_template: '{{ wait.trigger is not none and wait.trigger.event.data.action == action_btn_stop }}'
sequence:
- stop: "Alert cancelled by user"
# ── AUTO-CLEAR ────────────────────────────────────────────────────────────
- if:
- condition: template
value_template: >
{{ notification_tag != '' and
(auto_clear_delay.hours | default(0) | int) +
(auto_clear_delay.minutes | default(0) | int) +
(auto_clear_delay.seconds | default(0) | int) > 0 }}
then:
- delay: !input auto_clear_delay
- repeat:
count: '{{ number_of_notify_services }}'
sequence:
- service: '{{ notify_services_list[repeat.index-1] }}'
data:
message: clear_notification
data:
tag: '{{ notification_tag }}'
- if:
- condition: template
value_template: '{{ include_persistence == true }}'
then:
- service: timer.cancel
entity_id: !input persistence_timer
- service: input_text.set_value
entity_id: !input persistence_text
data:
value: '{}'
- choose: []
default: !input custom_action_on_clear

Comments are disabled for this gist.