First Project
One command scaffolds a working multi-node workspace; two more build and run it. C++ and CMake, publishing on your machine, with nothing else running — no router, no daemon, no ROS 2 installation.
This assumes the two-step install is done (nros on
your PATH, nros setup linux --rmw cyclonedds run once).
Scaffold
nros new my_robot --workspace
cd my_robot
That wrote a complete workspace, not a hello-world stub:
my_robot/
├── .colcon_workspace # marks the workspace root; there is no root build file
└── src/
├── talker_pkg/ # a C++ node: publishes std_msgs/Int32 on /chatter
├── listener_pkg/ # a C++ node: subscribes to /chatter
└── demo_bringup/ # launch file + system.toml — no code
There is no entry package and no root CMakeLists.txt: the binary is
generated from the bringup’s system.toml when you build. What each
directory is for is the next chapter,
Anatomy of What You Just Built. You don’t need it to run.
Build
nros sync
nros build
nros sync resolves the launch file into the system model; nros build
generates the entry for each [image.*] in system.toml and drives
CMake under build/. The first build compiles nano-ros’s runtime into
the build tree (~3 minutes); rebuilds are seconds. Inside a nano-ros
checkout that checkout is used; anywhere else nros uses its own SDK
(a release’s share/nano-ros, or the checkout an in-tree nros was
built in), and --nano-ros-path <dir> names one explicitly. Nothing is
exported. nros sdk-root --explain prints the root it would use and
where that came from.
Run
./build/posix-cyclonedds-linux/cmake/linux_entry
Published: 0
Received: 0
Published: 1
Received: 1
Published: 2
Received: 2
One process, two nodes: the talker publishes std_msgs/Int32 on
/chatter every 500 ms and the listener prints each one it receives —
the interleaved Received: lines are your proof of delivery — exactly as a ROS 2
composition container would run two composable nodes. Ctrl-C stops it.
Why nothing else needed to be running: the default RMW is CycloneDDS, which discovers peers directly — there is no router or daemon in the picture. The zenoh backend (for talking to a ROS 2 system) and XRCE (for the smallest targets) are one page away: Choosing an RMW, and switching is a one-word edit — Switching RMW in Config.
Same thing in Rust
nros new my_rust_robot --workspace --lang rust
cd my_rust_robot
nros sync
nros build
RUST_LOG=info ./build/posix-cyclonedds/linux_entry/target/debug/linux_entry
[INFO talker_pkg] Publishing: 0
[INFO listener_pkg] I heard: 0
[INFO talker_pkg] Publishing: 1
(The Rust talker ticks at 1 s.)
Known issue (1295). On CycloneDDS — the scaffold’s default RMW — the generated Rust entry currently stops at startup with
NodeError::Transport(PublisherCreationFailed). The C++ path above is not affected.
Same workspace shape, same system.toml, same launch file, same two
commands — only the node implementations and the driver nros build
picks (cargo instead of CMake) differ. Neither workspace has a root build
file: the entry is generated per image under build/.
If it didn’t work
nros: RMW session open failedon a busy machine usually means the DDS port range on yourROS_DOMAIN_IDis contended — pick another:ROS_DOMAIN_ID=57 ./build/posix-cyclonedds-linux/cmake/linux_entry.- Everything else: Troubleshooting — First 10 Minutes.
Where to go next
- Understand the four directories — Anatomy.
- Talk to a real ROS 2 system — ROS 2 Interoperability.
- Put it on a board — How Integration Works.
- Add nodes, parameters, more targets — Project layout and the Multi-Node Projects section.