nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
node.hpp
Go to the documentation of this file.
1// nros-cpp: Node class
2// Freestanding C++ — no exceptions, no STL required
3
10#ifndef NROS_RCLCPP_NODE_HPP
11#define NROS_RCLCPP_NODE_HPP
12
13#include <cstdint>
14#include <cstddef>
15#include <type_traits> // Phase 189.M3.3.e — SFINAE on the callback-style create_service
16#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
17#include <cstdlib> // getenv — Phase 123.B.3 env-aware init
18#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
19// <stdio.h> + unqualified `::fprintf` (issue 0942 / phase-472 W5): `<cstdio>` need not put
20// the C names in `std` on a freestanding libstdc++, and `std::fprintf` then fails.
21#include <stdio.h>
22#endif
23#endif
24
25// `<map>` is `NROS_CPP_STD` ONLY, and the `|| __STDC_HOSTED__` arm above is
26// exactly what it must not have (issue 1431).
27//
28// phase-417 W4.a put it in that block beside `<cstdlib>`/`<cstdio>`, which is
29// safe for THOSE: Zephyr's minimal libcpp ships `cstdlib` and `cstdio`, so the
30// hosted arm can be satisfied. It ships no `map`. And `__STDC_HOSTED__` is 1 on
31// Zephyr — the compiler is hosted, the include path is not — so the arm fired
32// and the header failed to open. MEASURED, `g++ -nostdinc++ -I zephyr/cxx-compat`:
33//
34// node.hpp:18:10: fatal error: map: No such file or directory
35//
36// That is issue 0112's half of the both-probes rule, which CLAUDE.md records:
37// `__STDC_HOSTED__` alone is a claim about the COMPILER and the question is
38// about the INCLUDE PATH.
39//
40// The fix is not `__has_include(<map>)`. It is to guard the include with the
41// same condition as the only code that needs it. Both consumers —
42// `Node::declare_parameters` below and `lifecycle.hpp`'s twin, which includes
43// no `<map>` of its own and relies on this one — sit inside
44// `NROS_CPP_HAS_STD_STRING` (phase-476 W3), which implies `NROS_CPP_STD`
45// because every capability probe is `#if defined(NROS_CPP_STD)`. So this is the
46// narrowest guard that still covers every use, and it restores the property
47// the block below states as the rule: a hosted STL include lives inside an
48// `NROS_CPP_STD` region, never one an `||` arm can reach from a freestanding
49// board (issue 0332).
50#if defined(NROS_CPP_STD) // hosted-family: container-interop
51#include <map> // phase-417 W4.a — rclcpp::Node::declare_parameters<T>'s argument
52#endif
53
54// Phase 118.D: ffi.h MUST come before qos.hpp so qos.hpp's
55// `#ifndef NROS_CPP_FFI_H` guard sees the canonical types and skips
56// its local redefinitions.
57#include "rclcpp/detail/nros_cpp_ffi.h"
58
59#include "rclcpp/detail/traits.hpp"
60#include "rclcpp/handle.hpp" // phase-482 W2 — `Node::SharedPtr`
61#include "rclcpp/result.hpp"
62#include "rclcpp/owned.hpp" // phase-456 W5 — the return type of the poll `create_service`
63#include "rclcpp/detail/hosted_block.hpp"
64#include "rclcpp/detail/nros_cpp_config_generated.h"
65// phase-482 W3 — `rclcpp::init` below reads `NROS_ENTRY_LOCATOR` /
66// `NROS_ENTRY_DOMAIN_ID`, so the ladder that derives them from Kconfig must be
67// visible in EVERY TU that calls it, not only in one that also includes
68// `<rclcpp/detail/main.hpp>`. A ported `main.cpp` includes `<rclcpp/rclcpp.hpp>` and
69// nothing else: on a Zephyr guest it compiled with the macro undefined, passed
70// a null locator, and the backend's default dialled the GUEST's own loopback
71// (`ConnectionFailed`, then `create_publisher`'s abort). Where nothing bakes a
72// locator the header yields `""`, which `nros_cpp_init` reads as absent — the
73// same answer a null gave.
74#include "rclc/detail/entry_config.h"
75#include "rclcpp/qos.hpp"
76// Phase 189.M3.1 — rclcpp-style named-options structs
77// (`SubscriptionOptions` / `PublisherOptions`) used by the 4-arg
78// `create_subscription` / `create_publisher` overloads below.
79#include "rclcpp/options.hpp"
80// Phase 84.G8: heavy entity headers (publisher / subscription / service /
81// client / action_server / action_client) are no longer pulled in here.
82// Each entity header provides the out-of-line definition of its
83// corresponding `Node::create_X<T>()` template and includes `node.hpp`
84// itself. Consumers that #include `rclcpp/rclcpp.hpp` (the umbrella) still
85// get every entity + every create method via that path; consumers that
86// only want lightweight Node access can include this header directly
87// and pay for only the light entities (timer, guard_condition,
88// executor) below.
89#include "rclcpp/timer.hpp"
90// Issue 0789 — `Node::get_clock()` / `Node::now()`. `clock.hpp` pulls in
91// `time.hpp` and `duration.hpp`, so a node that stamps a header needs no
92// further include.
93#include "rclcpp/clock.hpp"
94// phase-427 W4 — `ComponentNode` is merged into `Node`, so the two things its
95// header pulled in for the merged members come here with them:
96// * `log.hpp` — `NROS_ERROR`, the OVERRIDABLE sink `report_component_failure`
97// routes through so a freestanding image can be given a boot diagnostic
98// (issue 1015).
99// * `declared_qos.hpp` — `rclcpp::declared_depth` + `DECLARED_DEPTH_UNDECLARED`,
100// read by `Node::check_declared_depth` (phase-403 step 2).
101// Neither includes a nano-ros header of its own, so neither can close a cycle
102// back onto `node.hpp`. `component.hpp` — which DOES include this file — is why
103// the member-pointer `create_subscription_in` / `create_subscription_in_group`
104// family is DECLARED here and DEFINED there.
105#include "rclcpp/detail/declared_qos.hpp"
106#include "rclcpp/detail/declared_params.hpp" // phase-446 W6 -- each node's DECLARED parameters
107// phase-417 W4.a -- `rclcpp::ParameterType`, `ParameterDescriptor`,
108// `ParameterWrite` and the on-set callback types, which the parameter methods
109// below name. Freestanding (`<cstdint>`, `const char*`, the FFI header) and it
110// includes nothing that includes this file. `nros.hpp` included it already;
111// the declarations here are why it moves onto node.hpp's own include list.
112#include "rclcpp/node_parameters.hpp"
113#include "rclcpp/logging.hpp"
115#include "rclcpp/executor.hpp"
116// phase-417 stage 2b (RFC-0089) — `rclcpp::TopicEndpointInfo` and the visitor
117// typedef used by the graph forwarders below.
118#include "rclcpp/graph.hpp"
119// Phase 273 (RFC-0047) — callback-group token (value type, no heap).
120#include "rclcpp/callback_group.hpp"
121// phase-427 W1 declared the parameter facade here; phase-426 W4 took away the
122// store it read. The hosted block no longer holds a `ParameterServer` — the
123// facade forwards to the EXECUTOR's store — so this header needs
124// `parameter.hpp` for nothing of its own. It is kept because what that header
125// still holds (`rclcpp::Seq<T, N>`) is a public surface a consumer reaches
126// through the node header, and `parameter.hpp` depends on `result.hpp` and
127// `<nros/parameter.h>` only,
128// so there is no cycle.
129#include "rclcpp/parameter.hpp"
130
131// `<chrono>` is the one capability whose predicate is not the uniform one —
132// it needs `<ratio>` as a prerequisite too. The three measured corrections that
133// established that live with the predicate, at the one detection site.
134#include "rclcpp/detail/std_detect.hpp"
135
136// phase-476 W3 — the hosted-node macro (`NROS_CPP_` + `NODE_HOSTED`) IS GONE. It was the
137// conjunction of all four capability probes, wrapped around every hosted-shape member at once. Each
138// member now names the ONE capability its signature actually uses: the `std::string`-keyed
139// forwarders `NROS_CPP_HAS_STD_STRING`, the `std::shared_ptr` spellings `NROS_CPP_HAS_SHARED_PTR`,
140// and nothing needs
141// `<vector>` or `<functional>` any more. The rule it served is unchanged: a
142// probe gates METHODS, never a member.
143
144// `NROS_RCLCPP_MAX_PARAMS` IS GONE (phase-426 W4). It sized an inline
145// `nros::ParameterServer` on the merged node's hosted block — a SECOND
146// parameter store, node-local, which `ros2 param get` could not see and a
147// sibling node did not share. The member is deleted and the facade forwards to
148// the executor's store (`rclcpp/node_parameters.hpp`), so there is no per-node
149// arena left for a number to bound.
150//
151// The image's one parameter arena is now `NROS_MAX_PARAMETERS` on
152// `nros-params` (default 32, a build knob), and it is shared across every node
153// on the executor rather than multiplied by the node count. Do not reintroduce
154// a per-facade capacity: a second number is how a second store starts.
155
156#ifdef NROS_RMW_CYCLONEDDS
157extern "C" int32_t nros_rmw_cyclonedds_register(void);
158#endif
159#if defined(NROS_RMW_XRCE) || defined(NROS_RMW_XRCE_CFFI)
160extern "C" int32_t nros_rmw_xrce_register(void);
161#endif
162#ifdef NROS_RMW_ZENOH_CFFI
163extern "C" int32_t nros_rmw_zenoh_register(void);
164#endif
165#ifdef NROS_RMW_UORB
166extern "C" int32_t nros_rmw_uorb_register(void);
167#endif
168
169// Issue #229 pin (cross-space, C++-FFI half): ErrorCode must stay
170// value-identical to the NROS_CPP_RET_* codes (nros_cpp_ffi.h, included
171// above) that every shim below feeds into Result().
172static_assert(NROS_CPP_RET_NOT_FOUND == -4 && NROS_CPP_RET_ALREADY_EXISTS == -5 &&
173 NROS_CPP_RET_FULL == -6 && NROS_CPP_RET_NOT_INIT == -7 &&
174 NROS_CPP_RET_TRY_AGAIN == -14 && NROS_CPP_RET_REENTRANT == -15 &&
175 NROS_CPP_RET_UNSUPPORTED == -16,
176 "nros_cpp_ret_t diverged from the shared numbering (issue #229)");
177
178// Phase 84.G8: forward declarations of the heavy entity class
179// templates. Full definitions live in the corresponding `*.hpp`,
180// which also provide the out-of-line `Node::create_X<>` template
181// bodies — consumers only pay for the entities they #include.
182//
183// phase-428 (RFC-0089): six of them are DECLARED in the upstream namespace,
184// because that is where their definition now lives, and this header names them
185// that way. (Each also had an `nros::` migration alias, declared beside its
186// type in that type's own header, until phase-483 W1 deleted them.)
187namespace rclcpp {
188// phase-427 W7 — the node itself, defined further down in this header. Forward
189// declared up here because the `rclcpp::` free functions BELOW take it by
190// reference and are named by `Node`'s own qualified friend declarations, which
191// require a prior declaration of the function AND of its parameter type.
192class Node;
193template <typename M> class Publisher;
194template <typename M> class Subscription;
195template <typename S> class Service;
196template <typename S> class Client;
197} // namespace rclcpp
198
199namespace rclcpp_action {
200template <typename A> class Server;
201template <typename A> class Client;
202} // namespace rclcpp_action
203
204namespace rclcpp {
205
206// Phase 122.3.d.b — L1 polling-mode action wrappers.
207template <typename A> class PollingActionServer;
208template <typename A> class PollingActionClient;
209// phase-456 W2b — the two poll-side subscribers (`polling_subscription.hpp`).
210template <typename M> class PollSubscription;
211template <typename M> class PollingSubscription;
212// phase-456 W5 — the poll-side service server (`polling_service.hpp`).
213template <typename S> class PollService;
214// phase-456 W9 — the poll-side (future-style) service client
215// (`polling_client.hpp`), and the dispatch client's handle
216// (`client_handle.hpp`).
217template <typename S> class PollClient;
218template <typename S> class ClientHandle;
219
260 void* executor;
264 const char* launch_name;
266 const char* launch_namespace;
280
289 constexpr NodeHandle(void* exec, const char* name, const char* ns)
299 constexpr bool valid() const { return executor != nullptr; }
300
303 constexpr const char* resolve_name(const char* fallback) const {
304 return (launch_name != nullptr && launch_name[0] != '\0') ? launch_name : fallback;
305 }
307 constexpr const char* resolve_namespace(const char* fallback) const {
308 return (launch_namespace != nullptr && launch_namespace[0] != '\0') ? launch_namespace
309 : fallback;
310 }
311};
312
320// `constexpr` at namespace scope is implicitly const, so it already has internal
321// linkage per TU — `inline` bought nothing and cost C++14 compatibility, which
322// nano-ros otherwise keeps (see `just check cpp`'s freestanding c++14 syntax
323// gate). PX4 builds every module with -std=gnu++14 -Werror, so an inline
324// variable here made <rclcpp/rclcpp.hpp> uncompilable in a PX4 module (phase-325 W2).
326
327namespace detail {
328
339inline void report_component_failure(const char* node_name, const char* what, int32_t code) {
340 // Route through the OVERRIDABLE sink first, so a freestanding image can
341 // see this at all. Issue 1015's bisect ran aground here: on Zephyr both
342 // arms below compile to nothing, so a component that failed to register
343 // halted SILENTLY -- the board printed only the Zephyr banner, identical
344 // to a healthy boot, and "is it broken?" had no answer on the target where
345 // it mattered. Issue 0589 fixed exactly this class for the Rust side; the
346 // C++ side still had it.
347 NROS_ERROR("node \"%s\": FAILED at %s (code=%d)", (node_name != nullptr) ? node_name : "?",
348 (what != nullptr) ? what : "?", static_cast<int>(code));
349#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
350 ::fprintf(stderr, "[nros] FATAL: node \"%s\" failed to construct at %s (code=%d)\n",
351 (node_name != nullptr) ? node_name : "?", (what != nullptr) ? what : "?",
352 static_cast<int>(code));
353#endif
354}
355
364
368inline void report_declared_depth_mismatch(const char* node_name, const char* topic, int declared,
369 int passed) {
370 NROS_ERROR("node \"%s\": topic \"%s\" DECLARED depth %d but the QoS "
371 "passed states %d. Depth multiplies the executor arena, so they must agree.",
372 (node_name != nullptr) ? node_name : "?", (topic != nullptr) ? topic : "?", declared,
373 passed);
374#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
375 ::fprintf(stderr,
376 "[nros] FATAL: node \"%s\": topic \"%s\" was DECLARED depth %d in "
377 "the contract sidecar (<stem>.contract.yaml) but the QoS passed to "
378 "create_subscription_in states depth %d. Depth multiplies the executor "
379 "arena (cost is (depth+1)*bound per subscription), so the two must agree. "
380 "Fix the contract row or the call site.\n",
381 (node_name != nullptr) ? node_name : "?", (topic != nullptr) ? topic : "?", declared,
382 passed);
383#else
385 (void)topic;
386 (void)declared;
387 (void)passed;
388#endif
389}
390
397inline void report_callback_group_mismatch(const char* node_name, const char* how, const char* name,
398 int len, const char* list) {
399 const char* node = (node_name != nullptr) ? node_name : "?";
400 const char* group = (name != nullptr) ? name : "?";
401 const char* declared = (list != nullptr) ? list : "";
402 // "created": the code made a group the keyword never declared.
403 // "declared": the keyword declared a group the constructor never made.
404 const bool created = (how != nullptr && how[0] == 'c');
405 if (created) {
406 NROS_ERROR("node \"%s\": created callback group \"%.*s\", not in CALLBACK_GROUPS \"%s\"",
407 node, len, group, declared);
408 } else {
409 NROS_ERROR("node \"%s\": CALLBACK_GROUPS \"%s\" declares \"%.*s\", never created", node,
410 declared, len, group);
411 }
412#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
413 ::fprintf(stderr,
414 created ? "[nros] FATAL: node \"%s\" created callback group \"%.*s\", which its "
415 "registration's CALLBACK_GROUPS \"%s\" does not declare.\n"
416 : "[nros] FATAL: node \"%s\" never created callback group \"%.*s\", which "
417 "its registration's CALLBACK_GROUPS \"%s\" declares.\n",
418 node, len, group, declared);
419 ::fprintf(stderr,
420 " The tier derivation ranks the declared list and the executor binds what the "
421 "code creates, so the two must name the same groups (ONE declared group and none "
422 "created is node-level placement, and is allowed). Fix the "
423 "nano_ros_node_register(... CALLBACK_GROUPS ...) line or the create_callback_group "
424 "call.\n");
425#endif
426}
427
434
438inline void report_declared_policy_mismatch(const char* node_name, const char* topic,
439 const char* policy, const char* declared,
440 const char* passed) {
441 NROS_ERROR("node \"%s\": topic \"%s\" DECLARED %s %s but the QoS passed states %s. An "
442 "incompatible-QoS match never delivers, so they must agree.",
443 (node_name != nullptr) ? node_name : "?", (topic != nullptr) ? topic : "?", policy,
445#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
446 ::fprintf(stderr,
447 "[nros] FATAL: node \"%s\": topic \"%s\" was DECLARED %s %s in the contract "
448 "sidecar (<stem>.contract.yaml) but the QoS passed to create_subscription_in "
449 "states %s. Fix the contract row or the call site.\n",
450 (node_name != nullptr) ? node_name : "?", (topic != nullptr) ? topic : "?", policy,
452#else
454 (void)topic;
455 (void)policy;
456 (void)declared;
457 (void)passed;
458#endif
459}
460
462inline const char* declared_reliability_spelling(int ordinal) {
463 return ordinal == static_cast<int>(::rclcpp::ReliabilityPolicy::Reliable) ? "reliable"
465 ? "best_effort"
466 : "system_default";
467}
468
470inline const char* declared_durability_spelling(int ordinal) {
471 return ordinal == static_cast<int>(::rclcpp::DurabilityPolicy::Volatile) ? "volatile"
473 ? "transient_local"
474 : "system_default";
475}
476
480
485inline void report_declared_param_mismatch(const char* fqn, const char* param, const char* contract,
486 int declared, int passed) {
487 const char* f = (fqn != nullptr) ? fqn : "?";
488 const char* p = (param != nullptr) ? param : "?";
489 const char* c = (contract != nullptr) ? contract : "?";
491 NROS_ERROR("node \"%s\": parameter \"%s\" is not declared in its contract (%s).", f, p, c);
492 } else {
493 NROS_ERROR("node \"%s\": parameter \"%s\" is declared %s in its contract (%s) but the "
494 "code declares it as %s.",
497 }
498#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
500 ::fprintf(stderr,
501 "[nros] FATAL: node \"%s\": parameter \"%s\" is not declared in its "
502 "contract (%s, `params:`). The parameter store is sized from that "
503 "declaration, so a parameter it does not name has no slot. Declare it "
504 "there with its type, or stop declaring it in the code.\n",
505 f, p, c);
506 } else {
507 ::fprintf(stderr,
508 "[nros] FATAL: node \"%s\": parameter \"%s\" is declared `%s` in its "
509 "contract (%s) but the code declares it as `%s`. Fix the contract or "
510 "the code so they state one type.\n",
513 }
514#endif
515}
516
517// phase-476 W3 — `detail::NodeHosted` IS GONE. It was the lazily allocated
518// block behind `Node::hosted_`, and by W3 it held one member:
519// `rclcpp::NodeOptions`, a type with no data members (every accessor is a
520// REFUSE-LOUD template, `options.hpp`). Allocating a heap block to store an
521// empty object bought nothing, so `get_node_options()` answers a constant and
522// the block, the member and the `operator new` go.
523
524} // namespace detail
525
536inline Result init_in(const char* locator = nullptr, uint8_t domain_id = 0);
537
557inline Result init_in(const char* locator, uint8_t domain_id, const char* session_name);
558
577inline Result init_in(const char* locator, uint8_t domain_id, const char* session_name,
578 const char* node_namespace);
579
597inline Result init_with_rmw(const char* rmw, const char* locator = nullptr, uint8_t domain_id = 0,
598 const char* session_name = "node");
599
622inline Result init_in(int argc, char const* const* argv, const char* session_name = nullptr);
623
627inline Result shutdown_in();
628
629// phase-427 W7 — the remaining `rclcpp::` free functions `Node` befriends. A
630// QUALIFIED friend declaration (`friend Result(::rclcpp::ok)();`) names an
631// existing entity; it cannot introduce one. These four live in `nros.hpp`,
632// which includes this header, so without these declarations the friendship
633// would be unresolvable and the private `executor_handle_` unreachable from the
634// functions whose whole job is to set it. Default arguments stay on the
635// definitions — a default may be added by a later declaration, never repeated.
636inline bool ok();
637inline Result create_node(::rclcpp::Node& out, const char* name, const char* ns);
638inline Result create_node_on(::rclcpp::Node& out, void* executor_handle, const char* name,
639 const char* ns);
640inline Result spin_once(int32_t timeout_ms);
641inline Result spin_in();
642inline Result spin_in(uint32_t duration_ms, int32_t poll_ms);
643inline void* global_handle();
644
645} // namespace rclcpp
646
647// ============================================================================
648// rclcpp:: — the HOME of the C++ node type (RFC-0089 §"Settled: `rclcpp::` is
649// the HOME, not an alias onto `nros::`"). phase-427 W7 flipped the direction:
650// the class was DEFINED here and `nros::Node` became the migration alias, the
651// same shape the other nine types already carried (`clock.hpp`, `duration.hpp`,
652// `time.hpp`, `publisher.hpp`, `subscription.hpp`, `client.hpp`, `service.hpp`,
653// `action_client.hpp`, `action_server.hpp`). phase-483 W1 deleted the aliases;
654// `rclcpp::` is now the only namespace.
655//
656// `Node` was the TENTH and was held back because PRs #797 and #806 were stacked
657// on this header. Both merged. The measurement that blocked it before — the
658// parity extractor rooting our native surface at `{"nros"}` alone, so a class
659// defined here re-bucketed ~60 members to `theirs-only` — is gone too:
660// `scripts/api-parity.py` roots every C++ TU at `OUR_CPP_ROOTS`, both halves of
661// the vocabulary we ship.
662//
663// The ours-only names the body below reaches (`ErrorCode`, `detail::*`,
664// `NodeHandle`, `SchedContext`, the polling entity templates) are spelled
665// `::rclcpp::`-qualified. Until phase-483 W1 that qualifier was `::nros::` and
666// told an adopted name from an ours-only one; the namespace no longer carries
667// that distinction.
668// ============================================================================
669namespace rclcpp {
703class Node {
704 public:
707 Node() : handle_(), initialized_(false), executor_handle_(nullptr), clock_(RCL_ROS_TIME) {}
708
709 // ==== phase-427 W2 — construction ======================================
710 //
711 // Upstream constructs a node from a name and THROWS on failure. RFC-0018
712 // forbids exceptions here, so the constructor is kept [clause 2] and the
713 // failure channel changes [clause 1]: the node records the failure and
714 // `ok()` reports it.
715
724 explicit Node(const char* name, const char* ns = nullptr)
725 : handle_(), initialized_(false), executor_handle_(nullptr), clock_(RCL_ROS_TIME) {
726 (void)this->init(name, ns);
727 }
728
734 Result init(const char* name, const char* ns = nullptr) {
735 if (initialized_) return Result(::rclcpp::ErrorCode::AlreadyExists);
736 if (!Node::global_initialized()) return Result(::rclcpp::ErrorCode::NotInitialized);
737 executor_handle_ = Node::global_storage();
738 return Node::create(*this, name, ns);
739 }
740
745 Result init_on(void* executor_handle, const char* name, const char* ns = nullptr) {
746 if (initialized_) return Result(::rclcpp::ErrorCode::AlreadyExists);
748 executor_handle_ = executor_handle;
749 return Node::create(*this, name, ns);
750 }
751
779 explicit Node(::rclcpp::NodeHandle handle, const char* name, const char* ns = nullptr)
780 : handle_(), initialized_(false), executor_handle_(nullptr), clock_(RCL_ROS_TIME) {
781 if (!handle.valid()) {
782 this->set_error("ctor (null executor handle)", -1);
783 return;
784 }
785 Result r =
786 this->init_on(handle.executor, handle.resolve_name(name), handle.resolve_namespace(ns));
787 if (!r.ok()) {
788 this->set_error("node create", r.raw());
789 return;
790 }
791 // Issue 1854 -- the launch's QoS overrides, installed before the
792 // derived constructor body creates a single entity (a base is fully
793 // constructed before the derived body runs), which is the ordering
794 // the `configure` shape gets from the entry's own call.
795 if (handle.qos_overrides != nullptr && handle.qos_override_count != 0) {
796 nros_cpp_ret_t qrc = ::nros_cpp_node_set_qos_overrides(&handle_, handle.qos_overrides,
797 handle.qos_override_count);
798 if (qrc != NROS_CPP_RET_OK) {
799 this->set_error("set_qos_overrides", static_cast<int32_t>(qrc));
800 }
801 }
802 }
803
819 bool ok() const { return initialized_ && !__atomic_load_n(&has_error_, __ATOMIC_ACQUIRE); }
820
823 const char* error_what() const {
824 // Acquire-fence on the flag so a standalone call (not preceded by
825 // ok()) still sees the released `error_what_` write (issue #230).
827 return error_what_;
828 }
832 return error_code_;
833 }
834
835 // ==== phase-427 W1/W3 — the HOSTED shape ================================
836 //
837 // Upstream's own signatures, verbatim, on the same type. Everything below
838 // is spelled in `std::shared_ptr` / `std::string` / `std::vector` /
839 // `std::function`, so where those are absent the SIGNATURES are absent with
840 // them — a gate on METHODS, never on a member.
841 //
842 // ONE NAME, TWO SIGNATURES (RFC-0089 §"Entity creation"). The out-ref
843 // family above and the `shared_ptr` family below are OVERLOADS, and the
844 // compiler separates them without ambiguity because the hosted forms put
845 // `M` only in the return type — so a call that passes an out-param deduces
846 // nothing for them and they drop out, while an explicit
847 // `create_publisher<M>("chatter", 10)` cannot bind an out-ref parameter to
848 // a string literal. On a FREESTANDING target the hosted overload does not
849 // exist at all, so that same ported line fails to compile naming the
850 // out-ref overload — mechanical, which is what clause 2 asks for.
851 //
852 // phase-476 W3 — each member below is gated on the ONE capability its
853 // signature uses, not on a conjunction of all four.
854
857 Node(const char* name, const ::rclcpp::NodeOptions& options) : Node(name, nullptr) {
858 (void)options;
859 }
860
864 Node(const char* name, const char* ns, const ::rclcpp::NodeOptions& options) : Node(name, ns) {
865 (void)options;
866 }
867
871 const ::rclcpp::NodeOptions& get_node_options() const {
872 static constexpr ::rclcpp::NodeOptions kOptions{};
873 return kOptions;
874 }
875
876#ifdef NROS_CPP_HAS_STD_STRING // hosted-family: string-interop
878 explicit Node(const ::std::string& name) : Node(name.c_str(), nullptr) {}
879
881 Node(const ::std::string& name, const ::rclcpp::NodeOptions& options)
882 : Node(name.c_str(), options) {}
883
885 Node(const ::std::string& name, const ::std::string& ns,
886 const ::rclcpp::NodeOptions& options = ::rclcpp::NodeOptions())
887 : Node(name.c_str(), ns.c_str(), options) {}
888#endif // NROS_CPP_HAS_STD_STRING
889
906
921
925 bool initialized() const { return initialized_; }
926
929 Node& nros_node() { return *this; }
931 const Node& nros_node() const { return *this; }
932
933#ifdef NROS_CPP_HAS_STD_STRING // hosted-family: string-interop
934 // -- entity creation, upstream's signatures (bodies in `nros.hpp`) -------
935
941 template <typename M>
942 typename ::rclcpp::Publisher<M>::SharedPtr create_publisher(const ::std::string& topic,
943 const ::rclcpp::QoS& qos);
944
946 template <typename M>
947 typename ::rclcpp::Publisher<M>::SharedPtr create_publisher(const ::std::string& topic,
948 ::size_t depth);
949
952 template <typename M, typename Cb>
953 typename ::rclcpp::Subscription<M>::SharedPtr
954 create_subscription(const ::std::string& topic, const ::rclcpp::QoS& qos, Cb cb);
955
957 template <typename M, typename Cb>
958 typename ::rclcpp::Subscription<M>::SharedPtr create_subscription(const ::std::string& topic,
959 ::size_t depth, Cb cb);
960
971 template <typename S>
973 create_service(const ::std::string& name, const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
974
976 template <typename S, typename F,
977 typename = typename std::enable_if<std::is_convertible<
978 F, void (*)(const typename S::Request&, typename S::Response&)>::value>::type>
979 typename ::rclcpp::Service<S>::SharedPtr
980 create_service(const ::std::string& name, F callback,
981 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
982
985 template <typename S, typename F,
986 typename = typename std::enable_if<
988 !std::is_convertible<F, void (*)(const typename S::Request&,
989 typename S::Response&)>::value>::type,
990 typename = void>
991 typename ::rclcpp::Service<S>::SharedPtr
992 create_service(const ::std::string&, F, const ::rclcpp::QoS& = ::rclcpp::QoS::services());
993
1010 template <typename S>
1012 create_client(const ::std::string& name, const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1013
1015 template <typename S, typename F,
1016 typename = typename std::enable_if<
1017 std::is_convertible<F, void (*)(const typename S::Response&)>::value>::type>
1018 typename ::rclcpp::Client<S>::SharedPtr
1019 create_client(const ::std::string& name, F callback,
1020 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1021
1023 template <typename S, typename F,
1024 typename = typename std::enable_if<
1026 !std::is_convertible<F, void (*)(const typename S::Response&)>::value>::type,
1027 typename = void>
1028 typename ::rclcpp::Client<S>::SharedPtr
1029 create_client(const ::std::string&, F, const ::rclcpp::QoS& = ::rclcpp::QoS::services());
1030
1031#endif // NROS_CPP_HAS_STD_STRING
1032
1033 // -- the ported publisher line, on EVERY target — phase-456 W5 ----------
1034 //
1035 // OUTSIDE the hosted gate (phase-476 W3 deleted it), and that is the point
1036 // rather than a placement detail. `create_publisher<M>("chatter", 10)` is the single most
1037 // copied line in the porting corpus, and until W5 it was gated: the return
1038 // type was `std::shared_ptr<Publisher<M>>`, so on a minimal freestanding
1039 // libcpp the overload did not exist and the line did not compile.
1040 // `ported_create_publisher_freestanding_probe.cpp` asserted exactly that,
1041 // and the lane's own error text named the reason — "the hosted overload
1042 // returns std::shared_ptr and there is no allocator here".
1043 //
1044 // The return type is `Publisher<M>::SharedPtr` = `rclcpp::Owned<Publisher<M>>`
1045 // now, which needs no allocator and exists everywhere, so the reason is
1046 // gone and the gate goes with it. RFC-0096 D1: ONE API, byte-for-byte the
1047 // same shape on every target, reached by removing what made it differ.
1048 //
1049 // The `const char*` key is what makes it freestanding — a `std::string`
1050 // parameter would have re-imposed the gate through the argument instead of
1051 // the return. A string literal binds here exactly (array-to-pointer), so a
1052 // ported call reaches this overload on a hosted target too; the
1053 // `std::string` forwarders above stay for a caller that already holds one.
1054
1056 template <typename M>
1057 typename ::rclcpp::Publisher<M>::SharedPtr
1059 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
1060
1062 template <typename M>
1063 typename ::rclcpp::Publisher<M>::SharedPtr create_publisher(const char* topic, ::size_t depth);
1064
1065 // phase-476 W1 — the DISPATCH verbs join it, for the same reason. Their
1066 // results have been freestanding handles since phase-456 (the arena holds
1067 // the subscription, the server, the client and the callable), so the
1068 // `std::string` key was the only thing still keeping them hosted. The
1069 // `std::string` overloads in the hosted block forward here.
1070
1073 template <typename M, typename Cb>
1074 typename ::rclcpp::Subscription<M>::SharedPtr
1075 create_subscription(const char* topic, const ::rclcpp::QoS& qos, Cb cb);
1076
1078 template <typename M, typename Cb>
1079 typename ::rclcpp::Subscription<M>::SharedPtr create_subscription(const char* topic,
1080 ::size_t depth, Cb cb);
1081
1084 template <typename S>
1086 create_service(const char* name, const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1087
1089 template <typename S, typename F,
1090 typename = typename ::rclcpp::tr::enable_if<::rclcpp::tr::is_convertible<
1091 F, void (*)(const typename S::Request&, typename S::Response&)>::value>::type>
1092 typename ::rclcpp::Service<S>::SharedPtr
1094 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1095
1098 template <typename S, typename F,
1099 typename = typename ::rclcpp::tr::enable_if<
1101 !::rclcpp::tr::is_convertible<F, void (*)(const typename S::Request&,
1102 typename S::Response&)>::value>::type,
1103 typename = void>
1104 typename ::rclcpp::Service<S>::SharedPtr
1105 create_service(const char*, F, const ::rclcpp::QoS& = ::rclcpp::QoS::services());
1106
1109 template <typename S>
1111 create_client(const char* name, const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1112
1114 template <typename S, typename F,
1115 typename = typename ::rclcpp::tr::enable_if<::rclcpp::tr::is_convertible<
1116 F, void (*)(const typename S::Response&)>::value>::type>
1117 typename ::rclcpp::Client<S>::SharedPtr
1119 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1120
1122 template <
1123 typename S, typename F,
1124 typename = typename ::rclcpp::tr::enable_if<
1126 !::rclcpp::tr::is_convertible<F, void (*)(const typename S::Response&)>::value>::type,
1127 typename = void>
1128 typename ::rclcpp::Client<S>::SharedPtr
1129 create_client(const char*, F, const ::rclcpp::QoS& = ::rclcpp::QoS::services());
1130
1131 // -- parameters (bodies in `nros.hpp`) ----------------------------------
1132 //
1133 // Forwarders onto THE parameter store - the `nros_params::ParameterServer`
1134 // the EXECUTOR owns, reached across the FFI by `rclcpp/node_parameters.hpp`.
1135 // Declared here with the class and DEFINED in `nros.hpp` beside the other
1136 // out-of-line members, which is where the reasoning lives.
1137 //
1138 // Until phase-426 W4 these read an inline `ParameterServer` member on the
1139 // hosted block instead: a second store, node-local, that the six
1140 // `rcl_interfaces/srv/*` servers could not read - so a parameter declared
1141 // through this facade was invisible to `ros2 param get` and a sibling node
1142 // did not share it.
1143 //
1144 // OUTSIDE the hosted gate (phase-476 W3 deleted it), and that is the rest of
1145 // W4 rather than a tidy-up. The member leaving was "the last thing that made
1146 // the hosted gate decide whether a node HAS parameters rather than
1147 // whether it can spell them" - but the declarations stayed inside the gate,
1148 // so a freestanding node still had no parameter method, and
1149 // `nros::ParameterServer<Cap>` (deleted with this wave) was the only C++
1150 // parameter surface it had. Deleting that class while the gate stood would
1151 // have removed the capability from freestanding C++ instead of moving it,
1152 // which is what W4's ordering rule exists to prevent. The scalar FFI is
1153 // `<cstdint>` and a `const char*`; nothing about it needs the STL, and
1154 // `rclcpp::Seq<T, N>` gives the array form the same reach. Only the
1155 // `std::string`-KEYED overloads below need it, and they keep the gate - a
1156 // gate on METHODS, never on a member, which is what
1157 // `check-cpp-capability-layout` asks.
1158
1160 template <typename T> T declare_parameter(const char* name, T default_value = T());
1163 template <typename T> bool get_parameter(const char* name, T& out) const;
1165 template <typename T> T get_parameter(const char* name) const;
1169 template <typename T> Result set_parameter(const char* name, T value);
1171 bool has_parameter(const char* name) const;
1172
1173 // ---- phase-417 W4.a: the rest of rclcpp's parameter surface ------------
1174 //
1175 // All of it forwards to the executor's ONE store through
1176 // `rclcpp/node_parameters.hpp`. OUTSIDE any capability gate for the same
1177 // reason the four above are: the FFI is `<cstdint>` and `const char*`, so
1178 // a freestanding node can spell every one of them, and a gate here would
1179 // decide whether a node HAS parameters rather than whether it can name
1180 // them.
1181
1190 template <typename T>
1192
1196 template <typename T> bool get_parameter_or(const char* name, T& out, T fallback) const;
1197
1201 Result undeclare_parameter(const char* name);
1202
1205 ParameterType get_parameter_type(const char* name) const;
1206
1213 Result get_parameter_types(const char* const* names, ::size_t count, ParameterType* out) const;
1214
1223 ::size_t text_len) const;
1224
1232 Result list_parameters(const char* prefix, char* out_names, ::size_t name_stride,
1233 ::size_t max_names, ::size_t& count) const;
1234
1239 Result set_parameters_atomically(const ParameterWrite* writes, ::size_t count);
1240
1255
1258
1259#ifdef NROS_CPP_HAS_STD_STRING // hosted-family: string-interop
1263 template <typename T> T declare_parameter(const ::std::string& name, T default_value = T()) {
1264 return this->template declare_parameter<T>(name.c_str(), default_value);
1265 }
1266 template <typename T> bool get_parameter(const ::std::string& name, T& out) const {
1267 return this->template get_parameter<T>(name.c_str(), out);
1268 }
1269 template <typename T> T get_parameter(const ::std::string& name) const {
1270 return this->template get_parameter<T>(name.c_str());
1271 }
1272 template <typename T> Result set_parameter(const ::std::string& name, T value) {
1273 return this->template set_parameter<T>(name.c_str(), value);
1274 }
1275 bool has_parameter(const ::std::string& name) const {
1276 return this->has_parameter(name.c_str());
1277 }
1278 Result undeclare_parameter(const ::std::string& name) {
1279 return this->undeclare_parameter(name.c_str());
1280 }
1281 template <typename T>
1282 T declare_parameter(const ::std::string& name, T default_value,
1283 const ParameterDescriptor& descriptor) {
1284 return this->template declare_parameter<T>(name.c_str(), default_value, descriptor);
1285 }
1286 template <typename T>
1287 bool get_parameter_or(const ::std::string& name, T& out, T fallback) const {
1288 return this->template get_parameter_or<T>(name.c_str(), out, fallback);
1289 }
1290 ParameterType get_parameter_type(const ::std::string& name) const {
1291 return this->get_parameter_type(name.c_str());
1292 }
1293
1305 template <typename T>
1306 Result declare_parameters(const ::std::string& prefix, const ::std::map<::std::string, T>& m) {
1307 for (typename ::std::map<::std::string, T>::const_iterator it = m.begin(); it != m.end();
1308 ++it) {
1309 const ::std::string full = prefix.empty() ? it->first : (prefix + "." + it->first);
1310 const ::nros_cpp_node_t* h = this->ffi_handle();
1311 Result r = ::rclcpp::detail::node_param_declare(h, full.c_str(), it->second);
1312 if (!r.ok() && r.raw() != RCLC_RET_ALREADY_EXISTS) {
1313 return r;
1314 }
1315 }
1316 return Result(RCL_RET_OK);
1317 }
1318#endif // NROS_CPP_HAS_STD_STRING
1319
1339 static Result create(Node& out, const char* name, const char* ns = nullptr) {
1340 if (!out.executor_handle_) {
1342 }
1343
1344 nros_cpp_ret_t ret = nros_cpp_node_create(out.executor_handle_, name, ns, &out.handle_);
1345
1346 if (ret == 0) {
1347 out.initialized_ = true;
1348 }
1349 return Result(ret);
1350 }
1351
1353 const char* get_name() const {
1354 if (!initialized_) return "";
1355 return nros_cpp_node_get_name(&handle_);
1356 }
1357
1359 const char* get_namespace() const {
1360 if (!initialized_) return "";
1361 return nros_cpp_node_get_namespace(&handle_);
1362 }
1363
1378 Result get_fully_qualified_name(char* buf, size_t buf_len, size_t* out_len = nullptr) const {
1379 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1381 }
1382
1400 const char* serialization_format() const {
1401 if (!initialized_) return nullptr;
1403 }
1404
1429 if (!initialized_) return ::rclcpp::Logger("", nullptr);
1430 return ::rclcpp::Logger(nros_cpp_node_get_name(&handle_),
1431 nros_cpp_node_get_logger(&handle_));
1432 }
1433
1444 Clock* get_clock() { return &clock_; }
1446 const Clock* get_clock() const { return &clock_; }
1447
1455 Time now() const { return clock_.now(); }
1456
1458 bool is_valid() const { return initialized_; }
1459
1475 const nros_cpp_node_t* ffi_handle() const { return initialized_ ? &handle_ : nullptr; }
1476
1488 if (initialized_) {
1489 ::nros_cpp_node_set_qos_overrides(&handle_, overrides, len);
1490 }
1491 }
1492
1499 void* executor_handle() const { return initialized_ ? executor_handle_ : nullptr; }
1500
1510
1511 // ---- Graph queries — phase-417 stage 2b (RFC-0089) --------------------
1512 //
1513 // rclcpp puts these on the node. `rclcpp::Executor` owns them here because
1514 // one session per image makes the executor the graph's receiver
1515 // (RFC-0002), so each method below FORWARDS to the executor this node was
1516 // opened against and nothing else: no state, no loop, no caching, no name
1517 // construction. RFC-0019 — the behaviour is Rust's and stays there.
1518 //
1519 // The envelope every one of them shares (ADOPT-BOUNDED, RFC-0089): they
1520 // report what has been DISCOVERED and never block, so an empty result
1521 // means "nobody seen yet" and never "nobody exists" — poll rather than
1522 // calling once and concluding. `ErrorCode::Unsupported`, which is what a
1523 // backend with no graph at all returns, is a DIFFERENT answer from an
1524 // empty one and must not be collapsed into zero.
1525
1545 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1546 return Result(nros_cpp_executor_get_node_names(executor_handle_, visit, ctx));
1547 }
1548
1560
1568
1577 Result count_publishers(const char* topic_name, size_t* out_count) const {
1578 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1580 }
1581
1584 Result count_subscribers(const char* topic_name, size_t* out_count) const {
1585 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1587 }
1588
1599
1618
1629
1640
1661
1672 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1673 ::rclcpp::detail::EndpointInfoTrampoline tramp{visit, ctx};
1675 executor_handle_, topic_name, &::rclcpp::detail::EndpointInfoTrampoline::thunk,
1676 &tramp));
1677 }
1678
1689
1695 void* ctx) const {
1696 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1697 ::rclcpp::detail::EndpointInfoTrampoline tramp{visit, ctx};
1699 executor_handle_, topic_name, &::rclcpp::detail::EndpointInfoTrampoline::thunk,
1700 &tramp));
1701 }
1702
1709 template <typename M>
1711 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
1712
1725 template <typename M>
1727 const ::rclcpp::QoS& qos, const ::rclcpp::PublisherOptions& options);
1728
1740 template <typename M>
1742 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
1743
1759 template <typename M>
1761 const ::rclcpp::QoS& qos,
1762 const ::rclcpp::SubscriptionOptions& options);
1763
1806 template <
1807 typename M, typename F,
1808 typename = typename std::enable_if<std::is_convertible<F, void (*)(const M&)>::value>::type>
1810 const ::rclcpp::QoS& qos, F callback,
1811 const ::rclcpp::SubscriptionOptions& options = {});
1812
1824 template <typename M, typename F,
1825 typename = typename std::enable_if<
1826 std::is_convertible<F, void (*)(const M&, const uint8_t*, size_t)>::value>::type>
1828 const ::rclcpp::QoS& qos, F callback,
1829 const ::rclcpp::SubscriptionOptions& options = {});
1830
1831#if defined(NROS_SAFETY_E2E)
1850 template <typename M, typename F,
1851 typename = typename std::enable_if<std::is_convertible<
1852 F, void (*)(const M&, const ::nros_cpp_integrity_status_t&)>::value>::type>
1854 const ::rclcpp::QoS& qos, F callback,
1855 const ::rclcpp::SubscriptionOptions& options = {});
1856#endif // NROS_SAFETY_E2E
1857
1872 template <typename S>
1874 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1875
1889 template <typename S, typename F,
1890 typename = typename std::enable_if<std::is_convertible<
1891 F, void (*)(const typename S::Request&, typename S::Response&)>::value>::type>
1893 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services(),
1894 const ::rclcpp::ServiceOptions& options = {});
1895
1911 template <typename S>
1913 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1914
1926 template <typename S, typename F,
1927 typename = typename std::enable_if<
1928 std::is_convertible<F, void (*)(const typename S::Response&)>::value>::type>
1930 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services(),
1931 const ::rclcpp::ClientOptions& options = {});
1932
1943 template <typename A>
1944 Result create_action_server(::rclcpp_action::Server<A>& out, const char* action_name,
1945 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services(),
1946 const ::rclcpp::ActionServerOptions& options = {});
1947
1954 template <typename A>
1955 Result create_action_client(::rclcpp_action::Client<A>& out, const char* action_name,
1956 const ::rclcpp::QoS& qos = ::rclcpp::QoS::services());
1957
1961 template <typename A>
1963 const char* action_name);
1964
1966 template <typename A>
1968 const char* action_name);
1969
1975 template <typename M>
1977 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
1978
2003 nros_cpp_timer_callback_t callback, void* context = nullptr) {
2004 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
2005 size_t handle_id = 0;
2007 executor_handle_, static_cast<uint8_t>(clock.get_clock_type()), period_ms, callback,
2008 context, &handle_id);
2009 if (ret == 0) {
2010 out.executor_ = executor_handle_;
2011 out.handle_id_ = handle_id;
2012 out.initialized_ = true;
2013 }
2014 return Result(ret);
2015 }
2016
2029
2030#ifdef NROS_CPP_HAS_STD_CHRONO // hosted-family: chrono-interop
2034 template <typename Rep, typename Period, typename Cb>
2035 ::rclcpp::TimerHandle create_wall_timer(::std::chrono::duration<Rep, Period> period, Cb cb);
2036#endif
2037
2049 void* context = nullptr) {
2050 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
2051 size_t handle_id = 0;
2053 nros_cpp_timer_create(executor_handle_, period_ms, callback, context, &handle_id);
2054 if (ret == 0) {
2055 out.executor_ = executor_handle_;
2056 out.handle_id_ = handle_id;
2057 out.initialized_ = true;
2058 }
2059 return Result(ret);
2060 }
2061
2062 // ==== phase-427 W3 — member-function binding, on the upstream name ======
2063 //
2064 // `nros::bind_timer<C, &C::m>(node, out, ms, self)` was a FREE function
2065 // with an invented name doing what `create_wall_timer` does. Folding it in
2066 // removes an invention by reusing an upstream name, which is what clause 2
2067 // asks for — and it is the shape a component actually writes, so the verb a
2068 // reader meets first is the one they need.
2069 //
2070 // NO ALLOCATION and NO `std::function`: the member pointer is a TEMPLATE
2071 // PARAMETER, so the trampoline is a capture-less lambda that converts to
2072 // the executor's raw `void(*)(void*)` and `self` is the context. That is
2073 // why this overload, unlike the `std::chrono` one, reaches every target.
2074
2080 template <class C, void (C::*Method)()>
2082 return this->create_wall_timer(
2083 out, period_ms, [](void* ctx) { (static_cast<C*>(ctx)->*Method)(); }, self);
2084 }
2085
2092 template <class C, void (C::*Method)()>
2094 return this->create_timer(
2095 out, clock, period_ms, [](void* ctx) { (static_cast<C*>(ctx)->*Method)(); }, self);
2096 }
2097
2100 template <class C, void (C::*Method)()>
2101 Result create_timer_in_group(const ::rclcpp::CallbackGroup& group, Timer& out,
2103 return this->create_timer_in_group(
2104 group, out, period_ms, [](void* ctx) { (static_cast<C*>(ctx)->*Method)(); }, self);
2105 }
2106
2116 void* context = nullptr) {
2117 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
2118 size_t handle_id = 0;
2120 context, &handle_id);
2121 if (ret == 0) {
2122 out.executor_ = executor_handle_;
2123 out.handle_id_ = handle_id;
2124 out.initialized_ = true;
2125 }
2126 return Result(ret);
2127 }
2128
2129 // -- Phase 273 (RFC-0047) — Callback-group API -------------------------
2130
2147 if (!::rclcpp::detail::admit_callback_group(name)) {
2148 const ::rclcpp::detail::DeclaredCallbackGroups* g =
2149 ::rclcpp::detail::declared_callback_groups_scope();
2150 const char* list = (g != nullptr) ? g->list : nullptr;
2151 int len = 0;
2152 while (name[len] != '\0')
2153 ++len;
2154 ::rclcpp::detail::report_callback_group_mismatch(this->get_name(), "created", name, len,
2155 list);
2156 this->set_error("create_callback_group: the group is not in this registration's "
2157 "CALLBACK_GROUPS",
2158 ::rclcpp::detail::DECLARED_CALLBACK_GROUP_MISMATCH);
2159 }
2160 return ::rclcpp::CallbackGroup{name};
2161 }
2162
2174 Result create_timer_in_group(const ::rclcpp::CallbackGroup& group, Timer& out,
2176 void* context = nullptr) {
2177 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
2178 size_t handle_id = 0;
2180 executor_handle_, &handle_, period_ms, callback, context, group.get_name(), &handle_id);
2181 if (ret == 0) {
2182 out.executor_ = executor_handle_;
2183 out.handle_id_ = handle_id;
2184 out.initialized_ = true;
2185 }
2186 return Result(ret);
2187 }
2188
2208 template <
2209 typename M, typename F,
2210 typename = typename std::enable_if<std::is_convertible<F, void (*)(const M&)>::value>::type>
2211 Result create_subscription_in_group(const ::rclcpp::CallbackGroup& group,
2212 ::rclcpp::Subscription<M>& out, const char* topic,
2213 const ::rclcpp::QoS& qos, F callback,
2214 const ::rclcpp::SubscriptionOptions& options = {});
2215
2227 template <typename M>
2228 Result create_publisher_in_group(const ::rclcpp::CallbackGroup& /* group */,
2229 ::rclcpp::Publisher<M>& out, const char* topic,
2230 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile()) {
2231 return create_publisher<M>(out, topic, qos);
2232 }
2233
2242 void* context = nullptr) {
2243 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
2244 size_t handle_id = 0;
2245 nros_cpp_ret_t ret = nros_cpp_guard_condition_create(executor_handle_, callback, context,
2246 out.storage_, &handle_id);
2247 if (ret == 0) {
2248 out.initialized_ = true;
2249 out.executor_ = executor_handle_;
2250 out.handle_id_ = handle_id;
2251 }
2252 return Result(ret);
2253 }
2254
2255 // ==== phase-427 W4 — the ours-only family, under `_in` names =============
2256 //
2257 // These came off `nros::ComponentNode`. Every one of them is spelled with
2258 // an `_in` SUFFIX rather than upstream's bare verb, and the rename is the
2259 // whole point of the item.
2260 //
2261 // `ComponentNode::create_publisher<M>(const char*, const QoS&)` returned by
2262 // value and reported failure through the `ok()` latch; upstream's
2263 // `create_publisher<M>(const std::string&, …)` returns a `shared_ptr` and
2264 // throws. As OVERLOADS on one type those differ only in SIGNATURE, and C++
2265 // resolves a signature-only difference SILENTLY. Measured on the merged
2266 // shape before the rename, with gcc 13:
2267 //
2268 // create_publisher<M>("chatter", 10) -> UPSTREAM (shared_ptr)
2269 // create_publisher<M>("chatter", rclcpp::QoS(10)) -> OURS (by value),
2270 // accepted with only
2271 // a -Wextra-ish note
2272 // that ISO calls it
2273 // ambiguous
2274 //
2275 // So a ported file compiles either way and gets a different type, lifetime
2276 // and failure channel depending on which spelling of the depth it used.
2277 // That is exactly the compile-and-differ RFC-0089 forbids, and the merge is
2278 // what would have manufactured it. The fix is a different NAME, not a
2279 // cleverer signature — the third application of that rule in this campaign,
2280 // after the reordered C node initialiser and the clock-taking C timer verb.
2281 //
2282 // `tests/compile/one_node_type_ours_only_names.cpp` pins both halves: that
2283 // the ported spellings reach upstream's overload on the real type, and
2284 // (the negative control) that the pre-rename shape bound the wrong one.
2285
2291 template <typename M>
2294 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile()) {
2296 Result r = this->create_publisher(pub, topic, qos);
2297 if (!r.ok()) {
2298 this->set_error("create_publisher_in", r.raw());
2299 }
2300 return pub;
2301 }
2302
2316 template <typename M, class C, void (C::*Method)(const M& msg)>
2318 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
2319
2322 template <typename M, class C, void (C::*Method)(const M& msg)>
2323 void create_subscription_in_group(const ::rclcpp::CallbackGroup& group, const char* topic,
2324 const ::rclcpp::QoS& qos = ::rclcpp::QoS::default_profile());
2325
2342 bool check_declared_depth(const char* type_name, const char* topic, const ::rclcpp::QoS& qos) {
2344 // Nobody declared this endpoint. Not an error: the image has not opted
2345 // in, and anything sizing from depth refuses rather than defaulting.
2347 return true;
2348 }
2349 if (declared == qos.depth()) {
2350 return true;
2351 }
2352 // The two NUMBERS and the TOPIC go out first: `set_error` records one
2353 // `const char*`, which cannot carry them.
2355 qos.depth());
2356 this->set_error(
2357 "create_subscription_in: ::rclcpp::QoS depth disagrees with the depth declared for "
2358 "this topic in the contract sidecar. Depth multiplies the arena, so the "
2359 "declaration and the code must state one number, not two.",
2361 return false;
2362 }
2363
2375 bool check_declared_qos(const char* type_name, const char* topic, const ::rclcpp::QoS& qos) {
2376 if (!this->check_declared_depth(type_name, topic, qos)) {
2377 return false;
2378 }
2381 rel != static_cast<int>(qos.reliability())) {
2383 this->get_name(), topic, "reliability",
2386 static_cast<int>(qos.reliability())));
2387 this->set_error("create_subscription_in: ::rclcpp::QoS reliability disagrees with the "
2388 "reliability declared for this topic in the contract sidecar.",
2390 return false;
2391 }
2394 dur != static_cast<int>(qos.durability())) {
2396 this->get_name(), topic, "durability",
2398 ::rclcpp::detail::declared_durability_spelling(static_cast<int>(qos.durability())));
2399 this->set_error("create_subscription_in: ::rclcpp::QoS durability disagrees with the "
2400 "durability declared for this topic in the contract sidecar.",
2402 return false;
2403 }
2404 return true;
2405 }
2406
2425 bool check_declared_param(const char* name, int passed_type) {
2426 const char* ns = this->get_namespace();
2427 const char* nn = this->get_name();
2428 if (name == nullptr || nn == nullptr) {
2429 return true;
2430 }
2431 const ::rclcpp::declared_params::Node* declaring =
2432 ::rclcpp::declared_param_node((ns != nullptr) ? ns : "", nn);
2433 if (declaring == nullptr || ::rclcpp::declared_param_exempt(name)) {
2434 return true;
2435 }
2437 if (declared == passed_type) {
2438 return true;
2439 }
2442 this->set_error("declare_parameter: the code does not declare this parameter the way "
2443 "its contract does (see the line above for the node, the parameter and "
2444 "the contract). The parameter store is sized from the contract's "
2445 "`params:`, so the two must state one name and one type.",
2447 return false;
2448 }
2449
2457 void set_error(const char* what, int32_t code) {
2458 // Relaxed read for the first-failure guard: set_error runs during
2459 // construction, on the constructing thread, never concurrently with a
2460 // reader (issue #230).
2461 if (!__atomic_load_n(&has_error_, __ATOMIC_RELAXED)) {
2462 error_what_ = what;
2463 error_code_ = code;
2464 // RELEASE publishes the two plain writes above: a reader that
2465 // acquire-observes has_error_ == true is guaranteed to see them.
2466 __atomic_store_n(&has_error_, true, __ATOMIC_RELEASE);
2467 ::rclcpp::detail::report_component_failure(this->get_name(), what, code);
2468 }
2469 }
2470
2479 if (initialized_) {
2480 nros_cpp_node_destroy(&handle_);
2481 initialized_ = false;
2482 }
2483 }
2484
2485 // Move semantics (non-copyable)
2487 : handle_(other.handle_), initialized_(other.initialized_),
2488 executor_handle_(other.executor_handle_), clock_(other.clock_),
2489 // The latch travels with the node: a moved-from node's failure is
2490 // still this node's failure, and the entry checks `ok()` on whichever
2491 // object it ended up holding.
2492 has_error_(other.has_error_), error_what_(other.error_what_),
2493 error_code_(other.error_code_) {
2494 other.initialized_ = false;
2495 other.executor_handle_ = nullptr;
2496 }
2497
2499 if (this != &other) {
2500 if (initialized_) {
2501 nros_cpp_node_destroy(&handle_);
2502 }
2503 handle_ = other.handle_;
2504 initialized_ = other.initialized_;
2505 executor_handle_ = other.executor_handle_;
2506 clock_ = other.clock_;
2507 has_error_ = other.has_error_;
2508 error_what_ = other.error_what_;
2509 error_code_ = other.error_code_;
2510 other.initialized_ = false;
2511 other.executor_handle_ = nullptr;
2512 }
2513 return *this;
2514 }
2515
2516 private:
2517 Node(const Node&) = delete;
2518 Node& operator=(const Node&) = delete;
2519
2520 nros_cpp_node_t handle_;
2521 bool initialized_;
2522 void* executor_handle_; // Set by rclcpp::init() via friendship
2523 // Issue 0789 — the node's own clock, ROS time as in rclcpp. Constructing
2524 // it touches no platform service (only a steady clock records an epoch),
2525 // so a Node in static storage stays as cheap to create as it was.
2526 Clock clock_;
2527 // phase-476 W3 — `void* hosted_` and its lazy `hosted()` accessor are gone
2528 // with `detail::NodeHosted` (see there): the block's last member was an
2529 // empty `NodeOptions`. `sizeof(Node)` loses that pointer on EVERY target,
2530 // so the layout stays probe-independent.
2531
2532 // phase-427 W4 — the error latch, 24 UNCONDITIONAL bytes on every node.
2533 //
2534 // These did NOT go behind the old `hosted_` block, and the decision was deliberate rather
2535 // than an oversight the layout rule missed. This is the error channel a
2536 // `-fno-exceptions` target has INSTEAD of a throwing constructor: the
2537 // boot-halt mechanism (`NanoRosEntityInventory.cmake`, RFC-0044 Q2) is
2538 // exactly what a freestanding image needs, and a latch reachable only on a
2539 // hosted target would leave the firmware case — the one that cannot throw —
2540 // with no channel at all. So every node pays 24 bytes, which is the price
2541 // of one node type whose failure story works on both.
2542 //
2543 // Issue #230 — inverted flag: the HEALTHY value is the zero-init default,
2544 // so a cross-core reader sees "ok" without waiting for any store. Accessed
2545 // via __atomic builtins (release in set_error, acquire in ok()/error_*) —
2546 // NOT `<atomic>`, which the Zephyr `-nostdinc++` minimal libcpp may lack
2547 // (issue 0112 class). All three fields are zero-init-safe.
2548 bool has_error_ = false;
2549 const char* error_what_ = nullptr;
2550 int32_t error_code_ = 0;
2551
2552 // phase-427 W7 — the class moved to `rclcpp::` while its friends were still
2553 // `nros::` free functions, so each friend is QUALIFIED. phase-483 W1 moved
2554 // the friends into `rclcpp::` too; the qualified form still names exactly
2555 // the declarations at the top of this header.
2556 //
2557 // The declarator-id is PARENTHESIZED, and that is not style. A
2558 // nested-name-specifier is parsed greedily, so `friend Result ::rclcpp::init`
2559 // reads as `Result::rclcpp::init` and fails with `'rclcpp' in 'using Result ='
2560 // does not name a type` — a diagnostic that names neither the friend nor
2561 // the namespace. `(::rclcpp::init)` closes the return type first.
2562 friend class ::rclcpp::Executor;
2563 friend class ::rclcpp::NodeBuilder;
2564 friend Result(::rclcpp::init_in)(const char* locator, uint8_t domain_id);
2565 friend Result(::rclcpp::init_in)(const char* locator, uint8_t domain_id,
2566 const char* session_name);
2567 // Issue 1434 — the namespaced overload. A friend declaration names ONE
2568 // signature, so an overload that reaches `global_storage()` needs its own
2569 // line; without it the new body compiles everywhere the class is not
2570 // instantiated and fails only where a TU actually calls it.
2571 friend Result(::rclcpp::init_in)(const char* locator, uint8_t domain_id,
2572 const char* session_name, const char* node_namespace);
2573 friend Result(::rclcpp::init_with_rmw)(const char* rmw, const char* locator, uint8_t domain_id,
2574 const char* session_name);
2577 friend Result(::rclcpp::create_node)(Node& out, const char* name, const char* ns);
2578 friend Result(::rclcpp::create_node_on)(Node& out, void* executor_handle, const char* name,
2579 const char* ns);
2584
2585 // Global executor inline storage for init/shutdown free functions.
2586 //
2587 // Use a template-static-member trick instead of a function-local static.
2588 // Function-local statics need __cxa_guard_acquire/release on first-call
2589 // initialisation; on NuttX the resulting guard logic returns NULL for
2590 // the storage pointer (observed empirically with LTO on armv7a-nuttx-eabihf,
2591 // even with constant-initialisation `= {}`). A template static member is
2592 // emitted into .bss like a file-scope variable and gets COMDAT-folded by
2593 // the linker, sidestepping the guarded-init path entirely.
2594 template <int = 0> struct GlobalStorageHolder {
2595 alignas(8) static uint8_t storage[NROS_CPP_EXECUTOR_STORAGE_SIZE];
2596 };
2597 static uint8_t* global_storage() { return GlobalStorageHolder<>::storage; }
2598
2618 static bool global_initialized() {
2619 return nros_cpp_context_is_live(GlobalStorageHolder<>::storage);
2620 }
2621};
2622
2623// Out-of-class definitions for Node::GlobalStorageHolder<> — the template
2624// machinery means these get emitted as COMDAT symbols, so multiple TUs
2625// including this header all collapse to a single .bss allocation.
2626template <int N>
2627alignas(8) uint8_t Node::GlobalStorageHolder<N>::storage[NROS_CPP_EXECUTOR_STORAGE_SIZE] = {};
2628
2629} // namespace rclcpp
2630
2631namespace rclcpp {
2632
2633// ==== phase-427 W4 — the timer pool, as a TEMPLATE PARAMETER ================
2634//
2635// `ComponentNode` carried `Timer timers_[NROS_COMPONENT_MAX_TIMERS]` — 192
2636// unconditional bytes at the default depth of 8. Merging that onto `Node` would
2637// have charged every node in the tree for a pool most of them never touch, on
2638// targets chosen for having no memory to spare.
2639//
2640// So the depth is a template parameter and the DEFAULT IS ZERO: `Node` itself
2641// carries no pool and is unchanged in size by this half of the merge. A node
2642// that wants the storage-free timer verbs derives `NodeWithTimers<N>` and names
2643// the depth it needs.
2644//
2645// Why a derived template and not `template <size_t N> class Node` — measured,
2646// not preferred. `Node` has to stay ONE non-template type
2647// (`tests/compile/one_node_type.cpp`); 218 in-tree sites spell `Node` with
2648// no argument list, which a class template with a defaulted parameter does not
2649// permit; and every `Node&` parameter in the tree — `create_node(Node&)`,
2650// `Executor`, `spin` — would otherwise accept only ONE depth, so a component
2651// with a pool would not be a node the executor could take. Deriving keeps the
2652// IS-A that `ComponentNode` never had (it WRAPPED a node, which is the reason
2653// this merge exists at all) while keeping the bytes opt-in.
2654//
2655// `NodeWithTimers` is NOT a second node type. It adds storage and two verbs;
2656// it declares no identity, and every one of its instances IS-A `Node`.
2657
2661#ifndef NROS_COMPONENT_MAX_TIMERS
2662#define NROS_COMPONENT_MAX_TIMERS 8
2663#endif
2671#if NROS_COMPONENT_MAX_TIMERS < 1
2672#error "NROS_COMPONENT_MAX_TIMERS must be >= 1: it sizes a C array (issue 1015)"
2673#endif
2674
2694template <::size_t MaxTimers = NROS_COMPONENT_MAX_TIMERS>
2695class NodeWithTimers : public ::rclcpp::Node {
2696 static_assert(MaxTimers >= 1,
2697 "NodeWithTimers<0> has no pool -- derive ::rclcpp::Node directly, or use "
2698 "the out-ref create_wall_timer(Timer&, ...) family");
2699
2700 public:
2701 using ::rclcpp::Node::Node;
2702
2709 template <class C, void (C::*Method)()> void create_wall_timer_in(uint64_t period_ms) {
2710 Timer* slot = this->next_timer_slot("create_wall_timer_in");
2711 if (slot == nullptr) return;
2712 Result r = this->Node::template create_wall_timer<C, Method>(*slot, period_ms,
2713 static_cast<C*>(this));
2714 if (!r.ok()) {
2715 this->set_error("create_wall_timer_in", r.raw());
2716 return;
2717 }
2718 ++timer_count_;
2719 }
2720
2723 void* context = nullptr) {
2724 Timer* slot = this->next_timer_slot("create_wall_timer_in");
2725 if (slot == nullptr) return;
2726 Result r = this->Node::create_wall_timer(*slot, period_ms, callback, context);
2727 if (!r.ok()) {
2728 this->set_error("create_wall_timer_in", r.raw());
2729 return;
2730 }
2731 ++timer_count_;
2732 }
2733
2736 template <class C, void (C::*Method)()>
2738 Timer* slot = this->next_timer_slot("create_timer_in_group");
2739 if (slot == nullptr) return;
2740 Result r = this->Node::template create_timer_in_group<C, Method>(group, *slot, period_ms,
2741 static_cast<C*>(this));
2742 if (!r.ok()) {
2743 this->set_error("create_timer_in_group", r.raw());
2744 return;
2745 }
2746 ++timer_count_;
2747 }
2748
2751 nros_cpp_timer_callback_t callback, void* context = nullptr) {
2752 Timer* slot = this->next_timer_slot("create_timer_in_group");
2753 if (slot == nullptr) return;
2754 Result r = this->Node::create_timer_in_group(group, *slot, period_ms, callback, context);
2755 if (!r.ok()) {
2756 this->set_error("create_timer_in_group", r.raw());
2757 return;
2758 }
2759 ++timer_count_;
2760 }
2761
2762 // The out-ref forms on `Node` share these names; bring them in so a derived
2763 // node can still spell `create_timer_in_group(group, my_timer_, 100, cb, ctx)`
2764 // without the pool overloads hiding the base by name lookup.
2765 using ::rclcpp::Node::create_timer_in_group;
2766 using ::rclcpp::Node::create_wall_timer;
2767
2768 private:
2771 Timer* next_timer_slot(const char* what) {
2772 if (timer_count_ >= MaxTimers) {
2773 this->set_error(what, -1);
2774 return nullptr;
2775 }
2776 return &timers_[timer_count_];
2777 }
2778
2779 Timer timers_[MaxTimers];
2780 ::size_t timer_count_ = 0;
2781};
2782
2783// -- Free function implementations --
2784
2785inline Result init_in(const char* locator, uint8_t domain_id) {
2786 // Issue 0329 — forward RAW to the 3-arg overload, which resolves the whole
2787 // ladder (baked `NROS_ENTRY_LOCATOR`/`NROS_ENTRY_DOMAIN_ID` rungs, hosted
2788 // default, then the env overlay in the Rust resolver behind `nros_cpp_init`).
2789 // This overload previously re-resolved the locator rung itself — a second,
2790 // partial copy of the same ladder (it never applied `NROS_ENTRY_DOMAIN_ID`),
2791 // duplicating what the 3-arg already does. Phase 266: unified default session
2792 // name "node".
2793 return init_in(locator, domain_id, "node");
2794}
2795
2796inline Result init_in(const char* locator, uint8_t domain_id, const char* session_name) {
2797 // Issue 1434 — forward RAW to the 4-arg overload, exactly as the 2-arg
2798 // forwards here (issue 0329): one ladder, one body. `nullptr` is "this
2799 // image declares no namespace", which is what every caller of this
2800 // overload has always meant.
2801 return init_in(locator, domain_id, session_name, nullptr);
2802}
2803
2804inline Result init_in(const char* locator, uint8_t domain_id, const char* session_name,
2805 const char* node_namespace) {
2806 // NROS_CPP_RET_INVALID_ARGUMENT = -3 (defined in nros_cpp_ffi.h
2807 // which isn't included from this header — duplicate the value
2808 // inline; generated header is the source of truth).
2809 if (session_name == nullptr) {
2810 return Result(-3);
2811 }
2812#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0) // hosted-family: hosted-console
2813 // Issue #39 — apply the same `$NROS_LOCATOR` / `$ROS_DOMAIN_ID` env
2814 // fallback as the 2-arg `init()` when `locator` is null / `domain_id` is
2815 // 0. This makes `init_in(argc, argv)` (which delegates here with a
2816 // null locator) honor the env overlay instead of passing a null locator
2817 // to the backend → TransportError / degraded session.
2818 // Issue 0330 — the hard local default is GONE entirely (it was a zenoh
2819 // fact in an RMW-blind header); the backend now supplies it.
2820 // RFC-0045 / issue #206 — the env overlay (NROS_LOCATOR / ROS_DOMAIN_ID /
2821 // NROS_NODE_NAME) moved into the shared Rust resolver behind
2822 // nros_cpp_init (precedence model A: hosted env > this baked chain >
2823 // compiled default; malformed or >232 ROS_DOMAIN_ID is an init ERROR,
2824 // never a silent domain 0). This header only assembles the baked rung.
2825#endif
2826 // Phase-287 W6 — compile-time connect defaults, so ONE portable source
2827 // works native + embedded. `NROS_ENTRY_LOCATOR` / `NROS_ENTRY_DOMAIN_ID`
2828 // are target compile definitions the embedded board gate bakes
2829 // (NanoRosEntry.cmake; Kconfig on Zephyr via <rclcpp/detail/main.hpp>); on native
2830 // they are undefined. Precedence (model A, RFC-0045/#206): env (hosted,
2831 // applied in the Rust resolver) > explicit arg > baked macro > default.
2832#ifdef NROS_ENTRY_LOCATOR
2833 if (locator == nullptr) {
2834 locator = NROS_ENTRY_LOCATOR;
2835 }
2836#endif
2837#ifdef NROS_ENTRY_DOMAIN_ID
2838 // Only the UNSET sentinel (0) folds the baked macro in; an explicit
2839 // argument — including kDomainIdExplicitZero (255) for a literal
2840 // domain 0 (issue #227) — passes through untouched.
2841 if (domain_id == 0) {
2842 domain_id = static_cast<uint8_t>(NROS_ENTRY_DOMAIN_ID);
2843 }
2844#endif
2845 // Issue 0330 — there is deliberately NO hosted "tcp/127.0.0.1:7447"
2846 // fallback here. That value is a *zenoh* fact and this header is
2847 // RMW-blind; a cyclonedds or xrce build must not carry it. A null
2848 // locator flows through `nros_cpp_init` (→ `None` → the RFC-0045
2849 // resolver's empty bottom rung) to whichever backend is linked, and
2850 // that backend applies its own default (zenoh:
2851 // `nros_rmw_zenoh::DEFAULT_LOCATOR`; xrce: its agent default;
2852 // cyclonedds: ignores the locator). Precedence is otherwise unchanged:
2853 // hosted env > explicit arg > baked macro > backend default.
2854
2855 // Phase 128.C.1 / phase-241.D3-rev — RMW-blind init. The selected
2856 // backend registers itself before `main` via its `.init_array` ctor
2857 // (RFC-0042 §D3.3), and `nros_cpp_init` additionally calls the weak
2858 // `nros_app_register_backends()` board-override hook for RTOS targets
2859 // where `.init_array` ctors do not run. No `#ifdef NROS_RMW_*` chain
2860 // here, no CMake-driven fan-out — the user's
2861 // `target_link_libraries(... NanoRos::Rmw::<name>)` is the only
2862 // selector.
2863 // Issue 1050 defect (3) — the RMW selector's BAKED rung. `NROS_ENTRY_RMW`
2864 // is a target compile definition the entry gate bakes, exactly like
2865 // `NROS_ENTRY_LOCATOR` / `NROS_ENTRY_DOMAIN_ID` above; undefined means the
2866 // image names no backend and the registry must contain exactly one.
2867 //
2868 // This is what the paragraph above could not express. "The user's
2869 // `target_link_libraries(... NanoRos::Rmw::<name>)` is the only selector"
2870 // is true of the LINK and false of the REGISTRY: a hosted archive
2871 // registers whatever it carries, from `.init_array`, before `main`.
2872#ifdef NROS_ENTRY_RMW
2873 const char* rmw = NROS_ENTRY_RMW;
2874#else
2875 const char* rmw = nullptr;
2876#endif
2877 // Issue 1434 — the namespace travels as the BAKED rung, like `rmw` above
2878 // and `session_name` beside it. `nullptr` reaches `nros_cpp_init_rmw`'s
2879 // own "unset" arm, which is the root; an EMPTY string is normalised here
2880 // rather than there, because a bake macro that did not resolve expands to
2881 // `""` and treating that as "the empty namespace" would be a configured
2882 // value where the image stated nothing (the same reasoning 1050 applied to
2883 // an empty `rmw` selector).
2884 if (node_namespace != nullptr && node_namespace[0] == '\0') {
2885 node_namespace = nullptr;
2886 }
2887 // Issue 1568 — the one refusal every executor-storage path takes: the
2888 // linked library says whether this TU's `NROS_CPP_EXECUTOR_STORAGE_SIZE`
2889 // (the header it was compiled against — on NuttX the committed snapshot)
2890 // is enough for the executor it builds. Refused rather than overrun.
2891 {
2893 nros_cpp_executor_storage_check(::rclcpp::Node::global_storage(),
2894 sizeof(::rclcpp::Node::GlobalStorageHolder<>::storage));
2895 if (chk != 0) {
2896 return Result(chk);
2897 }
2898 }
2900 ::rclcpp::Node::global_storage());
2901 // No flag to set: `nros_cpp_init_rmw` stamps the context tag, and
2902 // `global_initialized()` reads it.
2903 return Result(ret);
2904}
2905
2906inline Result init_with_rmw(const char* rmw, const char* locator, uint8_t domain_id,
2907 const char* session_name) {
2908 // NROS_CPP_RET_INVALID_ARGUMENT = -3; see the 3-arg overload for why the
2909 // value is duplicated here rather than included.
2910 if (session_name == nullptr) {
2911 return Result(-3);
2912 }
2913#ifdef NROS_ENTRY_LOCATOR
2914 if (locator == nullptr) {
2915 locator = NROS_ENTRY_LOCATOR;
2916 }
2917#endif
2918#ifdef NROS_ENTRY_DOMAIN_ID
2919 if (domain_id == 0) {
2920 domain_id = static_cast<uint8_t>(NROS_ENTRY_DOMAIN_ID);
2921 }
2922#endif
2923 // No `NROS_ENTRY_RMW` fallback here: an explicit argument that resolves to
2924 // nullptr is the caller saying "no selector", and quietly substituting the
2925 // bake would make this overload unable to express that.
2926 // Issue 1568 — the one refusal every executor-storage path takes: the
2927 // linked library says whether this TU's `NROS_CPP_EXECUTOR_STORAGE_SIZE`
2928 // (the header it was compiled against — on NuttX the committed snapshot)
2929 // is enough for the executor it builds. Refused rather than overrun.
2930 {
2932 nros_cpp_executor_storage_check(::rclcpp::Node::global_storage(),
2933 sizeof(::rclcpp::Node::GlobalStorageHolder<>::storage));
2934 if (chk != 0) {
2935 return Result(chk);
2936 }
2937 }
2938 nros_cpp_ret_t ret = nros_cpp_init_rmw(rmw, locator, domain_id, session_name, nullptr,
2939 ::rclcpp::Node::global_storage());
2940 // No flag to set: `nros_cpp_init_rmw` stamps the context tag, and
2941 // `global_initialized()` reads it.
2942 return Result(ret);
2943}
2944
2946 if (!::rclcpp::Node::global_initialized()) {
2947 return Result::success();
2948 }
2949 // No flag to clear: `nros_cpp_fini` unstamps the tag, which is what
2950 // `global_initialized()` reads. One owner of the fact.
2951 return Result(nros_cpp_fini(::rclcpp::Node::global_storage()));
2952}
2953
2954inline void* global_handle() {
2955 if (!::rclcpp::Node::global_initialized()) return nullptr;
2956 return ::rclcpp::Node::global_storage();
2957}
2958
2959namespace detail {
2964inline int32_t apply_ros_args(void* executor, int argc, char const* const* argv) {
2965 // No `--ros-args` scope means nothing for the parser to read; skipping the
2966 // call keeps a plain `main(argc, argv)` off the FFI entirely.
2967 bool scoped = false;
2968 for (int i = 0; argv != nullptr && i < argc && !scoped; ++i) {
2969 const char* a = argv[i];
2970 const char* want = "--ros-args";
2971 while (a != nullptr && *a != '\0' && *a == *want) {
2972 ++a;
2973 ++want;
2974 }
2975 scoped = a != nullptr && *a == '\0' && *want == '\0';
2976 }
2977 if (!scoped) {
2978 return 0;
2979 }
2980 char why[256] = {0};
2981 const int32_t rc = nros_cpp_install_argv_remaps(executor, argc, argv, why, sizeof(why));
2982 if (rc != 0) {
2983 // issue 1303 — two records through `nros_log`, each short enough for
2984 // its format buffer: the refused token (bounded here), then the fixed
2985 // text, whose length `say_refused` checks at compile time. As one
2986 // `NROS_ERROR` record it was over 870 bytes, which a real sink cannot
2987 // carry: the body is dropped and only an ellipsis is printed.
2988 RCLC_LOG_ERROR(::rclcpp::detail::log_handle(nullptr), "rclcpp::init: refused: %.120s",
2989 why[0] != '\0' ? why : "invalid executor handle or argv");
2990 NROS_RCLCPP_SAY_REFUSED(NROS_RCLCPP_REFUSE_INIT_ARGV);
2991 }
2992 return rc;
2993}
2994
2998inline Result init_honouring_ros_args(int argc, char const* const* argv, const char* name) {
2999 const int32_t checked = apply_ros_args(nullptr, argc, argv);
3000 if (checked != 0) {
3001 return Result(checked);
3002 }
3003 Result r = init_in(nullptr, 0, name);
3004 if (!r.ok()) {
3005 return r;
3006 }
3007 const int32_t installed = apply_ros_args(::rclcpp::global_handle(), argc, argv);
3008 if (installed != 0) {
3009 (void)shutdown_in();
3010 return Result(installed);
3011 }
3012 return r;
3013}
3014} // namespace detail
3015
3016// -- init honouring --ros-args --
3017//
3018// `init_in(argc, argv)` delegates to the 3-arg `init_in`, which reads the
3019// environment. The session name falls back to `"nros_cpp"`.
3020
3021inline Result init_in(int argc, char const* const* argv, const char* session_name) {
3022 const char* name = (session_name != nullptr) ? session_name : "nros_cpp";
3023 return detail::init_honouring_ros_args(argc, argv, name);
3024}
3025
3031inline bool ok() {
3032 return ::rclcpp::Node::global_initialized() && !::nros_cpp_termination_requested();
3033}
3034
3043inline Result create_node(::rclcpp::Node& out, const char* name, const char* ns = nullptr) {
3044 if (!::rclcpp::Node::global_initialized()) {
3045 return Result(ErrorCode::NotInitialized);
3046 }
3047 out.executor_handle_ = ::rclcpp::Node::global_storage();
3048 return ::rclcpp::Node::create(out, name, ns);
3049}
3050
3063inline Result create_node_on(::rclcpp::Node& out, void* executor_handle, const char* name,
3064 const char* ns = nullptr) {
3065 if (executor_handle == nullptr) {
3066 return Result(ErrorCode::NotInitialized);
3067 }
3068 out.executor_handle_ = executor_handle;
3069 return ::rclcpp::Node::create(out, name, ns);
3070}
3071
3075inline ResultOf<::rclcpp::Node> make_node(const char* name, const char* ns = nullptr) {
3077 Result r = create_node(n, name, ns);
3078 if (!r.ok()) return ResultOf<::rclcpp::Node>::error(r);
3079 return ResultOf<::rclcpp::Node>::ok(::rclcpp::tr::forward_rvalue(n));
3080}
3081
3082// -- Executor::create_node implementation (requires full Node definition) --
3083
3084inline Result Executor::create_node(::rclcpp::Node& out, const char* name, const char* ns) {
3085 if (!initialized_) return Result(ErrorCode::NotInitialized);
3086 out.executor_handle_ = storage_;
3087 return ::rclcpp::Node::create(out, name, ns);
3088}
3089
3090// -- Phase 104.C.9 — NodeBuilder ----------------------------------------
3091//
3092// Mirrors Rust's `Executor::node_builder(name).rmw(...).locator(...).
3093// domain_id(...).namespace(...).sched(...).build()` chain. The C++
3094// wrapper is value-typed and stack-allocated; it accumulates options
3095// into an inline `nros_cpp_node_options_t` and ships it to
3096// `nros_cpp_node_create_ex` on `.build()`.
3097//
3098// Usage:
3099// ```cpp
3100// rclcpp::Node node;
3101// RCLCPP_TRY(executor.node_builder("egress")
3102// .rmw("cyclonedds")
3103// .domain_id(0)
3104// .build(node));
3105// ```
3106
3108 public:
3109 NodeBuilder(void* executor_handle, const char* name)
3110 : executor_handle_(executor_handle), name_(name),
3112
3117 NodeBuilder& rmw(const char* name) {
3118 copy_bounded(name, options_.rmw_name, &options_.rmw_name_len, NROS_CPP_RMW_NAME_LEN);
3119 return *this;
3120 }
3121
3124 NodeBuilder& locator(const char* loc) {
3125 copy_bounded(loc, options_.locator, &options_.locator_len, NROS_CPP_LOCATOR_LEN);
3126 return *this;
3127 }
3128
3132 options_.domain_id_override = id;
3133 return *this;
3134 }
3135
3141 NodeBuilder& namespace_(const char* ns) {
3142 copy_bounded(ns, options_.namespace_, &options_.namespace_len, NROS_CPP_NAMESPACE_LEN);
3143 return *this;
3144 }
3145
3149 options_.sched_context_id = sc_id;
3150 return *this;
3151 }
3152
3155 if (!executor_handle_) return Result(ErrorCode::NotInitialized);
3156 out.executor_handle_ = executor_handle_;
3158 nros_cpp_node_create_ex(executor_handle_, name_, &options_, &out.handle_);
3159 if (ret == 0) {
3160 out.initialized_ = true;
3161 }
3162 return Result(ret);
3163 }
3164
3165 private:
3166 static void copy_bounded(const char* src, uint8_t* dst, size_t* dst_len, size_t cap) {
3167 size_t n = 0;
3168 if (src != nullptr) {
3169 while (src[n] != '\0' && n < cap) {
3170 dst[n] = static_cast<uint8_t>(src[n]);
3171 ++n;
3172 }
3173 }
3174 // Zero out the tail so stale bytes don't leak across reuses.
3175 for (size_t i = n; i < cap; ++i) {
3176 dst[i] = 0;
3177 }
3178 *dst_len = n;
3179 }
3180
3181 void* executor_handle_;
3182 const char* name_;
3183 nros_cpp_node_options_t options_;
3184};
3185
3186inline NodeBuilder Executor::node_builder(const char* name) {
3187 return NodeBuilder(initialized_ ? handle() : nullptr, name);
3188}
3189
3190namespace detail {
3196inline void finish_declared_callback_groups(::rclcpp::Node& node,
3198 const char* missing = nullptr;
3199 int len = 0;
3201 report_callback_group_mismatch(node.get_name(), "declared", missing, len, groups.list);
3202 node.set_error("a CALLBACK_GROUPS group was never created",
3204 }
3205}
3206} // namespace detail
3207
3208} // namespace rclcpp
3209
3210// The process-level verbs — `rclcpp::init()`, `rclcpp::ok()`,
3211// `rclcpp::shutdown()`, `rclcpp::spin_once()` — are declared in `nros.hpp`,
3212// which is where the free functions they name live. This header carries only
3213// what the node itself needs.
3214
3215#endif // NROS_RCLCPP_NODE_HPP
Definition node.hpp:218
Definition client.hpp:129
Definition guard_condition.hpp:50
Definition node.hpp:3107
NodeBuilder & locator(const char *loc)
Definition node.hpp:3124
NodeBuilder & sched(uint8_t sc_id)
Definition node.hpp:3148
Result build(::rclcpp::Node &out) const
Materialize the Node.
Definition node.hpp:3154
NodeBuilder(void *executor_handle, const char *name)
Definition node.hpp:3109
NodeBuilder & rmw(const char *name)
Definition node.hpp:3117
NodeBuilder & domain_id(uint32_t id)
Definition node.hpp:3131
NodeBuilder & namespace_(const char *ns)
Definition node.hpp:3141
Definition node.hpp:2695
void create_timer_in_group(const CallbackGroup &group, uint64_t period_ms)
Definition node.hpp:2737
void create_timer_in_group(const CallbackGroup &group, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
The plain C callback + ctx form, in a group, parked in the pool.
Definition node.hpp:2750
void create_wall_timer_in(uint64_t period_ms)
Definition node.hpp:2709
void create_wall_timer_in(uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
The plain C callback + ctx escape hatch, parked in the pool.
Definition node.hpp:2722
Definition node.hpp:703
Node & operator=(Node &&other)
Definition node.hpp:2498
bool check_declared_depth(const char *type_name, const char *topic, const ::rclcpp::QoS &qos)
Definition node.hpp:2342
Result create_subscription(::rclcpp::PollSubscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, const ::rclcpp::SubscriptionOptions &options)
Result create_client(::rclcpp::PollClient< S > &out, const char *service_name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
void create_subscription_in(const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
Result create_timer(Timer &out, const Clock &clock, uint64_t period_ms, C *self)
Definition node.hpp:2093
Result create_subscription_in_group(const ::rclcpp::CallbackGroup &group, ::rclcpp::Subscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, F callback, const ::rclcpp::SubscriptionOptions &options={})
const nros_cpp_node_t * ffi_handle() const
Definition node.hpp:1475
Result count_subscribers(const char *topic_name, size_t *out_count) const
Definition node.hpp:1584
SharedPtr shared_from_this()
Definition node.hpp:918
Result create_timer(Timer &out, const Clock &clock, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:2002
Time now() const
Definition node.hpp:1455
Result create_subscription(::rclcpp::PollSubscription< M > &out, const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
Result create_service(::rclcpp::Service< S > &out, const char *service_name, F callback, const ::rclcpp::QoS &qos=::rclcpp::QoS::services(), const ::rclcpp::ServiceOptions &options={})
const char * error_what() const
Definition node.hpp:823
Result get_parameter_types(const char *const *names, ::size_t count, ParameterType *out) const
Definition rclcpp.hpp:1338
Result create_publisher(::rclcpp::Publisher< M > &out, const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
typename::rclcpp::Service< S >::SharedPtr create_service(const char *, F, const ::rclcpp::QoS &=::rclcpp::QoS::services())
Result create_polling_action_client(::rclcpp::PollingActionClient< A > &out, const char *action_name)
Phase 122.3.d.b — Create an L1 polling-mode action client.
typename::rclcpp::Subscription< M >::SharedPtr create_subscription(const char *topic, ::size_t depth, Cb cb)
create_subscription<M>(topic, depth, callback).
::rclcpp::Handle< const Node > ConstSharedPtr
rclcpp::Node::ConstSharedPtr.
Definition node.hpp:905
Result create_timer_in_group(const ::rclcpp::CallbackGroup &group, Timer &out, uint64_t period_ms, C *self)
Definition node.hpp:2101
Node(const char *name, const char *ns, const ::rclcpp::NodeOptions &options)
Definition node.hpp:864
Result create_client(::rclcpp::Client< S > &out, const char *service_name, F callback, const ::rclcpp::QoS &qos=::rclcpp::QoS::services(), const ::rclcpp::ClientOptions &options={})
::rclcpp::Logger get_logger() const
Definition node.hpp:1428
Result create_service(::rclcpp::PollService< S > &out, const char *service_name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Result create_subscription(::rclcpp::Subscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, F callback, const ::rclcpp::SubscriptionOptions &options={})
::rclcpp::Owned<::rclcpp::PollService< S > > create_service(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Result create_subscription_with_info(::rclcpp::Subscription< M > &out, const char *topic, const ::rclcpp::QoS &qos, F callback, const ::rclcpp::SubscriptionOptions &options={})
bool get_parameter(const char *name, T &out) const
Definition rclcpp.hpp:1257
typename::rclcpp::Subscription< M >::SharedPtr create_subscription(const char *topic, const ::rclcpp::QoS &qos, Cb cb)
Result create_wall_timer(Timer &out, uint64_t period_ms, C *self)
Definition node.hpp:2081
Result get_node_names(nros_cpp_node_visit_fn visit, void *ctx) const
Definition node.hpp:1544
Result init(const char *name, const char *ns=nullptr)
Definition node.hpp:734
typename::rclcpp::Publisher< M >::SharedPtr create_publisher(const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
create_publisher<M>(topic, qos) — available on every target.
::rclcpp::TimerHandle create_wall_timer(::rclcpp::Duration period, Cb cb)
bool check_declared_param(const char *name, int passed_type)
Definition node.hpp:2425
Result describe_parameter(const char *name, ParameterDescriptor &out, char *text, ::size_t text_len) const
Definition rclcpp.hpp:1349
T declare_parameter(const char *name, T default_value=T())
rclcpp::Node::declare_parameter<T>(name, default).
Definition rclcpp.hpp:1234
const char * serialization_format() const
Definition node.hpp:1400
~Node()
Definition node.hpp:2478
static Result create(Node &out, const char *name, const char *ns=nullptr)
Definition node.hpp:1339
bool get_parameter_or(const char *name, T &out, T fallback) const
Definition rclcpp.hpp:1318
ParameterType get_parameter_type(const char *name) const
Definition rclcpp.hpp:1330
bool ok() const
Definition node.hpp:819
bool has_parameter(const char *name) const
rclcpp::Node::has_parameter(name).
Definition rclcpp.hpp:1289
void * executor_handle() const
Definition node.hpp:1499
Result list_parameters(const char *prefix, char *out_names, ::size_t name_stride, ::size_t max_names, ::size_t &count) const
Definition rclcpp.hpp:1387
::rclcpp::Publisher< M > create_publisher_in(const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
Definition node.hpp:2293
Result get_publishers_info_by_topic(const char *topic_name, ::rclcpp::TopicEndpointInfoVisitFn visit, void *ctx) const
Definition node.hpp:1670
::rclcpp::Handle< Node > SharedPtr
Definition node.hpp:903
typename::rclcpp::Service< S >::SharedPtr create_service(const char *name, F callback, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Callback-style service server (void(const S::Request&, S::Response&)).
Node(Node &&other)
Definition node.hpp:2486
Node(const char *name, const ::rclcpp::NodeOptions &options)
Definition node.hpp:857
Result create_guard_condition(::rclcpp::GuardCondition &out, nros_cpp_guard_callback_t callback, void *context=nullptr)
Definition node.hpp:2241
const char * get_namespace() const
Get the node namespace.
Definition node.hpp:1359
bool initialized() const
Definition node.hpp:925
void set_error(const char *what, int32_t code)
Definition node.hpp:2457
Node & nros_node()
Definition node.hpp:929
Result get_client_names_and_types_by_node(const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1633
Result count_publishers(const char *topic_name, size_t *out_count) const
Definition node.hpp:1577
Result start_parameter_services()
Definition node.hpp:1507
int32_t error_code() const
The raw error code of the first latched failure, or 0.
Definition node.hpp:830
const char * get_name() const
Get the node name.
Definition node.hpp:1353
bool is_valid() const
Check if the node is initialized and valid.
Definition node.hpp:1458
Result create_wall_timer(Timer &out, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:2048
Result remove_on_set_parameters_callback(ParameterCallbackHandle handle)
rclcpp::Node::remove_on_set_parameters_callback(handle).
Definition rclcpp.hpp:1404
Result create_timer_in_group(const ::rclcpp::CallbackGroup &group, Timer &out, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:2174
::rclcpp::CallbackGroup create_callback_group(const char *name)
Definition node.hpp:2146
Node(::rclcpp::NodeHandle handle, const char *name, const char *ns=nullptr)
Definition node.hpp:779
Node(const char *name, const char *ns=nullptr)
Definition node.hpp:724
Result set_parameters_atomically(const ParameterWrite *writes, ::size_t count)
Definition rclcpp.hpp:1393
::rclcpp::Owned<::rclcpp::PollClient< S > > create_client(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
typename::rclcpp::Publisher< M >::SharedPtr create_publisher(const char *topic, ::size_t depth)
create_publisher<M>(topic, depth) — the integer-depth spelling.
Result get_subscription_names_and_types_by_node(const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1610
bool check_declared_qos(const char *type_name, const char *topic, const ::rclcpp::QoS &qos)
Definition node.hpp:2375
Result create_timer_oneshot(Timer &out, uint64_t delay_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:2115
Result get_service_names_and_types(nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1564
Result get_topic_names_and_types(nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1556
Result set_parameter(const char *name, T value)
Definition rclcpp.hpp:1281
const Clock * get_clock() const
Const overload of get_clock().
Definition node.hpp:1446
Result get_fully_qualified_name(char *buf, size_t buf_len, size_t *out_len=nullptr) const
Definition node.hpp:1378
Result get_subscriptions_info_by_topic(const char *topic_name, ::rclcpp::TopicEndpointInfoVisitFn visit, void *ctx) const
Definition node.hpp:1693
typename::rclcpp::Client< S >::SharedPtr create_client(const char *, F, const ::rclcpp::QoS &=::rclcpp::QoS::services())
REFUSED — upstream's shared_ptr handler shape.
Result create_polling_action_server(::rclcpp::PollingActionServer< A > &out, const char *action_name)
Clock * get_clock()
Definition node.hpp:1444
const Node & nros_node() const
Const overload of [nros_node].
Definition node.hpp:931
Result get_publisher_names_and_types_by_node(const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1592
Result get_publishers_info_by_topic(const char *topic_name, nros_cpp_endpoint_info_visit_fn visit, void *ctx) const
Definition node.hpp:1655
void create_subscription_in_group(const ::rclcpp::CallbackGroup &group, const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
Node()
Definition node.hpp:707
Result create_publisher_in_group(const ::rclcpp::CallbackGroup &, ::rclcpp::Publisher< M > &out, const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
Definition node.hpp:2228
Result undeclare_parameter(const char *name)
Definition rclcpp.hpp:1326
const ::rclcpp::NodeOptions & get_node_options() const
Definition node.hpp:871
Result create_publisher(::rclcpp::Publisher< M > &out, const char *topic, const ::rclcpp::QoS &qos, const ::rclcpp::PublisherOptions &options)
Result create_polling_subscription(::rclcpp::PollingSubscription< M > &out, const char *topic, const ::rclcpp::QoS &qos=::rclcpp::QoS::default_profile())
ConstSharedPtr shared_from_this() const
Const overload of [shared_from_this].
Definition node.hpp:920
void set_qos_overrides(const nros_cpp_qos_override_t *overrides, size_t len)
Definition node.hpp:1487
Result add_on_set_parameters_callback(OnSetParametersCallbackType callback, void *context, ParameterCallbackHandle &out_handle)
Definition rclcpp.hpp:1397
Result init_on(void *executor_handle, const char *name, const char *ns=nullptr)
Definition node.hpp:745
Result get_subscriptions_info_by_topic(const char *topic_name, nros_cpp_endpoint_info_visit_fn visit, void *ctx) const
Definition node.hpp:1683
Result get_service_names_and_types_by_node(const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx) const
Definition node.hpp:1622
typename::rclcpp::Client< S >::SharedPtr create_client(const char *name, F callback, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Callback-style service client (void(const S::Response&)).
Definition node.hpp:217
Definition node.hpp:213
Definition node.hpp:210
Definition node.hpp:208
Definition node.hpp:207
Definition node.hpp:211
static constexpr QoS services()
Services profile: RELIABLE + VOLATILE + KEEP_LAST(10).
Definition qos.hpp:720
static constexpr QoS default_profile()
Default profile: RELIABLE + VOLATILE + KEEP_LAST(10).
Definition qos.hpp:714
Definition result.hpp:216
int32_t raw() const
Get the raw integer code (for FFI interop).
Definition result.hpp:235
Definition result.hpp:331
Definition timer.hpp:115
Definition timer.hpp:187
Definition qos.hpp:790
Definition client.hpp:95
Definition server.hpp:340
rclcpp::Executor — drives transport I/O and dispatches callbacks.
int nros_cpp_ret_t
Definition future.hpp:21
rclcpp::GuardCondition — cross-thread wake source.
void report_declared_depth_mismatch(const char *node_name, const char *topic, int declared, int passed)
Definition node.hpp:368
const char * declared_reliability_spelling(int ordinal)
The contract spelling of a reliability ordinal, for the report above.
Definition node.hpp:462
void report_declared_policy_mismatch(const char *node_name, const char *topic, const char *policy, const char *declared, const char *passed)
Definition node.hpp:438
void report_component_failure(const char *node_name, const char *what, int32_t code)
Definition node.hpp:339
constexpr int32_t DECLARED_PARAM_MISMATCH
Definition node.hpp:479
const char * declared_durability_spelling(int ordinal)
The contract spelling of a durability ordinal, for the report above.
Definition node.hpp:470
constexpr int32_t DECLARED_DEPTH_MISMATCH
Definition node.hpp:363
void report_callback_group_mismatch(const char *node_name, const char *how, const char *name, int len, const char *list)
Definition node.hpp:397
void report_declared_param_mismatch(const char *fqn, const char *param, const char *contract, int declared, int passed)
Definition node.hpp:485
constexpr int32_t DECLARED_QOS_MISMATCH
Definition node.hpp:433
Definition node.hpp:199
Definition rclcpp.hpp:75
constexpr uint8_t kDomainIdExplicitZero
Definition node.hpp:325
Result shutdown_in()
Definition node.hpp:2945
inline ::rclcpp::TimerHandle create_timer(NodeT &&node, ::rclcpp::Clock *clock, ::rclcpp::Duration period, CallbackT &&callback)
Definition rclcpp.hpp:1156
Result create_node(::rclcpp::Node &out, const char *name, const char *ns)
Definition node.hpp:3043
Result init_in(const char *locator=nullptr, uint8_t domain_id=0)
Definition node.hpp:2785
ResultOf< void > Result
Definition result.hpp:246
Result spin_once(int32_t timeout_ms=10)
Definition rclcpp.hpp:97
void init()
rclcpp::init() — the zero-argument form. ADOPT.
Definition rclcpp.hpp:400
bool ok()
Definition node.hpp:3031
Result spin_in()
Definition rclcpp.hpp:192
Result create_node_on(::rclcpp::Node &out, void *executor_handle, const char *name, const char *ns)
Definition node.hpp:3063
ResultOf<::rclcpp::Node > make_node(const char *name, const char *ns=nullptr)
Definition node.hpp:3075
void * global_handle()
Definition node.hpp:2954
@ AlreadyExists
Already exists (duplicate declare/register).
Result init_with_rmw(const char *rmw, const char *locator=nullptr, uint8_t domain_id=0, const char *session_name="node")
Definition node.hpp:2906
rclcpp::QoS — full DDS-shaped QoS settings (Phase 108.B.7).
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
Definition node.hpp:259
constexpr const char * resolve_namespace(const char *fallback) const
The launch namespace if one was declared, else fallback.
Definition node.hpp:307
constexpr bool valid() const
Definition node.hpp:299
constexpr NodeHandle()
Definition node.hpp:281
const char * launch_name
Definition node.hpp:264
void * executor
Definition node.hpp:260
constexpr NodeHandle(void *exec)
Definition node.hpp:284
const char * launch_namespace
Launch-declared namespace, same contract.
Definition node.hpp:266
const nros_cpp_qos_override_t * qos_overrides
Definition node.hpp:277
constexpr const char * resolve_name(const char *fallback) const
Definition node.hpp:303
constexpr NodeHandle(void *exec, const char *name, const char *ns, const nros_cpp_qos_override_t *overrides, size_t override_count)
Definition node.hpp:295
constexpr NodeHandle(void *exec, const char *name, const char *ns)
Definition node.hpp:289
size_t qos_override_count
Entries in qos_overrides; 0 = none.
Definition node.hpp:279
rclcpp::Timer — periodic callback driven by the executor.