Skip to content

feat(oot): support external MAVLink handler, stream, and dialect with opt in custom init scripts in Out of Tree modules#27415

Open
methodmissing wants to merge 10 commits into
PX4:mainfrom
methodmissing:external-mods-flex
Open

feat(oot): support external MAVLink handler, stream, and dialect with opt in custom init scripts in Out of Tree modules#27415
methodmissing wants to merge 10 commits into
PX4:mainfrom
methodmissing:external-mods-flex

Conversation

@methodmissing
Copy link
Copy Markdown

mavlink: external handler, stream, and dialect support for out-of-tree modules

Summary

References #26261 , specifically to attempt to address some difficulties I bumped into with a "clean cut" out of tree external module

Adds first-class MAVLink support for out-of-tree (OOT) modules: custom dialect
code generation, inbound message handling, outbound streaming, and ROMFS init
script injection — all without patching PX4 source.

Currently, OOT modules that need custom MAVLink messages must manually symlink
dialect XMLs, patch mavlink_receiver.cpp, and hand-edit rcS. This PR
eliminates all of that with a convention-based approach.

Tested end-to-end with a PX4 module and driver for the SE05x secure element present on Pixhawk 6X boards that uses 6 custom MAVLink messages for PKI enrollment over USB, running on Pixhawk 6X-RT hardware.

Changes

1. External MAVLink handler registry (mavlink_ext_handler.{h,cpp})

Static callback table (max 8 entries, probably should be configurable) for inbound custom messages. Registration
is mutex-protected; dispatch is lock-free from the receiver thread's default
case:

MavlinkReceiver::handle_message()
  └─ switch(msg->msgid)
       ├─ case HEARTBEAT: ...
       ├─ case ...: (built-in handlers)
       └─ default:
            mavlink_ext_handler_dispatch(msg)  ← NEW
              └─ iterates registered callbacks

It should have no overhead in the hot / happy path as it injects at the default case handler which currently hard drops any unknown MAVLink message IDs.

OOT module usage:

#include <modules/mavlink/mavlink_ext_handler.h>

// In module init:
mavlink_ext_handler_register(MAVLINK_MSG_ID_SE05X_PROVISION,
                             handle_provision, this);

// In module stop:
mavlink_ext_handler_unregister(MAVLINK_MSG_ID_SE05X_PROVISION);

2. External MAVLink stream registry (mavlink_ext_stream.{h,cpp})

Static callback table for outbound streams, dispatched from Mavlink::task_main()
after the built-in stream loop. Supports rate-limited intervals and integrates
with SET_MESSAGE_INTERVAL so QGC/pymavlink can control OOT stream rates
identically to built-in streams:

#include <modules/mavlink/mavlink_ext_stream.h>

mavlink_ext_stream_register(
    MAVLINK_MSG_ID_SE05X_CSR, "SE05X_CSR",
    emit_csr_fragment, this,
    100000);  // 10 Hz

// SET_MESSAGE_INTERVAL fallback in mavlink_receiver.cpp:
mavlink_ext_stream_set_interval(msg_id, interval_us);

3. CMake dialect registration (px4_add_external_mavlink_dialect)

Shared CMake function that copies OOT dialect XMLs into mavgen's search path
and auto-overrides CONFIG_MAVLINK_DIALECT from common:

# In OOT module's CMakeLists.txt:
px4_add_external_mavlink_dialect(
    XML ${CMAKE_CURRENT_SOURCE_DIR}/../../mavlink/standard.xml
)

The dialect XML uses standard <include>common.xml</include> inheritance, so
all built-in messages remain available. Multiple dialects from different OOT
modules are supported.

4. ROMFS init script injection (ROMFS/CMakeLists.txt, rcS)

OOT modules can provide init/rc.ext_modules which is automatically copied
into the ROMFS image and sourced by rcS after the logger starts:

rcS boot sequence:
  ...
  rc.logging           ← logger starts first
  rc.ext_modules       ← OOT module init (if file exists)
  rc.vtxtable
  ...
  mavlink boot_complete

Example init/rc.ext_modules:

se05x start
se05x_mavlink start
se05x derive-keys &

Directory convention:

my_external_module/
├── init/
│   └── rc.ext_modules       ← auto-injected into ROMFS, optional
├── mavlink/
│   └── my_dialect.xml       ← custom MAVLink messages, optional
├── msg/
│   └── MyTopic.msg          ← custom uORB topics, already supported upstream
└── src/
    ├── CMakeLists.txt
    └── modules/
        └── my_module/

Note on msg/: OOT uORB message definitions currently must live in the
OOT module's msg/ directory and are registered via px4_add_module() with
the MSG_SRCS parameter. PX4's msg/ directory is reserved for in-tree
topics. See Out-of-Tree Modules.

Integration points (minimal PX4 source changes)

File Change Lines
mavlink_receiver.cpp #include + dispatch() in default: + set_interval fallback +17
mavlink_main.cpp #include + ext_stream_dispatch() in main loop +4
CMakeLists.txt include() dialect cmake + dialect auto-override after add_subdirectory +21
ROMFS/CMakeLists.txt Copy rc.ext_modules into ROMFS image +25
rcS Source rc.ext_modules after rc.logging +11

