Ring Alarm — Whole-House Audio Feedback Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Give the Ring alarm whole-house spoken feedback across its full lifecycle (trigger, exit/entry delay, arm, disarm, failed-to-arm), every knob live-tunable from a dedicated dashboard with per-event test buttons.
Architecture: Three layers. script.alarm_speak sets Echo volume then Alexa-announces a message to a speaker list. script.alarm_audio_event maps an event name → speakers + volume (from input_number helpers), gates on enable toggles, and calls alarm_speak. Per-transition automations build the live message and call alarm_audio_event; a single test-button automation and the six input_buttons reuse the same path with force: true.
Tech Stack: Home Assistant YAML (automations, scripts, helpers), Jinja2 templates, alexa_media notify services, a YAML-mode Lovelace dashboard, pytest test suite (tests/).
Global Constraints¶
- Spec:
docs/superpowers/specs/2026-07-05-ring-alarm-audio-feedback-design.md. - Alarm panel entity:
alarm_control_panel.blairmont_alarm(ring-mqtt). States:disarmed,armed_home,armed_away,arming,pending,triggered. Attributes:exitSecondsLeft,entrySecondsLeft,targetState. - The 7 Echoes (
ALL_ECHOES):media_player.kitchen_echo_show,media_player.family_room_echo,media_player.front_room_echo,media_player.garage_echo,media_player.master_bathroom_echo,media_player.master_bedroom_echo,media_player.office_echo. - Announce mechanism: per-device
notify.alexa_media_<object_id>withdata.type: announce(matches the #61 migration; no dependency on a genericnotify.alexa_media). - Helpers: define in
configuration.yamlwith NOinitial:so dashboard tweaks persist across restarts (same convention asinput_number.precool_high_threshold). - HA never arms/disarms/dispatches — this is observe-and-announce only. Do not add arm/disarm service calls.
- Never edit
automation_*.yamlfragments — all automations live inautomations.yaml. - Commit style: Conventional Commits, scope
alarm. End each commit body withCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>. - Test cycle:
make lint && make pytestafter every task (fast, no Docker). Run fullmake test(addscheck-config, needs Docker) once before the final push (Task 8). - Branch: all work on
feat/ring-alarm-audio(already checked out).
Task 1: Tunable helpers + allowlist forward-references¶
Files: - Modify: configuration.yaml (append to input_boolean: and input_number:; add a new input_button: top-level section) - Modify: tests/fixtures/entities_allow.txt
Interfaces: - Produces: 7 booleans input_boolean.alarm_audio_enabled, input_boolean.alarm_audio_{trigger,exit,entry,armed,disarmed,failed}_enabled; 8 numbers input_number.alarm_audio_{trigger,exit,entry,armed,disarmed,failed}_volume, input_number.alarm_audio_reset_volume, input_number.alarm_audio_trigger_interval; 6 buttons input_button.alarm_audio_test_{trigger,exit,entry,armed,disarmed,failed}.
- [ ] Step 1: Append the enable booleans to the existing
input_boolean:block inconfiguration.yaml
# ── Ring Alarm audio feedback (see docs/…/2026-07-05-ring-alarm-audio-feedback-design.md)
# Live on/off switches surfaced on the "Alarm Audio" dashboard. NO `initial:` so the
# dashboard state persists across restarts (restore_state); flip them on once after deploy.
alarm_audio_enabled:
name: "Alarm Audio — Master"
icon: mdi:bullhorn
alarm_audio_trigger_enabled:
name: "Alarm Audio — Trigger"
icon: mdi:bullhorn-alert
alarm_audio_exit_enabled:
name: "Alarm Audio — Exit Delay"
icon: mdi:exit-run
alarm_audio_entry_enabled:
name: "Alarm Audio — Entry Delay"
icon: mdi:location-enter
alarm_audio_armed_enabled:
name: "Alarm Audio — Armed"
icon: mdi:shield-check
alarm_audio_disarmed_enabled:
name: "Alarm Audio — Disarmed"
icon: mdi:shield-off
alarm_audio_failed_enabled:
name: "Alarm Audio — Failed to Arm"
icon: mdi:shield-alert
- [ ] Step 2: Append the volume/interval numbers to the existing
input_number:block
# ── Ring Alarm audio feedback: per-event announce volumes (0–1) + trigger repeat interval.
# NO `initial:` so dashboard tuning persists; each starts at its `min` on first boot after
# deploy — set to the design defaults (below, in the `name`) once from the Alarm Audio dashboard.
alarm_audio_trigger_volume:
name: "Trigger Volume (0.80)"
icon: mdi:volume-high
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_exit_volume:
name: "Exit-Delay Volume (0.60)"
icon: mdi:volume-medium
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_entry_volume:
name: "Entry-Delay Volume (0.80)"
icon: mdi:volume-high
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_armed_volume:
name: "Armed Volume (0.30)"
icon: mdi:volume-low
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_disarmed_volume:
name: "Disarmed Volume (0.30)"
icon: mdi:volume-low
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_failed_volume:
name: "Failed-to-Arm Volume (0.60)"
icon: mdi:volume-medium
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_reset_volume:
name: "Echo Reset Volume (0.40)"
icon: mdi:volume-source
min: 0
max: 1
step: 0.05
mode: slider
alarm_audio_trigger_interval:
name: "Trigger Repeat Interval (12s)"
icon: mdi:timer-sync
min: 5
max: 60
step: 1
unit_of_measurement: "s"
mode: box
- [ ] Step 3: Add a new top-level
input_button:section toconfiguration.yaml(there is none yet — place it right after theinput_number:block ends)
##############################################################################
# INPUT BUTTONS
##############################################################################
input_button:
# Alarm Audio dial-in: each press previews that event's announcement on its
# assigned speakers at its current volume, bypassing the enable toggles.
alarm_audio_test_trigger:
name: "Test — Trigger Announcement"
icon: mdi:play-circle
alarm_audio_test_exit:
name: "Test — Exit Delay"
icon: mdi:play-circle
alarm_audio_test_entry:
name: "Test — Entry Delay"
icon: mdi:play-circle
alarm_audio_test_armed:
name: "Test — Armed"
icon: mdi:play-circle
alarm_audio_test_disarmed:
name: "Test — Disarmed"
icon: mdi:play-circle
alarm_audio_test_failed:
name: "Test — Failed to Arm"
icon: mdi:play-circle
- [ ] Step 4: Append the forward-references to
tests/fixtures/entities_allow.txt(they won't be in the snapshot until the post-deploymake snapshot)
# Ring Alarm audio feedback helpers (configuration.yaml) — created on the next HA
# restart after deploy; fold into entities.txt with `make snapshot` post-deploy,
# then delete this block. Spec: docs/…/2026-07-05-ring-alarm-audio-feedback-design.md
input_boolean.alarm_audio_enabled
input_boolean.alarm_audio_trigger_enabled
input_boolean.alarm_audio_exit_enabled
input_boolean.alarm_audio_entry_enabled
input_boolean.alarm_audio_armed_enabled
input_boolean.alarm_audio_disarmed_enabled
input_boolean.alarm_audio_failed_enabled
input_number.alarm_audio_trigger_volume
input_number.alarm_audio_exit_volume
input_number.alarm_audio_entry_volume
input_number.alarm_audio_armed_volume
input_number.alarm_audio_disarmed_volume
input_number.alarm_audio_failed_volume
input_number.alarm_audio_reset_volume
input_number.alarm_audio_trigger_interval
input_button.alarm_audio_test_trigger
input_button.alarm_audio_test_exit
input_button.alarm_audio_test_entry
input_button.alarm_audio_test_armed
input_button.alarm_audio_test_disarmed
input_button.alarm_audio_test_failed
- [ ] Step 5: Run lint + tests
Run: make lint && make pytest Expected: PASS (yamllint clean; no entity-ref regressions — the new helpers are defined but not yet referenced, so Tier 3 is unaffected this task).
- [ ] Step 6: Commit
git add configuration.yaml tests/fixtures/entities_allow.txt
git commit -m "feat(alarm): add tunable helpers for whole-house alarm audio
7 enable toggles, 8 volume/interval numbers, 6 test buttons (no initial:
so dashboard tuning persists). Forward-ref'd in entities_allow.txt until
the post-deploy snapshot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 2: Audio primitive scripts (alarm_speak, alarm_audio_event)¶
Files: - Modify: scripts.yaml (add two scripts near the end, before the corner-lamp scene block or at end of file)
Interfaces: - Consumes: helpers from Task 1; the 7 Echo media_players. - Produces: - script.alarm_speak(message: str, speakers: list[str], volume: float) — volume_set + per-device announce. - script.alarm_audio_event(event: str, message: str, force: bool=false) — enable-gates, resolves speakers+volume by event, calls alarm_speak. Valid event values: trigger, exit, entry, armed, disarmed, failed.
- [ ] Step 1: Add
script.alarm_speaktoscripts.yaml
# ── Ring Alarm audio: low-level fan-out. Sets volume on the target Echoes, then
# announces `message` on each via its per-device alexa_media notify service
# (notify.alexa_media_<object_id>, data.type announce — the #61 migration path).
# The 0.4s delay lets volume_set settle before the announce fires.
alarm_speak:
alias: "Alarm — Speak"
icon: mdi:bullhorn
fields:
message:
description: "Text to announce"
example: "Alarm disarmed."
speakers:
description: "List of Echo media_player entity_ids"
example: "['media_player.master_bedroom_echo']"
volume:
description: "0.0–1.0 volume set before announcing"
example: 0.6
mode: parallel
max: 10
sequence:
- action: media_player.volume_set
target:
entity_id: "{{ speakers }}"
data:
volume_level: "{{ volume | float(0.4) }}"
- delay: "00:00:00.4"
- repeat:
for_each: "{{ speakers }}"
sequence:
- action: "notify.alexa_media_{{ repeat.item.split('.')[1] }}"
data:
message: "{{ message }}"
data:
type: announce
- [ ] Step 2: Add
script.alarm_audio_eventtoscripts.yaml(immediately afteralarm_speak)
# ── Ring Alarm audio: mid-level policy. Maps an event name → speaker set + volume
# helper, gates on the master + per-event enable toggles (bypassed by force: true
# for the dashboard test buttons), then calls script.alarm_speak. Single source of
# the per-event speaker assignment.
alarm_audio_event:
alias: "Alarm — Audio Event"
icon: mdi:bullhorn-variant
fields:
event:
description: "One of: trigger, exit, entry, armed, disarmed, failed"
example: disarmed
message:
description: "Pre-built text to speak (caller fills live vars)"
example: "Alarm disarmed."
force:
description: "true = bypass enable toggles (test buttons)"
default: false
mode: parallel
max: 10
variables:
speakers: >-
{% set allx = ['media_player.kitchen_echo_show','media_player.family_room_echo',
'media_player.front_room_echo','media_player.garage_echo',
'media_player.master_bathroom_echo','media_player.master_bedroom_echo',
'media_player.office_echo'] %}
{% set bedoff = ['media_player.master_bedroom_echo','media_player.office_echo'] %}
{% set m = {'trigger': allx, 'exit': ['media_player.front_room_echo'],
'entry': allx, 'armed': bedoff, 'disarmed': bedoff, 'failed': bedoff} %}
{{ m.get(event, bedoff) }}
vol: "{{ states('input_number.alarm_audio_' ~ event ~ '_volume') | float(0.4) }}"
ev_on: "{{ is_state('input_boolean.alarm_audio_' ~ event ~ '_enabled', 'on') }}"
master_on: "{{ is_state('input_boolean.alarm_audio_enabled', 'on') }}"
sequence:
- condition: "{{ force or (master_on and ev_on) }}"
- action: script.alarm_speak
data:
message: "{{ message }}"
speakers: "{{ speakers }}"
volume: "{{ vol }}"
- [ ] Step 3: Run lint + tests
Run: make lint && make pytest Expected: PASS. Tier 3 resolves input_number.alarm_audio_*_volume / input_boolean.alarm_audio_* via the Task 1 allowlist; the 7 Echoes exist in entities.txt.
- [ ] Step 4: Commit
git add scripts.yaml
git commit -m "feat(alarm): add alarm_speak + alarm_audio_event primitives
alarm_speak = volume_set + per-device alexa_media announce fan-out.
alarm_audio_event = event→speaker/volume map + enable-gating (force bypass
for test buttons). Single source of per-event speaker assignment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 3: Lifecycle automations + strip #50's old announce¶
Files: - Modify: automations.yaml (§22 block, ~lines 2775–2896): delete the single kitchen-announce action in #50; add six new automations.
Interfaces: - Consumes: script.alarm_audio_event (Task 2); input_number.alarm_audio_trigger_interval, input_number.alarm_audio_reset_volume (Task 1).
-
[ ] Step 1: In automation #50 ("Alarm — Ring Triggered Critical Alert"), delete ONLY the Alexa announce action (the
- alias: "Alexa announce — kitchen Echo"step and itsaction/data— lines ~2833–2838). Leave the two critical pushes and the persistent notification untouched. The new Trigger Loop (below) replaces house-wide audio. -
[ ] Step 2: Add the Trigger Loop automation (in §22, after #51 the Night Lights Beacon)
# 52. Alarm Audio — Trigger Loop (repeats the intrusion announcement house-wide)
- alias: "Alarm Audio — Trigger Loop"
id: alarm_audio_trigger_loop
description: >
While alarm_control_panel.blairmont_alarm is `triggered`, re-announce the
intrusion on all 7 Echoes every input_number.alarm_audio_trigger_interval
seconds until cleared. Mirrors §51's beacon loop (mode: restart).
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: triggered
mode: restart
max_exceeded: silent
variables:
zones: >
{% set ns = namespace(z=[]) %}
{% for e in ['binary_sensor.personnel_door', 'binary_sensor.foyer_motion_sensor'] %}
{% if is_state(e, 'on') %}{% set ns.z = ns.z + [state_attr(e, 'friendly_name')] %}{% endif %}
{% endfor %}
{{ ns.z | join(', ') if ns.z else 'zone unknown — check the Ring app' }}
actions:
- repeat:
while:
- condition: state
entity_id: alarm_control_panel.blairmont_alarm
state: triggered
sequence:
- action: script.alarm_audio_event
data:
event: trigger
message: "Security alert. The Ring alarm has been triggered. {{ zones }}."
- delay:
seconds: "{{ states('input_number.alarm_audio_trigger_interval') | int(12) }}"
- [ ] Step 3: Add the Exit-Delay automation
# 53. Alarm Audio — Exit Delay (front room echo; boundary announce + 10s warning)
- alias: "Alarm Audio — Exit Delay"
id: alarm_audio_exit_delay
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: arming
mode: restart
variables:
target: "{{ 'home' if is_state_attr('alarm_control_panel.blairmont_alarm','targetState','armed_home') else 'away' }}"
secs: "{{ state_attr('alarm_control_panel.blairmont_alarm','exitSecondsLeft') | int(60) }}"
actions:
- action: script.alarm_audio_event
data:
event: exit
message: "Arming to {{ target }}. You have {{ secs }} seconds to exit."
- delay:
seconds: "{{ [secs - 10, 0] | max }}"
- condition: state
entity_id: alarm_control_panel.blairmont_alarm
state: arming
- action: script.alarm_audio_event
data:
event: exit
message: "Ten seconds."
- [ ] Step 4: Add the Entry-Delay automation
# 54. Alarm Audio — Entry Delay (all echoes: disarm-now prompt)
- alias: "Alarm Audio — Entry Delay"
id: alarm_audio_entry_delay
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: pending
mode: restart
actions:
- action: script.alarm_audio_event
data:
event: entry
message: "Entry delay. Disarm now."
- [ ] Step 5: Add the Armed automation
# 55. Alarm Audio — Armed (bedroom + office echo confirmation)
- alias: "Alarm Audio — Armed"
id: alarm_audio_armed
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: armed_home
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: armed_away
variables:
target: "{{ 'home' if trigger.to_state.state == 'armed_home' else 'away' }}"
actions:
- action: script.alarm_audio_event
data:
event: armed
message: "Alarm armed to {{ target }}."
- [ ] Step 6: Add the Disarmed automation (announces + resets all Echoes to the reset volume)
# 56. Alarm Audio — Disarmed (bedroom + office confirm; reset all echoes to reset volume)
- alias: "Alarm Audio — Disarmed"
id: alarm_audio_disarmed
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: disarmed
from: armed_home
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: disarmed
from: armed_away
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: disarmed
from: pending
actions:
- action: script.alarm_audio_event
data:
event: disarmed
message: "Alarm disarmed."
- action: media_player.volume_set
target:
entity_id:
- media_player.kitchen_echo_show
- media_player.family_room_echo
- media_player.front_room_echo
- media_player.garage_echo
- media_player.master_bathroom_echo
- media_player.master_bedroom_echo
- media_player.office_echo
data:
volume_level: "{{ states('input_number.alarm_audio_reset_volume') | float(0.4) }}"
- [ ] Step 7: Add the Failed-to-Arm automation
# 57. Alarm Audio — Failed to Arm (arming aborted back to disarmed)
- alias: "Alarm Audio — Failed to Arm"
id: alarm_audio_failed_arm
description: >
arming → disarmed = an exit-delay that aborted instead of reaching armed_*.
Best-effort (a manual cancel during exit delay also fires this); revisit with a
ring-mqtt fault attribute if false positives are common. See spec.
triggers:
- trigger: state
entity_id: alarm_control_panel.blairmont_alarm
to: disarmed
from: arming
actions:
- action: script.alarm_audio_event
data:
event: failed
message: "The alarm could not arm. Check doors and windows."
- [ ] Step 8: Run lint + tests
Run: make lint && make pytest Expected: PASS. test_unique_automation_ids/aliases pass (new ids/aliases are unique); Tier 3 resolves all refs.
- [ ] Step 9: Commit
git add automations.yaml
git commit -m "feat(alarm): whole-house audio for the alarm lifecycle
Six §22 automations — trigger loop (repeats house-wide), exit-delay (front
room + 10s warning), entry-delay, armed, disarmed (with all-echo volume
reset), failed-to-arm. Removes #50's single kitchen announce (superseded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 4: Test-button automation¶
Files: - Modify: automations.yaml (§22, after #57)
Interfaces: - Consumes: input_button.alarm_audio_test_* (Task 1); script.alarm_audio_event (Task 2).
- [ ] Step 1: Add a single automation fanning all six test buttons into
alarm_audio_eventwithforce: true
# 58. Alarm Audio — Test Button Pressed (dial-in preview, bypasses enable toggles)
- alias: "Alarm Audio — Test Button Pressed"
id: alarm_audio_test_button
description: >
Any input_button.alarm_audio_test_* press previews that event's announcement
(force: true → ignores the enable toggles) with canned sample vars. Event name
is the button's object_id after `alarm_audio_test_`.
triggers:
- trigger: state
entity_id:
- input_button.alarm_audio_test_trigger
- input_button.alarm_audio_test_exit
- input_button.alarm_audio_test_entry
- input_button.alarm_audio_test_armed
- input_button.alarm_audio_test_disarmed
- input_button.alarm_audio_test_failed
variables:
event: "{{ trigger.entity_id.split('alarm_audio_test_')[1] }}"
msgmap:
trigger: "Security alert. The Ring alarm has been triggered. Foyer motion sensor."
exit: "Arming to away. You have 60 seconds to exit."
entry: "Entry delay. Disarm now."
armed: "Alarm armed to home."
disarmed: "Alarm disarmed."
failed: "The alarm could not arm. Check doors and windows."
actions:
- action: script.alarm_audio_event
data:
event: "{{ event }}"
force: true
message: "{{ msgmap[event] }}"
- [ ] Step 2: Run lint + tests
Run: make lint && make pytest Expected: PASS (unique id/alias; input_button.alarm_audio_test_* resolve via allowlist).
- [ ] Step 3: Commit
git add automations.yaml
git commit -m "feat(alarm): test-button automation for audio dial-in
One automation maps the six alarm_audio_test_* button presses to
alarm_audio_event(force: true) with canned sample messages — preview any
event without arming the real alarm.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 5: Alarm Audio dashboard + registration¶
Files: - Create: alarm_audio_dashboard.yaml (repo root) - Modify: configuration.yaml (lovelace.dashboards) - Modify: tests/conftest.py (dashboard refs map)
Interfaces: - Consumes: all Task 1 helpers.
- [ ] Step 1: Create
alarm_audio_dashboard.yaml(plainentitiescards — no custom components; per-cardtitle:is the standard card header, not a standalone title card)
title: Alarm Audio
views:
- title: Alarm Audio
path: default
icon: mdi:bullhorn
cards:
- type: entities
title: Master
entities:
- entity: input_boolean.alarm_audio_enabled
- entity: input_number.alarm_audio_reset_volume
- entity: input_number.alarm_audio_trigger_interval
- type: entities
title: Trigger
entities:
- entity: input_boolean.alarm_audio_trigger_enabled
- entity: input_number.alarm_audio_trigger_volume
- entity: input_button.alarm_audio_test_trigger
- type: entities
title: Exit Delay
entities:
- entity: input_boolean.alarm_audio_exit_enabled
- entity: input_number.alarm_audio_exit_volume
- entity: input_button.alarm_audio_test_exit
- type: entities
title: Entry Delay
entities:
- entity: input_boolean.alarm_audio_entry_enabled
- entity: input_number.alarm_audio_entry_volume
- entity: input_button.alarm_audio_test_entry
- type: entities
title: Armed
entities:
- entity: input_boolean.alarm_audio_armed_enabled
- entity: input_number.alarm_audio_armed_volume
- entity: input_button.alarm_audio_test_armed
- type: entities
title: Disarmed
entities:
- entity: input_boolean.alarm_audio_disarmed_enabled
- entity: input_number.alarm_audio_disarmed_volume
- entity: input_button.alarm_audio_test_disarmed
- type: entities
title: Failed to Arm
entities:
- entity: input_boolean.alarm_audio_failed_enabled
- entity: input_number.alarm_audio_failed_volume
- entity: input_button.alarm_audio_test_failed
- [ ] Step 2: Register the dashboard under
lovelace.dashboardsinconfiguration.yaml(add after thecommand-center:block, ~line 37).url_pathMUST contain a hyphen orcheck_configfails.
alarm-audio:
mode: yaml
title: Alarm Audio
icon: mdi:bullhorn
show_in_sidebar: true
filename: alarm_audio_dashboard.yaml
- [ ] Step 3: Register the dashboard in the entity-ref test — add to the
refsdict intests/conftest.pyall_referenced_entities()(after thecommand_dashboard.yamlline)
# bare_scan: entities-card dashboard referencing helpers via `entity:` (singular),
# which the entity_id:/states(...) regexes miss. All tokens are real helper ids.
"alarm_audio_dashboard.yaml": extract_yaml_refs("alarm_audio_dashboard.yaml", bare_scan=True),
- [ ] Step 4: Run lint + tests
Run: make lint && make pytest Expected: PASS. The new dashboard's helper refs resolve via the Task 1 allowlist. If bare_scan surfaces a stray non-entity token, add it to entities_allow.txt with a comment (mirrors the desk media_player.media_play_pause precedent) and re-run.
- [ ] Step 5: Commit
git add alarm_audio_dashboard.yaml configuration.yaml tests/conftest.py
git commit -m "feat(alarm): Alarm Audio tuning dashboard (separate YAML dashboard)
New sidebar dashboard (url_path alarm-audio) with per-event enable toggle +
volume slider + test button, plus master toggle/reset-volume/interval.
Registered in lovelace.dashboards and the conftest entity-ref test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 6: User manual + CLAUDE.md docs¶
Files: - Modify: docs/notifications.md, docs/security.md, docs/automations.md, CLAUDE.md
Interfaces: none (docs only).
-
[ ] Step 1:
docs/notifications.md— add a subsection listing the six spoken alarm announcements (trigger, exit, entry, armed, disarmed, failed-to-arm): what each says, which room speaks, when it fires. Note these are spoken on Echoes (not push). -
[ ] Step 2:
docs/security.md— add a "Whole-house alarm audio" section: the lifecycle announcements, the repeating trigger, and the Alarm Audio dashboard (enable toggles, volume sliders, test buttons) for tuning. Cross-link with a relative anchor tonotifications.mdif referenced (Material slug: lowercase, spaces→hyphens, punctuation stripped) somkdocs build --strictpasses. -
[ ] Step 3:
docs/automations.md— §22 technical reference: automations #52–#58,script.alarm_speak,script.alarm_audio_event, and the helper list. -
[ ] Step 4:
CLAUDE.md— (a) addAlarm Audio | yaml | alarm_audio_dashboard.yaml | alarm-audioto the dashboards table and the YAML-dashboard deploy list ("three YAML dashboards" → "four"); (b) under §22 in the Automations architecture list, note the audio primitive + six events + the tuning dashboard. -
[ ] Step 5: Verify the docs build
Run: mkdocs build --strict (if mkdocs is available locally; otherwise rely on the docs.yml CI job post-push) Expected: build succeeds, no broken-link/anchor errors.
- [ ] Step 6: Commit
git add docs/notifications.md docs/security.md docs/automations.md CLAUDE.md
git commit -m "docs(alarm): document whole-house alarm audio + tuning dashboard
notifications.md (spoken alerts), security.md (behavior + Alarm Audio
dashboard), automations.md (§22 #52–58 + scripts + helpers), CLAUDE.md
(dashboards table + §22 note).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Task 7: Full-suite verification (with container check_config)¶
Files: none (verification only).
- [ ] Step 1: Run the complete pre-deploy suite (needs Docker for
check-config)
Run: make test Expected: all tiers PASS — yamllint, JSON/Grafana guards, check_config on the pinned HA image (validates the new helpers, input_button: section, both scripts, seven automations, and the hyphenated alarm-audio url_path), unique-id/alias, entity refs.
- [ ] Step 2: If
check_configflags anything (e.g. a helper schema typo, an unknown service, or the dashboard filename), fix inline, re-runmake test, and amend the relevant task's commit or add afix(alarm):commit. Do not proceed until green.
Task 8: Merge to main, deploy, post-deploy snapshot¶
Files: tests/fixtures/entities.txt, tests/fixtures/entities_allow.txt (post-deploy).
- [ ] Step 1: Merge the branch to
mainand push (this is what triggers CI deploy — the feature branch does not deploy)
- [ ] Step 2: Verify the CI run (test job gates deploy;
configuration.yaml+scripts.yamlchanged → fullha core restart)
test job passes and deploy-ha runs. Confirm HA came back: ssh haos "ha core logs --lines 20". -
[ ] Step 3: Turn the audio on and set the design-default volumes ONCE (helpers boot at their
minon first run). On the Alarm Audio dashboard: master ON + each event ON; set volumes trigger 0.80 / exit 0.60 / entry 0.80 / armed 0.30 / disarmed 0.30 / failed 0.60 / reset 0.40; interval 12. Then press each Test button to confirm the right rooms speak at a sane level. Validate open question #3 here (if announces come through at the previous volume, raise thedelayinscript.alarm_speak). -
[ ] Step 4: Fold the helpers into the entity snapshot and drop the allowlist block
tests/fixtures/entities_allow.txt. - [ ] Step 5: Run tests and commit the snapshot
Run: make lint && make pytest Expected: PASS with the helpers now sourced from entities.txt (allowlist block removed).
git add tests/fixtures/entities.txt tests/fixtures/entities_allow.txt
git commit -m "chore(alarm): fold audio helpers into entity snapshot
make snapshot after deploy; remove the forward-ref allowlist block now that
the helpers are in the live registry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
git push
Notes for the implementer¶
- Open question #1 (fan-out): resolved by looping per-device
notify.alexa_media_<object_id>inalarm_speak(Task 2) — no dependency on a genericnotify.alexa_media. If the object-id→notify-slug mapping ever breaks for a renamed Echo, that's where to look. - Open question #2 (failed-to-arm): the
arming → disarmedheuristic (Task 3 #57) is best-effort. After deploy, arm with a door open and watchalarm_control_panel.blairmont_alarmin Developer Tools → States; if ring-mqtt exposes a distinct fault attribute/state, prefer it and tighten #57. - Open question #3 (volume timing): the 0.4s
delayinalarm_speakis the tunable knob; validate in Task 8 Step 3. - Speaker map is single-source in
script.alarm_audio_event— to move which room speaks for an event, edit only thatvariables.speakersmap, nowhere else.