Prerequisites
- A C11 compiler (GCC, Clang, or arm-none-eabi-gcc)
- CMake >= 3.15
- Rust nightly toolchain (needed to build
libnros_c.a)
- zenohd router (build from submodule with
just build-zenohd, or install a matching version from zenoh releases)
1. Create a CMake Project
mkdir my-c-talker && cd my-c-talker
mkdir src
Create CMakeLists.txt (Phase 140 — add_subdirectory is the only consumption shape):
cmake_minimum_required(VERSION 3.22)
project(my_c_talker LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(NANO_ROS_PLATFORM posix)
set(NANO_ROS_RMW zenoh)
add_subdirectory(/path/to/nano-ros nano_ros)
add_executable(my_c_talker src/main.c)
target_link_libraries(my_c_talker PRIVATE NanoRos::NanoRos)
nros_platform_link_app(my_c_talker)
NanoRos::NanoRos provides include directories, the static library, and platform link libraries (pthread, dl, m) automatically via the root CMake's INTERFACE target. The nros-c static library is built in-tree per-project via Corrosion.
2. Code Generation
Use nano_ros_generate_interfaces() to generate C message types. This CMake function becomes available automatically once add_subdirectory(nano-ros) runs (the root CMake includes cmake/NanoRosGenerateInterfaces.cmake). The codegen tool (nros-codegen) is a Corrosion-built target reachable via $<TARGET_FILE:nros-codegen>.
Never hand-write CDR serialization or struct definitions — always use the code generator.
# Standard ROS 2 package (resolved from bundled interfaces)
nano_ros_generate_interfaces(std_msgs
"msg/Int32.msg"
SKIP_INSTALL
)
# Service definitions
nano_ros_generate_interfaces(example_interfaces
"srv/AddTwoInts.srv"
SKIP_INSTALL
)
# Custom project-local interfaces
nano_ros_generate_interfaces(${PROJECT_NAME}
"msg/Temperature.msg"
SKIP_INSTALL
)
Resolution order for each file:
${CMAKE_CURRENT_SOURCE_DIR}/<file> (project-local)
${AMENT_PREFIX_PATH}/share/<target>/<file> (ROS 2 install)
<install_prefix>/share/nano-ros/interfaces/<target>/<file> (bundled)
Generated type info structs (nros_message_type_t, nros_service_type_t, nros_action_type_t) are defined in <nros/types.h>.
3. RMW Backend Selection
The NANO_ROS_RMW CMake variable selects which RMW staticlib to build in-tree. Set it in CMakeLists.txt (BEFORE add_subdirectory) or override on the command line (-DNANO_ROS_RMW=xrce). Valid: zenoh (default), dds, xrce, cyclonedds.
4. Write a Publisher
Create src/main.c:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
typedef struct { int32_t data; } std_msgs_Int32;
.type_hash = "RIHS01_5bf22a2e7c2c8a4ca3f55054648f6d8c7c77cc0ae5695a1ff1df0b7ef8df1f09",
.serialized_size_max = 8,
};
static int32_t serialize_int32(const std_msgs_Int32 *msg,
uint8_t *buf, size_t len) {
if (len < 8) return -1;
buf[0] = 0x00; buf[1] = 0x01; buf[2] = 0x00; buf[3] = 0x00;
buf[4] = (uint8_t)(msg->data);
buf[5] = (uint8_t)(msg->data >> 8);
buf[6] = (uint8_t)(msg->data >> 16);
buf[7] = (uint8_t)(msg->data >> 24);
return 8;
}
static int g_count = 0;
static void timer_cb(
struct nros_timer_t *timer,
void *ctx) {
(void)timer; (void)ctx;
g_count++;
std_msgs_Int32 msg = { .data = g_count };
uint8_t buf[64];
int32_t len = serialize_int32(&msg, buf, sizeof(buf));
printf("Published: %d\n", g_count);
}
}
static volatile sig_atomic_t running = 1;
static void on_signal(int sig) {
(void)sig;
running = 0;
}
int main(void) {
const char *locator = getenv("NROS_LOCATOR");
if (!locator) locator = "tcp/127.0.0.1:7447";
g_pub = &pub;
nros_executor_add_timer(&exec, &timer);
g_exec = &exec;
signal(SIGINT, on_signal);
printf("Publishing on /chatter (Ctrl+C to stop)...\n");
return 0;
}
Callback executor (polling) API.
Library initialisation and support context.
ROS 2 node creation and management.
nros_ret_t nros_executor_init(struct nros_executor_t *executor, const struct nros_support_t *support, size_t max_handles)
nros_ret_t nros_executor_fini(struct nros_executor_t *executor)
nros_ret_t nros_publisher_fini(struct nros_publisher_t *publisher)
nros_ret_t nros_node_init(struct nros_node_t *node, const struct nros_support_t *support, const char *name, const char *namespace_)
nros_ret_t nros_publisher_init(struct nros_publisher_t *publisher, const struct nros_node_t *node, const struct nros_message_type_t *type_info, const char *topic_name)
nros_ret_t nros_support_fini(struct nros_support_t *support)
struct nros_node_t nros_node_get_zero_initialized(void)
struct nros_support_t nros_support_get_zero_initialized(void)
struct nros_timer_t nros_timer_get_zero_initialized(void)
nros_ret_t nros_node_fini(struct nros_node_t *node)
nros_ret_t nros_executor_stop(struct nros_executor_t *executor)
nros_ret_t nros_publish_raw(const struct nros_publisher_t *publisher, const uint8_t *data, size_t len)
nros_ret_t nros_support_init(struct nros_support_t *support, const char *locator, uint8_t domain_id)
struct nros_publisher_t nros_publisher_get_zero_initialized(void)
struct nros_executor_t nros_executor_get_zero_initialized(void)
#define NROS_RET_OK
Definition nros_generated.h:2405
nros_ret_t nros_timer_fini(struct nros_timer_t *timer)
nros_ret_t nros_timer_init(struct nros_timer_t *timer, const struct nros_support_t *support, uint64_t period_ns, nros_timer_callback_t callback, void *context)
nros_ret_t nros_executor_spin_period(struct nros_executor_t *executor, uint64_t period_ns)
Definition nros_generated.h:972
Definition nros_generated.h:2269
const char * type_name
Definition nros_generated.h:2273
Definition nros_generated.h:1143
Definition nros_generated.h:1742
Definition nros_generated.h:919
Definition nros_generated.h:1875
5. Build and Run
mkdir build && cd build
cmake ..
make
# Terminal 1: start zenoh router
zenohd --listen tcp/127.0.0.1:7447
# Terminal 2: run the talker
./my_c_talker
System Install
Phase 140 — there is no system install for nano-ros. Your user project owns whatever install layout it needs for its binaries.
Zephyr Integration
For Zephyr RTOS, enable the C API in prj.conf:
# Enable nros C API
CONFIG_NROS=y
CONFIG_NROS_C_API=y
# Select RMW backend
CONFIG_NROS_RMW_ZENOH=y # Zenoh (default)
# CONFIG_NROS_RMW_XRCE=y # Or XRCE-DDS
# CONFIG_NROS_XRCE_AGENT_ADDR="127.0.0.1"
# CONFIG_NROS_XRCE_AGENT_PORT=2018
Zenoh requires CONFIG_POSIX_API=y. See existing examples in examples/zephyr/c/ for complete prj.conf templates.