Skip to content

New Feature: Dual switchable receivers#11748

Draft
xznhj8129 wants to merge 11 commits into
iNavFlight:maintenance-10.xfrom
xznhj8129:feature/dualrx
Draft

New Feature: Dual switchable receivers#11748
xznhj8129 wants to merge 11 commits into
iNavFlight:maintenance-10.xfrom
xznhj8129:feature/dualrx

Conversation

@xznhj8129

@xznhj8129 xznhj8129 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Dual RX: primary/secondary receivers with automatic failover

WIP, requires more elaboration on switchover states, hysteresis and dwell time, receiver configurations and telemetry

Summary

Adds a second receiver that runs alongside the primary. Both links stay active for
telemetry, but only one drives RC control at a time. When the primary link enters
failsafe, control passes to the secondary; the main failsafe only triggers when
both links are down.

  • Gated by dual_rx_enabled (default OFF); auto-disabled if either link is an MSP RX
    (MSP RC can arrive on any MSP port, so a dedicated secondary link makes no sense there).
  • A second identical serial protocol can bind to its own UART via the new
    FUNCTION_RX_SERIAL_SECONDARY serial function.
  • OSD gains an active-link indicator and a "primary in failsafe" warning.
  • Unit tests cover primary→secondary failover, secondary-active startup, and
    main-failsafe-only-when-both-down.
  • Based on the last stage of Mavlink_multiport branch

Configuration / CLI

New gate and secondary receiver settings in PG_RX_CONFIG:

  • dual_rx_enabled — bool, default OFF.
  • receiver_type_secondary — default NONE.
  • serialrx_provider_secondary — default per target.
  • serialrx_inverted_secondary — default OFF.
  • serialrx_halfduplex_secondary — default AUTO.
  • sbus_sync_interval_secondary — default 3000 µs (range 500–10000).

Serial function masks (assigned with serial <idx> <mask> ..., additive when a port is shared):

  • Primary: FUNCTION_RX_SERIAL (64)
  • Secondary: FUNCTION_RX_SERIAL_SECONDARY (8192 = 1 << 13, reusing the previously
    unused serial-function bit)

Runtime / selection / failsafe

  • rxLink_e abstraction with per-link runtime state (channels, link quality, stats,
    timing) maintained every cycle for both PRIMARY and SECONDARY, plus the APIs
    rxGetActiveLink, rxSetActiveLink, rxIsLinkReceivingSignal, rxIsPrimaryFailsafe.
  • Selection: primary while it has a valid signal, else secondary; if neither is valid,
    primary stays selected but marked invalid. Main failsafe fires only when both links
    are down.
  • Serial RX drivers take a port-function argument so they bind to the primary or
    secondary UART; CRSF writes link statistics through the per-link runtime pointer.
  • LOGIC_CONDITION_RX_SELECT_OVERRIDE (operation 63) lets the programming framework
    force the active link (PRIMARY/SECONDARY) via the OVERRIDE_RX_LINK global flag.

OSD / UI

  • New OSD_ACTIVE_RX_LINK element (default position (3,5), CMS-exposed) showing RX:1
    or RX:2; blinks when running on the secondary link or when no link is valid.
  • New multifunction warning "PRI RX FS" shown when the primary is in failsafe while the
    secondary still has signal. The multifunction warning bitfield was widened from 8 to
    16 bits to accommodate the added warning.

MSP

  • MSP2_INAV_MISC reply appends a trailing activeRX byte (rxLink_e) reporting the
    current active link; older clients that stop reading before it are unaffected.

Known limitations

  • Two identical receivers work for CRSF and SBUS only (for now). Those two drivers hold
    per-link parser/channel state, so a primary and secondary receiver of the same type run
    independently. The remaining serial drivers (Spektrum, SUMD, IBUS, JETI, FPORT/FPORT2,
    SRXL2, GHST) still keep parser state in shared module storage, so running the same one
    of those on both links would corrupt both. rxInit() therefore refuses dual RX only when
    both links are SERIAL with the same, not-yet-instance-safe serialrx_provider (it falls
    back to single-RX), mirroring the existing MSP guard. Two different providers always run
    independently. Making the remaining drivers instance-safe (same pattern as CRSF/SBUS) is a
    follow-up.
  • No fail-back hysteresis. Selection returns to the primary as soon as it regains a
    valid signal, with no dwell time or blending across the switch. A marginal primary link
    can cause the active link to flap frame-to-frame; a hold-off timer is a sensible future
    refinement.
  • PG_RX_CONFIG version bumped (13 → 14) because the new secondary fields change the
    stored rxConfig_t layout — RX config resets to defaults on upgrade.

Docs

  • docs/Settings.md and the MSP enum/message docs (docs/development/msp/) regenerated.

Configurator companion

The INAV Configurator companion lives on feature/dualrx (based on mavlink_multiport2)
and adds the secondary-receiver UI, the RX_SERIAL_SECONDARY port function, the
activeRX MSP read, and the "Select RX link" programming operator (id 63).

Tests / verification

  • rx_rx_unittest (RxDualLinkTest) rewritten for the dual-link API — 3/3 pass.
  • SITL builds clean with warnings-as-errors.
  • Hardware sanity: SPEEDYBEEF405WING builds and fits (FLASH 72.67%, RAM 93.01%).
  • Full test pending

Build/run the unit test:

cmake -S . -B build_unit -DTOOLCHAIN=
cmake --build build_unit --target rx_rx_unittest
./build_unit/src/test/unit/rx_rx_unittest

xznhj8129 added 11 commits July 20, 2026 00:14
- MAV_CMD_COMPONENT_ARM_DISARM and MSP2_INAV_ARM_DISARM through the
  normal arming path, succeeding only when the requested state is
  reached
- RTH via a temporary BOXNAVRTH source on the RC mode selector
  (activateRTHMode) instead of the failsafe/geozone forced-RTH latch;
  wired to MAV_CMD_NAV_RETURN_TO_LAUNCH, ArduPilot DO_SET_MODE RTL,
  MSP2_INAV_ACTIVATE_RTH, and Programming Framework operation 61.
  Cleared by a pilot flight-mode change or disarm
- QGC/ArduPilot pause: DO_SET_MODE Loiter/PosHold/Brake enters normal
  PosHold at the current position via a temporary BOXNAVPOSHOLD source
- Normal current-position LAND (transient waypoint, uploaded mission
  untouched) via MAV_CMD_NAV_LAND, MSP2_INAV_ACTIVATE_LANDING, PF op 62
- MAV_CMD_DO_SET_HOME through the native waypoint-0 backend
- MSP2_INAV_TIMESYNC returning the MAVLink TIMESYNC boot clock
- Temporary fixed-wing loiter-radius override: DO_REPOSITION.param3
  (meters) or int32 loiterRadius appended to MSP2_INAV_SET_GLOBAL_TARGET
  (cm); volatile, cleared on disarm/reboot, only active in PosHold
- SET_POSITION_TARGET_GLOBAL_INT / _LOCAL_NED guided handling
- MAV_CMD_CONDITION_YAW; explicit unsupported MAV_CMD_NAV_TAKEOFF stub
- GCSN OSD flight-mode element while GCS navigation is active

Unit slice: 81/81 passing.
A command-triggered landing (MAV_CMD_NAV_LAND / MSP direct land) borrows
NAV_STATE_WAYPOINT_RTH_LAND and the FW autoland FSM with a transient
waypoint, while activeWaypointIndex still points at whatever mission item
was last active. The unconditional reached-marking added for mission LAND
items would emit MISSION_ITEM_REACHED for that stale index and could mark
a loaded mission complete. Capture forcedLandingActivated before the
existing clears and skip the marking for commanded landings at both
finish sites.
MSP transport over MAVLink TUNNEL (private payload type 0x8001,
MAVLink 2 only) so the Configurator can talk MSP over an existing
MAVLink telemetry link. Reuses the MSP parser/encoder through narrow
msp_serial seams; replies are fragmented and returned on the ingress
port only; MSPv1/v2 framing symmetry is preserved end-to-end. Reboot
post-processing is allowed; serial passthrough and ESC 4-way are
rejected before dispatch. Malformed payload lengths are dropped and
stale partial frames time out. Reply/frame buffers are file-scope, not
task stack.

Companion configurator branch adds the MAVLink Tunnel wireless option.
Full mavlink_unittest suite: 86/86 passing.

Final PR of the mavlink_multiport2 stack: tree now matches the feature
branch (remaining deltas are upstream maintenance-10.x drift only).
docs/Mavlink.md documents the full MAVLink stack: multiport + routing,
datastream groups and CLI settings, identity/capabilities, mLRS
integration, supported outgoing/incoming messages and commands, mode
mappings, mission behavior and MSP parity gaps, MSP-over-MAVLink
tunnel, and high-latency mode. Updated from the development branch to
cover flight-mode-change STATUSTEXT notices and reworded for mainline.

