From 42ae3b91ccc0a3bdf7f9e7e1636c608833be53d6 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Tue, 23 Jun 2026 00:21:33 -0500 Subject: [PATCH] fix(docs): clear stale drone/operator map markers via availability gates Drone and operator markers lingered on the HA Map long after a track was gone. The service clears tracks correctly (purge at expire_after_s -> nearest_drone topic empty-retained -> sensor unavailable), but a Map marker only disappears when its entity goes unavailable, and the example `Airspace Drone Operator` template sensor had no `availability` clause. Its state is `operator_id or 'operator'`, which never evaluates falsy, so the sensor stayed available with stale/None coordinates and the operator pin persisted indefinitely. Gate the operator sensor on the nearest drone being live and having a broadcast operator position, and tighten the aircraft/drone markers to also require a non-null latitude. Now markers clear when the service purges the track. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 13 ++++++++++++- addon/config.yaml | 2 +- docs/dashboard.example.yaml | 22 +++++++++++++++++++--- pyproject.toml | 2 +- uv.lock | 2 +- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6785b..fc27861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ The **published MQTT payload contract** is versioned separately via Additive, optional payload fields are backwards-compatible and do **not** bump the major version; a removed/renamed/retyped field does. +## [0.2.37] — 2026-06-23 +### Fixed +- Example dashboard: drone/operator map markers lingered at stale coordinates + after a track was gone. A Map marker only disappears when its entity goes + `unavailable`; the `Airspace Drone Operator` template sensor had no + `availability` clause (its `operator_id or 'operator'` state kept it + "available" forever), so the operator pin persisted indefinitely. Added an + availability gate to the operator sensor (live nearest drone **and** a + broadcast operator position) and tightened the aircraft/drone markers to also + require a non-null latitude, so markers clear when the service purges a track. + ## [0.2.36] — 2026-06-22 ### Added - **Remote ID spoof detection (Tier 1).** New `enable_spoof_detection` toggle @@ -220,4 +231,4 @@ Initial development. Established the full pipeline and contracts: optional Prometheus `/metrics`. - Pydantic v2 config (strict, fail-fast), structlog logging. -[Unreleased]: https://github.com/ifnull/ha-airspace/compare/v0.2.36...HEAD +[Unreleased]: https://github.com/ifnull/ha-airspace/compare/v0.2.37...HEAD diff --git a/addon/config.yaml b/addon/config.yaml index 6cf07a9..af31bb4 100644 --- a/addon/config.yaml +++ b/addon/config.yaml @@ -3,7 +3,7 @@ # Display name is "Airspace" (friendly, matches the MQTT device); the slug, # package, repo, and image names stay ha-airspace as the technical identity. name: Airspace -version: "0.2.36" +version: "0.2.37" slug: ha_airspace description: >- Multi-source ADS-B + drone Remote ID enrichment to MQTT for Home Assistant. diff --git a/docs/dashboard.example.yaml b/docs/dashboard.example.yaml index 2db6234..1157d09 100644 --- a/docs/dashboard.example.yaml +++ b/docs/dashboard.example.yaml @@ -377,10 +377,17 @@ views: # template: # - sensor: # # Aircraft: ✈️ by default, 🚁 for rotorcraft (emitter category A7). +# # NOTE on `availability`: a Map marker only disappears when its entity +# # goes `unavailable`. The source sensors clear (go unavailable) once the +# # service purges a track, so each template sensor MUST gate availability +# # on its source having a live position — otherwise the marker lingers on +# # the map at its last coordinates after the aircraft/drone is gone. # - name: Airspace Aircraft Map # unique_id: airspace_aircraft_map # state: "{{ states('sensor.airspace_nearest_aircraft') }}" -# availability: "{{ states('sensor.airspace_nearest_aircraft') not in ['unknown','unavailable'] }}" +# availability: >- +# {{ states('sensor.airspace_nearest_aircraft') not in ['unknown','unavailable'] +# and state_attr('sensor.airspace_nearest_aircraft','latitude') is not none }} # icon: >- # {{ 'mdi:helicopter' if state_attr('sensor.airspace_nearest_aircraft','category') == 'A7' else 'mdi:airplane' }} # attributes: @@ -390,15 +397,24 @@ views: # - name: Airspace Drone Map # unique_id: airspace_drone_map # state: "{{ states('sensor.airspace_nearest_drone') }}" -# availability: "{{ states('sensor.airspace_nearest_drone') not in ['unknown','unavailable'] }}" +# availability: >- +# {{ states('sensor.airspace_nearest_drone') not in ['unknown','unavailable'] +# and state_attr('sensor.airspace_nearest_drone','latitude') is not none }} # icon: mdi:controller-classic # attributes: # latitude: "{{ state_attr('sensor.airspace_nearest_drone','latitude') }}" # longitude: "{{ state_attr('sensor.airspace_nearest_drone','longitude') }}" -# # Drone OPERATOR location (the security-relevant marker). +# # Drone OPERATOR location (the security-relevant marker). Available only +# # when the nearest drone is live AND broadcast an operator position — +# # otherwise this pin would persist at stale coordinates (the operator_id +# # `or 'operator'` fallback keeps the sensor from going unavailable on its +# # own, so the availability gate is what clears the marker). # - name: Airspace Drone Operator # unique_id: airspace_drone_operator_map # state: "{{ state_attr('sensor.airspace_nearest_drone','operator_id') or 'operator' }}" +# availability: >- +# {{ states('sensor.airspace_nearest_drone') not in ['unknown','unavailable'] +# and state_attr('sensor.airspace_nearest_drone','operator_lat') is not none }} # icon: mdi:account-alert # attributes: # latitude: "{{ state_attr('sensor.airspace_nearest_drone','operator_lat') }}" diff --git a/pyproject.toml b/pyproject.toml index f77721d..6f6a5e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ha-airspace" -version = "0.2.36" +version = "0.2.37" description = "Multi-source ADS-B enrichment service: aircraft.json → MQTT → Home Assistant." readme = "README.md" requires-python = ">=3.12" diff --git a/uv.lock b/uv.lock index 7827d06..6f2152b 100644 --- a/uv.lock +++ b/uv.lock @@ -290,7 +290,7 @@ wheels = [ [[package]] name = "ha-airspace" -version = "0.2.35" +version = "0.2.36" source = { editable = "." } dependencies = [ { name = "aiomqtt" },