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
rclc/rclc.h— convenience umbrella includerclc/init.h—rclc_support_init,rclc_support_finirclc/node.h—rclc_node_init_default,rcl_node_finirclc/executor.h— spin loop, executor lifecyclerclc/publisher.h/subscription.h— pub/subrclc/service.h/client.h— servicesrclc/action.h— actionsrclc_parameter/rclc_parameter.h— parameter servicesrclc_lifecycle/rclc_lifecycle.h— REP-2002 lifecycle states
Two-layer API. Every entity exposes two parallel C entry-point sets:
rclc_*_init_default(Layer 1, caller polls) andrclc_executor_add_*(Layer 2, executor callback). Layer 1 grew an_init_polling+take_*_raw/send_*_rawfamily for inline-storage callers (no executor arena). Layer 2 pairs the samerclc_*_init_defaultwith 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-ownedrclc_wake_state_tPOD; the backend firescb(ctx)on rx / reply / request arrival. Per-channel for actions (set_{goal,cancel,get_result}_wake_callbackon the server,set_{goal_response,cancel_response,result,feedback}_wake_callbackon the client). See the Doxygen for signatures.
Action server
<rclc/action_server.h> follows rclc’s model:
example_interfaces__action__Fibonacci_SendGoal_Request goal_requests[2];
rclc_action_server_init_default(&server, &node, &support,
ROSIDL_GET_ACTION_TYPE_SUPPORT(example_interfaces, action, Fibonacci), "/fibonacci");
rclc_executor_add_action_server(&executor, &server, 2, goal_requests,
sizeof(goal_requests[0]), goal_callback, cancel_callback, &ctx);
Each incoming goal takes one of the handles_number goal handles
(rclc_action_goal_handle_t, at most NROS_MAX_CONCURRENT_GOALS); its
request is deserialised into the matching element of the array, and
goal_callback returns RCL_RET_ACTION_GOAL_ACCEPTED or
RCL_RET_ACTION_GOAL_REJECTED. Do the work OUTSIDE the callback — between
rclc_executor_spin_some() calls, or from a timer in a C component — and
answer with rclc_action_publish_feedback(handle, &feedback_message) and
rclc_action_send_result(handle, GOAL_STATE_SUCCEEDED, &get_result_response).
Two bounds differ from rclc: a goal arriving with every handle in use is
rejected (rclc queues it), and an accepted goal executes at once, so a result
may be sent before the client asks for it. A component, which has no
rclc_support_t, passes NULL for support. See action_server_pkg in the C language
workspace, examples/workspaces/c; nros run action_bringup:linux_action_server
runs it on Linux.
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 withrclc_action_client_set_goal_response_callback(),rclc_action_client_set_feedback_callback(),rclc_action_client_set_result_callback(), and (for a cancel’s reply: its return code and how many goals are canceling)rclc_action_client_set_cancel_response_callback(). The callbacks fire fromrclc_executor_spin_some()— your spin loop is the only place callbacks run. - Blocking convenience:
rclc_action_send_goal()andrclc_action_get_result(). These call the async variant and then driverclc_executor_spin_some()internally on a wall-clock budget until the reply lands (or 15 s / 30 s timeout). They take therclc_executor_t*explicitly because the action client stores its handle as an opaque slot inside the executor (registered viarclc_executor_attach_action_client()). The same applies torclc_action_client_wait_for_action_server()andrclc_action_client_action_server_is_ready(). Calling any of them from inside a dispatch callback returnsRCLC_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, callrclc_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 bringrclc_executor_get_handle_count()back down. A program that tears the executor down withrclc_executor_fini()needs neither — that drops every entry. - The same holds for every other entity the executor registers:
rclc_executor_remove_subscription(),rclc_executor_remove_timer(),rclc_executor_remove_service()andrclc_executor_remove_client()— rclc’srclc_executor_remove_*shape — each drop the arena entry (receive ring included), free the slot and bringhandle_countback down. Call one before the entity’sfiniwhen the executor keeps running; a timer or client that isfini’d without it is still dispatched into. A removed client returns to INITIALIZED and can be re-added. All returnRCLC_RET_NOT_FOUNDfor an entity not registered on that executor andRCLC_RET_REENTRANTfrom inside a callback. A subscription whose callback also receives the sample’s attachment is registered withrclc_executor_add_subscription_with_info()into arcl_subscription_tand removed the same way; the older direct-argrclc_executor_add_subscription_raw_with_info()takes no subscription object, so its entry lives untilrclc_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
action_client_pkg in the C language workspace, examples/workspaces/c.
The same package runs on Linux and on FreeRTOS: its linux_action_client
and freertos_action_client image rows in action_bringup differ only in
the board.
A separate L1 polling family
(rclc_polling_action_client_init + the _raw send/recv siblings)
stores ActionClientCore inline in the separate type
rclc_polling_action_client_t 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.
Design background
- RFC-0122: behaviour bringups, single-node witnesses, and cadence-backed CI. The paired examples as image rows of behaviour bringups in the language workspaces.