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

First Node — C (Linux)

Build, run, and verify a single nano-ros publisher node on Linux from C. Uses CMake, the Zenoh backend, and add_subdirectory consumption.

Stuck? See Troubleshooting — First 10 Minutes for the common first-build errors.

Prereqs

Pick one path from a fresh checkout — just is NOT a prereq.

A. Bare machine (no Rust, no just, no cargo):

./scripts/bootstrap.sh base

Installs rustup, just, builds the in-tree nros CLI at packages/cli/target/release/nros, leaves the binary on PATH for this shell.

B. Already have cargo (most contributors):

cargo build --release --manifest-path packages/cli/Cargo.toml --bin nros
export PATH="$PWD/packages/cli/target/release:$PATH"

C. Tagged release, no Rust at all:

./scripts/install-nros-prebuilt.sh

Downloads the matching nros-<triple>.tar.gz from the GitHub release, sha256-verifies, installs to packages/cli/target/release/nros.

Every subsequent shell sources the workspace env via one of:

direnv allow                  # if you use direnv
source ./activate.sh          # bash / zsh
source ./activate.fish        # fish

Then provision the native host (installs the zenoh router zenohd into a shared store — no ROS 2 needed):

nros setup native --rmw zenoh

See Install + first build (Linux) for more.

Project layout

The talker is a standalone CMake project that pulls nano-ros via add_subdirectory(<repo-root>). Four files matter:

examples/native/c/talker/
├── CMakeLists.txt      # add_subdirectory + targets
├── package.xml         # ROS-style manifest (drives codegen tooling)
└── src/
    └── main.c          # ~100-line talker

An optional nros.toml sidecar can override the runtime locator / domain id (canonical schema described in Configuration). The shipped native C talker doesn’t carry one — locator + domain default to env vars.

The CMake preamble matches the canonical example shape in examples/native/c/talker/CMakeLists.txt — five set(...) lines plus the per-target link:

cmake_minimum_required(VERSION 3.22)
project(my_talker LANGUAGES C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# Pull nano-ros in. Adjust the relative path to your repo root.
# `NROS_RMW` is the user-facing cache var (overridable via
# `-DNROS_RMW=<rmw>`); the example forwards it to `NANO_ROS_RMW`,
# the var the nano-ros add_subdirectory reads.
set(NANO_ROS_PLATFORM posix)
set(NROS_RMW "zenoh" CACHE STRING
    "Active RMW (zenoh|xrce|cyclonedds) — selects the backend linked into my_talker.")
set(NANO_ROS_RMW "${NROS_RMW}")
add_subdirectory(<rel-path-to-nano-ros> nano_ros)

# Generate C bindings for std_msgs (transitively depends on
# builtin_interfaces; the dependency is declared explicitly).
nros_generate_interfaces(builtin_interfaces SKIP_INSTALL)
nros_generate_interfaces(std_msgs DEPENDENCIES builtin_interfaces
                                  SKIP_INSTALL)

add_executable(my_talker src/main.c)
target_link_libraries(my_talker PRIVATE
    std_msgs__nano_ros_c
    NanoRos::NanoRos)
nros_platform_link_app(my_talker)

nros_platform_link_app(my_talker) transitively wires the active RMW’s strong nros_app_register_backends() stub (calling nros_rmw_zenoh_register() for the zenoh build above). On POSIX you do not call nano_ros_link_rmw() explicitly — the platform module handles it.

The C entry point is int nros_app_main(int argc, char **argv) (not main); <nros/app_main.h> provides the OS-side main stub that wires signal handling and forwards to your function.

#include <nros/app_main.h>
#include <nros/init.h>
#include <nros/executor.h>
#include <nros/node.h>
#include <nros/publisher.h>
#include <nros/timer.h>
#include "std_msgs.h"

int nros_app_main(int argc, char** argv) {
    // 1. nros_init() — opens the zenoh session
    // 2. nros_executor_init() / nros_node_init()
    // 3. std_msgs_msg_int32_publisher_init() — typed publisher
    // 4. nros_timer_init() with a 1 Hz period + publish callback
    // 5. nros_executor_spin() until SIGINT
}

Configure

Three runtime knobs:

KnobDefaultEnv override
Zenoh locatortcp/127.0.0.1:7447NROS_LOCATOR
ROS domain ID0ROS_DOMAIN_ID
Node nametalkerhard-coded in source

nros.toml (optional) accepts the same [node] + [[transport]] schema as every other nano-ros tutorial (see Configuration); the C runtime reads it only when wired explicitly via nros_config_load() (see the example source).

Build

cd examples/native/c/talker
cmake -B build
cmake --build build

The first configure pulls and builds nano-ros’s Rust staticlibs (~3 minutes). Re-builds finish in seconds.

Run

Three terminals.

# 1. Start the zenoh router:
zenohd                               # installed by `nros setup native`

# 2. Run the talker:
cd examples/native/c/talker
./build/c_talker
# Expected output:
#   nros C Talker
#   =================
#   Published: 0
#   Published: 1
#   Published: 2
#   …

# 3. Verify from stock ROS 2:
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

Readiness signal. Within 5 seconds of ./build/c_talker, the binary should print Published: 0 on stdout — Rust + C + C++ all start the counter at 0 (Phase 208.D.9). If no Published: line in 30 seconds:

  1. Confirm zenohd is running (terminal 1). Without it, nros_support_init returns immediately with -4 (NROS_RET_NOT_FOUND — connection refused).
  2. Wrong locator / unreachable host → same -4 signature in stderr. Reachable host but mismatched port → talker hangs on session-open handshake rather than returning a code.
  3. See Troubleshooting — First 10 Minutes.

GitHub source

Canonical, copy-out: examples/native/c/talker/

Next