mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 22:01:13 +00:00
a61d2d3f4b
Adds the C8 inbound producer side: - TelemetryRing[T]: bounded drop-oldest ring; first-overflow INFO log + monotonic dropped_count. - SubscriptionBus + SubscriptionHandle: synchronous fan-out, lock- released-before-callback to avoid deadlock; subscriber crash caught + DEBUG-logged so one bad subscriber cannot kill the decode loop. - PymavlinkInboundDecoder: pymavlink-based AP decoder for RAW_IMU, SCALED_IMU2, ATTITUDE, GPS_RAW_INT, GPS2_RAW, HEARTBEAT, STATUSTEXT. Out-of-order drop (Invariant 7) per-kind WARN. STATUSTEXT spoofing sentinel promotes subsequent GPS to GpsStatus.SPOOFED within 5 s. AC-5.1 warm-start hint cached on first 3D+ fix; embedded into every FlightStateSignal. - Msp2InavInboundDecoder: YAMSPy-based iNav polling decoder for IMU / attitude / GPS / flight-state. signed=False always (RESTRICT-COMM-2); GpsStatus.SPOOFED is unreachable on iNav. Adds yamspy>=0.3.3 + pyserial>=3.5 to pyproject.toml. Tests: 443 pass / 2 skip / 0 fail (+33 in batch 9). Contract: no drift on fc_adapter_protocol.md v1.0.0; this batch implements the inbound producer side without changing signatures. Co-authored-by: Cursor <cursoragent@cursor.com>
5.5 KiB
5.5 KiB
Batch 09 — Cycle 1 Implementation Report
Batch: 9 of N Task landed: AZ-391 (C8 inbound subscription path — IMU / attitude / GPS health / MAV_STATE producer) Cycle: 1 Date: 2026-05-11
Scope
| Task | Component | Purpose |
|---|---|---|
| AZ-391 | C8 FC adapter (inbound) | MAVLink 2.0 (pymavlink) decoder for ArduPilot's 5 inbound message types + iNav MSP2 (yamspy) polling decoder; bounded per-kind telemetry rings with drop-oldest semantics + first-overflow INFO log; multi-subscriber fan-out bus with crash isolation; AC-5.1 warm-start hint surfacing (cached on first 3D+ fix; embedded into every subsequent FlightStateSignal); out-of-order frame detection (Invariant 7) with one WARN per drop; corrupt-frame isolation with DEBUG log + continued decode. |
Files added / modified
Added (prod)
src/gps_denied_onboard/components/c8_fc_adapter/_telemetry_rings.py—TelemetryRing[T]bounded drop-oldest ring with first-overflow INFO + monotonicdropped_count.src/gps_denied_onboard/components/c8_fc_adapter/_subscription.py—SubscriptionBus+SubscriptionHandle(concreteSubscriptionProtocol).src/gps_denied_onboard/components/c8_fc_adapter/_inbound_mavlink.py—PymavlinkInboundDecoder(ArduPilot path) +MAVLinkSourceProtocol.src/gps_denied_onboard/components/c8_fc_adapter/_inbound_msp2.py—Msp2InavInboundDecoder(iNav path) +MspSourceProtocol.
Added (tests)
tests/unit/c8_fc_adapter/test_az391_inbound_subscription.py— 33 unit tests covering all 10 ACs of AZ-391 + 2 NFR smoke tests.
Modified
pyproject.toml— addedyamspy>=0.3.3,<0.4+pyserial>=3.5(transitive ofyamspy).
Contract changes
_docs/02_document/contracts/c8_fc_adapter/fc_adapter_protocol.md— unchanged. This batch implements the inbound side of the v1.0.0 surface without altering signatures.
Test counts
| Metric | Before | After | Delta |
|---|---|---|---|
| Tests passing | 410 | 443 | +33 |
| Tests skipped | 2 | 2 | 0 |
| Tests failing | 0 | 0 | 0 |
Architectural notes
- Decoder lifecycle: the production driver constructs
PymavlinkInboundDecoder(source, bus)(or the iNav equivalent), spawns one thread that callsrun_decode_loop(AP) orrun_poll_loop(iNav), and never touches the source from any other thread.stop()flips anEvent; the next loop iteration exits cleanly. - Rings are decoder-owned: each decoder exposes
imu_ring,attitude_ring,gps_ring,state_ringas read-only attributes; consumerssnapshot()orpeek_latest()from any thread. - Subscription bus is synchronous-fan-out: callbacks fire on the decoder thread (Invariant 8). The bus snapshots its subscriber list under a single lock, then releases the lock BEFORE invoking callbacks, so a callback that calls back into
subscribe/cancelcannot deadlock. - Subscriber-crash isolation: every callback invocation is wrapped in
try/except; on raise the bus emits onekind="c8.inbound.callback_error"DEBUG record and continues with the next subscriber. A bad subscriber CANNOT kill the decode loop. - Warm-start hint (AC-5.1 / AC-8): the AP path caches the first
GPS_RAW_INTwithfix_type >= 3; the cachedLatLonAltand capture-time are embedded into every subsequentFlightStateSignalemitted viaHEARTBEATdecode. The iNav path mirrors this throughMSP_RAW_GPS/MSP2_INAV_GPS.current_flight_state()(to be wired in AZ-393 / AZ-394) consumes the latest entry fromstate_ring. - Out-of-order detection (AC-9 / Invariant 7) is per-kind, decode-boundary
monotonic_ns()based. A frame whosereceived_at <= last_received_at[kind]is dropped + a single WARN record (kind="c8.inbound.out_of_order_frame_dropped") is emitted. The drop is NOT silenced; an aggregated-counter approach is documented for AZ-392/393 follow-up. - GPS spoofing (AP-only, AC-3) uses an indirect signal:
STATUSTEXTmessages whose text contains"GPS spoofing"or"GPS jamming"set a per-decoder sentinel; subsequentGPS_RAW_INTdecodes within 5 s of the sentinel are promoted toGpsStatus.SPOOFED. The 5 s window is wired in code (not config) for batch-9 scope; promotion to a config knob is forward-action. - iNav has no spoofing (AC-5 / RESTRICT-COMM-2): the iNav decoder never produces
GpsStatus.SPOOFED; verified bytest_ac5_inav_spoofed_status_unreachable.
Dependencies introduced
yamspy>=0.3.3,<0.4— iNav MSP2 protocol library (38 KiB pure-Python wheel; built locally).pyserial>=3.5— transitive ofyamspy; production code uses serial transports for the live MSPy connection.pymavlink>=2.4— already pinned (AP path); installed by this batch since the test suite now imports it.
All three are runtime dependencies ([project.dependencies]).
Known forward-actions
- AZ-393 / AZ-394 will compose the decoders into the concrete
PymavlinkArdupilotAdapter/Msp2InavAdapterclasses (the AZ-391 producer side is component-shaped; the consumer wiring is the next task). - Spoofing-window timeout (5 s) is wired in code; promotion to
FcConfig.spoofing_sentinel_window_sis a forward-action contract bump. - Aggregate-counter INFO log every 60 s (mentioned in the spec's risk mitigation) is NOT implemented — we kept the per-drop WARN only, matching the AC-9 contract. The 60 s aggregate is a forward-action enhancement.
- C8-PT-01 sustained 200 Hz IMU NFR is not exercised by this batch's unit tests — that's an integration / PT-tier test. The unit-test NFR budget (1 ms avg per IMU callback) provides a sanity ceiling.