From 8bf61528f299f8f94ad2e9b975261e1adcc51a23 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Sun, 17 May 2026 22:58:48 +0100 Subject: [PATCH 01/10] mavlink: add external handler and stream registries for out-of-tree MAVLink dialect support --- src/modules/mavlink/CMakeLists.txt | 2 + src/modules/mavlink/mavlink_ext_handler.cpp | 127 ++++++++++++++++++++ src/modules/mavlink/mavlink_ext_handler.h | 91 ++++++++++++++ src/modules/mavlink/mavlink_ext_stream.cpp | 110 +++++++++++++++++ src/modules/mavlink/mavlink_ext_stream.h | 93 ++++++++++++++ src/modules/mavlink/mavlink_main.cpp | 4 + src/modules/mavlink/mavlink_receiver.cpp | 4 + 7 files changed, 431 insertions(+) create mode 100644 src/modules/mavlink/mavlink_ext_handler.cpp create mode 100644 src/modules/mavlink/mavlink_ext_handler.h create mode 100644 src/modules/mavlink/mavlink_ext_stream.cpp create mode 100644 src/modules/mavlink/mavlink_ext_stream.h diff --git a/src/modules/mavlink/CMakeLists.txt b/src/modules/mavlink/CMakeLists.txt index 9c2540686d84..40a75f926eb0 100644 --- a/src/modules/mavlink/CMakeLists.txt +++ b/src/modules/mavlink/CMakeLists.txt @@ -124,6 +124,8 @@ px4_add_module( MavlinkStatustextHandler.cpp open_drone_id_translations.cpp tune_publisher.cpp + mavlink_ext_handler.cpp + mavlink_ext_stream.cpp MODULE_CONFIG module.yaml mavlink_params.yaml diff --git a/src/modules/mavlink/mavlink_ext_handler.cpp b/src/modules/mavlink/mavlink_ext_handler.cpp new file mode 100644 index 000000000000..e9b5f3ad441b --- /dev/null +++ b/src/modules/mavlink/mavlink_ext_handler.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** + * + * Copyright (c) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file mavlink_ext_handler.cpp + * + * External MAVLink message handler registry implementation. + * + * Uses a simple static array with atomic count for lock-free dispatch. + * Registration is not thread-safe with itself (callers must not register + * concurrently), but dispatch is safe to call from the mavlink receiver + * thread while registration happens from a module init context. + */ + +#include "mavlink_bridge_header.h" // Full mavlink_message_t definition (for ->msgid) +#include "mavlink_ext_handler.h" + +#include +#include +#include + +struct mavlink_ext_handler_entry_t { + uint32_t msg_id; + mavlink_ext_handler_fn handler; + void *user_data; +}; + +static mavlink_ext_handler_entry_t _handlers[MAVLINK_EXT_HANDLER_MAX] {}; +static px4::atomic _handler_count {0}; + +int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler, void *user_data) +{ + if (!handler) { + return -1; + } + + unsigned count = _handler_count.load(); + + // Check for duplicate + for (unsigned i = 0; i < count; i++) { + if (_handlers[i].msg_id == msg_id) { + return -1; + } + } + + if (count >= MAVLINK_EXT_HANDLER_MAX) { + return -1; + } + + // Write entry before publishing count (release semantics via atomic store) + _handlers[count].msg_id = msg_id; + _handlers[count].handler = handler; + _handlers[count].user_data = user_data; + _handler_count.store(count + 1); + + PX4_INFO("ext_handler: registered msgid %lu (count=%u)", (unsigned long)msg_id, count + 1); + + return 0; +} + +int mavlink_ext_handler_unregister(uint32_t msg_id) +{ + unsigned count = _handler_count.load(); + + for (unsigned i = 0; i < count; i++) { + if (_handlers[i].msg_id == msg_id) { + // Shift remaining entries down + if (i < count - 1) { + memmove(&_handlers[i], &_handlers[i + 1], + (count - i - 1) * sizeof(mavlink_ext_handler_entry_t)); + } + + _handler_count.store(count - 1); + return 0; + } + } + + return -1; +} + +bool mavlink_ext_handler_dispatch(const mavlink_message_t *msg) +{ + if (!msg) { + return false; + } + + unsigned count = _handler_count.load(); + + for (unsigned i = 0; i < count; i++) { + if (_handlers[i].msg_id == msg->msgid) { + PX4_DEBUG("ext_handler: dispatching msgid %lu", (unsigned long)msg->msgid); + return _handlers[i].handler(msg, _handlers[i].user_data); + } + } + + return false; +} diff --git a/src/modules/mavlink/mavlink_ext_handler.h b/src/modules/mavlink/mavlink_ext_handler.h new file mode 100644 index 000000000000..c3076fee4e1a --- /dev/null +++ b/src/modules/mavlink/mavlink_ext_handler.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * + * Copyright (c) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file mavlink_ext_handler.h + * + * Generic external MAVLink message handler registration. + * + * Allows out-of-tree / external modules to register callbacks for + * custom MAVLink message IDs without modifying mavlink_receiver.cpp. + * Callbacks are invoked from the receiver's default switch case. + */ + +#pragma once + +#include + +// Forward declaration — the full definition comes from the dialect headers +struct __mavlink_message; +typedef struct __mavlink_message mavlink_message_t; + +/** + * Callback signature for external MAVLink message handlers. + * Receives the raw mavlink_message_t; the handler is responsible for + * decoding (e.g. mavlink_msg_*_decode) and publishing to uORB. + * + * @param msg Parsed MAVLink message (CRC already validated) + * @param user_data Opaque pointer passed at registration time + * @return true if the message was handled + */ +typedef bool (*mavlink_ext_handler_fn)(const mavlink_message_t *msg, void *user_data); + +/** Maximum number of concurrently registered external handlers */ +static constexpr unsigned MAVLINK_EXT_HANDLER_MAX = 8; + +/** + * Register a handler for a custom MAVLink message ID. + * + * @param msg_id MAVLink message ID to handle + * @param handler Callback function + * @param user_data Opaque context pointer (e.g. module instance) + * @return 0 on success, -1 if table full or msg_id already registered + */ +int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler, void *user_data); + +/** + * Unregister a previously registered handler. + * + * @param msg_id MAVLink message ID to unregister + * @return 0 on success, -1 if msg_id not found + */ +int mavlink_ext_handler_unregister(uint32_t msg_id); + +/** + * Dispatch a message to registered external handlers. + * Called from MavlinkReceiver::handle_message() default case. + * + * @param msg Parsed MAVLink message + * @return true if a handler was found and invoked + */ +bool mavlink_ext_handler_dispatch(const mavlink_message_t *msg); diff --git a/src/modules/mavlink/mavlink_ext_stream.cpp b/src/modules/mavlink/mavlink_ext_stream.cpp new file mode 100644 index 000000000000..be2687ac0462 --- /dev/null +++ b/src/modules/mavlink/mavlink_ext_stream.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** + * + * Copyright (c) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file mavlink_ext_stream.cpp + * + * External MAVLink outbound stream registry implementation. + */ + +#include "mavlink_ext_stream.h" + +#include +#include + +struct mavlink_ext_stream_entry_t { + uint32_t msg_id; + const char *name; + mavlink_ext_stream_fn fn; + void *user_data; +}; + +static mavlink_ext_stream_entry_t _streams[MAVLINK_EXT_STREAM_MAX] {}; +static px4::atomic _stream_count {0}; + +int mavlink_ext_stream_register(uint32_t msg_id, const char *name, + mavlink_ext_stream_fn fn, void *user_data) +{ + if (!fn) { + return -1; + } + + unsigned count = _stream_count.load(); + + // Check for duplicate + for (unsigned i = 0; i < count; i++) { + if (_streams[i].msg_id == msg_id) { + return -1; + } + } + + if (count >= MAVLINK_EXT_STREAM_MAX) { + return -1; + } + + _streams[count].msg_id = msg_id; + _streams[count].name = name; + _streams[count].fn = fn; + _streams[count].user_data = user_data; + _stream_count.store(count + 1); + + return 0; +} + +int mavlink_ext_stream_unregister(uint32_t msg_id) +{ + unsigned count = _stream_count.load(); + + for (unsigned i = 0; i < count; i++) { + if (_streams[i].msg_id == msg_id) { + if (i < count - 1) { + memmove(&_streams[i], &_streams[i + 1], + (count - i - 1) * sizeof(mavlink_ext_stream_entry_t)); + } + + _stream_count.store(count - 1); + return 0; + } + } + + return -1; +} + +void mavlink_ext_stream_dispatch(uint8_t channel) +{ + unsigned count = _stream_count.load(); + + for (unsigned i = 0; i < count; i++) { + _streams[i].fn(channel, _streams[i].user_data); + } +} diff --git a/src/modules/mavlink/mavlink_ext_stream.h b/src/modules/mavlink/mavlink_ext_stream.h new file mode 100644 index 000000000000..022dc14e4007 --- /dev/null +++ b/src/modules/mavlink/mavlink_ext_stream.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * + * Copyright (c) 2026 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/** + * @file mavlink_ext_stream.h + * + * Generic external MAVLink outbound stream registration. + * + * Allows out-of-tree / external modules to register callbacks that + * emit custom MAVLink messages on all active channels. The callbacks + * are invoked from the mavlink module's stream update loop. + * + * The callback receives a mavlink_channel_t and should call the + * appropriate mavlink_msg_*_send_struct() to emit the message. + */ + +#pragma once + +#include + +/** + * Callback signature for external outbound streams. + * + * Called from the mavlink main loop for each active mavlink instance. + * The callback should check for new data (e.g. uORB subscription) and + * send a MAVLink message via mavlink_msg_*_send_struct(channel, &msg). + * + * @param channel MAVLink channel index (cast to mavlink_channel_t in callback) + * @param user_data Opaque pointer passed at registration time + * @return true if a message was sent + */ +typedef bool (*mavlink_ext_stream_fn)(uint8_t channel, void *user_data); + +/** Maximum number of concurrently registered external streams */ +static constexpr unsigned MAVLINK_EXT_STREAM_MAX = 8; + +/** + * Register an external outbound stream. + * + * @param msg_id MAVLink message ID (for identification/logging) + * @param name Human-readable stream name (for `mavlink stream` command) + * @param fn Callback function invoked each iteration + * @param user_data Opaque context pointer + * @return 0 on success, -1 if table full or msg_id already registered + */ +int mavlink_ext_stream_register(uint32_t msg_id, const char *name, + mavlink_ext_stream_fn fn, void *user_data); + +/** + * Unregister a previously registered external stream. + * + * @param msg_id MAVLink message ID to unregister + * @return 0 on success, -1 if not found + */ +int mavlink_ext_stream_unregister(uint32_t msg_id); + +/** + * Dispatch all registered external streams on a given channel. + * Called from Mavlink::task_main() after the regular stream update loop. + * + * @param chan MAVLink channel to emit on + */ +void mavlink_ext_stream_dispatch(uint8_t channel); diff --git a/src/modules/mavlink/mavlink_main.cpp b/src/modules/mavlink/mavlink_main.cpp index f638099c77cd..9685dbbdc5a9 100644 --- a/src/modules/mavlink/mavlink_main.cpp +++ b/src/modules/mavlink/mavlink_main.cpp @@ -60,6 +60,7 @@ #include #include "mavlink_receiver.h" #include "mavlink_main.h" +#include "mavlink_ext_stream.h" #ifdef MAVLINK_UDP #include @@ -2540,6 +2541,9 @@ Mavlink::task_main(int argc, char *argv[]) } } + /* dispatch registered external outbound streams */ + mavlink_ext_stream_dispatch(static_cast(get_channel())); + /* check for ulog streaming messages */ if (_mavlink_ulog) { const int ret = _mavlink_ulog->handle_update(get_channel()); diff --git a/src/modules/mavlink/mavlink_receiver.cpp b/src/modules/mavlink/mavlink_receiver.cpp index 3f4a8525b8a5..541549f444b9 100644 --- a/src/modules/mavlink/mavlink_receiver.cpp +++ b/src/modules/mavlink/mavlink_receiver.cpp @@ -60,6 +60,7 @@ #include "mavlink_command_sender.h" #include "mavlink_main.h" #include "mavlink_receiver.h" +#include "mavlink_ext_handler.h" #include // For DeviceId union #include @@ -351,6 +352,7 @@ MavlinkReceiver::handle_message(mavlink_message_t *msg) #endif default: + mavlink_ext_handler_dispatch(msg); break; } @@ -1345,6 +1347,8 @@ MavlinkReceiver::handle_message_esc_eeprom(mavlink_message_t *msg) } #endif // MAVLINK_MSG_ID_ESC_EEPROM + + void MavlinkReceiver::handle_message_vision_position_estimate(mavlink_message_t *msg) { From 5b530836dc06c722acc402630c3e161411ff3f34 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 16:35:39 +0100 Subject: [PATCH 02/10] cmake: add mavlink/ directory convention for external module MAVLink dialects --- CMakeLists.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f249164aa63a..875a8eb85463 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -452,6 +452,33 @@ if (NOT EXTERNAL_MODULES_LOCATION STREQUAL "") add_subdirectory(${EXTERNAL_MODULES_LOCATION}/src/${external_module} external_modules/${external_module}) list(APPEND external_module_paths ${EXTERNAL_MODULES_LOCATION}/src/${external_module}) endforeach() + + # external MAVLink dialect: EXTERNAL_MODULES_LOCATION/mavlink/.xml + file(GLOB _ext_mavlink_xmls "${EXTERNAL_MODULES_LOCATION}/mavlink/*.xml") + if(_ext_mavlink_xmls) + list(LENGTH _ext_mavlink_xmls _n_xmls) + if(_n_xmls GREATER 1) + message(FATAL_ERROR "Multiple MAVLink dialect XMLs found in " + "${EXTERNAL_MODULES_LOCATION}/mavlink/. " + "Only one external dialect is supported.") + endif() + list(GET _ext_mavlink_xmls 0 _ext_xml) + get_filename_component(_ext_dialect_name "${_ext_xml}" NAME_WE) + + # Copy into mavgen search path (configure_file tracks dependencies) + set(_mavlink_defs "${PX4_SOURCE_DIR}/src/modules/mavlink/mavlink/message_definitions/v1.0") + configure_file("${_ext_xml}" "${_mavlink_defs}/${_ext_dialect_name}.xml" COPYONLY) + + # Override dialect if currently 'common' (external dialect must include common.xml) + if(CONFIG_MAVLINK_DIALECT STREQUAL "common") + set(CONFIG_MAVLINK_DIALECT "${_ext_dialect_name}" + CACHE STRING "MAVLink dialect (external: ${_ext_dialect_name})" FORCE) + message(STATUS "External MAVLink dialect: ${_ext_dialect_name} (auto-set from common)") + else() + message(STATUS "External MAVLink dialect: ${_ext_dialect_name} " + "(CONFIG_MAVLINK_DIALECT=${CONFIG_MAVLINK_DIALECT} kept as-is)") + endif() + endif() endif() #============================================================================= From cf803434662d8c269e291db196f0ede8b843bc92 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:28:58 +0100 Subject: [PATCH 03/10] Add support for rate intervals --- src/modules/mavlink/mavlink_receiver.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/modules/mavlink/mavlink_receiver.cpp b/src/modules/mavlink/mavlink_receiver.cpp index 541549f444b9..a55eb701c655 100644 --- a/src/modules/mavlink/mavlink_receiver.cpp +++ b/src/modules/mavlink/mavlink_receiver.cpp @@ -61,6 +61,7 @@ #include "mavlink_main.h" #include "mavlink_receiver.h" #include "mavlink_ext_handler.h" +#include "mavlink_ext_stream.h" #include // For DeviceId union #include @@ -2334,6 +2335,18 @@ MavlinkReceiver::set_message_interval(int msgId, float interval, float param3, f if (stream_name != nullptr) { _mavlink.configure_stream_threadsafe(stream_name, rate); found_id = true; + + } else { + // Fallback: check external (OOT) streams + int ext_interval_us = (interval > 0.00001f) ? (int)interval : -1; + + if (interval < -0.00001f) { + ext_interval_us = 0; // stop + } + + if (mavlink_ext_stream_set_interval((uint32_t)msgId, ext_interval_us) == 0) { + found_id = true; + } } } From d9b0cdc88bdb7453ef745b9dc5e1896259123367 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:29:46 +0100 Subject: [PATCH 04/10] Use mutex in mavlink_ext_handler_register --- src/modules/mavlink/mavlink_ext_handler.cpp | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/modules/mavlink/mavlink_ext_handler.cpp b/src/modules/mavlink/mavlink_ext_handler.cpp index e9b5f3ad441b..715ca1ab43ee 100644 --- a/src/modules/mavlink/mavlink_ext_handler.cpp +++ b/src/modules/mavlink/mavlink_ext_handler.cpp @@ -33,13 +33,6 @@ /** * @file mavlink_ext_handler.cpp - * - * External MAVLink message handler registry implementation. - * - * Uses a simple static array with atomic count for lock-free dispatch. - * Registration is not thread-safe with itself (callers must not register - * concurrently), but dispatch is safe to call from the mavlink receiver - * thread while registration happens from a module init context. */ #include "mavlink_bridge_header.h" // Full mavlink_message_t definition (for ->msgid) @@ -48,6 +41,7 @@ #include #include #include +#include struct mavlink_ext_handler_entry_t { uint32_t msg_id; @@ -57,6 +51,7 @@ struct mavlink_ext_handler_entry_t { static mavlink_ext_handler_entry_t _handlers[MAVLINK_EXT_HANDLER_MAX] {}; static px4::atomic _handler_count {0}; +static pthread_mutex_t _handler_mutex = PTHREAD_MUTEX_INITIALIZER; int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler, void *user_data) { @@ -64,25 +59,29 @@ int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler return -1; } + pthread_mutex_lock(&_handler_mutex); + unsigned count = _handler_count.load(); - // Check for duplicate for (unsigned i = 0; i < count; i++) { if (_handlers[i].msg_id == msg_id) { + pthread_mutex_unlock(&_handler_mutex); return -1; } } if (count >= MAVLINK_EXT_HANDLER_MAX) { + pthread_mutex_unlock(&_handler_mutex); return -1; } - // Write entry before publishing count (release semantics via atomic store) _handlers[count].msg_id = msg_id; _handlers[count].handler = handler; _handlers[count].user_data = user_data; _handler_count.store(count + 1); + pthread_mutex_unlock(&_handler_mutex); + PX4_INFO("ext_handler: registered msgid %lu (count=%u)", (unsigned long)msg_id, count + 1); return 0; @@ -90,21 +89,24 @@ int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler int mavlink_ext_handler_unregister(uint32_t msg_id) { + pthread_mutex_lock(&_handler_mutex); + unsigned count = _handler_count.load(); for (unsigned i = 0; i < count; i++) { if (_handlers[i].msg_id == msg_id) { - // Shift remaining entries down if (i < count - 1) { memmove(&_handlers[i], &_handlers[i + 1], (count - i - 1) * sizeof(mavlink_ext_handler_entry_t)); } _handler_count.store(count - 1); + pthread_mutex_unlock(&_handler_mutex); return 0; } } + pthread_mutex_unlock(&_handler_mutex); return -1; } From cad563f512df16dc3df59510bc03facebc325f36 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:32:43 +0100 Subject: [PATCH 05/10] Add mavlink_ext_stream_set_interval and use mutex in ext_stream --- src/modules/mavlink/mavlink_ext_stream.cpp | 52 +++++++++++++++++++--- src/modules/mavlink/mavlink_ext_stream.h | 15 ++++++- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/modules/mavlink/mavlink_ext_stream.cpp b/src/modules/mavlink/mavlink_ext_stream.cpp index be2687ac0462..40a3506ac0d4 100644 --- a/src/modules/mavlink/mavlink_ext_stream.cpp +++ b/src/modules/mavlink/mavlink_ext_stream.cpp @@ -33,42 +33,49 @@ /** * @file mavlink_ext_stream.cpp - * - * External MAVLink outbound stream registry implementation. */ #include "mavlink_ext_stream.h" #include +#include #include +#include struct mavlink_ext_stream_entry_t { uint32_t msg_id; const char *name; mavlink_ext_stream_fn fn; void *user_data; + int interval_us; // -1 = unlimited, 0 = disabled + hrt_abstime last_sent; }; static mavlink_ext_stream_entry_t _streams[MAVLINK_EXT_STREAM_MAX] {}; static px4::atomic _stream_count {0}; +static pthread_mutex_t _stream_mutex = PTHREAD_MUTEX_INITIALIZER; int mavlink_ext_stream_register(uint32_t msg_id, const char *name, - mavlink_ext_stream_fn fn, void *user_data) + mavlink_ext_stream_fn fn, void *user_data, + int interval_us) { if (!fn) { return -1; } + pthread_mutex_lock(&_stream_mutex); + unsigned count = _stream_count.load(); - // Check for duplicate for (unsigned i = 0; i < count; i++) { if (_streams[i].msg_id == msg_id) { + pthread_mutex_unlock(&_stream_mutex); return -1; } } if (count >= MAVLINK_EXT_STREAM_MAX) { + pthread_mutex_unlock(&_stream_mutex); return -1; } @@ -76,13 +83,19 @@ int mavlink_ext_stream_register(uint32_t msg_id, const char *name, _streams[count].name = name; _streams[count].fn = fn; _streams[count].user_data = user_data; + _streams[count].interval_us = interval_us; + _streams[count].last_sent = 0; _stream_count.store(count + 1); + pthread_mutex_unlock(&_stream_mutex); + return 0; } int mavlink_ext_stream_unregister(uint32_t msg_id) { + pthread_mutex_lock(&_stream_mutex); + unsigned count = _stream_count.load(); for (unsigned i = 0; i < count; i++) { @@ -93,18 +106,47 @@ int mavlink_ext_stream_unregister(uint32_t msg_id) } _stream_count.store(count - 1); + pthread_mutex_unlock(&_stream_mutex); return 0; } } + pthread_mutex_unlock(&_stream_mutex); return -1; } void mavlink_ext_stream_dispatch(uint8_t channel) +{ + unsigned count = _stream_count.load(); + hrt_abstime now = hrt_absolute_time(); + + for (unsigned i = 0; i < count; i++) { + if (_streams[i].interval_us == 0) { + continue; + } + + if (_streams[i].interval_us > 0) { + if (now - _streams[i].last_sent < (hrt_abstime)_streams[i].interval_us) { + continue; + } + } + + if (_streams[i].fn(channel, _streams[i].user_data)) { + _streams[i].last_sent = now; + } + } +} + +int mavlink_ext_stream_set_interval(uint32_t msg_id, int interval_us) { unsigned count = _stream_count.load(); for (unsigned i = 0; i < count; i++) { - _streams[i].fn(channel, _streams[i].user_data); + if (_streams[i].msg_id == msg_id) { + _streams[i].interval_us = interval_us; + return 0; + } } + + return -1; } diff --git a/src/modules/mavlink/mavlink_ext_stream.h b/src/modules/mavlink/mavlink_ext_stream.h index 022dc14e4007..4e9119c35a60 100644 --- a/src/modules/mavlink/mavlink_ext_stream.h +++ b/src/modules/mavlink/mavlink_ext_stream.h @@ -74,7 +74,8 @@ static constexpr unsigned MAVLINK_EXT_STREAM_MAX = 8; * @return 0 on success, -1 if table full or msg_id already registered */ int mavlink_ext_stream_register(uint32_t msg_id, const char *name, - mavlink_ext_stream_fn fn, void *user_data); + mavlink_ext_stream_fn fn, void *user_data, + int interval_us = -1); /** * Unregister a previously registered external stream. @@ -91,3 +92,15 @@ int mavlink_ext_stream_unregister(uint32_t msg_id); * @param chan MAVLink channel to emit on */ void mavlink_ext_stream_dispatch(uint8_t channel); + +/** + * Set the send interval for a registered external stream. + * + * Allows integration with SET_MESSAGE_INTERVAL so that QGC/pymavlink + * can control OOT stream rates the same way as built-in streams. + * + * @param msg_id MAVLink message ID + * @param interval_us Interval in microseconds (-1 = unlimited, 0 = disabled) + * @return 0 on success, -1 if not found + */ +int mavlink_ext_stream_set_interval(uint32_t msg_id, int interval_us); From bf8f1f621b07a198ea3f49d49177e5dd6479b6ed Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:33:37 +0100 Subject: [PATCH 06/10] Prefer explicit shared cmake function px4_add_external_mavlink_dialect --- CMakeLists.txt | 34 ++++----- cmake/px4_add_external_mavlink_dialect.cmake | 77 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 cmake/px4_add_external_mavlink_dialect.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 875a8eb85463..b6eb3a891dd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ define_property(GLOBAL PROPERTY PX4_SRC_FILES # include(px4_add_module) +include(px4_add_external_mavlink_dialect) set(config_module_list) set(config_kernel_list) @@ -453,31 +454,24 @@ if (NOT EXTERNAL_MODULES_LOCATION STREQUAL "") list(APPEND external_module_paths ${EXTERNAL_MODULES_LOCATION}/src/${external_module}) endforeach() - # external MAVLink dialect: EXTERNAL_MODULES_LOCATION/mavlink/.xml - file(GLOB _ext_mavlink_xmls "${EXTERNAL_MODULES_LOCATION}/mavlink/*.xml") - if(_ext_mavlink_xmls) - list(LENGTH _ext_mavlink_xmls _n_xmls) - if(_n_xmls GREATER 1) - message(FATAL_ERROR "Multiple MAVLink dialect XMLs found in " - "${EXTERNAL_MODULES_LOCATION}/mavlink/. " - "Only one external dialect is supported.") - endif() - list(GET _ext_mavlink_xmls 0 _ext_xml) - get_filename_component(_ext_dialect_name "${_ext_xml}" NAME_WE) - - # Copy into mavgen search path (configure_file tracks dependencies) - set(_mavlink_defs "${PX4_SOURCE_DIR}/src/modules/mavlink/mavlink/message_definitions/v1.0") - configure_file("${_ext_xml}" "${_mavlink_defs}/${_ext_dialect_name}.xml" COPYONLY) + # External MAVLink dialects (registered via px4_add_external_mavlink_dialect) + get_property(_ext_dialects GLOBAL PROPERTY PX4_EXTERNAL_MAVLINK_DIALECTS) + if(_ext_dialects) + list(LENGTH _ext_dialects _n_dialects) + list(GET _ext_dialects 0 _primary_ext_dialect) - # Override dialect if currently 'common' (external dialect must include common.xml) if(CONFIG_MAVLINK_DIALECT STREQUAL "common") - set(CONFIG_MAVLINK_DIALECT "${_ext_dialect_name}" - CACHE STRING "MAVLink dialect (external: ${_ext_dialect_name})" FORCE) - message(STATUS "External MAVLink dialect: ${_ext_dialect_name} (auto-set from common)") + set(CONFIG_MAVLINK_DIALECT "${_primary_ext_dialect}" + CACHE STRING "MAVLink dialect (external: ${_primary_ext_dialect})" FORCE) + message(STATUS "External MAVLink dialect: ${_primary_ext_dialect} (auto-set from common)") else() - message(STATUS "External MAVLink dialect: ${_ext_dialect_name} " + message(STATUS "External MAVLink dialect: ${_primary_ext_dialect} " "(CONFIG_MAVLINK_DIALECT=${CONFIG_MAVLINK_DIALECT} kept as-is)") endif() + + if(_n_dialects GREATER 1) + message(STATUS " Additional external dialects: ${_ext_dialects}") + endif() endif() endif() diff --git a/cmake/px4_add_external_mavlink_dialect.cmake b/cmake/px4_add_external_mavlink_dialect.cmake new file mode 100644 index 000000000000..e5bffc35567f --- /dev/null +++ b/cmake/px4_add_external_mavlink_dialect.cmake @@ -0,0 +1,77 @@ +############################################################################ +# +# Copyright (c) 2026 PX4 Development Team. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name PX4 nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +#============================================================================= +# +# px4_add_external_mavlink_dialect +# +# Registers an external MAVLink dialect XML for mavgen code generation. +# The dialect XML should common.xml (or another base +# dialect) so that all standard MAVLink messages remain available. +# +# Multiple external dialects are supported. The first registered dialect +# becomes the primary dialect (overrides CONFIG_MAVLINK_DIALECT from +# "common" if applicable). +# +# Usage: +# px4_add_external_mavlink_dialect( +# XML ${CMAKE_CURRENT_SOURCE_DIR}/../../mavlink/se05x.xml +# ) +# +# Effects: +# 1. Copies the XML into mavgen's message_definitions/v1.0/ search path +# 2. Appends the dialect name to PX4_EXTERNAL_MAVLINK_DIALECTS global property +# 3. Dialect override happens in root CMakeLists.txt after all add_subdirectory() +# calls have completed +# +function(px4_add_external_mavlink_dialect) + px4_parse_function_args( + NAME px4_add_external_mavlink_dialect + ONE_VALUE XML + REQUIRED XML + ARGN ${ARGN} + ) + + if(NOT EXISTS "${XML}") + message(FATAL_ERROR "px4_add_external_mavlink_dialect: XML not found: ${XML}") + endif() + + get_filename_component(_dialect_name "${XML}" NAME_WE) + set(_mavlink_defs "${PX4_SOURCE_DIR}/src/modules/mavlink/mavlink/message_definitions/v1.0") + + configure_file("${XML}" "${_mavlink_defs}/${_dialect_name}.xml" COPYONLY) + + set_property(GLOBAL APPEND PROPERTY PX4_EXTERNAL_MAVLINK_DIALECTS "${_dialect_name}") + + message(STATUS "External MAVLink dialect registered: ${_dialect_name} (from ${XML})") +endfunction() From f2d0b995a4eadddc3f560d70714fcd9e575a3cb6 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:34:51 +0100 Subject: [PATCH 07/10] Introduce support for optional external module auto-start / init scripts --- ROMFS/CMakeLists.txt | 25 +++++++++++++++++++++++++ ROMFS/px4fmu_common/init.d/rcS | 11 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/ROMFS/CMakeLists.txt b/ROMFS/CMakeLists.txt index 42e5d4293e52..58ca715de962 100644 --- a/ROMFS/CMakeLists.txt +++ b/ROMFS/CMakeLists.txt @@ -226,6 +226,31 @@ foreach(board_rc_file ${OPTIONAL_BOARD_RC}) endforeach() +# External module init scripts +if(NOT "${EXTERNAL_MODULES_LOCATION}" STREQUAL "") + set(_ext_init "${EXTERNAL_MODULES_LOCATION}/init/rc.ext_modules") + if(EXISTS "${_ext_init}") + file(RELATIVE_PATH _ext_init_relative ${PX4_SOURCE_DIR} ${_ext_init}) + message(STATUS "ROMFS: Adding ${_ext_init_relative} -> /etc/init.d/rc.ext_modules") + + add_custom_command( + OUTPUT + ${romfs_gen_root_dir}/init.d/rc.ext_modules + rc.ext_modules.stamp + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${_ext_init} ${romfs_gen_root_dir}/init.d/rc.ext_modules + COMMAND ${CMAKE_COMMAND} -E touch rc.ext_modules.stamp + DEPENDS + ${_ext_init} + romfs_copy.stamp + COMMENT "ROMFS: copying rc.ext_modules" + ) + + list(APPEND extras_dependencies rc.ext_modules.stamp) + endif() +endif() + + if(config_additional_init) if(EXISTS "${PX4_BOARD_DIR}/init/${config_additional_init}") file(RELATIVE_PATH rc_file_relative ${PX4_SOURCE_DIR} ${PX4_BOARD_DIR}/init/${config_additional_init}) diff --git a/ROMFS/px4fmu_common/init.d/rcS b/ROMFS/px4fmu_common/init.d/rcS index b6d517b45c2e..a8c378587a37 100644 --- a/ROMFS/px4fmu_common/init.d/rcS +++ b/ROMFS/px4fmu_common/init.d/rcS @@ -667,6 +667,17 @@ else fi unset BOARD_RC_EXTRAS + # + # Optional external module auto-start: rc.ext_modules + # + set EXT_MODULE_RC ${R}etc/init.d/rc.ext_modules + if [ -f $EXT_MODULE_RC ] + then + echo "External modules: ${EXT_MODULE_RC}" + . $EXT_MODULE_RC + fi + unset EXT_MODULE_RC + # # Start any custom addons from the sdcard. # From 1255684d983f5e14c689916aeb33121c9917b114 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:43:22 +0100 Subject: [PATCH 08/10] Add documentation relevant to OOT support for startup sequence and MAVLink dialect registration and handling --- docs/en/advanced/out_of_tree_modules.md | 119 ++++++++++++++++++++++++ docs/en/mavlink/custom_messages.md | 5 + 2 files changed, 124 insertions(+) diff --git a/docs/en/advanced/out_of_tree_modules.md b/docs/en/advanced/out_of_tree_modules.md index 7763e5478416..f1f3f76b02e5 100644 --- a/docs/en/advanced/out_of_tree_modules.md +++ b/docs/en/advanced/out_of_tree_modules.md @@ -82,3 +82,122 @@ Any other build target can be used, but the build directory must not yet exist. If it already exists, you can also just set the _cmake_ variable in the build folder. For subsequent incremental builds `EXTERNAL_MODULES_LOCATION` does not need to be specified. + +## Out-of-Tree MAVLink Dialect Definitions + +External modules can register custom MAVLink dialect XML files for mavgen code generation without modifying PX4 source. + +### Registering a Dialect + +Call `px4_add_external_mavlink_dialect()` from your module's `CMakeLists.txt`: + +```cmake +px4_add_external_mavlink_dialect( + XML ${CMAKE_CURRENT_SOURCE_DIR}/../../mavlink/my_dialect.xml +) +``` + +The dialect XML must `common.xml` so that all standard MAVLink messages remain available. +The function copies the XML into mavgen's search path and, if `CONFIG_MAVLINK_DIALECT` is `common`, automatically overrides it with your dialect name. + +Multiple external dialects from different modules are supported. + +### Directory Layout + +``` +my_external_module/ +├── mavlink/ +│ └── my_dialect.xml # Custom MAVLink dialect +├── src/ +│ ├── CMakeLists.txt # Calls px4_add_external_mavlink_dialect() +│ └── modules/ +│ └── my_module/ +``` + +## External MAVLink Message Handlers and Streams + +External modules can register callbacks for custom inbound and outbound MAVLink messages at runtime, without patching `mavlink_receiver.cpp` or `mavlink_main.cpp`. + +### Inbound Message Handlers + +Register a handler for a custom message ID from your module's `init` or `task_spawn`: + +```cpp +#include + +static bool handle_my_message(const mavlink_message_t *msg, void *user_data) +{ + // Decode and process message + return true; +} + +// Registration (typically in module init) +mavlink_ext_handler_register(MAVLINK_MSG_ID_MY_MESSAGE, handle_my_message, this); + +// Cleanup (module stop) +mavlink_ext_handler_unregister(MAVLINK_MSG_ID_MY_MESSAGE); +``` + +Registered handlers are invoked from the MAVLink receiver thread's `default` switch case. +Registration is mutex-protected; dispatch is lock-free. + +### Outbound Streams + +Register a stream callback to periodically emit custom messages: + +```cpp +#include + +static bool emit_my_message(uint8_t channel, void *user_data) +{ + mavlink_my_message_t msg{}; + // Fill message fields... + mavlink_msg_my_message_send_struct((mavlink_channel_t)channel, &msg); + return true; +} + +// Register with rate limiting (500000 = 2 Hz) +mavlink_ext_stream_register(MAVLINK_MSG_ID_MY_MESSAGE, "MY_MESSAGE", + emit_my_message, this, 500000); +``` + +The `interval_us` parameter controls rate limiting: +- `-1`: unlimited (fire every iteration) +- `0`: disabled +- `>0`: minimum microseconds between sends + +External stream rates can also be controlled at runtime via the standard MAVLink `SET_MESSAGE_INTERVAL` command from QGC or pymavlink: + +```cpp +// Programmatic rate change +mavlink_ext_stream_set_interval(MAVLINK_MSG_ID_MY_MESSAGE, 1000000); // 1 Hz +``` + +## Boot-Time Auto-Start + +External modules can declare startup commands that are baked into the firmware ROMFS image at build time, eliminating the need for manual SD card `extras.txt` files. + +### Setup + +Create `init/rc.ext_modules` in your external module directory: + +```sh +#!/bin/sh +my_driver start +my_mavlink_bridge start +``` + +When building with `EXTERNAL_MODULES_LOCATION`, PX4's build system automatically copies this file into the ROMFS. +At boot, `rcS` sources it after `rc.board_extras` and before the SD card `extras.txt`. + +### Boot Order + +``` +rcS boot sequence: +├── rc.board_extras # Board-specific init +├── rc.ext_modules # External module auto-start (ROMFS, build-time) +├── extras.txt # SD card overrides (runtime) +└── rc.logging # Logger start +``` + +The SD card `extras.txt` remains available as a runtime override for development and testing without reflashing. diff --git a/docs/en/mavlink/custom_messages.md b/docs/en/mavlink/custom_messages.md index 5bd8534cffdb..12f2b92ecc51 100644 --- a/docs/en/mavlink/custom_messages.md +++ b/docs/en/mavlink/custom_messages.md @@ -13,6 +13,11 @@ Custom definitions can be added in a new dialect file in the same directory as [ For example, create `PX4-Autopilot/src/modules/mavlink/mavlink/message_definitions/v1.0/custom_messages.xml`, and set `CONFIG_MAVLINK_DIALECT` to build the new file for SITL. This dialect file should include `development.xml` so that all the standard definitions are also included. +:::tip +If you are building an [external (out-of-tree) module](../advanced/out_of_tree_modules.md), use `px4_add_external_mavlink_dialect()` in your `CMakeLists.txt` instead of manually placing files in the PX4 source tree. +See [Out-of-Tree MAVLink Dialect Definitions](../advanced/out_of_tree_modules.md#out-of-tree-mavlink-dialect-definitions). +::: + For initial prototyping, or if you intend your message to be "standard", you can also add your messages to `common.xml` (or `development.xml`). This simplifies building, because you don't need to modify the dialect that is built. From 80d1ef64ef45418d8d5dc34454303e2e9508a30e Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 22:57:30 +0100 Subject: [PATCH 09/10] Cleanups --- cmake/px4_add_external_mavlink_dialect.cmake | 2 +- src/modules/mavlink/mavlink_ext_handler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/px4_add_external_mavlink_dialect.cmake b/cmake/px4_add_external_mavlink_dialect.cmake index e5bffc35567f..dc42a78a73d1 100644 --- a/cmake/px4_add_external_mavlink_dialect.cmake +++ b/cmake/px4_add_external_mavlink_dialect.cmake @@ -45,7 +45,7 @@ # # Usage: # px4_add_external_mavlink_dialect( -# XML ${CMAKE_CURRENT_SOURCE_DIR}/../../mavlink/se05x.xml +# XML ${CMAKE_CURRENT_SOURCE_DIR}/../../mavlink/my_dialect.xml # ) # # Effects: diff --git a/src/modules/mavlink/mavlink_ext_handler.cpp b/src/modules/mavlink/mavlink_ext_handler.cpp index 715ca1ab43ee..74bd463b7dba 100644 --- a/src/modules/mavlink/mavlink_ext_handler.cpp +++ b/src/modules/mavlink/mavlink_ext_handler.cpp @@ -82,7 +82,7 @@ int mavlink_ext_handler_register(uint32_t msg_id, mavlink_ext_handler_fn handler pthread_mutex_unlock(&_handler_mutex); - PX4_INFO("ext_handler: registered msgid %lu (count=%u)", (unsigned long)msg_id, count + 1); + PX4_DEBUG("ext_handler: registered msgid %lu (count=%u)", (unsigned long)msg_id, count + 1); return 0; } From 26f188d5e9ac0ddc4fb56c414c00f3ce606439f4 Mon Sep 17 00:00:00 2001 From: Lourens Naude Date: Mon, 18 May 2026 23:40:12 +0100 Subject: [PATCH 10/10] Reverse boot order so that logging has precedence over OOT module inits --- ROMFS/px4fmu_common/init.d/rcS | 22 +++++++++++----------- docs/en/advanced/out_of_tree_modules.md | 5 +++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ROMFS/px4fmu_common/init.d/rcS b/ROMFS/px4fmu_common/init.d/rcS index a8c378587a37..c0235bf19d31 100644 --- a/ROMFS/px4fmu_common/init.d/rcS +++ b/ROMFS/px4fmu_common/init.d/rcS @@ -667,17 +667,6 @@ else fi unset BOARD_RC_EXTRAS - # - # Optional external module auto-start: rc.ext_modules - # - set EXT_MODULE_RC ${R}etc/init.d/rc.ext_modules - if [ -f $EXT_MODULE_RC ] - then - echo "External modules: ${EXT_MODULE_RC}" - . $EXT_MODULE_RC - fi - unset EXT_MODULE_RC - # # Start any custom addons from the sdcard. # @@ -697,6 +686,17 @@ else fi unset RC_LOGGING + # + # Optional external module auto-start: rc.ext_modules + # + set EXT_MODULE_RC ${R}etc/init.d/rc.ext_modules + if [ -f $EXT_MODULE_RC ] + then + echo "External modules: ${EXT_MODULE_RC}" + . $EXT_MODULE_RC + fi + unset EXT_MODULE_RC + # # Start the VTX services. # diff --git a/docs/en/advanced/out_of_tree_modules.md b/docs/en/advanced/out_of_tree_modules.md index f1f3f76b02e5..909ca7b599f7 100644 --- a/docs/en/advanced/out_of_tree_modules.md +++ b/docs/en/advanced/out_of_tree_modules.md @@ -195,9 +195,10 @@ At boot, `rcS` sources it after `rc.board_extras` and before the SD card `extras ``` rcS boot sequence: ├── rc.board_extras # Board-specific init -├── rc.ext_modules # External module auto-start (ROMFS, build-time) ├── extras.txt # SD card overrides (runtime) -└── rc.logging # Logger start +├── rc.logging # Logger start +└── rc.ext_modules # External module auto-start (ROMFS, build-time) ``` +External modules run after the logger, ensuring that any slow hardware initialization (e.g. I2C secure elements) doesn't delay flight logging in a brownout recovery scenario. The SD card `extras.txt` remains available as a runtime override for development and testing without reflashing.