Skip to content

Dual Receiver support - #2694

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

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

Conversation

@xznhj8129

@xznhj8129 xznhj8129 commented Jul 25, 2026 •

Copy link
Copy Markdown

Dual Receiver support

Configurator support for INAV Dual RX.

This exposes the second receiver configuration, allows each receiver to be assigned to its own serial port, shows the state of both links live, and provides manual/Programming Framework handover controls.

Companion firmware PR: iNavFlight/inav#11748.

What this adds

Dual RX configuration

Dual RX can be enabled from the Configurator and exposes separate receiver configuration for:

  • RX1 receiver type
  • RX2 receiver type
  • RX1 serial provider
  • RX2 serial provider
  • inversion
  • half-duplex
  • RX2 SBUS sync interval where applicable

RX1 remains the normal existing receiver configuration. RX2 is an additional receiver configuration used when Dual RX is enabled.

The existing channel map is currently global. For example, an AETR map is applied to both RX1 and RX2 rather than being configured separately per receiver.

Ports

Adds the secondary serial receiver function:

RX_SERIAL_SECONDARY

using firmware serial-function bit 13:

FUNCTION_RX_SERIAL_SECONDARY = 1 << 13

A typical serial Dual RX setup therefore has:

UART A -> RX_SERIAL
UART B -> RX_SERIAL_SECONDARY

The two receivers may use the same supported protocol or different supported protocols.

Receiver status

The Receiver tab displays live Dual RX state, including:

  • active link
  • RX1 state
  • RX2 state
  • per-link RSSI/LQ/SNR when the receiver protocol supplies them
  • overall Dual RX status
  • last handover reason and time
  • manual handover controls

The status is obtained using:

MSP2_INAV_GET_LINK_STATS = 0x2103

The returned status includes configured, initialized and valid-link state separately, so the UI can distinguish cases such as:

OK
Lost
Not configured
Initialization error

rather than treating every unavailable receiver identically.

Per-link statistics are only displayed when the firmware reports both the link statistics and the corresponding statistic fields as valid.

Manual handover

The Receiver tab provides:

Select RX1
Select RX2

controls using:

MSP2_INAV_SET_RX_LINK = 0x2232

The firmware only accepts a handover to a currently valid receiver.

The button for the active receiver is disabled, as is a button for a receiver which is currently unavailable.

This is an explicit handover event, not a persistent receiver preference.

Programming Framework

Adds the Dual RX handover Logic Condition operation:

64: RX handover

matching the current firmware enum:

LOGIC_CONDITION_ACTIVATE_LANDING = 63
LOGIC_CONDITION_RX_HANDOVER      = 64

Operand A selects the requested receiver:

1 = RX1
2 = RX2

This allows a Logic Condition to request an explicit receiver handover.

OSD

Adds Configurator support for the firmware Dual RX OSD items:

ACTIVE_RX_LINK
RX1_LINK_STATS
RX2_LINK_STATS

These allow the active receiver and individual link state/statistics to be shown in the OSD.


How to configure it

A serial Dual RX setup currently involves three parts of Configurator.

1. Enable Dual RX

Enable Dual RX in the Configuration tab and save/reboot.

2. Assign the receiver ports

In the Ports tab:

RX1 UART -> Serial RX
RX2 UART -> Secondary Serial RX

Each receiver must have its own appropriate serial port.

If a receiver protocol also requires another serial function on the same port, such as MAVLink telemetry, configure that as required by that protocol.

3. Configure both receivers

In the Receiver tab, configure the primary and secondary receiver independently.

For example:

RX1:
Receiver type: SERIAL
Provider: CRSF

RX2:
Receiver type: SERIAL
Provider: SBUS

or:

RX1: CRSF
RX2: CRSF

Save and reboot.

The Dual RX Status section should then show both configured links and which one is currently active.


Runtime behavior

RX1 is the initial active receiver.

When both links are healthy, receiving valid data on RX2 does not automatically replace RX1.

If the currently active receiver becomes invalid and the other receiver is valid, firmware transfers control to the surviving link.

Recovery of the previously lost receiver does not automatically take control back.

For example:

Boot:
RX1 active
RX2 standby

RX1 lost:
RX2 becomes active

RX1 recovers:
RX2 remains active

RX2 later lost:
RX1 becomes active

The Configurator reflects this firmware state rather than implementing receiver-selection policy itself.

Manual handover through the Receiver tab or Programming Framework can explicitly select the other currently valid receiver.


Supported combinations

The firmware decides whether a receiver pair is supported.

Same-driver Dual RX is supported for drivers which have independent per-link state, including:

CRSF + CRSF
SBUS-family + SBUS-family
MAVLink + MAVLink

Mixed receiver types such as:

CRSF + SBUS
CRSF + MAVLink
CRSF + MSP

are also supported where the corresponding firmware drivers can operate independently.

Some same-driver combinations remain intentionally unsupported where the underlying receiver/telemetry implementation still uses shared singleton state.

Configurator reports these cases through the firmware Dual RX status instead of silently hiding the second receiver.


Firmware compatibility

This Configurator change targets the current INAV 10 Dual RX implementation.

Important parts of the current firmware contract are:

FUNCTION_RX_SERIAL_SECONDARY = 1 << 13

MSP2_INAV_GET_LINK_STATS = 0x2103
MSP2_INAV_SET_RX_LINK    = 0x2232

LOGIC_CONDITION_RX_HANDOVER = 64

Dual RX configuration is carried by the extended RX configuration data/settings. It does not use an activeRX byte appended to MSP2_INAV_MISC.

The active receiver is runtime state reported by MSP2_INAV_GET_LINK_STATS.


Testing

The Configurator has been exercised against current Dual RX firmware on real hardware.

Confirmed configurations include:

CRSF + CRSF
CRSF + SBUS

with live receiver state visible in Configurator and receiver failover functioning on hardware.

The Configurator-side status display has also been verified with real CRSF link statistics.

Firmware-side SITL coverage separately exercises receiver selection, failure/recovery behavior, mixed receiver configurations and explicit handover.


Current UI/workflow limitations

The functionality works, but the setup workflow can still be improved.

At present configuration is spread across:

Configuration tab -> enable Dual RX
Ports tab         -> assign RX1/RX2 ports
Receiver tab      -> configure both receivers and inspect status

That is functional but not especially discoverable.

There are also some smaller presentation issues worth improving:

  • the channel bars show the currently published/active RC stream but do not clearly label which RX is providing it
  • the channel map is global to both receivers, which is not obvious from the UI
  • the last-switch timestamp is currently displayed in milliseconds, which is unnecessarily fine-grained for the user-facing display
  • the status UI could identify the receiver involved in a link-loss handover more explicitly
  • secondary-receiver protocol-specific configuration could be presented more consistently

These are UI/workflow improvements rather than blockers for the current Dual RX functionality.

The current goal of this PR is to expose the firmware feature correctly and make Dual RX usable in Configurator without inventing a separate receiver-selection model in the UI.

@github-actions

Copy link
Copy Markdown

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change because it should never be included in any release, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@sensei-hacker

Copy link
Copy Markdown
Member

Just an FYI for contributors: The tentative schedule for INAV 10 is to have a full release in mid December. That means RC2 needs to be in early to mid November, which places INAV 10.0RC1 at September 1. Please plan to have any new features for INAV 10.0 ready for RC1 no later than September 1. After that, 10.1 will follow about six to seven months later.

@xznhj8129
xznhj8129 changed the base branch from master to maintenance-10.x August 30, 2026 02:34
@github-actions

github-actions Bot commented Aug 30, 2026 •

Copy link
Copy Markdown

Configurator test build ready — commit 050ad94

Download build artifacts for PR #2694

Available platforms (scroll to the Artifacts section at the bottom of the run page):

  • Windows x64 (ZIP, MSI) and x32 (ZIP, MSI)
  • macOS arm64 (ZIP, DMG) and x64 (ZIP, DMG)
  • Linux x64 (DEB, RPM, ZIP) and aarch64 (DEB, RPM, ZIP)

A GitHub login is required to download artifacts. Build is for testing only.

@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 receiver configuration, telemetry, and handover

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds secondary receiver configuration and UART assignment for firmware dual-link failover.
• Displays live per-link health, metrics, active receiver, and manual handover controls.
• Exposes dual-RX OSD elements and programming handover support.
Diagram

sequenceDiagram
    actor User
    participant UI as Configurator UI
    participant Settings as Settings API
    participant MSP as MSP Helper
    participant State as FC State
    participant Firmware as Flight Controller
    User->>UI: Configure dual RX
    UI->>Settings: Read and save settings
    Settings->>Firmware: Apply receiver configuration
    UI->>MSP: Poll link status
    MSP->>Firmware: GET_LINK_STATS
    Firmware-->>MSP: Versioned link metrics
    MSP->>State: Parse bounded response
    State-->>UI: Render link health
    User->>UI: Request handover
    UI->>MSP: Select RX link
    MSP->>Firmware: SET_RX_LINK
Loading
High-Level Assessment

The approach fits the configurator’s existing settings and MSP request-response architecture. Reusing data-setting controls, preserving older RX_CONFIG payload compatibility through length checks, versioning link-stat extensions, and polling at a moderate interval are preferable to introducing a separate state or transport layer for this feature.

Files changed (12) +601 / -48

Enhancement (9) +491 / -47
fc.jsModel dual-receiver configuration and link status +38/-0

Model dual-receiver configuration and link status

• Extends receiver configuration state with secondary-link fields and firmware-extension availability. Adds normalized runtime status storage for aggregate failover state and two receiver links.

js/fc.js

logicConditionOperators.jsRegister the RX handover logic operator +6/-0

Register the RX handover logic operator

• Adds logic operator ID 64 for selecting a receiver through a flight-parameter operation.

js/logicConditionOperators.js

MSPCodes.jsDefine link statistics and handover MSP commands +3/-1

Define link statistics and handover MSP commands

• Registers command IDs for retrieving receiver link statistics and selecting the active receiver link.

js/msp/MSPCodes.js

MSPHelper.jsParse dual-RX configuration and runtime telemetry +138/-0

Parse dual-RX configuration and runtime telemetry

• Adds backward-compatible parsing and serialization for the RX configuration extension. Parses versioned per-link statistics with payload bounds checks, clears stale state, and exposes status-loading and handover helpers.

js/msp/MSPHelper.js

receiver.cssStyle dual-receiver status and handover controls +26/-0

Style dual-receiver status and handover controls

• Adds flex layouts for status rows and actions, metric alignment, and disabled-action presentation.

src/css/tabs/receiver.css

osd.jsExpose dual-RX OSD elements +18/-0

Expose dual-RX OSD elements

• Adds positionable OSD entries for the active receiver and RX1/RX2 link statistics using firmware IDs 171–173.

tabs/osd.js

ports.jsGate and order the secondary RX port assignment +21/-2

Gate and order the secondary RX port assignment

• Loads the dual-receiver setting before rendering ports, hides RX2 assignment when disabled while preserving existing values, and orders receiver functions consistently.

tabs/ports.js

receiver.htmlAdd secondary receiver and failover status panels +60/-2

Add secondary receiver and failover status panels

• Adds settings controls for RX2 type, provider, inversion, and half-duplex mode. Adds active-link, health, metrics, switch-reason, and manual-handover UI.

tabs/receiver.html

receiver.jsDrive dual-receiver setup, telemetry, and handover +181/-42

Drive dual-receiver setup, telemetry, and handover

• Generalizes receiver-mode behavior across both links, conditionally reveals dual-RX panels, and polls link status every 500 ms. Renders localized health and metrics, validates handover availability, sends link-selection commands, and supports MSP controls on either receiver.

tabs/receiver.js

Documentation (1) +97 / -1
messages.jsonLocalize dual-receiver controls and status messages +97/-1

Localize dual-receiver controls and status messages

• Adds English labels and help text for secondary receiver setup, port assignment, failover health, handover outcomes, and new OSD elements. Renames the primary serial receiver label to Serial RX1.

locale/en/messages.json

Other (2) +13 / -0
serialPortHelper.jsAdd the secondary serial receiver function +6/-0

