Skip to content

fix(mqtt): make alert attributes self-sufficient for drone triggers - #65

Merged
ifnull merged 1 commit into
feat/alert-notification-blueprintfrom
fix/alert-info-drone-fields
Aug 31, 2026
Merged

ifnull merged 1 commit into
feat/alert-notification-blueprintfrom
fix/alert-info-drone-fields

Conversation

@ifnull

@ifnull ifnull commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Stacked on #64 (feat/alert-notification-blueprint). Merge that first;
this rebases cleanly onto main afterwards. Stacked rather than branched off
main to avoid a second version/CHANGELOG collision like #62 ↔ #63.

The bug

binary_sensor.airspace_alert_<rule>'s attributes come from the per-rule
info topic, and that is the only thing an automation sees when a rule
fires. It was AircraftPayload-shaped only — so a drone-triggered rule
(drone_nearby) carried no UA type, no height above ground, no operator
location, and no registry make/model.

The documented workaround had the automation cross-reference
sensor.airspace_nearest_drone:

# docs/automations.example.yaml, before
message: >-
  {% set s = 'sensor.airspace_nearest_drone' %}

That is wrong whenever more than one drone is up. The rule fires for
whichever drone matched; the message describes whichever drone is nearest.
Two drones airborne and the notification confidently names the wrong one, with
nothing to indicate it. The old example even documented this as a "known
limitation".

The fix

_alert_info now merges the RID-only half of DronePayload.from_state when
state.canonical.drone is set. A drone trigger carries is_drone, id_type,
ua_type, self_id, agl_ft, alt_geom_ft, ground_speed_kt, track_deg,
rid_source, and the full operator_* set including
operator_location_type.

docs/automations.example.yaml's drone_nearby example now reads the alert
entity, and the blueprint from #64 renders drone triggers:

aircraft         🇺🇸 RCH171 · C17 (00-0171) · 12.3 nm at 272° · 35000 ft
drone (FAA)      DJI Mavic 3 · 0.4 nm at 88° · 246 ft AGL · operator takeoff point
drone (ua)       Multirotor · 0.4 nm · 120 ft AGL · operator located
drone (selfid)   Drone (self-ID “Spoofing test”)
drone (bare)     Drone

Identity degrades FAA registry → UA category → Self-ID → "Drone". self_id is
operator-supplied free text and untrusted, so it is a last-resort label only,
never allowed to override a resolved registry hit.

operator_location_type is surfaced rather than blanket-labelling every
reading "operator located" — takeoff is the drone's own launch point, not a
live operator fix.

Second bug found on the way

db_metadata was never emitted on alert attributes, despite being listed
in the documented attribute set in docs/automations.example.yaml since the
alert topic shipped. For a drone that is the FAA UAS make/model — the entire
point of enable_drone_registry. Now published.

Design note

Aircraft keys stay present on a drone track rather than being branched away.
They are all None there (a drone observation carries hex=None, so
country_for returns None rather than a bogus registry hit from a UAS
serial), and a stable key set means a consumer template only ever guards for a
null value, never for a missing attribute. is_drone is the explicit
discriminator rather than making callers infer the band from which keys happen
to exist.

Drift guards

  • test_covers_every_rid_field_dronepayload_publishes — asserts _alert_info
    covers every RID field DronePayload publishes, with an explicit exempt
    set for the position/provenance fields the alert topic deliberately omits.
    The next field added to DronePayload fails here instead of silently going
    missing from alerts.
  • The blueprint's attribute guard now builds its reference set from both
    bands. It caught this during development: an aircraft-only reference set
    flagged the blueprint's new drone branch as reading unpublished attributes.

Breaking?

No. Additive fields only; PAYLOAD_SCHEMA_VERSION stays at 1 per the
CHANGELOG's stated policy (payloads.py is untouched — zero diff). Existing
automations reading aircraft attributes are unaffected; the keys they read keep
the same names and meanings.

🤖 Generated with Claude Code

The per-rule alert `info` topic feeds binary_sensor.airspace_alert_<rule>'s
attributes, which is the only thing an automation sees when a rule fires. It
was AircraftPayload-shaped only, so a drone-triggered rule carried no UA type,
no height above ground, no operator location, and no registry make/model.

The documented workaround was for the automation to cross-reference
sensor.airspace_nearest_drone instead. That is wrong whenever more than one
drone is up: the rule fires for whichever drone matched, but the message
describes whichever drone is *nearest*. Two drones airborne and the
notification confidently names the wrong one, with nothing to indicate it.

_alert_info now merges the RID-only half of DronePayload.from_state when
state.canonical.drone is set, so the alert describes the track that actually
triggered.

Aircraft keys stay present on a drone track rather than being branched away.
They are all None there — a drone observation carries hex=None, so country_for
returns None rather than a bogus registry hit from a UAS serial — and a stable
key set means a consumer template only ever guards for a null value, never for
a missing attribute. `is_drone` is the explicit discriminator rather than
making callers infer the band from which keys happen to exist.

Also emits db_metadata, which has been listed in the documented alert attribute
set since the topic shipped but was never actually emitted. For a drone that is
the FAA UAS make/model — the entire point of enable_drone_registry.

Additive fields only; PAYLOAD_SCHEMA_VERSION is unchanged per the CHANGELOG's
stated policy. A new drift guard asserts _alert_info covers every RID field
DronePayload publishes, with an explicit `exempt` set for the position and
provenance fields the alert topic deliberately omits — so the next field added
to DronePayload fails here instead of silently going missing from alerts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FiVqZDdasMeshTd7CqdxEp
@ifnull
ifnull merged commit 556ef4e into feat/alert-notification-blueprint Aug 31, 2026
3 checks passed
@ifnull
ifnull deleted the fix/alert-info-drone-fields branch August 31, 2026 03:24
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