Skip to content

feat(blueprints): add an HA automation blueprint for alert notifications - #64

Merged
ifnull merged 2 commits into
mainfrom
feat/alert-notification-blueprint
Aug 31, 2026
Merged

ifnull merged 2 commits into
mainfrom
feat/alert-notification-blueprint

Conversation

@ifnull

@ifnull ifnull commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Why

docs/automations.example.yaml opened with two automations that differed only
in which alert binary_sensor they watched, which notify service they called,
and the title string. Every alert rule a user adds means another ~20-line
copy-paste with the same entity id repeated inside two Jinja templates. There
are eight alert rules in a typical config.

The motivating failure is #62. drone_detected and
drone_conflict/drone_nearby fired on the same tick for essentially every
close-in drone, because nothing stopped a user assembling overlapping examples
by hand — the fix was a hand-written condition in the example YAML. A blueprint
instantiated once per rule cannot produce that class of bug at all.

What it does

Import once, add one instance per rule from the UI. Six inputs: alert entity,
notify service, title, watchpoint, include-photo, notification group.

full:  🇺🇸 RCH171 · C17 (00-0171) · 12.3 nm at 272° · 35000 ft
bare:  ae0001

Photo and grouping degrade to {} when unavailable, so an airframe with no
Planespotters photo just gets a plain notification.

Design notes

An entity selector, not a hardcoded entity id. Alert entity ids derive from
user-configured rule names, so the blueprint cannot know them — and does not
need to. The user picks from whatever their own config produced, including
rules that do not exist yet.

Reads only the triggering alert's own attributes. The existing drone
examples cross-reference sensor.airspace_nearest_drone for RID detail, which
silently describes the wrong drone whenever a second, non-nearest drone is
what actually tripped the rule (the alert-info-drone-fields TODO in
publisher.py). Reading one entity is strictly correct — it just cannot show
drone-specific fields until that TODO lands, which is why the drone
automations stay in the examples file rather than being replaced.

Blueprint variables: are safe here in a way they are not in a
hand-written automation: the stored automation is only
use_blueprint: {path, input}, so the UI editor never round-trips the body and
cannot drop them. That is why docs/automations.example.yaml has to inline
{% set %} in every field and this does not.

Only two examples removed. The emergency-squawk pair is stateful (creates
and dismisses a persistent notification) and the drone automations need the
cross-reference above, so neither reduces to a per-rule notification.

Drift guard

tests/test_blueprints.py (13 tests). A blueprint is a public compatibility
surface with no schema_version equivalent — rename an attribute and every
imported automation silently renders an empty notification, with nothing in HA
validating it. So:

  • every attribute the blueprint reads must be one publisher._alert_info
    actually publishes
  • every declared input must be referenced (an unused input is a UI field that
    silently does nothing)
  • every Jinja template must compile, and the message must render sanely with
    all attributes present and all absent

That last one earned its keep immediately: the first draft used
dict(**a, **b) to build the notification data. Valid Python; Jinja rejects it
with "invalid syntax for function call expression". It would have thrown at
runtime with no signal beyond a missing notification. Rewritten with a
namespace + dict() merge.

Breaking?

No. Zero files under src/ — verified. No payload, discovery, or topic
changes, so the MQTT contract and schema_version are untouched. The only
removal is two examples from a docs file; copy-pasted automations live in the
user's own config and are unaffected.

Notes

  • Adds a test-only jinja2 dev dependency for the template-compile check.
    Not in the runtime path — the service never renders templates.
  • Version bumped to 1.1.3 only to satisfy the version bumped CI gate. This
    release has no runtime change, and the blueprint is consumed from the repo by
    raw URL rather than from the add-on image — so tagging v1.1.3 is optional.
    Merging makes the blueprint importable immediately; tagging would push a
    functionally identical image to every install.
  • Still open, unchanged by this PR: the alert-info-drone-fields TODO
    (mqtt/publisher.py). Fixing it would let the blueprint fully replace the
    drone examples and fix the existing silent-mismatch bug.

🤖 Generated with Claude Code

ifnull and others added 2 commits August 30, 2026 20:26
docs/automations.example.yaml opened with two automations that differed only
in which alert binary_sensor they watched, which notify service they called,
and the title string. Every alert rule a user adds means another 20-line
copy-paste with the same entity id repeated inside two templates. That is the
shape a blueprint exists for.

The motivating failure is #62. `drone_detected` and `drone_conflict`/
`drone_nearby` fired on the same tick for essentially every close-in drone,
because nothing stopped a user assembling overlapping examples by hand; the
fix was a hand-written condition in the example. A blueprint instantiated once
per rule cannot produce that class of bug at all.

Design notes:

An entity selector, not a hardcoded entity id. Alert entity ids derive from
user-configured rule names, so the blueprint has no way to know them — but it
does not need to. The user picks from whatever their own config produced,
including rules that do not exist yet.

The templates read only the triggering alert's own attributes. The existing
drone examples cross-reference sensor.airspace_nearest_drone to get RID
detail, which silently describes the wrong drone whenever a second,
non-nearest drone is what actually tripped the rule (the alert-info-drone-
fields TODO in publisher.py). Reading one entity is strictly correct; it just
cannot show drone-specific fields until that TODO lands, so the drone
automations stay in the examples file rather than being replaced.

Blueprint `variables:` are safe here in a way they are not in a hand-written
automation. The stored automation is only `use_blueprint: {path, input}`, so
the UI editor never round-trips the body and cannot drop them — which is why
docs/automations.example.yaml has to inline `{% set %}` in every single field
and this does not.

Only the two military_close examples are removed. The emergency-squawk pair is
stateful (creates *and* dismisses a persistent notification) and the drone
automations need the cross-reference above, so neither reduces to a per-rule
notification.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiVqZDdasMeshTd7CqdxEp
Required by the version-bumped CI gate (v1.1.2 is tagged). Note this release
contains no runtime change at all — the diff is a blueprint, docs, and tests,
with zero files under src/. The blueprint is consumed from the repo by raw URL,
not from the add-on image, so it is available the moment this lands on main
and tagging v1.1.3 is optional.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ifnull
ifnull merged commit 99d6a55 into main Aug 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant