Skip to content

New Feature: Dual switchable receivers - #11748

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

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

Conversation

@xznhj8129

@xznhj8129 xznhj8129 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Dual RX: two live receiver links with latched failover and handover

Version 2

Status

This draft describes the current recovered, corrected, and SITL-validated firmware implementation as of 2026-08-29.

The source compiles and the Dual RX SITL validation suites pass. Hardware/bench validation is still pending, so this document does not claim flight readiness.

Summary

Adds two independently serviced receiver links, RX1 and RX2, while preserving INAV's existing single live RC stream downstream of the RX layer.

Both receivers remain hot. One is selected as the active control source. RX1 is selected at boot, but there is no permanent RX1 priority after that.

If the active link becomes invalid and the other link is valid, control transfers to the other link. Recovery of the inactive link does not switch back while the current active link remains valid. Normal INAV RX failsafe is invoked only when neither link is valid.

The active link can also be changed deliberately by a one-shot handover request.

Configuration

PG_RX_CONFIG is version 14.

New/current Dual RX settings:

  • dual_rx_enabled
  • receiver_type_rx2
  • serialrx_provider_rx2
  • serialrx_inverted_rx2
  • serialrx_halfduplex_rx2
  • sbus_sync_interval_rx2

RX2 serial input uses FUNCTION_RX_SERIAL_SECONDARY (1 << 13).

MSP_RX_CONFIG keeps the legacy 24-byte format and appends a seven-byte Dual RX extension for a total of 31 bytes. The setter accepts either 24 or 31 bytes.

Runtime architecture

Each receiver has private runtime state including:

  • runtime/driver configuration
  • decoded channels
  • signal freshness
  • flight-channel validity
  • processing state
  • normalized link-quality tracker
  • protocol statistics and validity mask

Both links are polled and processed continuously.

Only the selected link is published into the legacy global RX state used by the rest of INAV. Standby-link processing may update that link's private state but does not advance live stick processing, mode logic, RC smoothing, or other downstream control state machines.

Selection and failsafe

Selection is latched:

boot -> RX1

if active is valid:
    stay on active
else if other is valid:
    switch to other
else:
    retain active identity
    normal INAV failsafe applies

There is no automatic quality election and no automatic healthy-link fail-back.

When both links are invalid, the previous active identity stays latched. On recovery, the latched link resumes if it is valid; otherwise the other recovered link is selected because it is the only valid source.

failsafeOnValidDataReceived() is driven by validity of either usable link. Total RX failure is reported only when no valid link remains.

Explicit handover

rxRequestLinkHandover() queues a request for RX-task consumption instead of changing the live stream directly from a caller's scheduler context.

Current request sources:

  • MSP2_INAV_SET_RX_LINK (0x2232)
  • LOGIC_CONDITION_RX_HANDOVER operation 64
  • internal API callers

A request to the current valid link is a no-op success. An invalid/dead target is refused. A queued request is revalidated at consumption time.

Programming handover is edge-triggered. Holding the activator does not continuously force a receiver, and disarm/reset does not manufacture another rising edge while the switch remains held.

Supported pair safety

rxInit() refuses configurations known to share unsafe module-global state.

Explicitly unsupported:

  • MSP + MSP
  • SIM + SIM
  • same serial driver group unless that group is declared dual-instance-safe

Same-driver groups currently declared safe:

  • CRSF
  • SBUS / SBUS_FAST / SBUS2
  • MAVLink

FPORT/FPORT2/FBUS are grouped together and refused as a same-group pair because their SmartPort telemetry backend remains shared.

Spektrum 1024/2048 are grouped together. Other same-provider serial pairs remain refused until their drivers are explicitly made dual-instance-safe.

Different serial driver groups may be combined if both drivers initialize successfully.

Configuration errors are exposed through rxDualStatus_e rather than silently presenting RX2 as absent:

  • DISABLED
  • OK
  • RX1_NOT_CONFIGURED
  • RX2_NOT_CONFIGURED
  • UNSUPPORTED_PAIR
  • INIT_FAILED

CRSF

CRSF can run independently on both receiver links.

The implementation provides per-link:

  • parser/channel state
  • serial port
  • telemetry buffer
  • link statistics
  • MSP-over-CRSF shared-MSP context
  • request origin identity

MSP-over-CRSF request bytes are assembled from the serial callback, but MSP commands are executed later in telemetry task context. A pending endpoint cannot be overwritten by a second request, and the reply is routed back through the CRSF link that originated it.

MAVLink RX

MAVLink RX state is per link and ingress-port identity is mapped to RX1/RX2 through the serial-function mask.

A MAVLink receiver port must also be configured for FUNCTION_TELEMETRY_MAVLINK, because the telemetry runtime is the actual ingress path.

RC_CHANNELS_OVERRIDE updates only fields present in the message. However, it refreshes receiver liveness only when all four flight-control channels are present in that same message. AUX-only or empty override traffic cannot keep stale flight axes alive.

MAVLink + MAVLink is allowed.

MSP RX

Exactly one configured MSP receiver is supported in a Dual RX pair. It may be RX1 or RX2.

The configured MSP receiver has private frame state and must receive at least four flight-control channels for a frame to be considered usable control data. Shorter configured-MSP frames report RX failsafe instead of extending liveness.

The legacy MSP RC Override remains a separate path when MSP is not configured as a receiver.

When MSP is a configured receiver, MSP AUX overlay follows active-link authority. An inactive MSP link cannot mutate the live RC stream behind the selector.

MSP + MSP remains unsupported until generic MSP command dispatch preserves enough ingress identity for two logical MSP receivers.

SBUS

SBUS frame, channel, and port state are per link.

SBUS, SBUS_FAST, and SBUS2 share one driver group that is explicitly dual-instance-safe.

SBUS2 telemetry current slot, next-slot timing, telemetry page, last-frame timing, and port are tracked independently for RX1 and RX2.

Link statistics and RSSI

Each link owns its protocol statistics and a validity bitmask.

Statistics are informational and are not used by the selector.

When a link transitions to signal timeout or receiver-reported failsafe, its protocol statistics and validity mask are cleared. If it was active, the legacy active statistics mirror is cleared too. This prevents stale pre-loss RSSI/LQ/SNR values from reappearing as current values during recovery before fresh statistics arrive.

The normalized link-quality tracker retains its existing freshness timeout behavior.

When the active receiver changes, the RSSI moving-average filter is reseeded from the new active receiver for link-dependent RSSI sources, so old-link filter history does not bleed across the switch.

MSP observability

MSP2_INAV_GET_LINK_STATS preserves the existing three-byte active-link prefix and appends extension version 1 containing:

  • active RX
  • configured link mask
  • initialized link mask
  • valid link mask
  • statistics-valid link mask
  • Dual RX status
  • last switch reason
  • last switch time
  • per-link statistics validity, normalized quality, and protocol statistics

MSP2_INAV_SET_RX_LINK requests explicit handover with payload 0 for RX1 or 1 for RX2.

OSD and warnings

New OSD elements:

  • OSD_ACTIVE_RX_LINK -> RX:1 / RX:2
  • OSD_RX1_LINK_STATS
  • OSD_RX2_LINK_STATS

The active-link element blinks when the active link itself is invalid, not merely because RX2 is selected.

Per-link status rows distinguish unconfigured, initialization failure, lost link, available protocol statistics, normalized quality, and valid-without-metrics.

Multifunction warnings expose loss of either configured receiver while the other remains valid. Degraded redundancy is visible without creating a new aircraft failsafe or arming prohibition.

Blackbox

Blackbox slow state adds:

  • rxActiveLink
  • rxValidLinkMask
  • rxSwitchReason
  • rxSwitchTimeMs
  • rxDualStatus

The blackbox header records dual_rx_enabled, RX2 receiver type, and RX2 serial provider.

Bind behavior

rxbind defaults to the active receiver and accepts an RX number where supported. CRSF binding targets the requested link's CRSF instance.

Deliberate limitations

Dual RX v1 does not implement:

  • automatic RSSI/LQ/SNR quality arbitration
  • channel agreement checking
  • control-stream blending/interpolation
  • MSP + MSP
  • every possible same-driver legacy serial pair
  • a new RC-source field in MAVLink or MSP wire data

Verification

