PX4 Autopilot (external module)
Single-node starter on PX4 Autopilot via the external-module
copy-out template. PX4’s EXTERNAL_MODULES_LOCATION pattern lets
downstream firmware drop in nano-ros without forking PX4 itself.
C++ only — PX4’s uORB binding is C++-only (Rust + C not in the
coverage matrix).
Prereqs. A PX4-Autopilot ≥ v1.16 clone, the matching cross toolchain (e.g.
gcc-arm-none-eabifor Pixhawk targets), andpython3with the PX4 development requirements installed (bash ./Tools/setup/ubuntu.shonce).
Project layout
PX4 external modules live outside the PX4 source tree and are
hooked in at configure time. The template at
integrations/px4/module-template/ has the PX4-required
src/modules/<name>/ shape:
my_drone_firmware/
├── PX4-Autopilot/ # PX4 source tree (submodule)
└── px4-modules/ # passed via EXTERNAL_MODULES_LOCATION
└── nano-ros/ # copy-out from integrations/px4/module-template/
└── src/
├── CMakeLists.txt # populates config_module_list_external
└── modules/
└── nano_ros_app/
├── CMakeLists.txt # px4_add_module(... MAIN nano_ros_app)
└── nano_ros_app.cpp # the user-editable app
Prereq. PX4 is a full-tier dependency. Run
just setup px4first to populatethird-party/px4/PX4-Autopilotandthird-party/px4/px4-rs.just px4 doctorreports the gap on a fresh clone.
just setup px4 # equivalent to: just px4 setup
just px4 doctor
Vendor the template into your firmware repo, then point PX4 at its
parent directory + tell the template where nano-ros lives via
NANO_ROS_DIR. The template accepts either form — a CMake cache
variable (-DNANO_ROS_DIR=<path>) takes precedence over an
environment variable (NANO_ROS_DIR=…), then the in-tree default:
cmake -B build -S PX4-Autopilot \
-DCONFIG=px4_fmu-v5_default \
-DEXTERNAL_MODULES_LOCATION=$PWD/px4-modules/nano-ros \
-DNANO_ROS_DIR=$PWD/../nano-ros # point at your nano-ros clone
# Or via environment, if you prefer:
NANO_ROS_DIR=$PWD/../nano-ros cmake -B build -S PX4-Autopilot \
-DCONFIG=px4_fmu-v5_default \
-DEXTERNAL_MODULES_LOCATION=$PWD/px4-modules/nano-ros
(EXTERNAL_MODULES_LOCATION must point at the dir containing
the template’s src/ — that’s px4-modules/nano-ros, not its parent.
PX4’s root CMakeLists.txt does add_subdirectory("${EXTERNAL_MODULES_LOCATION}/src" …),
so the path resolves to px4-modules/nano-ros/src per the layout above.)
Inside the module, the canonical pattern bridges uORB → nano-ros.
The module is a PX4Module subclass that runs in its own work
queue, opens an nros executor, and forwards uORB messages onto a
zenoh / DDS topic (or vice versa). Edit nano_ros_app.cpp to add
your topic bindings.
Configure
The template does not ship a Kconfig overlay (no Kconfig.projbuild
files). Module enablement is implicit once EXTERNAL_MODULES_LOCATION
points at the template’s parent. Pass RMW + ROS-edition selection
via CMake cache vars rather than menuconfig:
cmake -B build -S PX4-Autopilot \
-DCONFIG=px4_fmu-v5_default \
-DEXTERNAL_MODULES_LOCATION=$PWD/px4-modules/nano-ros \
-DNANO_ROS_DIR=$PWD/../nano-ros \
-DNANO_ROS_RMW=zenoh
(Adding a Kconfig overlay so the module appears under
menuconfig → External modules is a follow-up task; for now the
template is always enabled.)
Build
cd PX4-Autopilot
make px4_fmu-v5_default
# Or for the SITL simulator (POSIX target — easier to develop against):
make px4_sitl_default gazebo
The first build cross-compiles nano-ros’s Rust staticlibs alongside PX4’s NuttX kernel + apps (~10 min on a fresh checkout).
Run
# SITL: PX4 boots Gazebo + the autopilot binary
cd PX4-Autopilot
make px4_sitl_default gazebo
# In the PX4 console:
pxh> nano_ros_app start
# Real hardware (Pixhawk): flash via QGroundControl or
# `make px4_fmu-v5_default upload` over the bootloader USB
# Verify from stock ROS 2 on the same network (only after you have
# added uORB → nano-ros forwarders to nano_ros_app.cpp — the shipped
# template forwards nothing on its own):
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 topic echo /vehicle_local_position px4_msgs/msg/VehicleLocalPosition
Readiness signal. After nano_ros_app start in the PX4
console, the shipped template logs nano-ros uORB backend registered and returns immediately — it doesn’t start a
publisher loop or print a “bridge started” line on its own.
You’re expected to edit nano_ros_app.cpp to wire your
uORB → nano-ros forwards. If nano_ros_app reports register failed, check:
- Module didn’t register — the template logs
nros_rmw_uorb_register() -> <rc>on startup (search the PX4 boot log fornros_rmw_uorb_register). A non-zero<rc>usually means a NuttX kernel-config / feature-gate mismatch. - Once you’ve added forwarders,
zenohdmust be reachable from the autopilot’s network (Pixhawk: configured via QGroundControl orparam set). - uORB topic not advertised yet — start the upstream PX4 module
that publishes it (
commander startetc.) first. - See Troubleshooting — First 10 Minutes.
GitHub source
- PX4 external-module template:
integrations/px4/module-template/ - Worked PX4 example:
examples/px4/cpp/uorb/nros-register-check/ - PX4 integration roadmap notes:
integrations/px4/README.md
Constraints to be aware of
- uORB binding is C++-only. The PX4 example collapses uORB registration to a C++ port; Rust / C variants exist for non-PX4 RTOSes but not for PX4.
- PX4’s NuttX kernel. Underneath, PX4 runs NuttX; if you need to debug at the kernel layer, the NuttX starter page applies too.
- uORB throughput vs zenoh hops. uORB is in-process pub/sub at ~µs latency; zenoh adds network-RTT. Plan accordingly when bridging high-rate streams.
Next
- Add your own uORB topics to the bridge: see the
nano_ros_app.cpptemplate’s topic-table section. - Multi-vehicle: PX4-XRCE-Agent → nano-ros XRCE backend gives you the standard PX4-ROS bridge with nano-ros on the autopilot side.
- For pure-NuttX (no PX4) firmware: see the NuttX starter.