Conversation
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)
|
Stress testing done. 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. |
|
No noticeable behavior difference in INAV 8.0.1 |
|
first off, MANY THX for this, much appreciated. I guess I have three first questions:
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 ... ;) |
|
Hi Olli. Let me answer your questions first:
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.
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 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.
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. |
|
One addition: |
…avoid confusion in code.
|
Update: So a LUA app could actually not read the actual flight mode based on 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. |
|
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 |
|
Little Github mishap. All sorted back to correct state. |
|
I suggest the following: |
|
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. |
|
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. |
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
Changed Files
Common/protocols/msp_protocol.hMSP_FLAG_CRSF_PASSTHROUGH = 0x04to flag enumCommon/thirdparty/mspx.hMSPX_FLAGS_CRSF_PASSTHROUGH = 0x20; encode/decode inmsp_parseX_to_msg(),msp_msg_to_frame_bufX(),msp_generate_v2_frame_bufX()CommonTx/crsf_interface_tx.hMspCrsfReassemble()(V1+V2 chunk reassembly),MspCrsfSendResponse()(optimized field copy, strips internal flag),SendMspResponseChunk()(V2 chunking),MspCrsfResponsePending(),GetMspCrsfMsg(),TelemetryUpdate()priority for MSP responsesCommonTx/msp_interface_tx.hInjectCrsfMspRequest()sets passthrough flag and enqueues to air link. Modified:putc()removedif (!ser) returnguard 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.cppTXCRSF_CMD_MSP_REQhandling (inject to MSP interface) andTXCRSF_SEND_MSP_RESPONSEhandling (send next chunk) in main loopCommonRx/msp_interface_rx.hcrsf_pt_pending/crsf_pt_functiontracking.parse_link_in_serial_out()records pending passthrough requests.parse_serial_in_link_out()force-tags matching FC responses.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 V2msp_message_tinternally, 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-bytemsp_message_tstruct. Also stripsMSP_FLAG_CRSF_PASSTHROUGHfrom the outgoing CRSF flag byte since it's an internal routing flag.TX
putc()null-guard removal: The previousif (!ser) returnintTxMsp::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