Current verified results on this implementation:

  • recovered/fixed source compiles
  • core Dual RX SITL selector/failsafe suite passes, including:
    • RX1 boot default and boot-order stability
    • standby-link RC and AUX isolation
    • RX1 -> RX2 and RX2 -> RX1 failover
    • no automatic fail-back when the inactive link recovers
    • both-links-down FC failsafe with active identity retained
    • recovery when either link returns first
    • explicit MSP handover in both directions and invalid-target rejection
    • stale statistics invalidation across loss and fresh-statistics recovery
    • malformed/CRC-bad CRSF isolation and byte-at-a-time parser recovery
    • deterministic randomized transition stress
  • Dual RX configuration matrix passes for accepted and deliberately rejected pairings, including CRSF + CRSF, SBUS + SBUS2, MAVLink + MAVLink, CRSF + SBUS, MSP + MSP rejection, SIM + SIM rejection, and FPORT-family same-group rejection
  • CRSF telemetry/MSP-over-CRSF ownership suite passes:
    • RX1-originated replies remain on RX1
    • RX2-originated replies remain on RX2
    • simultaneous requests retain independent endpoint ownership
    • an interrupted request on one link cannot block or steal the other link's reply
    • a link accepts a fresh request after an interrupted transaction
  • mixed-ingress SITL suite passes:
    • CRSF + MAVLink
    • CRSF + MSP
    • MAVLink + MAVLink
    • MAVLink partial/AUX-only messages do not establish or preserve RX liveness
    • configured MSP partial frames do not establish or preserve RX liveness
    • legacy MSP RC Override remains isolated from configured receiver ownership
    • selector, publication, recovery, no-failback, and AUX authority semantics hold across all three mixed pairings

Still required before claiming hardware/flight readiness:

  • representative hardware target builds
  • physical bench failover/reconnect tests with real receivers
  • real receiver telemetry/reply ownership checks
  • single-RX regression on representative hardware
  • explicit OSD visual verification
  • blackbox field/output verification

@sensei-hacker

sensei-hacker commented Aug 29, 2026

Copy link
Copy Markdown
Member

I see the build is failing on RAM.
Some of these ideas may be helpful:

https://github.com/sensei-hacker/inav/blob/d35955b5156f2f18b2c6f77daaa5995881b214d7/docs/development/ram-and-flash-optimization.md

@github-actions

Copy link
Copy Markdown

RAM / Flash usage vs. base branch — commit 3db4ab9

No size baseline is available yet for this PR's base commit (no per-commit baseline has been published for it). This comment will show deltas once one exists — rebasing the PR refreshes its base commit.

Target Flash Δ RAM Δ
MATEKF405 709699 B (no baseline) 152220 B (no baseline)
MATEKF722 470479 B (no baseline) 127892 B (no baseline)
MATEKF765 741127 B (no baseline) 168112 B (no baseline)
MATEKH743 778155 B (no baseline) 170536 B (no baseline)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

Copy link
Copy Markdown

Test firmware build ready — commit 3db4ab9

Download firmware for PR #11748

247 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

@xznhj8129
xznhj8129 marked this pull request as ready for review August 31, 2026 04:28
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add dual live receivers with latched failover and explicit handover

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Services two hot receiver links while publishing one authoritative RC stream.
• Adds latched loss failover, explicit handover, and per-link telemetry observability.
• Validates supported protocol pairings, isolation, recovery, and failsafe behavior in SITL.
Diagram

