# Source Registry — C8: MAVLink / MSP2 FC adapter

> Mode A Phase 2 — engine Step 2 (Source Tiering & Exhaustive Web Investigation). C8 batch 1 sources for the FC adapter (per-FC adapter pattern verified at SQ6 closure: ArduPilot Plane via MAVLink `GPS_INPUT`, iNav via `MSP2_SENSOR_GPS` primary OR UBX-impersonation alternate). Confidence labels per `references/source-tiering.md`. Cross-references back to SQ6 fact card sources (#4, #9, #10, #12, #13, #15) where the iNav inbound-handler reality and MSP2/UBX transport options were originally established.
>
> Index: [`00_summary.md`](00_summary.md). Sibling component categories: [C1 VIO](C1_vio.md), [C2 VPR](C2_vpr.md), [C3 Matchers](C3_matchers.md), [C4 Pose](C4_pose_estimation.md), [C5 State estimator](C5_state_estimator.md), [C6 Tile cache](C6_tile_cache_spatial_index.md), [C7 Inference runtime](C7_inference_runtime.md). Cross-cuts: [SQ6 external positioning](SQ6_external_positioning.md).

## Sources

### Source #106 — ArduPilot Pymavlink (context7-indexed `/ardupilot/pymavlink`)
- **Tier**: L1 (canonical Python MAVLink implementation maintained by ArduPilot)
- **Found via**: context7 `resolve-library-id` → `/ardupilot/pymavlink` → `query-docs` for GPS_INPUT send patterns
- **Library posture**: 32 code snippets indexed in context7 (Source Reputation: High); coverage emphasizes the JavaScript MAVLink generator output, with thinner Python-side examples in context7 — supplementary primary sources (canonical pymavlink GitHub README + ArduPilot GPS_INPUT dev docs Source #107) carry the canonical Python `master.mav.gps_input_send(...)` send pattern.
- **License**: LGPL v3 (pymavlink itself); MAVLink generated dialects are MIT — the project's runtime dependency is on the LGPL pymavlink Python package. **Compatible with project's Apache-2.0 dual-use track**: LGPL allows linking from a non-LGPL application without "infecting" application license; the only obligation is to publish/redistribute any modifications to pymavlink itself (project does not modify pymavlink), and to allow users to relink against an updated pymavlink (trivially satisfied for an open-source / company-internal deployment with published `requirements.txt`).
- **Critical-novelty-sensitivity**: Established baseline; no time window — pymavlink has been the canonical Python MAVLink stack since 2010+, and `GPS_INPUT` (msg 232) has been in `common.xml` since 2017 ArduPilot dev iteration.
- **Per-mode capability verification (context7 + SQ6 Source #4 AP_GPS_MAV.cpp cross-cite)**: ✅ `GPS_INPUT` decoder confirmed in AP_GPS_MAV.cpp master per SQ6 Fact #1; Python sender uses `master = mavutil.mavlink_connection(...)` + `master.mav.gps_input_send(time_usec, gps_id, ignore_flags, time_week_ms, time_week, fix_type, lat, lon, alt, hdop, vdop, vn, ve, vd, speed_accuracy, horiz_accuracy, vert_accuracy, satellites_visible, yaw)` per pymavlink generated dialect.
- **Used to support**: Fact #97 (ArduPilot Plane FC adapter primary candidate).

### Source #107 — ArduPilot Plane Non-GPS Position Estimation + MAVProxy GPS Input module documentation
- **Tier**: L1 (official ArduPilot dev docs portal; documented configuration + canonical injection example)
- **Found via**: web search for `pymavlink GPS_INPUT msg 232 example ArduPilot Plane non-GPS external positioning companion computer 2025`
- **Date accessed**: 2026-05-08
- **URLs**:
  - https://ardupilot.org/dev/docs/mavlink-nongps-position-estimation.html
  - https://ardupilot.org/plane/docs/common-non-gps-navigation-landing-page.html
  - https://ardupilot.org/mavproxy/docs/modules/GPSInput.html
  - https://ardupilot.org/plane/docs/common-companion-computers.html
- **Critical configuration captured**: `GPS1_TYPE = 14` (MAVLink) is required on the FC for `GPS_INPUT` ingestion. Without this parameter set, AP_GPS will not accept the message. `EK3_SRC1_POSXY = 3` (GPS) selects the GPS_INPUT-fed virtual GPS as the primary horizontal-position source. Per ArduPilot dev docs, the **preferred method** for non-GPS navigation is `ODOMETRY` or `VISION_POSITION_ESTIMATE` at ≥4 Hz — but `GPS_INPUT` remains supported and is the right choice when the project's outcome contract is "WGS84 coordinates as a real-GPS replacement" (AC-4.3 wording aligns with GPS_INPUT semantics, not ODOMETRY semantics).
- **Cross-cite**: SQ6 Fact #1 (AP_GPS_MAV.cpp ingestion path) + SQ6 Fact #4 (`ODOMETRY`-velocity-only NOT supported) — together these pin `GPS_INPUT` as the right transport for the project's `{satellite_anchored, visual_propagated, dead_reckoned}` source-label scheme.
- **Per-mode capability verification**: ✅ All required ACs (AC-4.3 / AC-NEW-2 / AC-NEW-4 / AC-NEW-8) map directly into GPS_INPUT field semantics per SQ6 working summary table.

### Source #108 — pyubx2 (context7-indexed `/semuconsulting/pyubx2` + canonical GitHub README)
- **Tier**: L1 (canonical Python UBX/NMEA/RTCM3 parser; benchmark score 86.8 in context7; 139 code snippets)
- **Found via**: context7 `resolve-library-id` → `/semuconsulting/pyubx2` → `query-docs` for UBX-NAV-PVT message construction with full attribute control + serialize-to-bytes pattern for UART transmission
- **Library posture**: BSD-3-Clause license (clean, dual-use compatible); semuconsulting publishes both the canonical GitHub repo + comprehensive readthedocs.io documentation also indexed in context7 as `/websites/semuconsulting_pyubx2` (239 additional code snippets, benchmark 85.2). The library supports `UBXMessage(ubxClass, ubxID, mode, **kwargs)` constructor with three modes: `GET (0x00)` for output from the receiver, `SET (0x01)` for command input, `POLL (0x02)` for query input. NAV-PVT belongs to the GET output set.
- **Critical-novelty-sensitivity**: Library/SDK API behaviour — must reflect currently shipped version; semuconsulting/pyubx2 is daily-active (last released 2025).
- **Per-mode capability verification (context7-confirmed)**: ✅ NAV-PVT message construction with all UBX-NAV-PVT fields supported as keyword arguments per `UBXMessage('NAV', 'NAV-PVT', GET, iTOW=..., year=..., lon=..., lat=..., height=..., hMSL=..., hAcc=..., vAcc=..., velN=..., velE=..., velD=..., gSpeed=..., headMot=..., sAcc=..., headAcc=..., pDOP=..., fixType=..., flags=..., numSV=..., valid=...)`. ✅ `serialize()` method returns the full UBX wire-format bytestring (sync-bytes 0xB5 0x62 + class + ID + length + payload + 8-bit Fletcher checksum). ✅ `parsebitfield=1` mode allows individual bit attributes for `flags` (e.g., `gnssFixOK`, `diffSoln`, `psmState`) and `valid` (e.g., `validDate`, `validTime`, `fullyResolved`, `validMag`) — required for the impersonation path to set the `gnssFixOK` bit that iNav's `gpsMapFixType()` validates.
- **Used to support**: Fact #98 (iNav UBX impersonation alternate candidate).

### Source #109 — u-blox NEO-M9N Integration Manual (UBX-19014286) + u-blox 8/M8 Receiver Description (UBX-13003221) — UBX-NAV-PVT canonical specification
- **Tier**: L1 (vendor-authoritative protocol specification PDFs)
- **Found via**: web search for `UBX-NAV-PVT frame structure spec u-blox protocol M8 M9 fix type fabricate inject iNav 2025`
- **Date accessed**: 2026-05-08
- **URLs**:
  - https://content.u-blox.com/sites/default/files/NEO-M9N_Integrationmanual_UBX-19014286.pdf
  - https://content.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_UBX-13003221.pdf
- **Frame structure captured**: NAV-PVT (class=0x01, ID=0x07) carries 92-byte payload — `iTOW (u32 ms)` + `year (u16)` + `month/day/hour/min/sec (u8 each)` + `valid (u8 bitmask)` + `tAcc (u32 ns)` + `nano (i32 ns)` + `fixType (u8 enum: 0=NoFix, 1=DeadReck, 2=2D, 3=3D, 4=GNSS+DR, 5=TimeOnly)` + `flags (u8 bitmask incl. gnssFixOK bit 0)` + `flags2 (u8)` + `numSV (u8)` + `lon (i32 deg×1e-7)` + `lat (i32 deg×1e-7)` + `height (i32 mm above ellipsoid)` + `hMSL (i32 mm above mean sea level)` + `hAcc (u32 mm)` + `vAcc (u32 mm)` + `velN/velE/velD (i32 each mm/s)` + `gSpeed (i32 mm/s)` + `headMot (i32 deg×1e-5)` + `sAcc (u32 mm/s)` + `headAcc (u32 deg×1e-5)` + `pDOP (u16 ×0.01)` + reserved bytes + `headVeh (i32)` + `magDec (i16)` + `magAcc (u16)`. M9N supersedes M8 with refined NAV-PVT semantics; both are accepted by iNav 9.0 (per Source #11 in SQ6 — UBX ≥ 15.00 protocol version).
- **Critical-novelty-sensitivity**: Established baseline + library/SDK API behaviour — u-blox NAV-PVT is a stable protocol surface since u-blox 8 (2014); minor field semantics evolve across vendor protocol versions, so exact wire format must be checked against the iNav-target version (iNav 9.0 expects ≥ 15.00).
- **Per-mode capability verification**: ✅ NAV-PVT contains all fields needed for iNav's `gpsMapFixType()` validation (Source #110 cross-cite): `flags` byte bit 0 `gnssFixOK` + `fixType` enum + `numSV` + `hAcc/vAcc` for AC-NEW-4 covariance honesty.
- **Used to support**: Fact #98 (iNav UBX impersonation alternate candidate) NAV-PVT frame fabrication spec.

### Source #110 — iNav `gps_ublox.c` source (master, GitHub) — UBX validation gates that the impersonation must pass
- **Tier**: L1 (canonical iNav firmware source, master branch, accessed via cached web fetch)
- **Found via**: web search for `iNav GPS UBX validation fixType numSat hDOP threshold reject GNSS spoofing companion computer 2025`
- **URL**: https://github.com/iNavFlight/inav/blob/master/src/main/io/gps_ublox.c
- **Date accessed**: 2026-05-08
- **Critical-novelty-sensitivity**: Library/SDK API behaviour — must reflect current shipped iNav version. iNav 9.0 master (post-2025-12-11 wiki update per SQ6 Source #10) confirmed via direct file read.
- **Validation logic captured (line-numbered evidence)**:
  - **Line 215-220**: `gpsMapFixType(fixValid, ubloxFixType)` returns `GPS_FIX_2D` if `fixValid && ubloxFixType == FIX_2D`, returns `GPS_FIX_3D` if `fixValid && ubloxFixType == FIX_3D`, otherwise `GPS_NO_FIX`. **THIS IS THE GATE** the impersonation must pass.
  - **Line 654**: NAV-PVT path computes `next_fix_type = gpsMapFixType(_buffer.pvt.fix_status & NAV_STATUS_FIX_VALID, _buffer.pvt.fix_type)`. The `fix_status & NAV_STATUS_FIX_VALID` masks the lowest bit of NAV-PVT's `flags` byte (bit 0 = `gnssFixOK`).
  - **Lines 656-683**: NAV-PVT-driven full state population including `lon (1e-7 deg)`, `lat (1e-7 deg)`, `altitude_msl (mm)`, NED velocity (mm/s converted to cm/s), `speed_2d (mm/s)`, `heading_2d (deg×1e-5 → deg×10)`, `satellites`, `horizontal_accuracy (mm)`, `vertical_accuracy (mm)`, `position_DOP`, valid date/time bits.
  - **Lines 1024-1060**: Configuration logic — for u-blox version ≥ 15.0 (iNav 9.0+), iNav configures NAV-PVT-only via `configureMSG(MSG_CLASS_UBX, MSG_PVT, 1)`. For older receivers, configures the legacy NAV-POSLLH + NAV-SOL + NAV-VELNED + NAV-TIMEUTC quad. **Implication**: companion impersonator should advertise version ≥ 15.0 via NAV-VER (CLASS=0x0A, ID=0x04) to drive iNav into the simpler NAV-PVT-only protocol.
- **Per-mode capability verification**: ✅ Validation gate fully decoded; impersonation viability confirmed at the firmware-source level (no opaque downstream filter discovered).
- **Used to support**: Fact #98 — provides the iNav-firmware-side validation contract that the UBX impersonation must satisfy.

### Source #111 — iNav `docs/development/msp/README.md` (master, GitHub) — MSP2_SENSOR_GPS canonical payload specification
- **Tier**: L1 (canonical iNav protocol-reference documentation, master branch, accessed via cached web fetch)
- **Found via**: web search for `MSP2_SENSOR_GPS Python library iNav msp2 protocol companion computer external GPS injection 2025 2026`
- **URL**: https://github.com/iNavFlight/inav/blob/master/docs/development/msp/README.md
- **Date accessed**: 2026-05-08
- **Payload structure captured (line 2999-3031 of the master README)**: `MSP2_SENSOR_GPS (7939 / 0x1F03)` — request payload 36 bytes containing `instance (u8)` + `gpsWeek (u16)` + `msTOW (u32 ms)` + `fixType (u8 = gpsFixType_e)` + `satellitesInView (u8)` + `hPosAccuracy (u16 mm)` + `vPosAccuracy (u16 mm)` + `hVelAccuracy (u16 cm/s)` + `hdop (u16 ×0.01)` + `longitude (i32 deg×1e7)` + `latitude (i32 deg×1e7)` + `mslAltitude (i32 cm)` + `nedVelNorth (i32 cm/s)` + `nedVelEast (i32 cm/s)` + `nedVelDown (i32 cm/s)` + `groundCourse (u16 deg×100)` + `trueYaw (u16 deg×100, 65535 = unavailable)` + `year (u16)` + `month/day/hour/min/sec (u8 each)`. **Reply payload: None.** **Notes: Requires `USE_GPS_PROTO_MSP`. Calls `mspGPSReceiveNewData()`.**
- **Critical-novelty-sensitivity**: Library/SDK API behaviour — verified against iNav master (post-9.0).
- **Per-mode capability verification**: ✅ Full payload spec covers all AC-NEW-4 covariance honesty fields (`hPosAccuracy`, `vPosAccuracy`, `hVelAccuracy`); ✅ AC-NEW-8 graceful-degrade signal carried via `fixType` enum (`gpsFixType_e`) — companion can emit `GPS_NO_FIX` (0) or `GPS_FIX_2D` (1) for the "covariance >100 m" / "covariance >500 m" thresholds; ✅ AC-1.4 95% covariance proxy carried in `hPosAccuracy`.
- **Used to support**: Fact #99 (iNav MSP2_SENSOR_GPS primary candidate).

### Source #112 — Python MSP2 implementations: YAMSPy + INAV-Toolkit `inav_msp.py`
- **Tier**: L2 (community implementations; NOT vendor-canonical but actively maintained)
- **Found via**: web search for Python MSP2_SENSOR_GPS libraries; iNav Issue #4465 confirms YAMSPy as community-recommended; agoliveira/INAV-Toolkit confirmed via direct GitHub source read
- **URLs**:
  - YAMSPy mention: https://github.com/iNavFlight/inav/issues/4465
  - INAV-Toolkit `inav_msp.py`: https://github.com/agoliveira/INAV-Toolkit/blob/5c4ef789068399b4dc7461b71c6f71c25aef5e4e/inav_msp.py
- **Date accessed**: 2026-05-08
- **Library posture**:
  - **YAMSPy** (`thecognifly/YAMSPy`): MIT-licensed Python library with explicit MSP V2 support; community-blessed for iNav external-device communication per the iNav issue thread.
  - **INAV-Toolkit `inav_msp.py`**: 951-line MIT-licensed module implementing `msp_v2_encode(cmd, payload)` + `msp_v2_decode(buffer)` with CRC-8 DVB-S2 checksumming + serial transport. Direct primary-source implementation reference for MSP V2 frame construction.
- **Critical-novelty-sensitivity**: Library/SDK API behaviour — both libraries are recent (post-2024 commits). **Risk**: community libraries may lag the iNav protocol surface (e.g., MSP V2 sensor message range 0x1F00-0x1FFF was added later than the original MSP V2 baseline). The project may need to either (a) extend the chosen community library with MSP2_SENSOR_GPS-specific encoding helpers, or (b) implement a thin custom encoder using the canonical msp_v2_encode primitive — both paths verified feasible from primary sources.
- **License notes**: MIT throughout — clean dual-use compatible.
- **Per-mode capability verification**: ⚠️ MSP V2 frame envelope (0x24 + 'X' + 0x3C + flag + cmd_lo + cmd_hi + len_lo + len_hi + payload + CRC8-DVB-S2) confirmed via INAV-Toolkit primary source; ✅ MSP2_SENSOR_GPS payload structure confirmed via Source #111. Combining the two yields a complete companion-side encoder for the iNav primary path.
- **Used to support**: Fact #99 (iNav MSP2_SENSOR_GPS primary candidate, Python implementation path).

### Source #113 — iNav `src/main/msp/msp_protocol_v2_sensor.h` (master, GitHub) — MSP2 sensor command-ID range
- **Tier**: L1 (canonical iNav firmware source, master branch)
- **Found via**: web search co-result with Source #112; opens via the `msp_protocol_v2_sensor.h` direct link
- **URL**: https://github.com/iNavFlight/inav/blob/master/src/main/msp/msp_protocol_v2_sensor.h
- **Date accessed**: 2026-05-08
- **Critical fact captured**: `MSP2_SENSOR_GPS = 0x1F03` (= 7939 decimal); MSP V2 sensor-message range `0x1F00-0x1FFF` is reserved for sensor injection plugins. iNav 9.0 master expectation: MSP2 frame must use the MSP V2 envelope (sync = 0x24 0x58 0x3C; flag = 0x00; cmd = LE 16-bit; len = LE 16-bit; CRC = CRC-8 DVB-S2 over flag through end of payload).
- **Per-mode capability verification**: ✅ MSP2_SENSOR_GPS = 0x1F03 confirmed at source; ✅ MSP V2 envelope spec confirmed.
- **Used to support**: Fact #99 — provides the canonical MSP V2 sensor-message-range definition.
