Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -452,6 +453,26 @@ 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 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)

if(CONFIG_MAVLINK_DIALECT STREQUAL "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: ${_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()

#=============================================================================
Expand Down
25 changes: 25 additions & 0 deletions ROMFS/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
11 changes: 11 additions & 0 deletions ROMFS/px4fmu_common/init.d/rcS
Original file line number Diff line number Diff line change
Expand Up @@ -686,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.
#
Expand Down
77 changes: 77 additions & 0 deletions cmake/px4_add_external_mavlink_dialect.cmake
Original file line number Diff line number Diff line change
@@ -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 <include>common.xml</include> (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/my_dialect.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()
120 changes: 120 additions & 0 deletions docs/en/advanced/out_of_tree_modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,123 @@ 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 `<include>common.xml</include>` 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 <modules/mavlink/mavlink_ext_handler.h>

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
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?


Register a stream callback to periodically emit custom messages:

```cpp
#include <modules/mavlink/mavlink_ext_stream.h>

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
├── extras.txt # SD card overrides (runtime)
├── 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.
5 changes: 5 additions & 0 deletions docs/en/mavlink/custom_messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions src/modules/mavlink/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading