NuttX (apps/external)
Single-node starter on NuttX. nano-ros plugs into the standard NuttX
app discovery as an external app under apps/external/nano-ros/,
exposing Kconfig knobs under Application Configuration → External Modules → nano-ros. Use this entry when your NuttX board ships its
own kernel build and you want to add ROS 2 communication.
Contributor path? Building nano-ros’s own NuttX QEMU examples straight from this repository (no NuttX-managed workspace) is covered at NuttX (contributor). The page below is the canonical user entry.
Prereqs. Install the
nrosCLI once per machine, then runnros setup qemu-armv7a-nuttx --rmw <zenoh|xrce|cyclonedds>(--rmwdefaults tozenoh). This fetches a mostly-prebuilt toolchain set into${NROS_HOME:-~/.nros}/sdk— the NuttX cross-compiler, the emulator, the NuttX sources, and the RMW host daemon — so you do not hand-install a cross-toolchain and do not need a ROS 2 install:./scripts/bootstrap.sh # builds packages/cli/target/release/nros (Phase 218) source ./activate.sh # OR: direnv allow / source ./activate.fish nros setup qemu-armv7a-nuttx --rmw zenohYou still need a NuttX ≥ nuttx-12 checkout with an
apps/sibling and Python 3.10+ for the NuttX configure scripts.
Project layout
NuttX’s “external apps” pattern places the app shim under
$NUTTX_APPS_DIR/external/<name>/:
$NUTTX_DIR/ # NuttX kernel checkout
$NUTTX_APPS_DIR/ # sibling: apps tree
└── external/
└── nano-ros/ # symlink or submodule of
├── Make.defs # integrations/nuttx/
├── Makefile
├── CMakeLists.txt # (cmake-driven NuttX builds)
└── Kconfig
my_app/ # your application
├── system.toml # WHAT this deploys to — the one file you edit
├── package.xml
├── Cargo.toml | CMakeLists.txt # language-toolchain facts only
├── generated/ # generated message bindings (gitignored)
└── src/main.{rs,c,cpp}
Wire the shell into your NuttX apps tree. Two commands, both run once:
nros setup qemu-armv7a-nuttx --rmw zenoh # toolchain + NuttX sources + SDK
# from the nano-ros checkout:
scripts/nuttx/stage-external-apps.sh "$NUTTX_APPS_DIR"
The staging script writes $NUTTX_APPS_DIR/external/Make.defs + Kconfig
and symlinks the integration shell in as external/nano-ros. Menuconfig
surfaces it under Application Configuration → External Modules.
Contributors (in-tree checkout):
just setup nuttxruns both of the above in one step against this repository’s own NuttX checkout.
The script stages the integration shell only. A NuttX workspace image (a
bringup with several components) is not built through the apps tree: add an
[image.<id>] with board = "qemu-armv7a-nuttx" to the bringup’s
system.toml and run nros build — see examples/workspaces/rust
[image.nuttx]. The script’s old --bringup <dir> / --image <id> flags
staged a per-bringup app that could not build an image, and now refuse
(issue 1883).
If you’d rather wire it yourself (e.g. into a vendored apps tree):
ln -s /path/to/nano-ros/integrations/nuttx \
$NUTTX_APPS_DIR/external/nano-ros
# then copy integrations/nuttx/external-Make.defs.in →
# $NUTTX_APPS_DIR/external/Make.defs and add a matching
# $NUTTX_APPS_DIR/external/Kconfig that `source`s the shell.
Configure
NuttX uses Kconfig as its single source of truth. After the symlink above:
cd $NUTTX_DIR
make menuconfig
# Navigate to:
# Application Configuration → External Modules → nano-ros
# [*] nano-ros ROS 2 client
# RMW backend → zenoh # zenoh | xrce | cyclonedds
# ROS 2 edition → humble
Networking Kconfig requirements live under
Networking Support — enable CONFIG_NET, CONFIG_NET_TCP,
CONFIG_NET_IPv4. For QEMU nsh_smp configurations the defaults
already include these.
Those Kconfig knobs govern what the integration shell compiles into
libnros_c.a — NuttX’s build owns that, the way Zephyr’s Kconfig owns its
lane. They are not where your application states its deployment, and the two
must name the same RMW.
Your application’s deployment — board, RMW, domain id, and an optional
locator — is declared in system.toml beside the package and baked at
compile time. Verbatim from the in-tree
examples/qemu-armv7a-nuttx/rust/talker/system.toml:
[system]
name = "nuttx_talker"
rmw = "zenoh"
domain_id = 0
[[component]]
pkg = "nuttx_talker"
class = "nuttx_rs_talker::Talker"
name = "talker"
[image.qemu-armv7a-nuttx]
board = "nuttx"
locator = "tcp/10.0.2.2:8200"
The C / C++ variants carry the same file, in the same schema — their
package.xml has no <nano_ros …/> tuple and their CMakeLists.txt names no
board. Nor does anything here name a target triple: armv7a-nuttx-eabihf is a
fact about the board, and it lives in the board descriptor. Run nros sync in
the application directory after editing this file; it writes the generated
message bindings and the build settings the board choice implies.
The guest network shape (eth0 10.0.2.15, Slirp gateway 10.0.2.2 — QEMU’s
own network, which the board’s [board.net] network = "qemu" names)
comes from the board; add a locator = "tcp/10.0.2.2:<port>"
field to [image.<id>] to dial a non-default router port. The prebuilt test fixtures
bake distinct per-language allocator ports so parallel suites don’t
collide on one router. Start the router (ROS’s rmw_zenohd) on the
port your app dials — it must listen on 0.0.0.0, not loopback,
because the guest reaches it through QEMU’s Slirp gateway:
ZENOH_CONFIG_OVERRIDE='listen/endpoints=["tcp/0.0.0.0:8200"];scouting/multicast/enabled=false' \
ros2 run rmw_zenoh_cpp rmw_zenohd
Build
NuttX owns the build verb here — make, from the configured kernel — the way
Zephyr owns west build. nros sync still runs first, in the application
directory, because the generated message bindings must exist before anything
compiles against them:
cd my_app
nros sync # bindings + settings from system.toml
cd $NUTTX_DIR
make # full kernel + apps build
The Cargo build of nano-ros’s Rust staticlibs runs as a sub-step of
the NuttX app build; libnros_c.a is linked at the final app
link stage.
For CMake-driven NuttX builds:
cmake -B build -DBOARD=qemu-armv7a \
-DCONFIG=nsh_smp
cmake --build build
Run
# 1. Start the router on the host (Slirp forwards 10.0.2.2:<port> →
# host). It must listen on 0.0.0.0 for the Slirp gateway; 8200 is
# the port the in-tree lanes use — match whatever `locator` your
# app bakes:
ZENOH_CONFIG_OVERRIDE='listen/endpoints=["tcp/0.0.0.0:8200"];scouting/multicast/enabled=false' \
ros2 run rmw_zenoh_cpp rmw_zenohd &
# 2. QEMU NuttX (ARM). For a NuttX-managed workspace where you've
# staged the integration shell + your own app, boot QEMU directly:
qemu-system-arm -M virt -cpu cortex-a7 -nographic \
-icount shift=auto \
-kernel $NUTTX_DIR/nuttx \
-netdev user,id=net0 \
-device virtio-net-device,netdev=net0
# `$NUTTX_DIR/nuttx` is the linked NuttX ELF produced by `make`
# at the NuttX source root — adjust if your workspace puts it
# elsewhere (e.g. an out-of-tree build dir).
# At the NSH prompt, run the example's PROGNAME — the
# `make`-driven build registers every nano-ros example as a
# built-in command via Application.mk's `-Dmain=<PROGNAME>_main`
# rename. Real PROGNAMEs (from
# `packages/testing/nros-tests/tests/nuttx_make_e2e.rs::EXPECTED_PROGNAMES`):
nsh> nuttx_c_talker # C talker
# nsh> nuttx_cpp_talker # C++ talker
# nsh> nuttx_c_listener # ...and listener / service / action variants
# Real hardware: standard NuttX flash flow (openocd / J-Link / etc.)
# 3. Verify from stock ROS 2 in another terminal:
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
# Talker publishes best-effort; stock `ros2 topic echo` defaults to
# RELIABLE, so the QoS-mismatched echo silently delivers nothing.
# Force best-effort to receive:
ros2 topic echo /chatter std_msgs/msg/String --qos-reliability best_effort
Contributors: the in-tree fixture/test lanes for nano-ros’s own QEMU examples are in Per-Platform Contributor Lanes.
Readiness signal. After typing the app’s NSH command (e.g.
nuttx_c_talker), expect Publishing: 'Hello World: 1' on the NSH
console within ~6 seconds — Rust + C + C++ talkers all start the
count at 1, matching the official ROS 2 demo talker. If no
Publishing: line:
- Confirm the app actually ran —
psshould show your task. - Confirm networking —
ifconfigshows a configured interface. With the virtio-net + Slirp wiring above,eth0comes up at10.0.2.15(the board’s default on QEMU’s network; an image’snetaddress overrides it). - Confirm
zenohdreachable; the image’slocator(or therclc_support_initarguments) must match the router’s listen port. - See Troubleshooting — First 10 Minutes.
Auto-configure glue (NSH built-in registration)
The make-driven build above relies on a host-side glue layer. In-tree
that is scripts/nuttx/stage-external-apps.sh plus kconfig-tweak. If you
wire the integration shell into your own NuttX tree by hand, reproduce the same
three steps:
- Bring the required symbols into your config. Stock NuttX
qemu-armv7a/nshships withoutCONFIG_NET=y, virtio-net, orTLS_NELEM. If you have no config of your own, copy the board defconfigpackages/boards/nros-board-nuttx-qemu/nuttx-config/arm/defconfigto$NUTTX_DIR/.configand runmake olddefconfig. If you already maintain a vendor defconfig, do NOT clobber it — merge the nano-ros requirements into it instead (CONFIG_NET=y, the virtio-net / netdev options,CONFIG_TLS_NELEM=8,CONFIG_LIBCXXTOOLCHAIN=y,CONFIG_ALLSYMS=n; diff the board defconfig againstnshfor the authoritative list) and re-runmake olddefconfig. - Stage the integration shell. Run
scripts/nuttx/stage-external-apps.sh "$NUTTX_APPS_DIR"to symlinkintegrations/nuttx/into$NUTTX_APPS_DIR/external/. Remove$NUTTX_APPS_DIR/Kconfigso NuttX’smkkconfig.shrediscovers the newapps/external/Kconfig. - Flip the nano-ros Kconfig knobs via
kconfig-tweak. The recipe enablesNROS,NROS_C_API,NROS_CPP_API, everyNROS_EXAMPLE_<EX>_<LANG>, setsTLS_NELEM=8, disablesLIBCXXNONE+ enablesLIBCXXTOOLCHAIN, and disablesALLSYMSfor the bootstrap link. Re-runmake olddefconfigso the newly-visible dependencies settle, thenmake.
kconfig-tweak ships in the kconfig-frontends package on most
distros. Without it the recipe skips ("NuttX skip: kconfig-tweak not on PATH"); install it before retrying.
GitHub source
- NuttX integration shell:
integrations/nuttx/ - Worked NuttX QEMU examples:
examples/qemu-armv7a-nuttx/rust/,examples/qemu-armv7a-nuttx/c/,examples/qemu-armv7a-nuttx/cpp/ - Kconfig schema:
integrations/nuttx/Kconfig
Next
- Multiple apps: each app declares its own
prognameinApplication Configuration → External Modules; they share the onelibnros_c.abuild via the external-app shell. - DDS on NuttX: bump the netbuffer Kconfig knobs (similar to the Zephyr DDS profile under Choosing an RMW Backend).
- Build nano-ros’s own NuttX QEMU tests without a NuttX-managed workspace: NuttX (contributor).