All new files: mavlink_ext_handler.{h,cpp}, mavlink_ext_stream.{h,cpp},
px4_add_external_mavlink_dialect.cmake.

Design decisions

  • Static arrays, not dynamic allocation. Both registries use fixed-size
    arrays (8 slots each, can be configurable). No heap allocation at runtime. Lock-free dispatch.
  • Mutex on registration only. register/unregister are mutex-protected
    (called once at init/shutdown). dispatch iterates the array without locking
    — safe because entries are only appended/cleared while the slot count is
    atomically visible.
  • Dialect override is opt-in. Only overrides CONFIG_MAVLINK_DIALECT if
    it's the default common. If a board already sets a specific dialect, it's
    preserved.
  • Init script is optional. The rc.ext_modules hook is if [ -f ... ]
    guarded. No impact on builds without EXTERNAL_MODULES_LOCATION.

Testing

Validated on Pixhawk 6X-RT (fmu-v6xrt) with the SE05x secure element OOT
module performing:

  • Full PKI enrollment pipeline (6 custom MAVLink messages, fragmented
    certificate transfers)
  • Bidirectional ECIES crypto verification (ECDH + HKDF + AES-256-CBC +
    HMAC-SHA384)
  • ECDSA-P384 signature verification
  • Session key derivation with HMAC proof
  • ROMFS init auto-start

Example usage in OOT module

Header file of the MAVLink handler concerns

/**
 * @file se05x_mavlink.h
 * @brief SE05x MAVLink bridge (uORB ↔ SE05x driver for PKI enrollment)
 */

#pragma once

#define MODULE_NAME "se05x_mavlink"

#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/time.h>
#include <px4_platform_common/px4_work_queue/ScheduledWorkItem.hpp>
#include <pthread.h>

#include <uORB/Subscription.hpp>
#include <uORB/Publication.hpp>
#include <uORB/topics/se05x_certificate_enrollment_response.h>
#include <uORB/topics/se05x_certificate_signing_request.h>
#include <uORB/topics/se05x_provision.h>
#include <uORB/topics/se05x_ecies_test_request.h>
#include <uORB/topics/se05x_ecies_test_response.h>
#include <uORB/topics/se05x_command.h>
#include <uORB/topics/se05x_response.h>

/**
 * @brief SE05x MAVLink bridge module
 *
 * Processes uORB-based PKI requests and delegates to the SE05x driver library.
 */
class SE05xMavlink : public ModuleBase, public ModuleParams, public px4::ScheduledWorkItem
{
public:
    SE05xMavlink();
    ~SE05xMavlink() override;

    static Descriptor desc;

    /** @see ModuleBase */
    static int task_spawn(int argc, char *argv[]);

    /** @see ModuleBase */
    static int custom_command(int argc, char *argv[]);

    /** @see ModuleBase */
    static int print_usage(const char *reason = nullptr);

    /** @see ScheduledWorkItem::Run() */
    void Run() override;

    /** Print driver status */
    int print_status() override;

    /**
     * @brief Sign data with ECDSA-P384 (CNSA Suite 2.0) via the SE05x
     *
     * Hashes the input with SHA-384, then signs the hash using the SE05x
     * signing key. Returns the signature in ASN.1 DER format.
     *
     * @param command_data Data to sign
     * @param command_len Length of data
     * @param signature Output buffer for signature (ASN.1 DER, typically 104-106 bytes)
     * @param signature_len Input: buffer size, Output: actual signature length
     * @return 0 on success, negative on error
     */
    int sign_command(const uint8_t *command_data, size_t command_len,
                     uint8_t *signature, size_t *signature_len);

    // emit_csr_stream is a static free function used as a mavlink ext_stream callback.
    friend bool emit_csr_stream(uint8_t channel, void *user_data);

    // emit_enc_csr_stream streams the encryption CSR (key_purpose=1) after the signing CSR.
    friend bool emit_enc_csr_stream(uint8_t channel, void *user_data);

    // emit_ecies_response is a static free function used as a mavlink ext_stream callback.
    friend bool emit_ecies_response(uint8_t channel, void *user_data);

    /**
     * @brief Store an enrollment fragment directly (called from MAVLink handler)
     *
     * Bypasses uORB to avoid single-slot publication races when the host
     * sends multiple fragments faster than the 100ms Run() cycle.
     */
    void handle_enrollment_fragment(uint16_t total, uint16_t offset,
                                    const uint8_t *data, uint8_t data_len);

    /**
     * @brief Store an encryption enrollment fragment (called from MAVLink handler)
     */
    void handle_enc_enrollment_fragment(uint16_t total, uint16_t offset,
                                        const uint8_t *data, uint8_t data_len);

    /**
     * @brief Store the provisioner encryption public key (called from MAVLink handler)
     */
    void store_provisioner_key(const uint8_t *key, size_t len);

private:
    /**
     * @brief Initialize the module and schedule work
     */
    int init();

    /**
     * @brief Process incoming uORB requests and call library functions
     */
    void process_uorb_requests();

    /**
     * @brief Check for responses from the SE05x service thread
     */
    void process_service_responses();

