|
nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
|
#include <executor.hpp>
Static Public Member Functions | |
| static Result | create (Executor &out, const char *locator, uint8_t domain_id, const char *session_name) |
| static Result | create (Executor &out, const char *locator=nullptr, uint8_t domain_id=0) |
| static Result | create_with_rmw (Executor &out, const char *rmw, const char *locator=nullptr, uint8_t domain_id=0, const char *session_name="nros_cpp") |
Explicit executor for managing ROS 2 entities and spinning.
Mirrors rclcpp::executors::SingleThreadedExecutor. Provides an explicit alternative to the global rclcpp::init()/rclcpp::spin_once() free functions.
The executor uses inline opaque storage — no heap allocation required.
Usage:
|
inline |
Default constructor — creates an uninitialized executor.
|
inline |
Destructor — shuts down if still active.
|
inline |
|
inline |
Register a callback to run AFTER this executor's entities are torn down — rclcpp's Context::add_on_shutdown_callback / rclcpp::on_shutdown.
The entities are gone by the time it runs, so anything that needs the wire belongs in [add_pre_shutdown_callback] instead. Use this for what survives the middleware: releasing a GPIO, stopping a peripheral clock, writing a log line.
|
inline |
Register a callback to run BEFORE this executor's entities are torn down — rclcpp's Context::add_pre_shutdown_callback. Issue 0790.
This is the load-bearing half and the one with no workaround. The callback runs while every publisher, subscription, service and client still works, so a node can publish a final state, answer a last request, park an actuator or release a bus. After teardown it cannot: on a device there is no OS to reclaim a claimed SPI bus, an armed DMA channel or an actuator holding its last commanded position.
The callbacks run from [shutdown] (and from the destructor, which shuts down if still active). They are a CLEAN-STOP facility: a watchdog reset, a hard fault or an abort does not come through here, and nothing backed by a static table could promise otherwise.
| callback | Function to invoke. Must not be null. |
| context | Opaque pointer handed back to callback. Must stay valid until the callback runs or is removed. |
| out | Receives the handle remove_pre_shutdown_callback takes. Optional. |
ErrorCode::Full when the fixed table is exhausted (NROS_EXECUTOR_MAX_SHUTDOWN_CBS, default 2), or ErrorCode::NotInitialized.
|
inline |
Ask a running [spin] to stop, WITHOUT tearing down the session — rclcpp::Executor::cancel. phase-417 W4.c.
ADOPT-BOUNDED (RFC-0089): cancel sets a flag the spin loop observes at the NEXT POLL BOUNDARY, so it returns BEFORE spinning has actually stopped; [is_spinning] is the observable that tells you when it has. The boundary is one poll_ms wide (the spin() argument), so the delay is a duration you chose rather than an unknown. rclcpp's cancel() makes the same "returns immediately" promise; naming the width is the part we add.
This is the verb a signal handler wants. [shutdown] is the other one and still tears everything down: after cancel() the executor is still ok(), still owns its publishers, subscriptions, services and clients, and can be spun again with no rediscovery. Collapsing the two — which is what a C++ node had to do before this existed — costs a full discovery round on every mode change.
Safe to call from another thread or a signal handler.
phase-381 W4 — how many publishers are visible on topic_name.
topic_name is a ROS name ("/chatter"). The count reflects what has been DISCOVERED, so it can be low right after startup and is never a proof of absence.
phase-381 W4 — how many subscribers are visible on topic_name. See [count_publishers] for the caveats.
|
inlinestatic |
Create and initialize an executor with an explicit session name.
session_name flows through to the XRCE-DDS RMW backend as the per-process key derivation seed. Two processes sharing one XRCE Agent MUST use distinct names; see rclcpp::init's named overload for the full discussion.
|
inlinestatic |
Create and initialize an executor.
Opens a middleware connection. This is the explicit alternative to rclcpp::init().
| out | Receives the initialized executor. |
| locator | Middleware locator (e.g., "tcp/127.0.0.1:7447"), or nullptr. |
| domain_id | ROS domain ID (0-232). |
|
inline |
Create a node on this executor.
| out | Receives the initialized node. |
| name | Node name (null-terminated). |
| ns | Node namespace (null-terminated); nullptr or "" = unset (inherit THIS executor's namespace), "/" = the root. See rclcpp::Node::create (issue 1473). |
|
inlinestatic |
Issue 1050 defect (3) — [create](Executor::create) against a named RMW backend.
rmw is the baked rung of RFC-0045's precedence model A: a hosted $NROS_RMW still wins, and nullptr / "" means "name none", which resolves only when exactly one backend is registered. See rclcpp::init_with_rmw for why naming one is sometimes the only way to get the backend the image declared.
|
inline |
phase-381 W4 — what services one named node CALLS, with the types.
|
inline |
phase-381 W4 — every node on the graph, with its namespace.
visit(name, ns, enclave) is called once per node; enclave is nullptr where the backend tracks none, which is what lets one call answer both rmw_get_node_names forms. Return false to stop early.
Takes a plain function pointer plus ctx rather than a std::function: this header is compiled -nostdinc++ against Zephyr's minimal libcpp in embedded builds, where <functional> does not exist (issue 0112). Every string is BORROWED for the duration of the call.
Reports what has been DISCOVERED and never blocks. The first call after startup legitimately sees a partial graph — the backend keeps a standing query fed by the spin loop — so poll rather than calling once and concluding. An empty enumeration means "nobody seen yet", never "nobody exists", and ErrorCode::Unsupported (a backend with no graph) stays distinct from it.
|
inline |
phase-381 W4 — what one named node PUBLISHES, with the types.
|
inline |
phase-381 W4 — the publishers on topic_name, one visit each.
The endpoint carries no QoS: the GRANTED profile is what would answer "why is nothing arriving", no backend can read one back yet, and reporting the remote's DECLARED profile would be a confident wrong answer.
|
inline |
phase-381 W4 — every service on the graph, with its types. As [get_topic_names_and_types], over servers and clients.
|
inline |
phase-381 W4 — what services one named node SERVES, with the types.
|
inline |
phase-381 W4 — what one named node SUBSCRIBES to, with the types.
subscription, not subscriber: the C++ surface takes rclcpp's vocabulary (create_subscription, Subscription<T>, get_subscriptions_info_by_topic). rclcpp has no *_by_node form for subscriptions at all, so the WORD comes from its vocabulary rather than from a method it lacks. The C surface says subscriber because rcl does, and the vtable slot because upstream rmw does.
|
inline |
phase-381 W4 — the subscriptions on topic_name, one visit each.
|
inline |
phase-381 W4 — every topic on the graph, with the types on it.
visit(name, types, types_count) is called once per distinct TOPIC: a topic carrying two types is one call with two entries, not two calls. types_count may legitimately be 0 on a partially discovered graph. Same discovery caveat as [get_node_names].
|
inline |
Get the raw executor storage (for advanced use).
Non-const: downstream FFI mutates executor state through this pointer (e.g. spin_once), so exposing it as const would be a lie. Callers that only need to observe the handle should do so through methods on Executor directly.
|
inline |
Is a spin loop running on this executor right now? — rclcpp::Executor::is_spinning. phase-417 W4.c.
A DIFFERENT question from [ok], which answers "is this executor
INITIALISED" — an executor that has never spun is ok() and not spinning, and one that has been cancelled mid-poll is both. Before this existed, ok() was the closest thing on offer and it answered neither half of the cancel envelope.
This is the second half of [cancel]'s contract: poll it to learn when the cancel has been ACTED on.
const because it is a pure read, and a predicate a caller cannot ask of a const Executor& is a predicate with an arbitrary restriction. The FFI slot takes void* because every OTHER nros_cpp_executor_* entry point mutates through it (see [handle]); this one does not, and the const_cast is confined to that mismatch.
|
inline |
Phase 104.C.9 — chainable Node-creation builder.
Mirrors Rust's Executor::node_builder(name).rmw(...).locator(...) .domain_id(...).namespace(...).sched(...).build(). Use this when binding a Node to a specific RMW backend, locator, domain, or SchedContext. Definition follows the full NodeBuilder class in node.hpp.
|
inline |
Check if the executor is initialized.
Phase 124.F.3 — session-level connectivity probe.
Wire-level round-trip ("is the peer / agent / router
reachable?") with timeout_ms budget. Returns Result::success() on reply, ErrorCode::Timeout on no reply, ErrorCode::Unsupported when the active backend can't probe. Mirrors micro-ROS's rmw_uros_ping_agent.
Useful for reconnect-on-link-loss patterns — call periodically and tear down / re-open the executor on timeout.
|
inline |
Remove a registered on-shutdown callback — rclcpp's Context::remove_on_shutdown_callback.
|
inline |
Remove a registered pre-shutdown callback — rclcpp's Context::remove_pre_shutdown_callback.
bool for rclcpp's reason: "it was not there" is an ordinary answer (the callback may already have run), not an error.
handle named a live callback.
|
inline |
Shut down the executor and close the middleware connection.
Spin until this executor is shut down (blocking) — rclcpp::Executor::spin.
Issue 0338 — this verb used to mean the OPPOSITE here: spin was the BOUNDED form and there was no way to say "spin forever" on an executor, while spin blocks until shutdown in rclcpp, in the C API (nros_executor_spin) and in Rust. A user porting rclcpp code wrote exec.spin() and it did not compile; reaching for spin(ms) instead silently returned early. The bounded form is now [spin_for].
Exit condition: [cancel] on THIS executor — typically from a signal handler or another thread, which is rclcpp's clean-shutdown idiom. Returns the first non-success spin_once result, or success after a clean cancel.
phase-417 W4.c — the loop MOVED. It used to be right here, spelled while (initialized_), which made [shutdown] the only way out; and shutdown() calls nros_cpp_fini, so a C++ node could not stop spinning without tearing down the middleware and paying a full discovery round to come back. Two things were wrong with that: the missing verb, and a polling loop living in the wrapper (RFC-0020 violation class 2, the same defect issue 0329 fixed for the BOUNDED form). Both are gone — nros_cpp_spin is the loop, Rust-side, and this is a forwarder.
A concurrent shutdown() is no longer the supported way to end a spin. It never really was: it frees the executor the loop is polling. Say cancel(), then shutdown() once spin() has returned.
| poll_ms | Individual spin_once timeout (default: 10ms). |
Spin for a bounded duration (blocking) — rclcpp's spin_some(max_duration).
Repeatedly calls spin_once() until duration_ms has elapsed.
| duration_ms | Total time to spin, in milliseconds. |
| poll_ms | Individual spin_once timeout (default: 10ms). |
REFUSED — spin_once() with no budget. phase-417 stage 3.
A member template with a defaulted parameter, not = delete: these headers are parsed as C++14, where a deleted function carries no message (= delete("reason") is C++26). The static_assert fires on INSTANTIATION, so the name stays declarable and only USING it fails, with the migration attached.
Drive transport I/O and dispatch callbacks.
Processes pending subscriptions, timers, services, and guard conditions. Call this periodically in your main loop.
The budget is REQUIRED — phase-417 stage 3. Upstream's spin_once(std::chrono::nanoseconds timeout = -1) blocks indefinitely by default; this one cannot, so there is no default to give that would not be a budget the caller never chose. It used to be = 10, and a ported argument-free spin_once() therefore returned after 10 ms where upstream was still waiting — silently. The no-argument form is now a compile error carrying NROS_RCLCPP_REFUSE_UNBOUNDED_SPIN; a negative timeout_ms is refused at the call, for the same reason under the same message.
ADOPT-BOUNDED envelope (RFC-0089 W3.d), and it is the part no signature carries: this dispatches EVERY ready arena entry, where upstream's spin_once executes the next ONE. RFC-0002 §3 computes the ready bitmap once per cycle, so "one item" is not a shape this executor has. A spin_once(0) here is therefore upstream's spin_some. That naming question is open and lives on the cpp:Executor::spin_some ledger row.
| timeout_ms | Maximum time to block waiting for I/O. 0 polls; negative is REFUSED (see above). |