Zephyr (west module)
Single-node starter on Zephyr via the in-tree zephyr/
west module. nano-ros ships as a Zephyr module — west discovers it
from your workspace’s west.yml, drops in a prj.conf Kconfig
surface, and the standard west build / west flash flow takes
care of the rest.
Contributor path? Building nano-ros’s own Zephyr examples straight from this repository (no west-managed workspace) is covered at Zephyr (contributor). The page below is the canonical user entry.
Just want a working starter? Clone the
nano-ros-zephyr-examplerepo (west init -m …) — a manifest + zenoh talker app pinned to a tested Zephyr, with the same quickstart as below baked in. The steps here explain what it does so you can adapt it to your own workspace.
Prereqs. Run
nros setup zephyr --rmw <rmw>once (see Prerequisites below) — it provisions the Zephyr west workspace + Zephyr SDK bits, the emulator, and your RMW’s host daemon into the shared store. No hand-installed Zephyr SDK,west, or cross-toolchain, and no ROS 2 install required. Python: 3.10+ on Zephyr 3.7 LTS, but ≥ 3.12 on Zephyr 4.x (4.x’sfind_package(Python3)requires 3.12 — see the version matrix below). nano-ros’s imported west fragmentzephyr/west.ymlis a manifest-only file — it does NOT pull Zephyr itself; that has to be in your parent manifest (zephyrproject-rtos/zephyr).
Which Zephyr? — 3.7 LTS and 4.x both supported
nano-ros consumes as a module on both the 3.7 LTS line (supported to Jan 2027; the safety-island default) and the current 4.x rolling line. You build against whatever Zephyr your workspace already pins — nano-ros adapts. The two lines differ only in how you select an RMW and apply nano-ros’s Zephyr patches:
| Capability | Zephyr 3.7 LTS | Zephyr 4.x |
|---|---|---|
| Min Python | 3.10 | 3.12 (find_package(Python3)) |
| RMW selection | prj-<rmw>.conf overlay (-DCONF_FILE=...) | -S nros-<rmw> snippet (or the overlay) |
| nano-ros patches | applied during nros setup zephyr provisioning | applied during nros setup zephyr provisioning |
| Examples as samples / Twister | — | samples: + Twister (sample.nano-ros.*) |
| zenoh (native_sim) | ✅ build + e2e | ✅ build + e2e |
| cyclonedds (native_sim) | ✅ build + e2e | ✅ build · publish · receive · multicast-join (stable 2-node run pending a tracked k_mutex fix) |
| xrce | ✅ | build path WIP |
native_sim networking uses NSOS (host loopback) on both lines — no
TAP/bridge/root. The copy-out, snippet, patch-apply, and dual-line build
flows are exercised in CI (just zephyr ci-both, just zephyr check-copy-out).
Project layout
A Zephyr workspace using nano-ros looks like any other Zephyr project — the nano-ros module sits beside Zephyr, your application sits beside both:
my_zephyr_ws/
├── .west/
├── zephyr/ # cloned by `west init`
├── modules/
│ └── nano-ros/ # imported via west.yml
└── apps/
└── my_app/ # your application
├── CMakeLists.txt
├── prj.conf # Kconfig — selects nros + RMW
├── west.yml # (optional) per-app manifest
└── src/
└── main.c # nros user code
The application CMakeLists.txt is a stock Zephyr app — find_package(Zephyr)
target_sources. Noadd_subdirectory(<nano-ros>)is needed; the module shell handles it onceCONFIG_NROS=yflips on.
A minimal apps/my_app/ looks like this (mirrors
examples/zephyr/c/talker/
with the names stripped to the essentials):
# apps/my_app/CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)
target_sources(app PRIVATE src/main.c)
// apps/my_app/src/main.c
#include <nros/nros.h>
#include <nros/publisher.h>
#include <std_msgs/msg/int32.h>
int main(void) {
nros_init_args_t args = nros_init_args_default();
nros_context_t ctx;
nros_init(&args, &ctx);
nros_node_t node;
nros_create_node(&ctx, "my_app", &node);
nros_publisher_t pub;
nros_create_publisher(&node, std_msgs__msg__Int32_type_support(),
"/chatter", &pub);
std_msgs__msg__Int32 msg = { .data = 0 };
while (nros_ok(&ctx)) {
nros_publish(&pub, &msg);
msg.data++;
k_sleep(K_SECONDS(1));
}
return 0;
}
prj.conf is the one shown in Configure below.
Prerequisites
nros setup provisions the parts nano-ros owns — the RMW host daemon
(zenohd / Micro-XRCE-DDS agent) and the RMW transport submodules
(zenoh-pico + mbedtls for zenoh, the cyclonedds fork) — from a pinned index into
${NROS_HOME:-~/.nros}/sdk / the nano-ros checkout. It does not replace Zephyr’s own SDK,
and interface codegen still needs the ROS message definitions.
- Build the in-tree
nrosCLI (Phase 218, from the nano-ros checkout):source ./activate.sh # OR: direnv allow / source ./activate.fish just setup-cli # builds packages/cli/target/release/nros - Provision the RMW (daemon + transports) from the nano-ros checkout:
( cd modules/nano-ros && nros setup zephyr --rmw zenoh ) # zenohd + zenoh-pico + mbedtls ( cd modules/nano-ros && nros setup --source px4-rs ) # workspace cargo-load dep - Install the Zephyr SDK the standard Zephyr way (
nros setupdoes not provide it) and expose it —export ZEPHYR_SDK_INSTALL_DIR=/path/to/zephyr-sdk-<ver>(or register it via the SDK’ssetup.sh -c). - Message definitions for codegen. The interface codegen resolves a
package’s
msg/*.msgfromNROS_<PKG>_DIR(e.g.export NROS_STD_MSGS_DIR=/opt/ros/humble/share/std_msgs) — point it at a ROS install or any dir holding the.msgfiles.
The RMW host daemon must be running before an example connects (zenohd -l tcp/127.0.0.1:7456 for zenoh; the Micro-XRCE-DDS agent for xrce).
Configure
Add nano-ros (and a Zephyr pin — zephyr/west.yml is manifest-only;
it does not pull Zephyr itself) to your workspace west.yml:
manifest:
remotes:
- name: nano-ros
url-base: https://github.com/NEWSLabNTU
- name: zephyr
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyr
revision: v3.7.0 # or your chosen 3.7 LTS / 4.x SHA
path: zephyr
import: true # pulls Zephyr's own modules
- name: nano-ros
remote: nano-ros
revision: main # required — repo's default branch is
# `main`; west defaults to `master`
# otherwise and the fetch fails.
path: modules/nano-ros
import:
file: zephyr/west.yml # pulls nano-ros's transport deps
Then per-application prj.conf:
CONFIG_NROS=y
CONFIG_NROS_C_API=y
CONFIG_NROS_RMW_ZENOH=y # bool per RMW: NROS_RMW_{ZENOH,XRCE,CYCLONEDDS}
# (ROS edition is a build-time Cargo feature, NOT a Kconfig symbol — do not set
# CONFIG_NROS_ROS_EDITION; Zephyr aborts on the undefined symbol.)
# Required for any networked RMW on QEMU / native_sim:
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_TCP=y
CONFIG_NROS=y activates the shell, which maps Kconfig values to
NANO_ROS_* CMake cache vars and add_subdirectory()s the root
nano-ros CMake. NanoRos::NanoRos is linked into your app
library transparently.
Build
If your workspace is a fresh manifest-only dir (no .west/), initialise
it first so west knows which west.yml is the manifest:
cd my_zephyr_ws
west init -l . # one-time; points west at the
# local west.yml in cwd
west update # clones nano-ros + Zephyr into the workspace
(If you started from west init -m <remote>, both calls above are
already done — go straight to west build below.)
The transports + px4-rs come from the prerequisites step
(west update clones nano-ros but not its submodules). With the Zephyr SDK +
NROS_STD_MSGS_DIR exported (also prerequisites), build your app — nros on
PATH is auto-resolved as the codegen tool:
# native_sim (POSIX, no QEMU). The 3.7 line needs the NSOS line overlay; apply
# the NSOS patches first (see "apply nano-ros's patches" below).
overlay="$PWD/modules/nano-ros/cmake/zephyr/native-sim-line-3.7.conf"
west build -b native_sim/native/64 apps/my_app -- -DCONF_FILE="prj.conf;$overlay"
# A real board, e.g. Cortex-A9 (no native_sim overlay):
west build -b qemu_cortex_a9 apps/my_app
(Verified end-to-end on a fresh BYO west workspace: this builds to zephyr.exe
and runs to Published: 0 against zenohd -l tcp/127.0.0.1:7456. On the 4.4
line, find_package(Python3) requires ≥ 3.12 and you select the RMW with
-S nros-zenoh instead of the overlay.)
For a quick sanity check that the module is wired correctly:
west build -t menuconfig # confirm CONFIG_NROS=y is visible
Rust applications
Two things differ for a Rust app (C/C++ apps skip this section):
-
The Rust crate’s
[lib]must be namedrustapp(crate-type = ["staticlib"]) — azephyr-lang-rustcontract: itsrust_cargo_application()linkslibrustapp.a. The Cargo package name is free. -
Generate the interface crates + the
[patch.crates-io]wiring for YOUR layout — do not copy an in-repo example’s.cargo/config.toml: its../../../../packages/core/...paths are repo-relative and break in a copied-out app. From your app dir, run (after the Prerequisitesnros setup, which provides the codegen toolchain + message sources):nros generate-rust --generate-config \ --nano-ros-path "$PWD/../../modules/nano-ros/packages/core"This writes
generated/<pkg>/(the message crates) and a.cargo/config.tomlwhose[patch.crates-io]points thenros-*crates at yourmodules/nano-ros/packages/core/*and the generated interfaces atgenerated/*. Adjust--nano-ros-pathto your workspace’smodules/nano-ros/packages/core(the dir holdingnros-core,nros-node, …). The example apps’ committed.cargo/config.tomlis for the in-tree build only.
Run
# 1. Start zenohd on the host. The in-tree just recipe runs the
# pinned in-tree zenohd on the zephyr fixture port (7456) — the
# same port the example apps' `nros.toml` / Kconfig defaults pick
# up:
just zephyr zenohd &
# Or directly:
# zenohd --listen tcp/0.0.0.0:7456 --no-multicast-scouting
# 2. Boot the app. nano-ros's own in-tree zephyr talker has a
# matching just recipe for the canonical `native_sim` build path:
just zephyr talker
# For a BYO west workspace + your own app:
# QEMU Cortex-A9:
west build -t run
# native_sim:
./build/zephyr/zephyr.exe
# 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/Int32 --qos-reliability best_effort
The Zephyr boot banner runs first, then nano-ros prints
Published: 0, Published: 1, … as the talker fires — Rust + C +
C++ all start at 0 (Phase 208.D.9).
Readiness signal. On native_sim, expect Published: 0
within 5 seconds of just zephyr talker (or
./build/zephyr/zephyr.exe); on qemu_cortex_a9 expect it within
~15 seconds (QEMU cold boot + Zephyr init). If no Published: line
in 30 seconds:
- Confirm
CONFIG_NROS=ylit up viawest build -t menuconfig; without it the module shell neveradd_subdirectory’s nano-ros. - Check
CONFIG_NETWORKING=y,CONFIG_NET_IPV4=y,CONFIG_NET_TCP=yinprj.conf— Zephyr networking is opt-in. - Confirm
zenohdreachable from the simulated network (Slirp needs10.0.2.2:7456on QEMU; native_sim uses host loopback). - See Troubleshooting — First 10 Minutes.
Zephyr 4.x build gotchas.
Could NOT find Python3 ... required is at least "3.12"— 4.x needs Python ≥ 3.12. Provision one without sudo (e.g.uv venv --python 3.12 .venv312 && uv pip install west -r zephyr/scripts/requirements.txt) and run west through it (.venv312/bin/python -m west build ...), so the ROS descriptor-codegen subprocess still uses the system ROS Python.attempt to assign the value ... to the undefined symbol ETH_NATIVE_POSIX— that symbol was renamedETH_NATIVE_TAPin 4.x; the version-aware NSOS overlay handles it (just zephyr build-onedoes this automatically).
Zephyr 4.x: select the RMW with a snippet
On 4.x, nano-ros ships west snippets so you pick the RMW on the
build line instead of hand-writing the overlay:
west build -b native_sim/native/64 -S nros-cyclonedds apps/my_app
# ^^^^^^^^^^^^^^^^^^^ nros-zenoh | nros-cyclonedds | nros-xrce
The snippet (shipped via the module’s snippet_root) carries the
RMW-common Kconfig — equivalent to merging prj-<rmw>.conf. The
prj-<rmw>.conf / -DCONF_FILE path still works (and is the only option
on 3.7).
nano-ros patches into your workspace
nano-ros needs a few patches to Zephyr’s native_sim NSOS driver
(recvmsg, IPv4-multicast) so the host-loopback-based examples work.
Both supported lines (3.7 LTS and 4.x) take the same path —
nros setup zephyr reads zephyr/patches.yml and applies each patch
against the workspace’s Zephyr tree, sha256-checked. No extra step
on your side beyond the provisioning command (already run during
Prerequisites).
To re-apply (e.g. after a west update reset the tree):
nros setup zephyr --rmw zenoh
(Earlier nano-ros revisions documented a west patch apply flow on
4.x — that required a workspace-side west extension that doesn’t ship
with stock Zephyr. Phase 208.D.7 / E.9 unified both lines on the
provisioner. Cyclone-DDS-on-Zephyr patches stay baked into the pinned
cyclonedds submodule, not delivered through patches.yml.)
Copy out an example as your starting point
The examples/zephyr/<lang>/<role>/ dirs are copy-out clean — copy one
into your own app tree and it builds against the nano-ros module with no
reference back into the nano-ros repo:
cp -r modules/nano-ros/examples/zephyr/c/talker apps/my_app
# cyclonedds examples need the host idlc + the ROS message dirs:
export NROS_STD_MSGS_DIR=/opt/ros/humble/share/std_msgs # PKG_DIR contract
west build -b native_sim/native/64 -S nros-zenoh apps/my_app
Cyclone idlc and the descriptor-gen scripts are located via the module’s
exported cache vars (NROS_CYCLONE_IDLC, NROS_CYCLONE_SCRIPTS_DIR,
NROS_CYCLONE_CMAKE_DIR); message-package dirs come from NROS_<PKG>_DIR
env (defaulting to /opt/ros/humble/share/<pkg>). No /opt/ros or
repo-relative paths are baked into the example.
See the zephyr/
module dir + its Kconfig
for the canonical in-repo surface.
GitHub source
- Zephyr module shell:
zephyr/ - Worked examples:
examples/zephyr/rust/,examples/zephyr/c/,examples/zephyr/cpp/ - Module manifest:
zephyr/module.yml - Kconfig surface (canonical post-Phase-208.D.7 fold — every
CONFIG_NROS*symbol the doc cites lives here):zephyr/Kconfig - Patches applied by
nros setup zephyr(thewest patchflow on this page was retired in Phase 208.E.9):zephyr/patches.yml
Next
- Pick a real board (Nordic, NXP, STM32, …): swap
-b <board>and add a board-specific overlay to yourprj.conf. - Cyclone DDS on Cortex-A/R: see the DDS section of Choosing an RMW Backend for the required Kconfig deltas.
- Build nano-ros’s own Zephyr examples without west: Zephyr (contributor).