    /**
     * @brief Provisioning state machine (driven by service thread responses)
     */
    enum class ProvState : uint8_t {
        Idle = 0,
        WaitProvision,       // Waiting for OP_PROVISION response
        WaitSigningCSR,      // Waiting for OP_GENERATE_CSR(signing) response
        WaitEnrollment,      // Waiting for OP_ENROLLMENT response
        WaitEncCSR,          // Waiting for OP_GENERATE_CSR(encryption) response
        WaitEncEnrollment,   // Waiting for OP_ENC_ENROLLMENT response
        WaitEciesTest,       // Waiting for OP_ECIES_TEST response
    };

    ProvState _prov_state{ProvState::Idle};

    /** Publish a command to the SE05x service thread */
    void publish_command(uint8_t op, const uint8_t *payload = nullptr, uint16_t payload_len = 0);

    // uORB subscriptions for inbound PKI material (offboard → vehicle)
    uORB::Subscription _provision_sub{ORB_ID(se05x_provision)};

    // uORB subscription for ECIES test requests
    uORB::Subscription _ecies_test_request_sub{ORB_ID(se05x_ecies_test_request)};

    // uORB command/response for SE05x service thread
    orb_advert_t _cmd_pub{nullptr};
    uORB::Subscription _resp_sub{ORB_ID(se05x_response)};

    // Fragment reassembly buffer for CA certificate (from multiple uORB messages)
    static constexpr size_t CA_CERT_MAX = 480;  // P-384 DER certs ~441 bytes
    uint8_t  _ca_cert_buf[CA_CERT_MAX]{};
    uint16_t _ca_cert_total_len{0};
    uint16_t _ca_cert_received{0};
    uint8_t  _ca_cert_target_system{0};
    uint8_t  _ca_cert_target_component{0};
    uint8_t  _ca_cert_force_regenerate{0};

    // Provisioner encryption public key (from SE05X_PROVISIONER_KEY, stored before provisioning)
    uint8_t  _provisioner_key[97]{};
    bool     _provisioner_key_received{false};

    // Device identity for CSR generation (populated from board info during provisioning)
    char     _csr_serial[40]{};
    char     _csr_model[16]{};

    // CSR output buffer (populated from service response, fragmented by emit_csr_stream)
    static constexpr size_t CSR_MAX = 512;
    static constexpr size_t CSR_FRAG_MAX = 190;  // csr_data[190] per MAVLink frame
    uint8_t  _csr_buf[CSR_MAX]{};
    uint16_t _csr_total_len{0};
    uint16_t _csr_send_offset{0};
    bool     _csr_ready{false};

    // Fragment reassembly buffer for device certificate enrollment
    // Populated directly by handle_mavlink_enrollment (not via uORB)
    static constexpr size_t DEV_CERT_MAX = 480;
    uint8_t  _dev_cert_buf[DEV_CERT_MAX]{};
    uint16_t _dev_cert_total_len{0};
    uint16_t _dev_cert_received{0};
    bool     _enrollment_ready{false};

    // Encryption CSR output buffer (populated from service response after signing CSR)
    uint8_t  _enc_csr_buf[CSR_MAX]{};
    uint16_t _enc_csr_total_len{0};
    uint16_t _enc_csr_send_offset{0};
    bool     _enc_csr_ready{false};

    // Encryption certificate enrollment (key_purpose=1)
    uint8_t  _enc_cert_buf[DEV_CERT_MAX]{};
    uint16_t _enc_cert_total_len{0};
    uint16_t _enc_cert_received{0};
    bool     _enc_enrollment_ready{false};

    // ECIES test state (populated by handle_mavlink_ecies_test, consumed by service thread)
    uint8_t  _ecies_phase{0};  // 0=ECIES encrypt, 1=ECDSA sign, 2=session key derivation
    uint8_t  _ecies_host_pubkey[97]{};
    uint8_t  _ecies_plaintext[32]{};
    uint8_t  _ecies_target_system{0};
    uint8_t  _ecies_target_component{0};

    // ECIES test response (populated from service response, sent by emit_ecies_response)
    int8_t   _ecies_resp_result{0};
    uint8_t  _ecies_resp_phase{0};
    uint8_t  _ecies_resp_ephem_pubkey[97]{};
    uint8_t  _ecies_resp_iv[12]{};
    uint8_t  _ecies_resp_ciphertext[32]{};
    uint8_t  _ecies_resp_hmac_tag[48]{};
    uint8_t  _ecies_response_sends{0};  // >0 = send on next N dispatch cycles (covers all channels)

    static constexpr uint32_t SCHEDULE_INTERVAL_US = 100000; // 100ms
};

Custom MAVLink SE05x dialect

Includes common.xml

