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

C API

The C user-facing surface lives in packages/api/nros-c/include/, at upstream’s paths (rclc/, rcl/, rcl_action/, …). rclc/detail/ is internal. Generated by Doxygen; always reflects the current main branch.

API reference

C API Doxygen — start here.

The umbrella header rclc/rclc.h pulls in every public surface a user application needs.

Where to start

Two-layer API. Every entity exposes two parallel C entry-point sets: rclc_*_init_default (Layer 1, caller polls) and rclc_executor_add_* (Layer 2, executor callback). Layer 1 grew an _init_polling + take_*_raw / send_*_raw family for inline-storage callers (no executor arena). Layer 2 pairs the same rclc_*_init_default with the executor-add call, which is where the callback is supplied (rclc’s binding site). See Two-Layer API for the verb discipline and per-layer trade-offs.

Event-driven wake callbacks. Each L1 polling entity supports nros_*_set_wake_callback(entity, &state, cb, ctx) for event- driven RTOS / embassy callers. State is a caller-owned rclc_wake_state_t POD; the backend fires cb(ctx) on rx / reply / request arrival. Per-channel for actions (set_{goal,cancel,get_result}_wake_callback on the server, set_{goal_response,cancel_response,result,feedback}_wake_callback on the client). See the Doxygen for signatures.

Async action client

<rclc/action.h> exposes two parallel entry-point families for sending goals, requesting results, and cancelling:

  • Async (non-blocking): rclc_action_send_goal_async(), rclc_action_get_result_async(), rclc_action_cancel_goal(), rclc_action_try_recv_feedback(). Return immediately. Replies are delivered through the callbacks registered with rclc_action_client_set_goal_response_callback(), rclc_action_client_set_feedback_callback(), and rclc_action_client_set_result_callback(). The callbacks fire from rclc_executor_spin_some() — your spin loop is the only place callbacks run.
  • Blocking convenience: rclc_action_send_goal() and rclc_action_get_result(). These call the async variant and then drive rclc_executor_spin_some() internally on a wall-clock budget until the reply lands (or 15 s / 30 s timeout). They take the rclc_executor_t* explicitly because the action client stores its handle as an opaque slot inside the executor (registered via rclc_executor_attach_action_client()). The same applies to rclc_action_client_wait_for_action_server() and rclc_action_client_action_server_is_ready(). Calling any of them from inside a dispatch callback returns RCLC_RET_REENTRANT.
  • Teardown: rcl_action_server_fini() / rcl_action_client_fini() reset only the struct. To take an action off an executor that keeps running, call rclc_executor_remove_action_server() / rclc_executor_remove_action_client() first: they drop the arena entry (the action leaves the graph), free its callback slot and arena bytes for reuse, and bring rclc_executor_get_handle_count() back down. A program that tears the executor down with rclc_executor_fini() needs neither — that drops every entry (issue 1609).
  • The same holds for every other entity the executor registers (issue 1631): rclc_executor_remove_subscription(), rclc_executor_remove_timer(), rclc_executor_remove_service() and rclc_executor_remove_client() — rclc’s rclc_executor_remove_* shape — each drop the arena entry (receive ring included), free the slot and bring handle_count back down. Call one before the entity’s fini when the executor keeps running; a timer or client that is fini’d without it is still dispatched into. A removed client returns to INITIALIZED and can be re-added. All return RCLC_RET_NOT_FOUND for an entity not registered on that executor and RCLC_RET_REENTRANT from inside a callback. A subscription whose callback also receives the sample’s attachment is registered with rclc_executor_add_subscription_with_info() into a rcl_subscription_t and removed the same way (issue 1668); the older direct-arg rclc_executor_add_subscription_raw_with_info() takes no subscription object, so its entry lives until rclc_executor_fini().

The equivalent service client helper rclc_client_call() does not take an explicit executor — the client stashes the executor pointer during rclc_executor_attach_client() and recovers it internally — but it follows the same async-then-spin contract.

Canonical pattern: declare the executor, register the client, then either call the blocking helper or drive rclc_executor_spin_some() yourself between async calls until your callback flags the result. See the example layouts in examples/linux/c/action-client/ and examples/mps2-an385-freertos/c/action-client/.

Phase 122.3 added a separate L1 polling family (rclc_polling_action_client_init + the _raw send/recv siblings) that stores ActionClientCore inline in the separate type rclc_polling_action_client_t (phase-487 W4) and skips the executor entirely; see the Doxygen for the full L1 verb list.

CMake integration

Out-of-tree projects link the NanoRos::NanoRos CMake target and generate C bindings with nano_ros_generate_interfaces(<pkg> LANGUAGE C) (the function defaults to LANGUAGE CPP; the full which-spelling table is in Message Generation) — covered in docs/reference/c-api-cmake.md.

Generating locally

doxygen packages/api/nros-c/Doxyfile
xdg-open target/doc/c-api/html/index.html

Internals (porting only)

The rmw-cffi and platform-cffi Doxygen sites are not part of the user API — they document the C vtables for porters writing a custom RMW backend or platform port. See RMW API and Platform API.