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 — shipped in the SDK root at
integrations/px4/module-template/ (the SDK root is the installed release’s
share/nano-ros, nros locate nano-ros prints it) — 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 your own checkout — the firmware tree you already build. Clone it (or use the one you have) and initialize its submodules; nano-ros is tested against PX4 v1.17.0.
git clone --branch v1.17.0 https://github.com/PX4/PX4-Autopilot.git
git -C PX4-Autopilot submodule update --init \
--depth 1 --recommend-shallow --recursive
PX4’s own Python dependencies come from its Tools/setup scripts
(e.g. PX4-Autopilot/Tools/setup/ubuntu.sh) — run those per PX4’s
documentation if the firmware build complains about missing Python
modules.
Copy the template into your firmware repo; PX4 is pointed at the copy at build time:
mkdir -p px4-modules
sdk="$(nros locate nano-ros)" # this project's nano-ros SDK root
cp -r "$sdk/integrations/px4/module-template" px4-modules/nano-ros
(Releases after 0.6.0-nros4 ship the template; with an older one, copy it from GitHub.)
PX4 enforces the src/modules/<name>/ layout exactly. To make the module
your own, rename nano_ros_app in all three places at once: the directory,
the module list in src/CMakeLists.txt, and the MAIN name in the module’s
own CMakeLists.txt. PX4 has no central module registry, so copying the
template into your tree is how it is distributed.
PX4’s own make <target> is the build verb, and it configures and builds ONE
tree, PX4-Autopilot/build/<target>/. The module joins it through
EXTERNAL_MODULES_LOCATION, passed on that same make line — PX4’s
Makefile forwards it to its CMake configure — and pointing at the dir that
contains 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" …)).
The template finds nano-ros itself: an explicit -Dnano_ros_ROOT=<path>
cache variable wins, otherwise it asks the nros on PATH,
which answers with the SDK that binary belongs to. No environment variable is
read. To name the root anyway, pass it through PX4’s
CMAKE_ARGS:
make -C PX4-Autopilot px4_sitl_default \
EXTERNAL_MODULES_LOCATION="$PWD/px4-modules/nano-ros" \
CMAKE_ARGS="-Dnano_ros_ROOT=$sdk" # optional
Not replayed. No probe track runs the PX4 SITL build yet, so this line is unmeasured on the installed track (tracking issue).
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. Which RMW backends the module registers is the
BACKENDS list of nros_px4_add_module(...) in
src/modules/nano_ros_app/CMakeLists.txt — the template ships BACKENDS uorb
— not a cache variable. (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
One make, one tree. For the SITL simulator (POSIX target — easier to
develop against), or for a Pixhawk (px4_fmu-v5_default here; use your
board’s PX4 target):
make -C PX4-Autopilot px4_sitl_default \
EXTERNAL_MODULES_LOCATION="$PWD/px4-modules/nano-ros"
make -C PX4-Autopilot px4_fmu-v5_default \
EXTERNAL_MODULES_LOCATION="$PWD/px4-modules/nano-ros"
The firmware lands in PX4’s own tree, PX4-Autopilot/build/<target>/
(px4_fmu-v5_default.px4 for the Pixhawk). The first build cross-compiles
nano-ros’s Rust staticlibs alongside PX4’s NuttX kernel + apps (~10 min on a
fresh checkout). Note that the module links prebuilt nano-ros archives
(nros_px4_add_module resolves them at configure time and, when one is
missing, stops naming the command that builds it).
Run on the SITL simulator
PX4’s SITL runs the autopilot as a host process against a simulator. With Gazebo (Harmonic) installed per PX4’s documentation:
make -C PX4-Autopilot px4_sitl_default gz_x500 \
EXTERNAL_MODULES_LOCATION="$PWD/px4-modules/nano-ros"
# In the PX4 console:
pxh> nano_ros_app start
# 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
Flash a real board
PX4’s own upload target flashes over the bootloader USB, with the same
EXTERNAL_MODULES_LOCATION as the build (it rebuilds if anything changed):
make -C PX4-Autopilot px4_fmu-v5_default upload \
EXTERNAL_MODULES_LOCATION="$PWD/px4-modules/nano-ros"
Not replayed. No probe runs this flash line (a CI runner has no board), so it is written from the board’s runner, not measured (tracking issue).
QGroundControl can flash the same build/px4_fmu-v5_default/px4_fmu-v5_default.px4
instead. Start the module from the NSH console (nano_ros_app start) or the
board’s startup script.
No probe runs the upload line (a CI runner has no board): it stands unverified until run against a named board. PX4 SITL is the replay that can run in CI, and it is not yet run there either.
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, the source of truth for the register-check shape
(including the weak
nros-rmw-cffiregister stub on the Rust side):packages/testing/nros-px4-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.