Add the secondary serial receiver function

• Registers RX_SERIAL_SECONDARY as a unique receiver function and maps it to firmware serial-function bit 13.

js/serialPortHelper.js

configuration.htmlAdd the global dual-receiver setting +7/-0

Add the global dual-receiver setting

• Adds a settings-backed checkbox and help tooltip for enabling dual receivers.

tabs/configuration.html

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

qodo-free-for-open-source-projects Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Wrong RX operator ID 🐞 Bug ≡ Correctness
Description
The new RX handover operator is keyed as 64 even though the Dual RX firmware contract assigns
“Select RX link” to operation 63. Because the UI uses the object key as the serialized operation ID,
selecting this entry sends operation 64 and will not invoke the intended firmware operation.
Code

js/logicConditionOperators.js[R378-379]

+    64: {
+        name: "RX handover",
Evidence
The added table entry is keyed as 64, while LogicCondition converts each table key directly into the
operation id. The repository's firmware-derived constants are the sibling operation enumeration
and currently contain neither the intended operation 63 nor this operation 64, confirming the new
mapping was not synchronized with the protocol enum.

js/logicConditionOperators.js[378-383]
js/logicCondition.js[265-275]
js/transpiler/transpiler/inav_constants.js[2-8]
js/transpiler/transpiler/inav_constants.js[30-92]

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 Dual RX logic operator is registered as operation 64, but the firmware contract defines `LOGIC_CONDITION_RX_SELECT_OVERRIDE` as operation 63. The configurator serializes the operator-table key directly, so the current entry sends the wrong operation.
## Issue Context
Keep the configurator's operator metadata and any firmware-derived enum copies synchronized with the firmware value and intended “Select RX link” semantics.
## Fix Focus Areas
- js/logicConditionOperators.js[378-383]
- js/transpiler/transpiler/inav_constants.js[30-92]

ⓘ 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 start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +378 to +379
64: {
name: "RX handover",

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. Wrong rx operator id 🐞 Bug ≡ Correctness

The new RX handover operator is keyed as 64 even though the Dual RX firmware contract assigns
“Select RX link” to operation 63. Because the UI uses the object key as the serialized operation ID,
selecting this entry sends operation 64 and will not invoke the intended firmware operation.
Agent Prompt
## Issue description
The Dual RX logic operator is registered as operation 64, but the firmware contract defines `LOGIC_CONDITION_RX_SELECT_OVERRIDE` as operation 63. The configurator serializes the operator-table key directly, so the current entry sends the wrong operation.

## Issue Context
Keep the configurator's operator metadata and any firmware-derived enum copies synchronized with the firmware value and intended “Select RX link” semantics.

## Fix Focus Areas
- js/logicConditionOperators.js[378-383]
- js/transpiler/transpiler/inav_constants.js[30-92]

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

@xznhj8129

Copy link
Copy Markdown
Author

body updated

@sensei-hacker sensei-hacker added this to the Future milestone Sep 5, 2026
@sensei-hacker

Copy link
Copy Markdown
Member

Confirmed still accurate — neither of the last two commits touched this. cfa5bb8a (Aug 29) is what added the 64: { name: "RX handover", ... } entry to js/logicConditionOperators.js, and it's still 64 as of the current head; ae33d65 (Aug 31) only touched tabs/osd.js.

On the firmware side (xznhj8129/feature/dualrx), the operation is LOGIC_CONDITION_RX_SELECT_OVERRIDE = 63, so the configurator is still serializing the wrong ID.

One extra wrinkle: that firmware branch is stale vs. current maintenance-10.x — it's missing LOGIC_CONDITION_DISABLE_AUTOSPEED_AIRSPEED (61 upstream), so ACTIVATE_RTH/ACTIVATE_LANDING currently sit at 61/62 there instead of 62/63. After rebasing onto maintenance-10.x, 63 will already be taken by ACTIVATE_LANDING, so RX_SELECT_OVERRIDE will likely need to become 64 there too. Please rebase the firmware branch first, then set the configurator's key to match whatever RX_SELECT_OVERRIDE ends up as — not just flip it to 63.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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