<?xml version="1.0"?>
<mavlink>
    <include>common.xml</include>
    <version>3</version>
    <dialect>1</dialect>
    <!--
        SE05x Secure Element Custom MAVLink Messages

        These messages provide secure provisioning capabilities for the NXP SE05x
        secure element (CNSA Suite 2.0 / FIPS 140-3).

        Message IDs: 55000-55005 (custom range, outside common/standard allocation)

        P-384 certificates exceed a single MAVLink frame (253-byte payload limit).
        All certificate/CSR fields support fragment-based delivery via offset and
        total_len fields.  The receiver reassembles fragments by offset before
        triggering provisioning.
    -->
    <messages>
        <!-- SE05X_PROVISION: Trigger CSR/keypair generation on-vehicle -->
        <message id="55000" name="SE05X_PROVISION">
            <description>Trigger SE05x provisioning: generate (or reuse) keypair and publish CSR+attestation for server-side signing. CA certificate may span multiple fragments.</description>
            <field type="uint8_t" name="target_system">Target system ID (0 = broadcast)</field>
            <field type="uint8_t" name="target_component">Target component ID (0 = broadcast)</field>
            <field type="uint8_t" name="force_regenerate">0 = reuse existing if present, 1 = regenerate keypair</field>
            <field type="uint16_t" name="ca_certificate_total_len">Total CA certificate length in bytes (across all fragments)</field>
            <field type="uint16_t" name="ca_certificate_offset">Byte offset of this fragment within the full certificate</field>
            <field type="uint8_t" name="ca_certificate_len">Length of certificate data in this fragment (0-244)</field>
            <field type="uint8_t[244]" name="ca_certificate">CA certificate fragment (DER, max 244 bytes per frame)</field>
        </message>

        <!-- SE05X_CERTIFICATE_SIGNING_REQUEST: Device CSR response (outbound) -->
        <message id="55001" name="SE05X_CERTIFICATE_SIGNING_REQUEST">
            <description>Device certificate signing request for PKI enrollment. CSR data may span multiple fragments. Device identity fields are populated in every fragment.</description>
            <field type="uint8_t" name="target_system">Target system ID (0 = broadcast)</field>
            <field type="uint8_t" name="target_component">Target component ID (0 = broadcast)</field>
            <field type="uint16_t" name="csr_data_total_len">Total CSR length in bytes (across all fragments)</field>
            <field type="uint16_t" name="csr_data_offset">Byte offset of this fragment</field>
            <field type="uint8_t" name="csr_data_len">Length of CSR data in this fragment (0-190)</field>
            <field type="uint8_t[190]" name="csr_data">CSR fragment in DER format (PKCS#10, max 190 bytes per frame)</field>
            <field type="uint8_t[40]" name="device_serial">PX4 GUID hex string (null-terminated)</field>
            <field type="uint8_t[16]" name="device_model">Device model identifier (null-terminated)</field>
            <field type="uint8_t" name="key_purpose">0 = signing (digitalSignature), 1 = encryption (keyAgreement)</field>
        </message>

        <!-- SE05X_CERTIFICATE_ENROLLMENT_RESPONSE: CA-signed device certificate (inbound) -->
        <message id="55002" name="SE05X_CERTIFICATE_ENROLLMENT_RESPONSE">
            <description>CA-signed device certificate for enrollment into the SE05x secure element. Certificate may span multiple fragments.</description>
            <field type="uint8_t" name="target_system">Target system ID</field>
            <field type="uint8_t" name="target_component">Target component ID</field>
            <field type="int8_t" name="result">Result code (0 = success, negative = error code)</field>
            <field type="uint16_t" name="device_certificate_total_len">Total certificate length (across all fragments)</field>
            <field type="uint16_t" name="device_certificate_offset">Byte offset of this fragment</field>
            <field type="uint8_t" name="device_certificate_len">Length of certificate data in this fragment (0-244)</field>
            <field type="uint8_t[244]" name="device_certificate">Device certificate fragment (DER, max 244 bytes per frame)</field>
            <field type="uint8_t" name="key_purpose">0 = signing cert, 1 = encryption cert</field>
        </message>

        <!-- SE05X_ECIES_TEST_REQUEST: Host-initiated ECIES encryption test (host → FC) -->
        <message id="55003" name="SE05X_ECIES_TEST_REQUEST">
            <description>ECIES encryption test request. Host sends its ephemeral P-384 public key and a 32-byte plaintext challenge. The FC encrypts the challenge using ECIES (P-384 ECDH + HKDF-SHA384 + AES-256-CBC + HMAC-SHA384) and returns the result in SE05X_ECIES_TEST_RESPONSE.</description>
            <field type="uint8_t" name="target_system">Target system ID</field>
            <field type="uint8_t" name="target_component">Target component ID</field>
            <field type="uint8_t" name="phase">Test phase: 0=ECIES encrypt (FC encrypts challenge), 1=ECDSA sign (FC signs challenge), 2=session key derivation (FC derives and returns HMAC proof)</field>
            <field type="uint8_t[97]" name="host_public_key">Host P-384 public key (uncompressed: 0x04 || X || Y, 97 bytes). Used in phase 0.</field>
            <field type="uint8_t[32]" name="plaintext">Challenge plaintext (32 random bytes from host). Used in phase 0 and 1.</field>
        </message>

        <!-- SE05X_ECIES_TEST_RESPONSE: FC ECIES encryption result (FC → host) -->
        <message id="55004" name="SE05X_ECIES_TEST_RESPONSE">
            <description>ECIES encryption test response. Contains the FC's ephemeral public key and the encrypted challenge (IV + ciphertext + HMAC-SHA384 tag). The host decrypts by performing ECDH with its private key against the ephemeral public key, then HKDF + AES-CBC-decrypt + HMAC-verify.</description>
            <field type="uint8_t" name="target_system">Target system ID</field>
            <field type="uint8_t" name="target_component">Target component ID</field>
            <field type="int8_t" name="result">0 = success, negative = error code</field>
            <field type="uint8_t" name="phase">Test phase (mirrors request phase)</field>
            <field type="uint8_t[97]" name="ephemeral_public_key">Phase 0: FC ephemeral P-384 public key. Phase 1: ECDSA signature bytes [0..96]. Phase 2: session ephemeral P-384 public key.</field>
            <field type="uint8_t[12]" name="iv">Phase 0: AES-CBC IV. Phase 1: signature bytes [97..103] + signature_len at [7].</field>
            <field type="uint8_t[32]" name="ciphertext">Phase 0: AES-256-CBC encrypted challenge. Phase 1: echo of signed plaintext. Phase 2: unused.</field>
            <field type="uint8_t[48]" name="hmac_tag">Phase 0: HMAC-SHA384 tag. Phase 1: unused. Phase 2: HMAC-SHA384(derived_hmac_key, "PX4-SE05X-SESSION-TEST") proof.</field>
        </message>

        <!-- SE05X_PROVISIONER_KEY: Provisioner encryption public key for session key derivation -->
        <message id="55005" name="SE05X_PROVISIONER_KEY">
            <description>Provisioner P-384 encryption public key. Stored on the SE05x during provisioning. Used for ECDH in session key derivation, enabling the provisioner to decrypt ULog files offline via ECDH(provisioner_priv, boot_ephemeral_pub). Send this before SE05X_PROVISION.</description>
            <field type="uint8_t" name="target_system">Target system ID</field>
            <field type="uint8_t" name="target_component">Target component ID</field>
            <field type="uint8_t[97]" name="provisioner_encryption_key">Provisioner P-384 public key (uncompressed: 0x04 || X || Y, 97 bytes)</field>
        </message>
    </messages>
</mavlink>

Custom init to boostrap the SE05x driver, MAVLink handler and session keys

#!/bin/sh
# SE05x auto-start (baked into ROMFS at build time)
echo "[SE05x ext] Starting se05x driver..."
se05x start
echo "[SE05x ext] se05x rc=$?"
echo "[SE05x ext] Starting se05x_mavlink..."
se05x_mavlink start
echo "[SE05x ext] se05x_mavlink rc=$?"
se05x derive-keys &

Build output demonstrating MAVLink dialect and ROMFS integration

methodmissing@Mac PX4-Autopilot % make px4_fmu-v6xrt_default CONFIG_SYSTEMCMDS_SE05X=y CONFIG_CRYPTO=y CONFIG_MBEDTLS_VERSION=3.4.0 CONFIG_CRYPTO_MBEDTLS=y CONFIG_CLOCK_GETTIME=y CONFIG_GETTIMEOFDAY=y CONFIG_SE05X_DEBUG_LOGS=y EXTERNAL_MODULES_LOCATION=/Users/methodmissing/src/px4-se05x upload
-- PX4_GIT_TAG: v1.18.0-alpha1-75-g26f188d5e9
-- PX4 config file: /Users/methodmissing/src/PX4-Autopilot/boards/px4/fmu-v6xrt/default.px4board
-- PLATFORM nuttx
-- TOOLCHAIN arm-none-eabi
-- ARCHITECTURE cortex-m7
-- ROMFSROOT px4fmu_common
-- IO px4_io-v2_default
-- ETHERNET y
-- SERIAL_GPS1 /dev/ttyS1
-- SERIAL_GPS2 /dev/ttyS3
-- SERIAL_TEL1 /dev/ttyS2
-- SERIAL_TEL2 /dev/ttyS5
-- SERIAL_TEL3 /dev/ttyS6
-- SERIAL_RC /dev/ttyS4
-- ROOT_PATH /fs/microsd
-- PARAM_FILE /fs/mtd_params
-- UAVCAN_INTERFACES 3
-- PWM_FREQ 1000000
-- PX4 config: px4_fmu-v6xrt_default
-- PX4 platform: nuttx
warning: ARCH_CHIP_UNSET (defined at platforms/nuttx/Kconfig:5) was assigned the value 'y' but got the value 'n' -- check dependencies
-- Enabling double FP precision hardware instructions
-- cmake build type: MinSizeRel
-- ccache enabled (export CCACHE_DISABLE=1 to disable)
-- Board forcing alignment
Synchronizing submodule url for 'src/lib/cdrstream/cyclonedds'
Submodule path 'src/lib/cdrstream/cyclonedds': checked out '314887ca403c2fb0a0316add22672102936ed36c'
-- Configuring idlc :/Users/methodmissing/src/PX4-Autopilot/build/px4_fmu-v6xrt_default/msg/idlc
-- Building idlc :/Users/methodmissing/src/PX4-Autopilot/build/px4_fmu-v6xrt_default/msg/idlc
-- External modules: /Users/methodmissing/src/px4-se05x
-- SE05x: Copied mbedtls_config.h to NuttX apps/include/crypto/
-- External MAVLink dialect registered: se05x (from /Users/methodmissing/src/px4-se05x/mavlink/se05x.xml)
-- External MAVLink dialect: se05x (auto-set from common)
-- Unable to determine version from Git, defaulting to version v0.0.0
-- Defaulting MIP_ARCH to cortex-m7
-- C Standard: 99
-- C++ Standard: 14
-- Build Examples: OFF
-- Build Tools: OFF
-- Use Deprecated Macros: ON
-- drivers/px4io: ROMFS including px4_io-v2_default
-- Release build type: MinSizeRel
-- ROMFS: ROMFS/px4fmu_common
-- ROMFS:  Adding platforms/nuttx/init/imxrt/rc.board_arch_defaults -> /etc/init.d/rc.board_arch_defaults
-- ROMFS:  Adding boards/px4/fmu-v6xrt/init/rc.board_defaults -> /etc/init.d/rc.board_defaults
-- ROMFS:  Adding boards/px4/fmu-v6xrt/init/rc.board_sensors -> /etc/init.d/rc.board_sensors
-- ROMFS:  Adding boards/px4/fmu-v6xrt/init/rc.board_mavlink -> /etc/init.d/rc.board_mavlink
-- ROMFS:  Adding ../px4-se05x/init/rc.ext_modules -> /etc/init.d/rc.ext_modules
-- ROMFS:  Adding boards/px4/fmu-v6xrt/extras/px4_io-v2_default.bin -> /etc/extras/px4_io-v2_default.bin
-- Configuring done (4.6s)
-- Generating done (0.8s)
-- Build files have been written to: /Users/methodmissing/src/PX4-Autopilot/build/px4_fmu-v6xrt_default

@github-actions github-actions Bot added scope:build-system CMake, Kconfig, board config, or build tooling. scope:offboard Offboard mode, external setpoints, companion-computer control, or offboard failsafe behavior. scope:mavlink MAVLink module, streams, commands, or protocol handling. scope:docs labels May 20, 2026
@DronecodeBot
Copy link
Copy Markdown

This pull request has been mentioned on Dronecode Forum | Open Source Drone Development. There might be relevant details there:

https://discuss.px4.io/t/px4-dev-call-may-20-2026-team-sync-and-community-q-a/48985/3

@github-actions
Copy link
Copy Markdown
Contributor

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 776 byte (0.04 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.0%    +456  +0.0%    +456    .text
    +0.1%    +124  +0.1%    +124    g_cromfs_image
    [NEW]     +92  [NEW]     +92    mavlink_ext_stream_dispatch()
    [NEW]     +84  [NEW]     +84    mavlink_ext_handler_dispatch()
    [NEW]     +60  [NEW]     +60    CSWTCH.1996
     +23%     +56   +23%     +56    MavlinkReceiver::set_message_interval()
    [NEW]     +56  [NEW]     +56    mavlink_ext_stream_set_interval()
    [NEW]     +44  [NEW]     +44    CSWTCH.2795
    +0.7%     +16  +0.7%     +16    _GLOBAL__sub_I_mavlink_system
    [NEW]     +14  [NEW]     +14    CSWTCH.3572
    [NEW]     +14  [NEW]     +14    CSWTCH.3573
     +38%     +12   +38%     +12    px4::atomic<>::load()
    +0.3%      +8  +0.3%      +8    Mavlink::task_main()
    +4.8%      +4  +4.8%      +4    FlightTask
    +0.4%      +4  +0.4%      +4    MavlinkReceiver::handle_message()
    +1.6%      +4  +1.6%      +4    MavlinkStreamOdometry::send()
    -2.0%      -4  -2.0%      -4    MavlinkReceiver::handle_message_att_pos_mocap()
    [DEL]     -14  [DEL]     -14    CSWTCH.3557
    [DEL]     -14  [DEL]     -14    CSWTCH.3558
    [DEL]     -44  [DEL]     -44    CSWTCH.2789
    [DEL]     -60  [DEL]     -60    CSWTCH.1990
  [ = ]       0  +0.3%    +320    .bss
    [ = ]       0  [NEW]    +256    _streams
    [ = ]       0  [NEW]     +96    _handlers
    [ = ]       0  [NEW]      +8    _stream_count
    [ = ]       0  [NEW]      +4    _handler_count
    [ = ]       0 -50.0%      -4    Mavlink::_boot_complete
    [ = ]       0 -66.7%      -8    _bdshot_channel_mask
    [ = ]       0 -61.5%     -32    [section .bss]
  +0.0%     +78  [ = ]       0    .debug_abbrev
  +0.0%     +64  [ = ]       0    .debug_aranges
  +0.1%    +256  [ = ]       0    .debug_frame
  +0.0% +2.81Ki  [ = ]       0    .debug_info
  +0.0% +1.32Ki  [ = ]       0    .debug_line
   -75.0%      -3  [ = ]       0    [Unmapped]
    +0.0% +1.32Ki  [ = ]       0    [section .debug_line]
  +0.0% +1.09Ki  [ = ]       0    .debug_loclists
  +0.0%    +171  [ = ]       0    .debug_rnglists
    +100%      +1  [ = ]       0    [Unmapped]
    +0.0%    +170  [ = ]       0    [section .debug_rnglists]
  +0.0%    +745  [ = ]       0    .debug_str
  -0.4%      -1  [ = ]       0    .shstrtab
  +0.0%    +217  [ = ]       0    .strtab
    [DEL]     -12  [ = ]       0    CSWTCH.1990
    [NEW]     +12  [ = ]       0    CSWTCH.1996
    [DEL]     -12  [ = ]       0    CSWTCH.2789
    [NEW]     +12  [ = ]       0    CSWTCH.2795
    [DEL]     -24  [ = ]       0    CSWTCH.3557
    [DEL]     -24  [ = ]       0    CSWTCH.3558
    [NEW]     +24  [ = ]       0    CSWTCH.3572
    [NEW]     +24  [ = ]       0    CSWTCH.3573
    +100%     +16  [ = ]       0    __memcpy_veneer
   -32.7%     -16  [ = ]       0    __param_get_default_value_veneer
    [NEW]     +20  [ = ]       0    _handler_count
    [NEW]     +14  [ = ]       0    _handlers
    [NEW]     +19  [ = ]       0    _stream_count
    [NEW]     +13  [ = ]       0    _streams
    [NEW]     +54  [ = ]       0    mavlink_ext_handler_dispatch()
    [NEW]     +33  [ = ]       0    mavlink_ext_stream_dispatch()
    [NEW]     +38  [ = ]       0    mavlink_ext_stream_set_interval()
     +46%     +26  [ = ]       0    px4::atomic<>::load()
  +0.0%    +288  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.1990
    [NEW]     +32  [ = ]       0    CSWTCH.1996
    [DEL]     -32  [ = ]       0    CSWTCH.2789
    [NEW]     +32  [ = ]       0    CSWTCH.2795
    [DEL]     -48  [ = ]       0    CSWTCH.3557
    [DEL]     -48  [ = ]       0    CSWTCH.3558
    [NEW]     +48  [ = ]       0    CSWTCH.3572
    [NEW]     +48  [ = ]       0    CSWTCH.3573
     +20%     +16  [ = ]       0    ManualControlSelector::isInputValid()
   -25.0%     -16  [ = ]       0    Mavlink::handleAndGetCurrentCommandAck()
   -25.0%     -16  [ = ]       0    Mavlink::handleCommands()
     +33%     +16  [ = ]       0    Mavlink::update_radio_status()
     +33%     +16  [ = ]       0    MavlinkCommandSender::check_timeout()
    +100%     +16  [ = ]       0    MavlinkLogHandler::send()
   -25.0%     -16  [ = ]       0    MavlinkLogHandler::state_listing()
   -25.0%     -16  [ = ]       0    MavlinkMissionManager::check_active_mission()
     +50%     +16  [ = ]       0    MavlinkMissionManager::handle_mission_request_int()
   -15.4%     -32  [ = ]       0    MavlinkReceiver::handle_message()
   -25.0%     -16  [ = ]       0    MavlinkStreamCommandLong::send()
    +100%     +16  [ = ]       0    MavlinkStreamDebug::send()
   -97.6%    +304  [ = ]       0    [26 Others]
  -3.8%    -456  [ = ]       0    [Unmapped]
  +0.0% +6.99Ki  +0.0%    +776    TOTAL

px4_fmu-v6x [Total VM Diff: 760 byte (0.04 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.0%    +440  +0.0%    +440    .text
    +0.1%    +124  +0.1%    +124    g_cromfs_image
    [NEW]     +92  [NEW]     +92    mavlink_ext_stream_dispatch()
    [NEW]     +84  [NEW]     +84    mavlink_ext_handler_dispatch()
    [NEW]     +60  [NEW]     +60    CSWTCH.1996
     +23%     +56   +23%     +56    MavlinkReceiver::set_message_interval()
    [NEW]     +56  [NEW]     +56    mavlink_ext_stream_set_interval()
    [NEW]     +44  [NEW]     +44    CSWTCH.2795
    +0.7%     +16  +0.7%     +16    _GLOBAL__sub_I_mavlink_system
    [NEW]     +14  [NEW]     +14    CSWTCH.3572
    [NEW]     +14  [NEW]     +14    CSWTCH.3573
     +27%     +12   +27%     +12    px4::atomic<>::load()
    +0.3%      +8  +0.3%      +8    Mavlink::task_main()
    +0.4%      +4  +0.4%      +4    MavlinkReceiver::handle_message()
    +1.6%      +4  +1.6%      +4    MavlinkStreamOdometry::send()
    -4.5%      -4  -4.5%      -4    FlightTask
    -2.0%      -4  -2.0%      -4    MavlinkReceiver::handle_message_att_pos_mocap()
   -100.4%      -8 -100.4%      -8    [10 Others]
    [DEL]     -14  [DEL]     -14    CSWTCH.3557
    [DEL]     -14  [DEL]     -14    CSWTCH.3558
    [DEL]     -44  [DEL]     -44    CSWTCH.2789
    [DEL]     -60  [DEL]     -60    CSWTCH.1990
  [ = ]       0  +0.3%    +320    .bss
    [ = ]       0  [NEW]    +256    _streams
    [ = ]       0  [NEW]     +96    _handlers
    [ = ]       0  +400%     +16    _bdshot_channel_mask
    [ = ]       0  [NEW]      +8    _stream_count
    [ = ]       0  [NEW]      +4    _handler_count
    [ = ]       0  +100%      +4    g_dma_perf
    [ = ]       0 -50.0%      -4    Mavlink::_boot_complete
    [ = ]       0 -75.0%     -60    [section .bss]
  +0.0%     +78  [ = ]       0    .debug_abbrev
  +0.0%     +64  [ = ]       0    .debug_aranges
  +0.1%    +256  [ = ]       0    .debug_frame
  +0.0% +2.81Ki  [ = ]       0    .debug_info
  +0.0% +1.32Ki  [ = ]       0    .debug_line
   -60.0%      -3  [ = ]       0    [Unmapped]
    +0.0% +1.32Ki  [ = ]       0    [section .debug_line]
  +0.0% +1.10Ki  [ = ]       0    .debug_loclists
  +0.0%    +171  [ = ]       0    .debug_rnglists
    [NEW]      +1  [ = ]       0    [Unmapped]
    +0.0%    +170  [ = ]       0    [section .debug_rnglists]
  +0.0%    +745  [ = ]       0    .debug_str
  -0.4%      -1  [ = ]       0    .shstrtab
  +0.0%    +217  [ = ]       0    .strtab
    [DEL]     -12  [ = ]       0    CSWTCH.1990
    [NEW]     +12  [ = ]       0    CSWTCH.1996
    [DEL]     -12  [ = ]       0    CSWTCH.2789
    [NEW]     +12  [ = ]       0    CSWTCH.2795
    [DEL]     -24  [ = ]       0    CSWTCH.3557
    [DEL]     -24  [ = ]       0    CSWTCH.3558
    [NEW]     +24  [ = ]       0    CSWTCH.3572
    [NEW]     +24  [ = ]       0    CSWTCH.3573
    [NEW]     +20  [ = ]       0    _handler_count
    [NEW]     +14  [ = ]       0    _handlers
    [NEW]     +19  [ = ]       0    _stream_count
    [NEW]     +13  [ = ]       0    _streams
    [NEW]     +54  [ = ]       0    mavlink_ext_handler_dispatch()
    [NEW]     +33  [ = ]       0    mavlink_ext_stream_dispatch()
    [NEW]     +38  [ = ]       0    mavlink_ext_stream_set_interval()
     +23%     +26  [ = ]       0    px4::atomic<>::load()
  +0.0%    +288  [ = ]       0    .symtab
    [DEL]     -32  [ = ]       0    CSWTCH.1990
    [NEW]     +32  [ = ]       0    CSWTCH.1996
    [DEL]     -32  [ = ]       0    CSWTCH.2789
    [NEW]     +32  [ = ]       0    CSWTCH.2795
    [DEL]     -48  [ = ]       0    CSWTCH.3557
    [DEL]     -48  [ = ]       0    CSWTCH.3558
    [NEW]     +48  [ = ]       0    CSWTCH.3572
    [NEW]     +48  [ = ]       0    CSWTCH.3573
   -25.0%     -16  [ = ]       0    Mavlink::handleAndGetCurrentCommandAck()
   -25.0%     -16  [ = ]       0    Mavlink::handleCommands()
     +33%     +16  [ = ]       0    Mavlink::update_radio_status()
     +33%     +16  [ = ]       0    MavlinkCommandSender::check_timeout()
    +100%     +16  [ = ]       0    MavlinkLogHandler::send()
   -25.0%     -16  [ = ]       0    MavlinkLogHandler::state_listing()
   -25.0%     -16  [ = ]       0    MavlinkMissionManager::check_active_mission()
     +50%     +16  [ = ]       0    MavlinkMissionManager::handle_mission_request_int()
   -15.4%     -32  [ = ]       0    MavlinkReceiver::handle_message()
     +20%     +16  [ = ]       0    MavlinkReceiver::handle_messages_in_gimbal_mode()
   -25.0%     -16  [ = ]       0    MavlinkStreamCommandLong::send()
    +100%     +16  [ = ]       0    MavlinkStreamDebug::send()
   -97.6%    +304  [ = ]       0    [24 Others]
   +88% +3.57Ki  [ = ]       0    [Unmapped]
  +0.0% +11.0Ki  +0.0%    +760    TOTAL

Updated: 2026-05-20T13:55:55

@github-actions
Copy link
Copy Markdown
Contributor

No broken links found in changed files.

@methodmissing methodmissing changed the title Out of tree modules: support external MAVLink handler, stream, and dialect support alongside opt in custom init scripts feat(oot): support external MAVLink handler, stream, and dialect with opt in custom init scripts in Out of Tree modules May 20, 2026
@github-actions github-actions Bot added the kind:feature Request or change that adds new functionality. label May 20, 2026
@dakejahl dakejahl requested a review from bkueng May 20, 2026 17:40
Registered handlers are invoked from the MAVLink receiver thread's `default` switch case.
Registration is mutex-protected; dispatch is lock-free.

### Outbound Streams
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is cool, but what if I just want to send a message or command, not stream it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind:feature Request or change that adds new functionality. scope:build-system CMake, Kconfig, board config, or build tooling. scope:docs scope:mavlink MAVLink module, streams, commands, or protocol handling. scope:offboard Offboard mode, external setpoints, companion-computer control, or offboard failsafe behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants