Skip to content

MSP-over-CRSF Passthrough#439

Open
b14ckyy wants to merge 5 commits intoolliw42:mainfrom
b14ckyy:msp-passthrough
Open

MSP-over-CRSF Passthrough#439
b14ckyy wants to merge 5 commits intoolliw42:mainfrom
b14ckyy:msp-passthrough

Conversation

@b14ckyy
Copy link
Copy Markdown
Contributor

@b14ckyy b14ckyy commented Apr 3, 2026

PR: MSP-over-CRSF Passthrough

Summary

This PR adds bidirectional MSP-over-CRSF passthrough to mLRS, enabling EdgeTX/ETHOS Lua scripts and configurators (e.g. INAV Configurator Lite) to communicate with the flight controller through the mLRS TX↔RX radio link using standard CRSF MSP frames (0x7A request, 0x7B response).

Problem

CRSF-based radios can tunnel MSP messages to flight controllers via CRSF frame types 0x7A (MSP request) and 0x7B (MSP response). ELRS and Crossfire support this natively. mLRS previously had no support for this path — MSP was only available via direct serial connection to the TX module.

Architecture

Radio (Lua/Configurator)
  │  CRSF 0x7A request chunks
  ▼
TX Module (crsf_interface_tx.h)
  │  MspCrsfReassemble() → msp_message_t
  │  InjectCrsfMspRequest() → MspX → fifo_link_out
  ▼
═══ Air Link (MspX wire format) ═══
  ▼
RX Module (msp_interface_rx.h)
  │  parse_link_in_serial_out() → MSP V2 frame → FC UART
  │  [tracks pending CRSF passthrough function]
  ▼
Flight Controller (INAV)
  │  MSP response
  ▼
RX Module (msp_interface_rx.h)
  │  parse_serial_in_link_out() → force-tags response → MspX → fifo_link_out
  ▼
═══ Air Link (MspX wire format) ═══
  ▼
TX Module (msp_interface_tx.h)
  │  parse_link_in_serial_out() → detects CRSF_PASSTHROUGH flag
  │  → MspCrsfSendResponse() → SendMspResponseChunk()
  ▼
Radio (Lua/Configurator)
    CRSF 0x7B response chunks

Changed Files

File Changes
Common/protocols/msp_protocol.h Added MSP_FLAG_CRSF_PASSTHROUGH = 0x04 to flag enum
Common/thirdparty/mspx.h Added MSPX_FLAGS_CRSF_PASSTHROUGH = 0x20; encode/decode in msp_parseX_to_msg(), msp_msg_to_frame_bufX(), msp_generate_v2_frame_bufX()
CommonTx/crsf_interface_tx.h New: MspCrsfReassemble() (V1+V2 chunk reassembly), MspCrsfSendResponse() (optimized field copy, strips internal flag), SendMspResponseChunk() (V2 chunking), MspCrsfResponsePending(), GetMspCrsfMsg(), TelemetryUpdate() priority for MSP responses
CommonTx/msp_interface_tx.h New: InjectCrsfMspRequest() sets passthrough flag and enqueues to air link. Modified: putc() removed if (!ser) return guard so CRSF routing works without serial port. parse_link_in_serial_out() detects passthrough flag and routes to CRSF instead of serial.
CommonTx/mlrs-tx.cpp Added TXCRSF_CMD_MSP_REQ handling (inject to MSP interface) and TXCRSF_SEND_MSP_RESPONSE handling (send next chunk) in main loop
CommonRx/msp_interface_rx.h Added crsf_pt_pending/crsf_pt_function tracking. parse_link_in_serial_out() records pending passthrough requests. parse_serial_in_link_out() force-tags matching FC responses.
msp-crsf-test.lua EdgeTX test tool: 8 MSP tests (V1+V2) + CH17 toggle loop for uplink/downlink validation (can be provided for testing)

Key Design Decisions

RX-side flag tracking (root cause fix): The flight controller (INAV) does not preserve the MSP V2 flag byte from request to response. Our internal MSP_FLAG_CRSF_PASSTHROUGH (0x04) was lost in the FC roundtrip. The RX now tracks the function number of pending passthrough requests and force-tags the matching response before encoding it to MspX for air transmission. This is the same pattern mLRS already uses for its own telemetry (matching by function number, not by flags).

Always V2 internally: MspCrsfReassemble() always produces an MSP V2 msp_message_t internally, even for V1 CRSF chunks. This simplifies all downstream handling.

Always V2 on CRSF response: SendMspResponseChunk() always sends V2 format (0x40 version bits) back to the radio. Lua scripts/configurators that send V1 requests will receive V2 responses — this is standard CRSF behavior (ELRS does the same).

Optimized response copy: MspCrsfSendResponse() copies only the header fields + actual payload bytes instead of the full ~777-byte msp_message_t struct. Also strips MSP_FLAG_CRSF_PASSTHROUGH from the outgoing CRSF flag byte since it's an internal routing flag.

TX putc() null-guard removal: The previous if (!ser) return in tTxMsp::putc() blocked ALL air link MSP parsing when no serial port was configured (e.g. MBRIDGE destination). Now only the serial output is guarded (if (send && ser)), so CRSF passthrough routing always works.

Testing

  • Validated with EdgeTX test tool against ELRS 4.0: 8/8 MSP tests pass (V1 + V2)
  • Validated with mLRS TX (Matek mR900-30) + RX (Matek mR900-22) + INAV FC: all 8 tests pass, CH17 uplink/downlink loop confirmed
  • Existing serial MSP tunnel (INAV Configurator) continues to work
  • Existing telemetry (MSP_STATUS, MSP_ATTITUDE, etc.) unaffected
  • Long term stability testing
  • Stress test with large messages
  • INAV 8.x and 9.x compatibility
  • MWPTools Compatibility (Clean serial)

b14ckyy added 2 commits April 3, 2026 16:47
Implement bidirectional MSP-over-CRSF passthrough through mLRS TX/RX link,
allowing EdgeTX/ETHOS Lua scripts and configurators to communicate with
the flight controller via CRSF frames (0x7A request, 0x7B response).

- CRSF MSP request reassembly with V1 and V2 chunk support
- CRSF MSP response chunking back to radio
- MspX wire format extended with CRSF passthrough flag (0x20)
- RX-side flag tracking to force-tag FC responses (FC does not preserve
  MSP V2 flag byte in responses)
- TX-side CRSF routing in parse_link_in_serial_out()
- Optimized MspCrsfSendResponse: copy only needed fields instead of
  full struct memcpy, strip internal routing flag from outgoing CRSF
- Test tool: tools/msp-crsf-test.lua (EdgeTX)
@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 3, 2026

Stress testing done.
120 WP download with just 10ms wait time after each response > 6.8WP/s in FSK > 3.6WP/s in 19Hz (multiple downloads, no errors)
ADSB Data download > 160byte per response x10 > 4.0s download time in FSK / 5.4s in 19Hz Mode (900MHz and no errors)
16 Channel Stress test > 6.6 updates per second in all modes but in FSK mode EdgeTX Crashes into Emergency mode (10ms grace time is too low for that amount of data, just for testing)

all tests with enabled replies and no errors detected. Serial out is clean and telemetry stays stable.

Long term test: 10.000 RC Channel override messages via FSK and 19Hz LoRa with no errors.

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 3, 2026

No noticeable behavior difference in INAV 8.0.1
PR ready.

@b14ckyy b14ckyy changed the title MSP-over-CRSF Passthrough - Under testing MSP-over-CRSF Passthrough Apr 3, 2026
@olliw42
Copy link
Copy Markdown
Owner

olliw42 commented Apr 4, 2026

first off, MANY THX for this, much appreciated.
But I'm stiff, meaning not saying no but am hesitating and need more convincement :)
that's a lot of code and change for what to me looks like a pretty dirty hack.

I guess I have three first questions:

  • why not directly sending MSP in the lua? If I recall correctly, it's becasue EdgeTx can't do it, so all this about getting around a limitation in EdgeTx? Isn't it the correct approach to get EdgetTx right? If in some near future EdgeTx would get it right, what would we do then?
  • is that official MSP or just your hack? Or, if your hack momentarily, is it going to become official MSP?
  • how does it cooperate with other MSP clients/dgcses. Does it work together with MWP? If it works with MSP, they wouldn't read the message requested by the other, unneccessarily spillking the rx->tx link rate?

My general thought, get your damed MSP protocol right ... it was time already last time, it's getting even more timely now ... hack for hack for hack ... ;)

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 4, 2026

Hi Olli. Let me answer your questions first:

why not directly sending MSP in the lua? If I recall correctly, it's becasue EdgeTx can't do it, so all this about getting around a limitation in EdgeTx? Isn't it the correct approach to get EdgetTx right? If in some near future EdgeTx would get it right, what would we do then?

I am not quite sure if I understand that question. We have one wire to communicate between the radio and the mLRS module. That line runs the one wire CRSF protocol, so how should the LUA directly send MSP? The official way by CRSF specifications (As well as SmartPort and GHOST) to communicate with a FC directly in a non-CRSF language, is packing MSP frames into CRSF frames. In Chunks if needed for the 47 byte payload limit per frame. Thats not a EdgeTX or ETHOS limit, thats how CRSF is designed to do it.

