From 3e98e14882cab20bbed9c777f9b35bcf870899de Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 23 Apr 2026 20:34:13 +0300 Subject: [PATCH] Add debug mode and harden notify flow in `Alarm Notification` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `debug_enabled` input that surfaces per-stage persistent notifications ([1/4] trigger, [1.5/4] conditions, [2/4] message, [2.25/4]/[2.75/4] around the service call, [3/4] result, [3.5/4] post-branch checkpoint, [4/4] alarm control). - Include a `blueprint_version` stamp in [1/4] so it's obvious when a stale cached copy is still running. - Gate the notify branch on `trigger.id == 'sensor_on'` so clearing a sensor no longer fires a stray notification when another sensor is still active. - Replace `binary_sensors.index()` (sandbox-restricted) with a namespace loop and drop the `sensor_index.__class__` reference that broke the [2/4] render. - Drop `response_variable` from the notify call — it could abort the script past `continue_on_error` when the service didn't declare response support. - Add `continue_on_error` on notify and alarm service calls so one misconfigured step doesn't cancel the whole automation. - Tighten `has_notify_target`/`has_alarm_switch` to also reject whitespace, '[]', and 'None' string forms. Version bumped 2.5.2 → 2.6.4. --- Common/Alarm Notification/README.md | 45 +++- Common/Alarm Notification/blueprint.yaml | 286 ++++++++++++++++++++--- manifest.json | 2 +- 3 files changed, 300 insertions(+), 33 deletions(-) diff --git a/Common/Alarm Notification/README.md b/Common/Alarm Notification/README.md index 08851bd..379849d 100644 --- a/Common/Alarm Notification/README.md +++ b/Common/Alarm Notification/README.md @@ -8,7 +8,50 @@ This blueprint monitors multiple binary sensors and triggers push notifications - Debounce timer to prevent false alarms from brief sensor triggers - Optional melody and volume selection for alarm devices - Automatic alarm turn-off when all sensors clear +- Notifications are only sent on sensor ON (not on clear) +- Built-in debug mode that creates persistent notifications at each stage + +## Debug Mode + +Enable the **Debug** → **Enable Debug Logging** toggle to create persistent +notifications at each execution stage. Use this when notifications are not +reaching the target device: + +1. **[1/4] Trigger Received** — shows trigger ID, entity, state transition, + which sensors are currently on, the resolved notify target, and whether + it is considered "usable" by the condition check. +2. **[2/4] Sending Notification** — shows the target entity, sensor index, + and the exact message that will be sent. +3. **[3/4] Notification Sent** — shown after the `notify.send_message` call. + Contains a troubleshooting checklist if the notification never arrives. +4. **[4/4] Alarm Control Done** — shows which alarm switch action was taken. + +Additionally, stage info is written to the HA system log under the +`blueprint.alarm_notification` logger. + +### Common causes of "no notification delivered" + +- The notify integration is offline or the target device has notifications + disabled. +- `notify_target` is not actually a notify **entity**. Test from Developer + Tools → Actions → `notify.send_message` with the same target and + message; if that fails there, it will fail in the blueprint too. +- The sensor state change was shorter than the debounce duration and the + `sensor_on` trigger never fired. Temporarily set debounce to `0` to rule + this out. +- The notify integration rejects the message (e.g., Telegram bot with an + invalid `chat_id` in its configuration). Check the HA log for the + failing service call. + +### Implementation note: notify via `notify.send_message` + +The blueprint uses `action: notify.send_message` with +`target.entity_id: "{{ notify_target }}"`. This is the canonical modern HA +pattern and requires `notify_target` to be a notify **entity** (visible in +Developer Tools → States). Integrations that only register a `notify.` +service but no entity are not supported by this blueprint directly — create +a notify entity for them or adapt the blueprint. ## Author -Alexei Dolgolyov (dolgolyov.alexei@gmail.com) +Alexei Dolgolyov () diff --git a/Common/Alarm Notification/blueprint.yaml b/Common/Alarm Notification/blueprint.yaml index 393f0d8..e26c359 100644 --- a/Common/Alarm Notification/blueprint.yaml +++ b/Common/Alarm Notification/blueprint.yaml @@ -1,5 +1,5 @@ # Multi-Sensor Alarm & Notification Blueprint -# Monitors sensors and triggers notifications and alarms when activated. +# Monitors sensors and triggers notifications and alarm actions when activated. # See README.md for detailed documentation. blueprint: @@ -119,6 +119,23 @@ blueprint: selector: text: + # ------------------------------------------------------------------------- + # Debug Configuration + # ------------------------------------------------------------------------- + debug_group: + name: "Debug" + collapsed: true + input: + debug_enabled: + name: Enable Debug Logging + description: > + When enabled, creates persistent notifications at each stage of execution: + trigger received, notification attempt, notification result, alarm control. + Use to troubleshoot why notifications aren't reaching the target. + default: false + selector: + boolean: + # Restart mode ensures rapid sensor changes are handled cleanly mode: restart @@ -153,23 +170,31 @@ variables: melody_id: !input melody_id volume_select: !input volume_select volume_id: !input volume_id + is_debug: !input debug_enabled # Computed state values enabled_sensors: "{{ binary_sensors | select('is_state', 'on') | list }}" - is_any_sensor_on: "{{ enabled_sensors | length > 0 }}" + is_any_sensor_on: "{{ (binary_sensors | select('is_state', 'on') | list | length) > 0 }}" + + # Whether a usable notify target is configured + has_notify_target: "{{ notify_target is not none and (notify_target | string | trim) not in ['', '[]', 'None'] }}" + + # Whether alarm control is configured + has_alarm_switch: "{{ alarm_switch is not none and (alarm_switch | string | trim) not in ['', '[]', 'None'] }}" # Small delay between setting melody/volume to ensure device processes each command delay_between_commands_ms: 100 - # Debug flag - set to true to enable persistent notifications for troubleshooting - is_debug: false + # Version stamp so debug output can confirm which blueprint revision is live. + # Bump this whenever debug/flow logic changes. + blueprint_version: "2.6.4 (notify.send_message + entity_id)" # ============================================================================= # Actions # ============================================================================= action: # --------------------------------------------------------------------------- - # Debug Logging (optional) + # Debug Stage 1: Trigger Received # --------------------------------------------------------------------------- - choose: - conditions: @@ -178,45 +203,222 @@ action: sequence: - service: persistent_notification.create data: - title: "Alarm Notification Debug" + notification_id: "alarm_debug_trigger" + title: "Alarm Debug [1/4]: Trigger Received" message: > - Trigger: {{ trigger.id }} + Blueprint version: {{ blueprint_version }} + + + Trigger ID: {{ trigger.id }} + Entity: {{ trigger.entity_id }} - Sensors: {{ binary_sensors }} - Active: {{ enabled_sensors }} - Any On: {{ is_any_sensor_on }} + From: {{ trigger.from_state.state if trigger.from_state is not none else 'n/a' }} + → To: {{ trigger.to_state.state if trigger.to_state is not none else 'n/a' }} + + + All monitored sensors: {{ binary_sensors }} + + Currently active (on): {{ enabled_sensors }} + + Any sensor on: {{ is_any_sensor_on }} + + + Notify target: {{ notify_target }} + + Has notify target: {{ has_notify_target }} + + Alarm switch: {{ alarm_switch }} + + Has alarm switch: {{ has_alarm_switch }} + - service: system_log.write + data: + level: info + logger: blueprint.alarm_notification + message: >- + Trigger '{{ trigger.id }}' from {{ trigger.entity_id }}; + active={{ enabled_sensors }}; + notify_target={{ notify_target }}; + has_notify_target={{ has_notify_target }} # --------------------------------------------------------------------------- - # Send Notification (when sensor turns ON) + # Debug Stage 1.5: What the notification-branch conditions evaluate to # --------------------------------------------------------------------------- - choose: - conditions: - condition: template - # Only notify if a sensor is on and notification target is configured - value_template: "{{ is_any_sensor_on and notify_target is not none }}" + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_pre_notify" + title: "Alarm Debug [1.5/4]: Evaluating notify conditions" + message: > + trigger.id = {{ trigger.id }} + + trigger.id == 'sensor_on': {{ trigger.id == 'sensor_on' }} + + has_notify_target: {{ has_notify_target }} + + Combined (should be True to enter notify branch): + {{ trigger.id == 'sensor_on' and has_notify_target }} + + # --------------------------------------------------------------------------- + # Send Notification (only on sensor_on, when target configured) + # --------------------------------------------------------------------------- + - choose: + - conditions: + # Single template condition avoids any quirks with condition: trigger + # inside a choose block. + - condition: template + value_template: "{{ trigger.id == 'sensor_on' and has_notify_target }}" sequence: - variables: - # Get the sensor that triggered this automation + # The sensor that fired this trigger sensor: "{{ trigger.entity_id }}" - # Find index of this sensor in the list - sensor_index: > - {% set idx = binary_sensors | list %} - {{ idx.index(sensor) if sensor in idx else -1 }} - # Get custom message or use default - message: > - {% set messages = notify_texts | list %} - {% if sensor_index >= 0 and sensor_index < messages | length %} - {{ messages[sensor_index] }} - {% else %} - Alarm: {{ state_attr(sensor, 'friendly_name') | default(sensor) }} triggered - {% endif %} + # Namespace-loop avoids Jinja sandbox restrictions on list.index() + sensor_index: >- + {%- set ns = namespace(idx=-1) -%} + {%- for s in binary_sensors -%} + {%- if s == sensor -%}{%- set ns.idx = loop.index0 -%}{%- endif -%} + {%- endfor -%} + {{ ns.idx }} + # Resolve message (strip whitespace to keep notifications clean) + message: >- + {%- set messages = notify_texts | list -%} + {%- set idx = sensor_index | int(-1) -%} + {%- if idx >= 0 and idx < messages | length -%} + {{ messages[idx] }} + {%- else -%} + Alarm: {{ state_attr(sensor, 'friendly_name') or sensor }} triggered + {%- endif -%} - - service: notify.send_message + # Debug Stage 2: Variables computed, about to send + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_notify_attempt" + title: "Alarm Debug [2/4]: Sending Notification" + message: > + Target: {{ notify_target }} + + Sensor: {{ sensor }} + + Sensor index (raw): "{{ sensor_index }}" + + Messages defined: {{ notify_texts | length }} + + Resolved message: "{{ message }}" + + # Debug Stage 2.25: About to invoke service + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_pre_service" + title: "Alarm Debug [2.25/4]: Before notify.send_message" + message: > + Blueprint version: {{ blueprint_version }} + + About to call: action notify.send_message + + with target.entity_id: {{ notify_target }} + + and data.message: "{{ message }}" + + # Send via notify.send_message targeting the notify entity. + # This matches the canonical HA pattern used by working automations. + # continue_on_error ensures the alarm-control stage still runs + # if the notify integration is misconfigured. + - action: notify.send_message + metadata: {} target: entity_id: "{{ notify_target }}" data: message: "{{ message }}" + continue_on_error: true + + # Debug Stage 2.75: Service call returned (success or caught error) + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_post_service" + title: "Alarm Debug [2.75/4]: After notify.send_message" + message: > + notify.send_message to {{ notify_target }} returned + (either success or continue_on_error caught it). + + If this notification is missing but [2.25/4] is + present, the call raised an error that + continue_on_error did NOT catch. Check the HA log + and verify {{ notify_target }} is a notify entity + (Developer Tools → States should list it). + + # Debug Stage 3: Notification result + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_notify_result" + title: "Alarm Debug [3/4]: Notification Sent" + message: > + notify.send_message dispatched to {{ notify_target }} + with message: "{{ message }}". + + If nothing arrived on the device, check: + + 1) HA log for errors from the notify integration + + 2) That the underlying integration (Telegram bot, + mobile app, etc.) is online + + 3) Test from Developer Tools → Actions: + notify.send_message with target {{ notify_target }} + + - service: system_log.write + data: + level: info + logger: blueprint.alarm_notification + message: >- + Notification attempted to {{ notify_target }}; + sensor={{ sensor }}; message='{{ message }}' + + # --------------------------------------------------------------------------- + # Debug Stage 3.5: Unconditional checkpoint so we can tell whether the + # notification branch silently aborted the script above. If this appears + # but [2/4] or [3/4] don't, the variables block or notify.send_message + # errored silently. + # --------------------------------------------------------------------------- + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_after_notify" + title: "Alarm Debug [3.5/4]: After notify branch" + message: > + Execution reached this point. trigger.id = {{ trigger.id }}. + + If [2/4] and [3/4] did NOT appear above, the notify branch + either did not enter (check [1.5/4]), or errored silently + in the variables / notify.send_message step. + Check Home Assistant log for Jinja or service errors. # --------------------------------------------------------------------------- # Alarm Device Control @@ -224,20 +426,20 @@ action: - choose: - conditions: - condition: template - # Only control alarm if alarm switch is configured - value_template: "{{ alarm_switch | length > 0 }}" + value_template: "{{ has_alarm_switch }}" sequence: # Set melody (if configured) - choose: - conditions: - condition: template - value_template: "{{ melody_select | length > 0 and melody_id | length > 0 }}" + value_template: "{{ melody_select is not none and (melody_select | string | trim) not in ['', '[]', 'None'] and (melody_id | string | length) > 0 }}" sequence: - service: select.select_option target: entity_id: "{{ melody_select }}" data: option: "{{ melody_id }}" + continue_on_error: true - delay: milliseconds: "{{ delay_between_commands_ms }}" @@ -245,13 +447,14 @@ action: - choose: - conditions: - condition: template - value_template: "{{ volume_select | length > 0 and volume_id | length > 0 }}" + value_template: "{{ volume_select is not none and (volume_select | string | trim) not in ['', '[]', 'None'] and (volume_id | string | length) > 0 }}" sequence: - service: select.select_option target: entity_id: "{{ volume_select }}" data: option: "{{ volume_id }}" + continue_on_error: true - delay: milliseconds: "{{ delay_between_commands_ms }}" @@ -265,9 +468,30 @@ action: - service: switch.turn_on target: entity_id: "{{ alarm_switch }}" + continue_on_error: true # All sensors clear -> turn alarm OFF default: - service: switch.turn_off target: entity_id: "{{ alarm_switch }}" + continue_on_error: true + + # Debug Stage 4: Alarm control done + - choose: + - conditions: + - condition: template + value_template: "{{ is_debug }}" + sequence: + - service: persistent_notification.create + data: + notification_id: "alarm_debug_alarm_control" + title: "Alarm Debug [4/4]: Alarm Control Done" + message: > + Alarm switch: {{ alarm_switch }} + + Action: {{ 'turn_on' if is_any_sensor_on else 'turn_off' }} + + Melody: {{ melody_id }} → {{ melody_select }} + + Volume: {{ volume_id }} → {{ volume_select }} diff --git a/manifest.json b/manifest.json index 3ae48a7..7962e7a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,3 +1,3 @@ { - "version": "2.5.2" + "version": "2.6.4" }