Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Application Workflow

nano-ros users usually want one path: prepare package, write node, build it, then deploy to target. Use Concepts only when workflow raises a technical question.

1. Prepare Workspace and Package

nano-ros is shipped source-only — vendor it next to (or inside) your workspace, then provision the board’s toolchain with nros setup:

# Build the in-tree nros CLI (Phase 218), then provision your board (+ RMW):
./scripts/bootstrap.sh base
source ./activate.sh        # OR: direnv allow / source ./activate.fish
nros setup linux --rmw zenoh        # or mps2-an385-freertos, zephyr, …

nros setup provides most components prebuilt per platform per RMW; a few without a seeded asset (the vendored submodules — zenoh-pico, mbedtls, RTOS sources) are built from source. nros setup <board> --rmw <rmw> --dry-run shows which is which on your host — see Installation.

It does not provide a zenoh router: that is ROS’s rmw_zenohd, so a multi-process zenoh example needs a ROS 2 install (or NROS_RMW_ZENOHD). --rmw cyclonedds needs no daemon at all.

For multi-package workspaces (Pattern A — recommended for POSIX + mixed C / C++ / Rust deployments), put nano-ros and your packages side-by-side under a shared src/:

~/ros2_ws/
├── src/
│   ├── nano-ros/                  # this repo
│   ├── my_robot_node/
│   │   ├── package.xml
│   │   ├── Cargo.toml             # Rust — dependencies; nothing about the board
│   │   ├── CMakeLists.txt         # C/C++ — targets; nothing about the board
│   │   └── src/
│   └── my_robot_bringup/
│       ├── system.toml            # board, RMW, components — the one place
│       └── launch/
└── build/ log/                    # generated; the workspace has no root build file

A package’s Cargo.toml and CMakeLists.txt carry language-toolchain facts only. Which board the program deploys to, which RMW it speaks, and which components it runs are stated once in system.toml — in the bringup package for a workspace, beside the package for a single-package project — and everything that choice implies is generated by nros sync (RFC-0098).

For third-party C/C++ projects without colcon (Pattern B), pull nano-ros in as a third_party/nano-ros/ git submodule and consume it via add_subdirectory(third_party/nano-ros nano_ros).

See Installation for both patterns. The per-language starter pages document the canonical package shape in their “Project layout” sections: Rust, C, C++. For two or more nodes, use the Multi-Node Projects group: start from the full project layout, then drill into Node, Bringup, and Entry packages.

2. Write Node Code

Choose API language first:

  • Rust — use nros, generated message crates, and Executor.
  • C — include rclc/rclc.h and generate interfaces with nros_find_interfaces(LANGUAGE C) in CMake (one of five codegen entry points — which to use).
  • C++ — include rclcpp/rclcpp.hpp and use typed wrappers.

Start with one of the Linux starters above, then adapt to your target via the Embedded Starters section.

3. Generate Messages

If you use custom .msg, .srv, or .action files, generate bindings inside the workspace/build tree. See Message Binding Generation.

4. Configure Target

The board and the RMW backend are a build-time choice — there is no RMW_IMPLEMENTATION runtime switch on embedded targets — and you state them in system.toml, the same way in every language:

[system]
name      = "my_robot"
rmw       = "zenoh"          # zenoh | cyclonedds | xrce
domain_id = 0

[[component]]
pkg   = "my_robot_node"
class = "my_robot_node::Talker"
name  = "talker"

[image.linux]               # one per program you want to build
board = "linux"

[image.mps2]                 # an embedded image carries its network identity too
board   = "qemu-mps2-an385"
locator = "tcp/10.0.2.2:10500"
net     = { eth0 = { network = "qemu", host = 10 } }

Switching board is editing [image.<id>] board and re-running nros sync. The platform, the rustc triple, the linker flags, the cross compiler and the pool budgets all follow from that one word — none of them is yours to write. The board crate follows too in a workspace, where the entry is generated, and in a single-package Rust project that depends on its generated <pkg>_nros_selection package (every in-tree single-package example on a supported board does); one that still names its board crate in [dependencies] must move the two together (issue 1305).

Supported pairs: posix / freertos / nuttx / threadx / zephyr / esp32 / baremetal × zenoh / xrce / cyclonedds. Not every cell is implemented — see the Coverage Matrix.

Runtime configuration (ROS_DOMAIN_ID, NROS_LOCATOR, …) works on POSIX; embedded targets bake the [image.*] values into the firmware, plus Kconfig on Zephyr. See Configuration.

5. Build, Test, Deploy

Two commands, everywhere:

nros sync          # generate what the board choice implies
nros build         # build every declared image; or: nros build <image-id>

nros sync writes the generated message crates, resolves the launch files into the system model, and writes each image’s build settings under build/. nros build generates that image’s entry, then hands off to the native tool — cargo, CMake or west — so compiler diagnostics are exactly what you would see running it yourself. nros build is where a ROS 2 user’s colcon build goes: same discovery of src/, same dependency order, same build/ and log/ beside the workspace.

Sync is a separate step, not an implicit one. Re-run it after editing system.toml or a .msg file; if you forget, nros build stops at its preflight and says so in one line — generated message bindings (this workspace has never been synced) / run: nros sync — rather than failing somewhere inside cargo.

A single Rust package works the same way:

cd examples/linux/rust/talker
nros sync
nros build

A single C/C++ package keeps its own CMake, and needs no sync — its message bindings are a CMake-time output:

cd examples/linux/c/talker
cmake -B build -Dnano_ros_ROOT="$(nros sdk-root)"   # raw cmake names the SDK root
cmake --build build

(nros build in the leaf runs the same pair into build/<image>/cmake, with the board’s toolchain file — issue 1296, resolved.)

Zephyr is the carve-out: west build is still its build verb, with nros sync run first — see Deployment Workflow.

# Contributors: per-platform multi-example fixture build, in-tree
# checkout only (these run `nros sync` for you):
just freertos build-fixtures
just zephyr  build-fixtures
just nuttx   build-fixtures

# Contributors: discover full-matrix commands for a platform:
just --group full-matrix --list zephyr

Which shape your target uses is Workflow by Platform and Language.

nros sync resolves the bringup’s system.toml (plus its launch files) into a SystemModel under the workspace build tree. SystemModels are build artifacts — never committed, and never named by an entry package; an entry names its input (nros::main!(launch = "bringup"), nano_ros_entry(BRINGUP … LAUNCH …)) and the build locates the artifact. nros model-path prints where a resolved one landed.

nros metadata / nros plan / nros check are the inspection path, not the build path: they produce and validate an nros-plan.json you can read with nros explain. You do not need them to build.

For target-specific deployment, go to the matching platform guide. Each guide covers toolchain setup, package layout, code example, build command, run/flash command, and deployment notes.

See Deployment Workflow.