is that official MSP or just your hack? Or, if your hack momentarily, is it going to become official MSP?

Its an official CRSF protocol feature and not a "hack" at all. On a regular RC system where you have CRSF on both ends, or SmartPort/FPort, or GHOST, its official practice and used on INAV and Betaflight as well. In these systems the RC link is basically "transparent" for this CRSF-MSP-Pack frame. See here: https://github.com/tbs-fpv/tbs-crsf-spec/blob/98f4d71e1eef287d2c6740839013c561368b6c94/crsf.md?plain=1#L1105
On other systems, the FC would do the reassembly of the MSP message that is supported by INAV, Betaflight and Rotorflight in the same way.

But since we don't have bidirectional CRSF on both ends like on the other systems, the reassembly into MSP frames and the re-packaging of MSP into CRSF frames for the replies, has to be done on the mLRS module side and not in the FC. Since MLRS does not do CRSF communication on the Rx side with the FC. So we need regular MSP communication there. Or in short > Since CRSF communication does end in the module CRSF Parser already by mLRS design, I have to do it there.

how does it cooperate with other MSP clients/dgcses. Does it work together with MWP? If it works with MSP, they wouldn't read the message requested by the other, unneccessarily spillking the rx->tx link rate?

I have tested it with INAV Configurator and MWPTools via BT and BLE while in parallel stress testing the CRSF Passthrough with 32CH MSP_SET_RAW_RC messages (to control CH17-32 via LUA MSP bypassing the CRSF limit of 16) and see no noticeable functional or performance degradation in MWP or Configurator. The Serial port stays clean as I still use the ILMI flag plus the new CRSF Passthrough flag, so the module knows exactly where to route the data.

it was high priority to not affect any existing function.

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 4, 2026

One addition:
what my PR generally does is, it puts mLRS in line with existing CRSF based RC systems by implementing this Passthrough feature.
The Goal is, that any Radio or any LUA or App (considering that we have ELRS Android Radios now too) can do MSP communication over CRSF no matter if you have a Crossfire, ELRS or mLRS module installed. For the radio and the LUA/App it makes no difference and they all can communicate the same way with an MSP based FC, on all 3 RC Link Systems.

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 4, 2026

Update:
I actually found a little bug with my update.
On the response path, the MSP_BOXNAMES check for the MSPX compression is called before my check for the MSP_FLAG_CRSF_PASSTHROUGH. Then the MSPX compression happens and the original MSP_BOXNAMES is flagged as send = false and never arrives on the Tx Module end. Just the MSPX compressed message then gets sent down but is ignored by the CRSF-Passthrough parser, since its a different message and does not carry the MSPX_FLAGS_SOURCE_ID_RADIO flag.

So a LUA app could actually not read the actual flight mode based on BOXNAMES.

I will fix that. Aside from that I double and triple checked everything and did a full Path rundown with Claude and could not find any other potential issue with that implementation.

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 4, 2026

Fix is in and looks good. Flight modes and ready state still show correctly on telemetry. No Serial spam. MWP and Configurator show Flight modes and states correctly over BLE/BT

@b14ckyy b14ckyy marked this pull request as draft April 6, 2026 10:37
@b14ckyy b14ckyy marked this pull request as ready for review April 6, 2026 10:39
@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 6, 2026

Little Github mishap. All sorted back to correct state.

@olliw42
Copy link
Copy Markdown
Owner

olliw42 commented Apr 7, 2026

I suggest the following:
Let's get rid of the new bit and what is related to it, and only do the crsf part, will work for only either via serial or via crsf.
Architecture wise it should align with the mbridge/mavlink approach.
This should make it also future proof, e.g., help us whenever where will be routing in MSP.

@b14ckyy
Copy link
Copy Markdown
Contributor Author

b14ckyy commented Apr 7, 2026

So just to understand you right. You want me to change it that we switch between either Serial for MSP communication OR CRSF? Not parallel?

That defeats the whole purpose of it. It would mean that even if someone just wants to use the MSP-Channel-Extender LUA, for RC channel control >16 (what TBS CRSF limits us to), could not use the serial port anymore.

I could do an alternative way without that extra bit flag to realize both parallel but that would really be a hack solution and cause more overhead. If you insist on the one or another approach just to not "supersede" mbridge or Mavlink functionally in that regards, I feel like we can close here and I will stick to my working branch.

@fatbaldmen
Copy link
Copy Markdown

fatbaldmen commented Apr 7, 2026

I've been following this project at it's conception ,and just my two cents, there is quite a bit of interest and anticipation in this project from the community. I personally think it's another step in the right direction. This brings more options to a table that has limited options already. It's, personally, a win win.

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.

3 participants