docs/Settings.md regenerated from settings.yaml (picks up the per-port
mavlink_port{1-4}_* settings).
End-to-end live harness against a running SITL/FC MAVLink endpoint:
API version, FC variant/version, build info, EEPROM write, reboot over
the tunnel, and reconnect recovery after reboot.
- Document per-peer reconnect STATUSTEXT/arming re-announcement
- Fix STATUSTEXT severity: arming-disable and mode notices are NOTICE only
- Correct MSP-parity LAND/RTH rows: leg speed and RTH land flag are captured on upload, dropped on download
- Restrict stream-rate CLI settings to port 1 (ports 2-4 have none)
- Complete upload-translator list (RTH/SET_POI/SET_HEAD) and DO_REPOSITION param4 heading
- Note downloads always reply MISSION_ITEM_INT
- Fix dead MSP-Navigation-Messages.md link -> Navigation.md wp section
Review feedback on the reconnect STATUSTEXT commit:

- Track heartbeat presence per peer in the route table instead of one
  timestamp per port, so a steady peer cannot mask a newly joining one
  behind the same port, and a peer failing over to another port
  registers as a reconnect there. If the route table is full the new
  peer degrades to broadcast-only behavior.
- Rate-limit arming snapshots per port so a peer heartbeating slower
  than the gap threshold cannot elicit a resend on every beat.
- Drop the enable-transition snapshot: it fired into a port nobody had
  connected to yet (boot) and duplicated the first-heartbeat snapshot on
  shared-port enable. Reconnect is now driven purely by the heartbeat
  signal; the enable transition still resets per-port one-shot state via
  the port reset path.
The release/9.1 merge (iNavFlight#11727) hit a conflict in taskSystem() where two
independent fixes for the SITL load-accounting bug (iNavFlight#11710) — one from
release/9.1, one from the mavlink stack — were concatenated instead of
one being chosen. The result nested a duplicate '#if defined(SITL_BUILD)'
block inside the '#else' branch, leaving the outer '#if' with no matching
'#endif' and failing every SITL build with 'unterminated #else'.

Remove the duplicated block, restoring the single intended (release/9.1)
version. No functional change to load accounting; unit builds were
unaffected (UNIT_TEST, not SITL_BUILD).
…ver)

Adds a second receiver that runs alongside the primary. Both links stay
active for telemetry, but only one drives RC control at a time. When the
primary link enters failsafe, control passes to the secondary; the main
failsafe now only triggers when both links are down.

- Gated by CLI `dual_rx_enabled` (default OFF); new secondary PG_RX_CONFIG
  settings (receiver_type_secondary, serialrx_provider_secondary,
  serialrx_inverted_secondary, serialrx_halfduplex_secondary,
  sbus_sync_interval_secondary).
- New serial function FUNCTION_RX_SERIAL_SECONDARY reuses the free
  FUNCTION_UNUSED_1 bit (1 << 13) so identical protocols can bind to
  distinct UARTs. All serial RX drivers take a port-function argument.
- rxLink_e runtime abstraction with per-link state each cycle and the
  rxGetActiveLink/rxSetActiveLink/rxIsLinkReceivingSignal/rxIsPrimaryFailsafe
  APIs; LOGIC_CONDITION_RX_SELECT_OVERRIDE to force a link.
- OSD OSD_ACTIVE_RX_LINK element (RX:P / RX:S, blinks on secondary/no-link)
  and a "PRI RX FS" multifunction warning. Widened the multifunction warning
  bitfield to 16 bits to fit the added warning.
- MSP2_INAV_MISC reports the active link (activeRX byte).
- rx_rx_unittest rewritten for the dual-link API (primary->secondary
  failover, secondary-active start, main-failsafe only when both down).

Regenerated docs/Settings.md and the MSP enum/message docs.
…afe fix

Addresses review findings on the dual RX feature:

- Bump PG_RX_CONFIG version 13->14. The new secondary receiver fields change
  the stored rxConfig_t layout, so without a version bump an upgrade would
  reinterpret old EEPROM with the new layout and corrupt RX config.
- Make the CRSF and SBUS drivers hold per-link parser and channel state so a
  primary and secondary receiver of the same type run independently instead of
  aliasing shared module buffers. Primary stays index 0, leaving the single-RX
  path unchanged. Remaining serial drivers still share state, so rxInit()
  refuses dual RX when both links use the same not-yet-instance-safe provider.
- Gate the LOGIC_CONDITION_RX_SELECT_OVERRIDE link override on the forced link
  actually having signal; otherwise fall back to automatic selection so a dead
  forced link can't hold control on stale data with failsafe suppressed.
- Remove the unused rxReceiverRole_e enum and document the activeRX MSP field
  with the real rxLink_e type; regenerate MSP docs.
Guard on the backing driver rather than the raw provider value so provider
pairs that share one driver are also caught: FBUS + FPORT2 both use the fport2
driver, and the SBUS variants share the sbus driver. Prevents an aliasing pair
(e.g. FBUS primary + FPORT2 secondary) from slipping past the equality check.
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