graph TD
  RX1["RX1 Driver"] --> STATE["Per-Link State"] --> SELECT{"Select Link"} --> LIVE["Legacy RC Stream"] --> CONTROL["Control Pipeline"]
  RX2["RX2 Driver"] --> STATE
  HANDOVER["Handover Request"] --> SELECT
  SELECT --> FAILSAFE["Failsafe State"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Uniform instance-based receiver driver interface
  • ➕ Eliminates protocol-group allowlists over time
  • ➕ Makes every receiver driver explicitly reentrant and independently testable
  • ➖ Requires a much larger migration across legacy drivers and telemetry backends
  • ➖ Increases regression risk and delays useful redundancy support
2. External hardware receiver arbitration
  • ➕ Keeps firmware's RX pipeline single-source
  • ➕ Can isolate failover from flight-controller software faults
  • ➖ Cannot expose both links' detailed state through existing firmware interfaces
  • ➖ Adds hardware cost and offers less control over handover semantics

Recommendation: Use the PR's staged design: isolate state for proven dual-instance protocols, reject unsafe same-driver pairs, and preserve the legacy downstream RC contract. A uniform instance-based driver architecture is the best long-term direction, but it should be pursued incrementally rather than expanding this already safety-critical change.

Files changed (61) +4481 / -902

Enhancement (48) +2146 / -881
blackbox.cRecord Dual RX state in blackbox logs +23/-0

Record Dual RX state in blackbox logs

• Adds active link, validity mask, switch metadata, status, and RX2 configuration to slow frames and headers.

src/main/blackbox/blackbox.c

cms_menu_osd.cExpose Dual RX elements in the OSD menu +3/-0

Expose Dual RX elements in the OSD menu

• Adds menu entries for active receiver and per-link statistics elements.

src/main/cms/cms_menu_osd.c

cli.cSupport receiver-specific serial binding +41/-18

Support receiver-specific serial binding

• Extends bind_rx to target RX1 or RX2, defaulting to the active link and supporting CRSF.

src/main/fc/cli.c

fc_init.cRecognize RX2 Spektrum startup binding +10/-10

Recognize RX2 Spektrum startup binding

• Runs board-level Spektrum bind initialization when either configured receiver uses Spektrum.

src/main/fc/fc_init.c

fc_mavlink.cRoute MAVLink RX ingress and statistics per link +145/-61

Route MAVLink RX ingress and statistics per link

• Maps ingress ports to receiver links, validates override targets, and stores only valid per-link radio metrics.

src/main/fc/fc_mavlink.c

fc_msp.cAdd Dual RX MSP configuration and control +133/-26

Add Dual RX MSP configuration and control

• Extends RX configuration and link statistics compatibly, adds handover control, and routes bind and metrics by link.

src/main/fc/fc_msp.c

fc_tasks.cEnable SBUS2 telemetry for either receiver +4/-1

Enable SBUS2 telemetry for either receiver

• Schedules SBUS2 telemetry when RX1 or initialized RX2 uses the provider.

src/main/fc/fc_tasks.c

multifunction.cWarn about Dual RX errors and degraded redundancy +25/-4

Warn about Dual RX errors and degraded redundancy

• Adds configuration, RX1 loss, and RX2 loss warnings without converting single-link degradation into failsafe.

src/main/fc/multifunction.c

multifunction.hExpand multifunction warning capacity +1/-1

Expand multifunction warning capacity

• Widens warning flags to accommodate the new Dual RX warnings.

src/main/fc/multifunction.h

osd.cRender active receiver and per-link status +53/-5

Render active receiver and per-link status

• Adds active-link and RX1/RX2 status rows with validity-aware metrics, errors, loss states, and blinking.

src/main/io/osd.c

osd.hDeclare Dual RX OSD elements +3/-0

Declare Dual RX OSD elements

• Adds identifiers for active receiver and both per-link statistics rows.

src/main/io/osd.h

mavlink_runtime.cEnable MAVLink runtime for RX2 ports +12/-8

Enable MAVLink runtime for RX2 ports

• Uses receiver-link mapping to force telemetry ingress and select per-link half-duplex settings.

src/main/mavlink/mavlink_runtime.c

msp_protocol_v2_inav.hDefine the RX handover MSP command +1/-0

Define the RX handover MSP command

• Adds MSP2_INAV_SET_RX_LINK for one-shot RX1/RX2 handover requests.

src/main/msp/msp_protocol_v2_inav.h

logic_condition.cImplement edge-triggered RX handover logic +28/-1

Implement edge-triggered RX handover logic

• Queues one handover per activator edge and preserves edge state across generic resets.

src/main/programming/logic_condition.c

logic_condition.hDeclare the RX handover logic operation +1/-0

Declare the RX handover logic operation

• Assigns programming operation 64 to receiver handover.

src/main/programming/logic_condition.h

crsf.cMake CRSF receiver state dual-instance safe +194/-113

Make CRSF receiver state dual-instance safe

• Separates parser, channels, ports, telemetry buffers, metrics, and binding for RX1 and RX2.

src/main/rx/crsf.c

crsf.hExpose link-aware CRSF APIs +7/-2

Expose link-aware CRSF APIs

• Adds per-link initialization, telemetry buffering, buffer availability, and binding interfaces.

src/main/rx/crsf.h

fport.cSelect the configured FPORT receiver port +3/-3

Select the configured FPORT receiver port

• Accepts a serial function so FPORT can initialize in either receiver slot when paired safely.

src/main/rx/fport.c

fport.hUpdate the FPORT initialization contract +3/-1

Update the FPORT initialization contract

• Declares link-port-aware FPORT initialization.

src/main/rx/fport.h

fport2.cSelect the configured FPORT2 or FBUS port +3/-3

Select the configured FPORT2 or FBUS port

• Accepts a serial function for slot-specific FPORT2 and FBUS initialization.

src/main/rx/fport2.c

fport2.hUpdate the FPORT2 initialization contract +3/-1

Update the FPORT2 initialization contract

• Declares link-port-aware FPORT2 and FBUS initialization.

src/main/rx/fport2.h

ghst.cSelect the configured GHST receiver port +3/-3

Select the configured GHST receiver port

• Initializes GHST against the serial function assigned to its receiver slot.

src/main/rx/ghst.c

ghst.hUpdate the GHST initialization contract +2/-1

Update the GHST initialization contract

• Declares link-port-aware GHST initialization.

src/main/rx/ghst.h

ibus.cSelect the configured IBUS receiver port +4/-4

Select the configured IBUS receiver port

• Uses the slot-specific serial function for IBUS initialization and shared telemetry detection.

src/main/rx/ibus.c

ibus.hUpdate the IBUS initialization contract +3/-1

Update the IBUS initialization contract

• Declares link-port-aware IBUS initialization.

src/main/rx/ibus.h

jetiexbus.cSelect the configured Jeti EX Bus port +3/-3

Select the configured Jeti EX Bus port

• Initializes Jeti EX Bus on the serial function assigned to its receiver slot.

src/main/rx/jetiexbus.c

jetiexbus.hUpdate the Jeti EX Bus initialization contract +3/-1

Update the Jeti EX Bus initialization contract

• Declares link-port-aware Jeti EX Bus initialization.

src/main/rx/jetiexbus.h

mavlink.cMaintain independent MAVLink receiver state +92/-43

Maintain independent MAVLink receiver state

• Maps telemetry ports to RX links and requires all flight channels in one override message to refresh liveness.

src/main/rx/mavlink.c

mavlink.hExpose link-aware MAVLink RX APIs +4/-2

Expose link-aware MAVLink RX APIs

• Declares port mapping, per-link message handling, and slot-specific initialization.

src/main/rx/mavlink.h

msp.cEnforce configured MSP receiver validity +57/-31

Enforce configured MSP receiver validity

• Separates configured-receiver semantics from legacy override and rejects short MSP control frames as failsafe.

src/main/rx/msp.c

msp.hSplit MSP receiver and override initialization +3/-2

Split MSP receiver and override initialization

• Declares link-aware configured MSP initialization and a distinct legacy override path.

src/main/rx/msp.h

rx.cImplement the Dual RX runtime and selector +690/-163

Implement the Dual RX runtime and selector

• Adds isolated link state, pair safety checks, continuous polling, latched failover, queued handover, aggregate failsafe, statistics invalidation, and RSSI reseeding.

src/main/rx/rx.c

rx.hDefine Dual RX configuration, state, and APIs +86/-18

Define Dual RX configuration, state, and APIs

• Adds RX2 settings, link/status/switch enums, statistics validity, runtime pointers, and observability interfaces.

src/main/rx/rx.h

sbus.cMake SBUS and SBUS2 state link-local +49/-35

Make SBUS and SBUS2 state link-local

• Separates parser, channels, ports, timing, telemetry page, and sync configuration for both links.

src/main/rx/sbus.c

sbus.hExpose link-aware SBUS interfaces +5/-5

Expose link-aware SBUS interfaces

• Adds slot-specific initialization and per-link SBUS2 telemetry state accessors.

src/main/rx/sbus.h

spektrum.cSelect the configured Spektrum receiver port +4/-4

Select the configured Spektrum receiver port

• Uses the slot-specific serial function and preserves deterministic ownership of shared telemetry.

src/main/rx/spektrum.c

spektrum.hUpdate the Spektrum initialization contract +4/-2

Update the Spektrum initialization contract

• Declares link-port-aware Spektrum initialization.

src/main/rx/spektrum.h

srxl2.cSelect the configured SRXL2 receiver port +3/-3

Select the configured SRXL2 receiver port

• Initializes SRXL2 on the serial function assigned to its receiver slot.

src/main/rx/srxl2.c

srxl2.hUpdate the SRXL2 initialization contract +1/-1

Update the SRXL2 initialization contract

• Declares link-port-aware SRXL2 initialization.

src/main/rx/srxl2.h

sumd.cSelect the configured SUMD receiver port +4/-4

Select the configured SUMD receiver port

• Uses the slot-specific serial function and preserves deterministic shared telemetry ownership.

src/main/rx/sumd.c

sumd.hUpdate the SUMD initialization contract +3/-1

Update the SUMD initialization contract

• Declares link-port-aware SUMD initialization.

src/main/rx/sumd.h

crsf.cRoute CRSF telemetry transactions by receiver +109/-70

Route CRSF telemetry transactions by receiver

• Adds independent MSP contexts and request origins, defers command execution to task context, and returns replies through their ingress link.

src/main/telemetry/crsf.c

crsf.hDeclare link-aware CRSF MSP buffering +2/-3

Declare link-aware CRSF MSP buffering

• Replaces global scheduling APIs with per-link request assembly.

src/main/telemetry/crsf.h

ibus.cRecognize telemetry shared with RX2 +2/-1

Recognize telemetry shared with RX2

• Avoids reopening IBUS telemetry ports shared with the secondary receiver function.

src/main/telemetry/ibus.c

msp_shared.cMake MSP-over-telemetry endpoint scoped +241/-154

Make MSP-over-telemetry endpoint scoped

• Refactors request assembly, dispatch, fragmentation, busy-state protection, and completion around caller-owned contexts.

src/main/telemetry/msp_shared.c

msp_shared.hDefine reusable MSP telemetry contexts +38/-19

Define reusable MSP telemetry contexts

• Introduces caller-owned endpoint state, buffers, pending-state queries, deferred processing, and completion callbacks.

src/main/telemetry/msp_shared.h

sbus2.cService SBUS2 telemetry independently per link +34/-48

Service SBUS2 telemetry independently per link

• Tracks slots and timing separately and writes telemetry through each configured receiver port.

src/main/telemetry/sbus2.c

telemetry.cTreat RX2 ports as telemetry-shareable +1/-1

Treat RX2 ports as telemetry-shareable

• Includes the secondary receiver function when detecting shared telemetry ports.

src/main/telemetry/telemetry.c

Bug fix (2) +22 / -8
fc_core.cIsolate standby traffic from control processing +13/-5

Isolate standby traffic from control processing

• Advances downstream RC state machines only when the active published stream actually changes.

src/main/fc/fc_core.c

msp_override.cConstrain MSP override authority to the active link +9/-3

Constrain MSP override authority to the active link

• Prevents an inactive configured MSP receiver from bypassing selection through channel or flight-axis overrides.

src/main/rx/msp_override.c

Refactor (2) +16 / -7
sbus2.hRemove obsolete global SBUS2 slot APIs +0/-3

Remove obsolete global SBUS2 slot APIs

• Drops helpers replaced by per-link scheduling state inside the telemetry task.

src/main/telemetry/sbus2.h

smartport.cAdopt endpoint-scoped shared MSP state +16/-4

Adopt endpoint-scoped shared MSP state

• Migrates SmartPort MSP-over-telemetry to the reusable context API with right-sized buffers.

src/main/telemetry/smartport.c

Tests (5) +2181 / -3
dualrx_config_matrix.pyTest supported and rejected receiver pairings +155/-0

Test supported and rejected receiver pairings

• Boots SITL across same-driver, mixed-driver, MSP, SIM, FPORT-family, SBUS, CRSF, and MAVLink combinations.

src/test/dualrx/dualrx_config_matrix.py

dualrx_crsf_telemetry_test.pyTest dual CRSF telemetry ownership +332/-0

Test dual CRSF telemetry ownership

• Verifies per-link reply routing, simultaneous requests, interrupted transactions, and endpoint reuse in SITL.

src/test/dualrx/dualrx_crsf_telemetry_test.py

dualrx_mixed_sitl_test.pyTest mixed receiver ingress paths +485/-0

Test mixed receiver ingress paths

• Exercises CRSF/MAVLink, CRSF/MSP, and dual MAVLink selection, liveness, AUX isolation, and legacy override behavior.

src/test/dualrx/dualrx_mixed_sitl_test.py

dualrx_sitl_test.pyAdd end-to-end Dual RX selector tests +1168/-0

Add end-to-end Dual RX selector tests

• Covers boot ordering, failover, recovery, no-failback, handover, failsafe, parser corruption, stale metrics, isolation, and randomized transitions.

src/test/dualrx/dualrx_sitl_test.py

mavlink_unittest.ccUpdate MAVLink tests for receiver-link routing +41/-3

Update MAVLink tests for receiver-link routing

• Configures RX ownership explicitly, verifies target mismatch rejection, and supplies new per-link test stubs.

src/test/unit/mavlink_unittest.cc

Documentation (1) +80 / -0
Settings.mdDocument Dual RX configuration settings +80/-0

Document Dual RX configuration settings

• Documents Dual RX enablement and all RX2 receiver, serial, inversion, half-duplex, and SBUS timing settings.

docs/Settings.md

Other (3) +36 / -3
settings.yamlDefine Dual RX CLI settings +35/-0

Define Dual RX CLI settings

• Registers Dual RX enablement and RX2 type, provider, electrical, and SBUS timing configuration.

src/main/fc/settings.yaml

serial.hAllocate a secondary serial RX function +1/-1

Allocate a secondary serial RX function

• Repurposes serial function bit 13 as FUNCTION_RX_SERIAL_SECONDARY.

src/main/io/serial.h

target.hEnable CRSF telemetry features in SITL +0/-2

Enable CRSF telemetry features in SITL

• Stops disabling CRSF telemetry and MSP-over-telemetry so dual-link ownership can be tested in simulation.

src/main/target/SITL/target.h

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. RX upgrade erases configuration 🐞 Bug ≡ Correctness
Description
Bumping PG_RX_CONFIG to 14 makes pgLoad() reset every stored v13 RX group without copying any
prior receiver settings, so upgrading can leave an aircraft with default or unusable RX
configuration. The new fields can be placed after the existing serialized fields and loaded with the
existing version/size-prefix behavior, preserving old settings while defaulting the Dual RX
extension.
Code

src/main/rx/rx.c[125]

+PG_REGISTER_WITH_RESET_TEMPLATE(rxConfig_t, rxConfig, PG_RX_CONFIG, 14);
Evidence
The PR changes the RX group version and inserts fields into its persisted layout. Repository loading
semantics reset first and copy stored bytes only for an exact version match, proving all v13 values
are discarded; because the loader already copies MIN(stored_size,current_size) after reset, a
trailing compatible extension or migration can retain them.

src/main/rx/rx.c[125-125]
src/main/rx/rx.h[110-121]
src/main/config/parameter_group.c[48-59]
src/main/config/parameter_group.c[86-93]
src/main/config/config_eeprom.c[234-240]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Upgrading from PG_RX_CONFIG v13 to this PR's v14 discards the entire stored RX group because parameter groups are copied only when versions match. Preserve existing RX settings while adding defaults for the Dual RX fields.

## Issue Context
The loader resets the current object and then copies the stored-size prefix when versions match. Rework the persisted struct so all legacy fields retain their offsets and the new fields form a trailing extension, or add an explicit v13-to-v14 migration.

## Fix Focus Areas
- src/main/rx/rx.c[125-125]
- src/main/rx/rx.h[110-121]
- src/main/config/parameter_group.c[86-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. OSD upgrade erases layouts 🐞 Bug ≡ Correctness
Description
Changing PG_OSD_LAYOUTS_CONFIG from 3 to 4 causes pgLoad() to reset the whole group and skip
copying every stored custom item position. Thus installing this feature silently replaces all user
OSD layouts with defaults rather than merely adding the three new items.
Code

src/main/io/osd.c[232]

+PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t, osdLayoutsConfig, PG_OSD_LAYOUTS_CONFIG, 4);
Evidence
The changed registration rejects stored v3 records. pgLoad() always resets the destination and
copies EEPROM bytes only when the versions match, while osdLayoutsConfig_t stores all item
positions in the affected group, so every customized layout is lost.

src/main/io/osd.c[231-232]
src/main/io/osd.h[368-375]
src/main/io/osd.h[439-444]
src/main/config/parameter_group.c[86-93]
src/main/config/config_eeprom.c[234-240]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The OSD layout group version bump discards all v3 custom layouts on upgrade. Add a migration that preserves every old item position and initializes only the newly appended Dual RX items.

## Issue Context
Simply retaining version 3 is insufficient because `item_pos` is a two-dimensional array whose per-layout stride grows with `OSD_ITEM_COUNT`; migrate each old layout row explicitly into the new row shape.

## Fix Focus Areas
- src/main/io/osd.c[232-232]
- src/main/io/osd.h[368-375]
- src/main/io/osd.h[439-444]
- src/main/config/parameter_group.c[86-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Uninitialized RX2 bind succeeds 🐞 Bug ≡ Correctness
Description
bind_rx 2 checks only that RX2 is configured, so an RX2 disabled by dual_rx_enabled=OFF, an
unsupported pair, or initialization failure still reaches srxl2Bind() and prints success. SRXL2
only transmits the queued frame through its initialized singleton serial port, so this command
either sends nothing or can use the primary SRXL2 backend instead of the requested RX2.
Code

src/main/fc/cli.c[R3635-3637]

+    if (!rxIsLinkConfigured(link)) {
+        cliPrint("RX not configured.");
+        return;
Evidence
Core initialization records configuration before disabling RX2 for non-enabled/unsupported pairs, so
configured does not imply initialized. The CLI gates on configured alone, then unconditionally
queues and announces SRXL2 binding; the SRXL2 send path depends on singleton state and a serial port
established only by initialization.

src/main/rx/rx.c[424-464]
src/main/rx/rx.c[501-515]
src/main/fc/cli.c[3635-3655]
src/main/rx/srxl2.c[428-450]
src/main/rx/srxl2.c[470-478]
src/main/rx/srxl2.c[557-575]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new link-selectable bind command accepts a configured but inactive/uninitialized RX2 and reports SRXL2 binding even though that link has no serial backend. Validate runtime initialization before dispatching the bind operation.

## Issue Context
`rxIsLinkConfigured()` intentionally remains true for requested links that Dual RX did not activate, while `srxl2Bind()` only queues data for the module-global backend created by `srxl2RxInit()`.

## Fix Focus Areas
- src/main/fc/cli.c[3635-3655]
- src/main/rx/rx.c[424-464]
- src/main/rx/srxl2.c[470-478]
- src/main/rx/srxl2.c[557-575]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/rx/rx.c
static uint8_t rcSampleIndex = 0;

PG_REGISTER_WITH_RESET_TEMPLATE(rxConfig_t, rxConfig, PG_RX_CONFIG, 13);
PG_REGISTER_WITH_RESET_TEMPLATE(rxConfig_t, rxConfig, PG_RX_CONFIG, 14);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Rx upgrade erases configuration 🐞 Bug ≡ Correctness

Bumping PG_RX_CONFIG to 14 makes pgLoad() reset every stored v13 RX group without copying any
prior receiver settings, so upgrading can leave an aircraft with default or unusable RX
configuration. The new fields can be placed after the existing serialized fields and loaded with the
existing version/size-prefix behavior, preserving old settings while defaulting the Dual RX
extension.
Agent Prompt
## Issue description
Upgrading from PG_RX_CONFIG v13 to this PR's v14 discards the entire stored RX group because parameter groups are copied only when versions match. Preserve existing RX settings while adding defaults for the Dual RX fields.

## Issue Context
The loader resets the current object and then copies the stored-size prefix when versions match. Rework the persisted struct so all legacy fields retain their offsets and the new fields form a trailing extension, or add an explicit v13-to-v14 migration.

## Fix Focus Areas
- src/main/rx/rx.c[125-125]
- src/main/rx/rx.h[110-121]
- src/main/config/parameter_group.c[86-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/main/io/osd.c

PG_REGISTER_WITH_RESET_TEMPLATE(osdConfig_t, osdConfig, PG_OSD_CONFIG, 0);
PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t, osdLayoutsConfig, PG_OSD_LAYOUTS_CONFIG, 3);
PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t, osdLayoutsConfig, PG_OSD_LAYOUTS_CONFIG, 4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Osd upgrade erases layouts 🐞 Bug ≡ Correctness

Changing PG_OSD_LAYOUTS_CONFIG from 3 to 4 causes pgLoad() to reset the whole group and skip
copying every stored custom item position. Thus installing this feature silently replaces all user
OSD layouts with defaults rather than merely adding the three new items.
Agent Prompt
## Issue description
The OSD layout group version bump discards all v3 custom layouts on upgrade. Add a migration that preserves every old item position and initializes only the newly appended Dual RX items.

## Issue Context
Simply retaining version 3 is insufficient because `item_pos` is a two-dimensional array whose per-layout stride grows with `OSD_ITEM_COUNT`; migrate each old layout row explicitly into the new row shape.

## Fix Focus Areas
- src/main/io/osd.c[232-232]
- src/main/io/osd.h[368-375]
- src/main/io/osd.h[439-444]
- src/main/config/parameter_group.c[86-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/main/fc/cli.c
Comment on lines +3635 to +3637
if (!rxIsLinkConfigured(link)) {
cliPrint("RX not configured.");
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Uninitialized rx2 bind succeeds 🐞 Bug ≡ Correctness

bind_rx 2 checks only that RX2 is configured, so an RX2 disabled by dual_rx_enabled=OFF, an
unsupported pair, or initialization failure still reaches srxl2Bind() and prints success. SRXL2
only transmits the queued frame through its initialized singleton serial port, so this command
either sends nothing or can use the primary SRXL2 backend instead of the requested RX2.
Agent Prompt
## Issue description
The new link-selectable bind command accepts a configured but inactive/uninitialized RX2 and reports SRXL2 binding even though that link has no serial backend. Validate runtime initialization before dispatching the bind operation.

## Issue Context
`rxIsLinkConfigured()` intentionally remains true for requested links that Dual RX did not activate, while `srxl2Bind()` only queues data for the module-global backend created by `srxl2RxInit()`.

## Fix Focus Areas
- src/main/fc/cli.c[3635-3655]
- src/main/rx/rx.c[424-464]
- src/main/rx/srxl2.c[470-478]
- src/main/rx/srxl2.c[557-575]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

2 participants