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_CPP_NODE_HPP
11#define NROS_CPP_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)
17#include <cstdlib> // getenv — Phase 123.B.3 env-aware init
18#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0)
19#include <cstdio> // fopen — Phase 212.L.5 init_with_launch path-exists check
20#endif
21#endif
22
23// Phase 118.D: ffi.h MUST come before qos.hpp so qos.hpp's
24// `#ifndef NROS_CPP_FFI_H` guard sees the canonical types and skips
25// its local redefinitions.
26#include "nros_cpp_ffi.h"
27
28#include "nros/result.hpp"
29#include "nros/nros_cpp_config_generated.h"
30#include "nros/qos.hpp"
31// Phase 189.M3.1 — rclcpp-style named-options structs
32// (`SubscriptionOptions` / `PublisherOptions`) used by the 4-arg
33// `create_subscription` / `create_publisher` overloads below.
34#include "nros/options.hpp"
35// Phase 84.G8: heavy entity headers (publisher / subscription / service /
36// client / action_server / action_client) are no longer pulled in here.
37// Each entity header provides the out-of-line definition of its
38// corresponding `Node::create_X<T>()` template and includes `node.hpp`
39// itself. Consumers that #include `nros/nros.hpp` (the umbrella) still
40// get every entity + every create method via that path; consumers that
41// only want lightweight Node access can include this header directly
42// and pay for only the light entities (timer, guard_condition,
43// executor) below.
44#include "nros/timer.hpp"
45// Issue 0789 — `Node::get_clock()` / `Node::now()`. `clock.hpp` pulls in
46// `time.hpp` and `duration.hpp`, so a node that stamps a header needs no
47// further include.
48#include "nros/clock.hpp"
50#include "nros/executor.hpp"
51// Phase 273 (RFC-0047) — callback-group token (value type, no heap).
52#include "nros/callback_group.hpp"
53
54#ifdef NROS_RMW_CYCLONEDDS
55extern "C" int32_t nros_rmw_cyclonedds_register(void);
56#endif
57#if defined(NROS_RMW_XRCE) || defined(NROS_RMW_XRCE_CFFI)
58extern "C" int32_t nros_rmw_xrce_register(void);
59#endif
60#ifdef NROS_RMW_ZENOH_CFFI
61extern "C" int32_t nros_rmw_zenoh_register(void);
62#endif
63#ifdef NROS_RMW_UORB
64extern "C" int32_t nros_rmw_uorb_register(void);
65#endif
66
67// Issue #229 pin (cross-space, C++-FFI half): ErrorCode must stay
68// value-identical to the NROS_CPP_RET_* codes (nros_cpp_ffi.h, included
69// above) that every shim below feeds into Result().
70static_assert(NROS_CPP_RET_NOT_FOUND == -4 && NROS_CPP_RET_ALREADY_EXISTS == -5 &&
71 NROS_CPP_RET_FULL == -6 && NROS_CPP_RET_NOT_INIT == -7 &&
72 NROS_CPP_RET_TRY_AGAIN == -14 && NROS_CPP_RET_REENTRANT == -15 &&
73 NROS_CPP_RET_UNSUPPORTED == -16,
74 "nros_cpp_ret_t diverged from the shared numbering (issue #229)");
75
76namespace nros {
77
78// Phase 84.G8: forward declarations of the heavy entity class
79// templates. Full definitions live in the corresponding `*.hpp`,
80// which also provide the out-of-line `Node::create_X<>` template
81// bodies — consumers only pay for the entities they #include.
82template <typename M> class Publisher;
83template <typename M> class Subscription;
84template <typename S> class Service;
85template <typename S> class Client;
86template <typename A> class ActionServer;
87template <typename A> class ActionClient;
88// Phase 122.3.d.b — L1 polling-mode action wrappers.
89template <typename A> class PollingActionServer;
90template <typename A> class PollingActionClient;
91template <typename M> class PollingSubscription;
92// Phase 242.1 (RFC-0044) — rclcpp-faithful IS-A-node base. It wraps an owned
93// `Node` and creates that node against an executor-bound handle in its ctor, so
94// it needs friend access to set `executor_handle_` + call `Node::create`
95// (the same private-create pattern `Executor` / `NodeBuilder` already use).
96class ComponentNode;
97
105// `constexpr` at namespace scope is implicitly const, so it already has internal
106// linkage per TU — `inline` bought nothing and cost C++14 compatibility, which
107// nano-ros otherwise keeps (see `just check cpp`'s freestanding c++14 syntax
108// gate). PX4 builds every module with -std=gnu++14 -Werror, so an inline
109// variable here made <nros/nros.hpp> uncompilable in a PX4 module (phase-325 W2).
110constexpr uint8_t kDomainIdExplicitZero = 255;
111
122inline Result init(const char* locator = nullptr, uint8_t domain_id = 0);
123
143inline Result init(const char* locator, uint8_t domain_id, const char* session_name);
144
162inline Result init_with_rmw(const char* rmw, const char* locator = nullptr, uint8_t domain_id = 0,
163 const char* session_name = "node");
164
183inline Result init_with_launch_auto(int argc = 0, char** argv = nullptr,
184 const char* session_name = nullptr);
185
192inline Result init_with_launch(const char* path, int argc = 0, char** argv = nullptr,
193 const char* session_name = nullptr);
194
198inline Result shutdown();
199
211class Node {
212 public:
215 : handle_(), initialized_(false), executor_handle_(nullptr), clock_(NROS_CLOCK_ROS_TIME) {}
216
223 static Result create(Node& out, const char* name, const char* ns = nullptr) {
224 if (!out.executor_handle_) {
226 }
227
228 nros_cpp_ret_t ret = nros_cpp_node_create(out.executor_handle_, name, ns, &out.handle_);
229
230 if (ret == 0) {
231 out.initialized_ = true;
232 }
233 return Result(ret);
234 }
235
237 const char* get_name() const {
238 if (!initialized_) return "";
239 return nros_cpp_node_get_name(&handle_);
240 }
241
243 const char* get_namespace() const {
244 if (!initialized_) return "";
245 return nros_cpp_node_get_namespace(&handle_);
246 }
247
265 const char* serialization_format() const {
266 if (!initialized_) return nullptr;
267 return nros_cpp_node_get_serialization_format(&handle_);
268 }
269
276 const void* get_logger() const {
277 if (!initialized_) return nullptr;
278 return nros_cpp_node_get_logger(&handle_);
279 }
280
291 Clock* get_clock() { return &clock_; }
293 const Clock* get_clock() const { return &clock_; }
294
302 Time now() const { return clock_.now(); }
303
305 bool is_valid() const { return initialized_; }
306
322 const nros_cpp_node_t* ffi_handle() const { return initialized_ ? &handle_ : nullptr; }
323
334 void set_qos_overrides(const nros_cpp_qos_override_t* overrides, size_t len) {
335 if (initialized_) {
336 ::nros_cpp_node_set_qos_overrides(&handle_, overrides, len);
337 }
338 }
339
346 void* executor_handle() const { return initialized_ ? executor_handle_ : nullptr; }
347
354 template <typename M>
355 Result create_publisher(Publisher<M>& out, const char* topic,
356 const QoS& qos = QoS::default_profile());
357
370 template <typename M>
371 Result create_publisher(Publisher<M>& out, const char* topic, const QoS& qos,
372 const PublisherOptions& options);
373
380 template <typename M>
381 Result create_subscription(Subscription<M>& out, const char* topic,
382 const QoS& qos = QoS::default_profile());
383
396 template <typename M>
397 Result create_subscription(Subscription<M>& out, const char* topic, const QoS& qos,
398 const SubscriptionOptions& options);
399
418 template <
419 typename M, typename F,
420 typename = typename std::enable_if<std::is_convertible<F, void (*)(const M&)>::value>::type>
421 Result create_subscription(Subscription<M>& out, const char* topic, F callback,
422 const QoS& qos = QoS::default_profile(),
423 const SubscriptionOptions& options = {});
424
432 template <typename M, typename F,
433 typename = typename std::enable_if<
434 std::is_convertible<F, void (*)(const M&, const uint8_t*, size_t)>::value>::type>
435 Result create_subscription_with_info(Subscription<M>& out, const char* topic, F callback,
436 const QoS& qos = QoS::default_profile(),
437 const SubscriptionOptions& options = {});
438
439#if defined(NANO_ROS_SAFETY_E2E)
454 template <typename M, typename F,
455 typename = typename std::enable_if<std::is_convertible<
456 F, void (*)(const M&, const nros_cpp_integrity_status_t&)>::value>::type>
457 Result create_subscription_with_safety(Subscription<M>& out, const char* topic, F callback,
458 const QoS& qos = QoS::default_profile(),
459 const SubscriptionOptions& options = {});
460#endif // NANO_ROS_SAFETY_E2E
461
468 template <typename S>
469 Result create_service(Service<S>& out, const char* service_name,
470 const QoS& qos = QoS::services());
471
484 template <typename S, typename F,
485 typename = typename std::enable_if<std::is_convertible<
486 F, void (*)(const typename S::Request&, typename S::Response&)>::value>::type>
487 Result create_service(Service<S>& out, const char* service_name, F callback,
488 const QoS& qos = QoS::services(), const ServiceOptions& options = {});
489
496 template <typename S>
497 Result create_client(Client<S>& out, const char* service_name,
498 const QoS& qos = QoS::services());
499
510 template <typename S, typename F,
511 typename = typename std::enable_if<
512 std::is_convertible<F, void (*)(const typename S::Response&)>::value>::type>
513 Result create_client(Client<S>& out, const char* service_name, F callback,
514 const QoS& qos = QoS::services(), const ClientOptions& options = {});
515
526 template <typename A>
527 Result create_action_server(ActionServer<A>& out, const char* action_name,
528 const QoS& qos = QoS::services(),
529 const ActionServerOptions& options = {});
530
537 template <typename A>
538 Result create_action_client(ActionClient<A>& out, const char* action_name,
539 const QoS& qos = QoS::services());
540
544 template <typename A>
546
548 template <typename A>
550
556 template <typename M>
558 const QoS& qos = QoS::default_profile());
559
583 Result create_timer(Timer& out, const Clock& clock, uint64_t period_ms,
584 nros_cpp_timer_callback_t callback, void* context = nullptr) {
585 if (!initialized_) return Result(ErrorCode::NotInitialized);
586 size_t handle_id = 0;
587 nros_cpp_ret_t ret = nros_cpp_timer_create_on_clock(
588 executor_handle_, static_cast<uint8_t>(clock.get_clock_type()), period_ms, callback,
589 context, &handle_id);
590 if (ret == 0) {
591 out.executor_ = executor_handle_;
592 out.handle_id_ = handle_id;
593 out.initialized_ = true;
594 }
595 return Result(ret);
596 }
597
608 Result create_wall_timer(Timer& out, uint64_t period_ms, nros_cpp_timer_callback_t callback,
609 void* context = nullptr) {
610 if (!initialized_) return Result(ErrorCode::NotInitialized);
611 size_t handle_id = 0;
612 nros_cpp_ret_t ret =
613 nros_cpp_timer_create(executor_handle_, period_ms, callback, context, &handle_id);
614 if (ret == 0) {
615 out.executor_ = executor_handle_;
616 out.handle_id_ = handle_id;
617 out.initialized_ = true;
618 }
619 return Result(ret);
620 }
621
630 Result create_timer_oneshot(Timer& out, uint64_t delay_ms, nros_cpp_timer_callback_t callback,
631 void* context = nullptr) {
632 if (!initialized_) return Result(ErrorCode::NotInitialized);
633 size_t handle_id = 0;
634 nros_cpp_ret_t ret = nros_cpp_timer_create_oneshot(executor_handle_, delay_ms, callback,
635 context, &handle_id);
636 if (ret == 0) {
637 out.executor_ = executor_handle_;
638 out.handle_id_ = handle_id;
639 out.initialized_ = true;
640 }
641 return Result(ret);
642 }
643
644 // -- Phase 273 (RFC-0047) — Callback-group API -------------------------
645
654 CallbackGroup create_callback_group(const char* name) { return CallbackGroup{name}; }
655
667 Result create_timer_in(const CallbackGroup& group, Timer& out, uint64_t period_ms,
668 nros_cpp_timer_callback_t callback, void* context = nullptr) {
669 if (!initialized_) return Result(ErrorCode::NotInitialized);
670 size_t handle_id = 0;
671 nros_cpp_ret_t ret = nros_cpp_timer_create_in_group(
672 executor_handle_, &handle_, period_ms, callback, context, group.get_name(), &handle_id);
673 if (ret == 0) {
674 out.executor_ = executor_handle_;
675 out.handle_id_ = handle_id;
676 out.initialized_ = true;
677 }
678 return Result(ret);
679 }
680
695 template <
696 typename M, typename F,
697 typename = typename std::enable_if<std::is_convertible<F, void (*)(const M&)>::value>::type>
698 Result create_subscription_in(const CallbackGroup& group, Subscription<M>& out,
699 const char* topic, F callback,
700 const QoS& qos = QoS::default_profile(),
701 const SubscriptionOptions& options = {});
702
714 template <typename M>
715 Result create_publisher_in(const CallbackGroup& /* group */, Publisher<M>& out,
716 const char* topic, const QoS& qos = QoS::default_profile()) {
717 return create_publisher<M>(out, topic, qos);
718 }
719
727 Result create_guard_condition(GuardCondition& out, nros_cpp_guard_callback_t callback,
728 void* context = nullptr) {
729 if (!initialized_) return Result(ErrorCode::NotInitialized);
730 nros_cpp_ret_t ret =
731 nros_cpp_guard_condition_create(executor_handle_, callback, context, out.storage_);
732 if (ret == 0) {
733 out.initialized_ = true;
734 }
735 return Result(ret);
736 }
737
740 if (initialized_) {
741 nros_cpp_node_destroy(&handle_);
742 initialized_ = false;
743 }
744 }
745
746 // Move semantics (non-copyable)
747 Node(Node&& other)
748 : handle_(other.handle_), initialized_(other.initialized_),
749 executor_handle_(other.executor_handle_), clock_(other.clock_) {
750 other.initialized_ = false;
751 other.executor_handle_ = nullptr;
752 }
753
754 Node& operator=(Node&& other) {
755 if (this != &other) {
756 if (initialized_) {
757 nros_cpp_node_destroy(&handle_);
758 }
759 handle_ = other.handle_;
760 initialized_ = other.initialized_;
761 executor_handle_ = other.executor_handle_;
762 clock_ = other.clock_;
763 other.initialized_ = false;
764 other.executor_handle_ = nullptr;
765 }
766 return *this;
767 }
768
769 private:
770 Node(const Node&) = delete;
771 Node& operator=(const Node&) = delete;
772
773 nros_cpp_node_t handle_;
774 bool initialized_;
775 void* executor_handle_; // Set by nros::init() via friendship
776 // Issue 0789 — the node's own clock, ROS time as in rclcpp. Constructing
777 // it touches no platform service (only a steady clock records an epoch),
778 // so a Node in static storage stays as cheap to create as it was.
779 Clock clock_;
780
781 friend class Executor;
782 friend class NodeBuilder;
783 friend class ComponentNode; // Phase 242.1 — ctor-creates the owned node
784 friend Result init(const char* locator, uint8_t domain_id);
785 friend Result init(const char* locator, uint8_t domain_id, const char* session_name);
786 friend Result init_with_rmw(const char* rmw, const char* locator, uint8_t domain_id,
787 const char* session_name);
788 friend Result shutdown();
789 friend bool ok();
790 friend Result create_node(Node& out, const char* name, const char* ns);
791 friend Result create_node_on(Node& out, void* executor_handle, const char* name,
792 const char* ns);
793 friend Result spin_once(int32_t timeout_ms);
794 friend Result spin();
795 friend Result spin(uint32_t duration_ms, int32_t poll_ms);
796 friend void* global_handle();
797
798 // Global executor inline storage for init/shutdown free functions.
799 //
800 // Use a template-static-member trick instead of a function-local static.
801 // Function-local statics need __cxa_guard_acquire/release on first-call
802 // initialisation; on NuttX the resulting guard logic returns NULL for
803 // the storage pointer (observed empirically with LTO on armv7a-nuttx-eabihf,
804 // even with constant-initialisation `= {}`). A template static member is
805 // emitted into .bss like a file-scope variable and gets COMDAT-folded by
806 // the linker, sidestepping the guarded-init path entirely.
807 template <int = 0> struct GlobalStorageHolder {
808 alignas(8) static uint8_t storage[NROS_CPP_EXECUTOR_STORAGE_SIZE];
809 static bool initialized;
810 };
811 static uint8_t* global_storage() { return GlobalStorageHolder<>::storage; }
812 static bool& global_initialized() { return GlobalStorageHolder<>::initialized; }
813};
814
815// Out-of-class definitions for Node::GlobalStorageHolder<> — the template
816// machinery means these get emitted as COMDAT symbols, so multiple TUs
817// including this header all collapse to a single .bss allocation.
818template <int N>
819alignas(8) uint8_t Node::GlobalStorageHolder<N>::storage[NROS_CPP_EXECUTOR_STORAGE_SIZE] = {};
820template <int N> bool Node::GlobalStorageHolder<N>::initialized = false;
821
822// -- Free function implementations --
823
824inline Result init(const char* locator, uint8_t domain_id) {
825 // Issue 0329 — forward RAW to the 3-arg overload, which resolves the whole
826 // ladder (baked `NROS_ENTRY_LOCATOR`/`NROS_ENTRY_DOMAIN_ID` rungs, hosted
827 // default, then the env overlay in the Rust resolver behind `nros_cpp_init`).
828 // This overload previously re-resolved the locator rung itself — a second,
829 // partial copy of the same ladder (it never applied `NROS_ENTRY_DOMAIN_ID`),
830 // duplicating what the 3-arg already does. Phase 266: unified default session
831 // name "node".
832 return init(locator, domain_id, "node");
833}
834
835inline Result init(const char* locator, uint8_t domain_id, const char* session_name) {
836 // NROS_CPP_RET_INVALID_ARGUMENT = -3 (defined in nros_cpp_ffi.h
837 // which isn't included from this header — duplicate the value
838 // inline; generated header is the source of truth).
839 if (session_name == nullptr) {
840 return Result(-3);
841 }
842#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0)
843 // Issue #39 — apply the same `$NROS_LOCATOR` / `$ROS_DOMAIN_ID` env
844 // fallback as the 2-arg `init()` when `locator` is null / `domain_id` is
845 // 0. This makes `init_with_launch_auto()` (which delegates here with a
846 // null locator) honor the env overlay instead of passing a null locator
847 // to the backend → TransportError / degraded session.
848 // Issue 0330 — the hard local default is GONE entirely (it was a zenoh
849 // fact in an RMW-blind header); the backend now supplies it.
850 // RFC-0045 / issue #206 — the env overlay (NROS_LOCATOR / ROS_DOMAIN_ID /
851 // NROS_NODE_NAME) moved into the shared Rust resolver behind
852 // nros_cpp_init (precedence model A: hosted env > this baked chain >
853 // compiled default; malformed or >232 ROS_DOMAIN_ID is an init ERROR,
854 // never a silent domain 0). This header only assembles the baked rung.
855#endif
856 // Phase-287 W6 — compile-time connect defaults, so ONE portable source
857 // works native + embedded. `NROS_ENTRY_LOCATOR` / `NROS_ENTRY_DOMAIN_ID`
858 // are target compile definitions the embedded board gate bakes
859 // (NanoRosEntry.cmake; Kconfig on Zephyr via <nros/main.hpp>); on native
860 // they are undefined. Precedence (model A, RFC-0045/#206): env (hosted,
861 // applied in the Rust resolver) > explicit arg > baked macro > default.
862#ifdef NROS_ENTRY_LOCATOR
863 if (locator == nullptr) {
864 locator = NROS_ENTRY_LOCATOR;
865 }
866#endif
867#ifdef NROS_ENTRY_DOMAIN_ID
868 // Only the UNSET sentinel (0) folds the baked macro in; an explicit
869 // argument — including kDomainIdExplicitZero (255) for a literal
870 // domain 0 (issue #227) — passes through untouched.
871 if (domain_id == 0) {
872 domain_id = static_cast<uint8_t>(NROS_ENTRY_DOMAIN_ID);
873 }
874#endif
875 // Issue 0330 — there is deliberately NO hosted "tcp/127.0.0.1:7447"
876 // fallback here. That value is a *zenoh* fact and this header is
877 // RMW-blind; a cyclonedds or xrce build must not carry it. A null
878 // locator flows through `nros_cpp_init` (→ `None` → the RFC-0045
879 // resolver's empty bottom rung) to whichever backend is linked, and
880 // that backend applies its own default (zenoh:
881 // `nros_rmw_zenoh::DEFAULT_LOCATOR`; xrce: its agent default;
882 // cyclonedds: ignores the locator). Precedence is otherwise unchanged:
883 // hosted env > explicit arg > baked macro > backend default.
884
885 // Phase 128.C.1 / phase-241.D3-rev — RMW-blind init. The selected
886 // backend registers itself before `main` via its `.init_array` ctor
887 // (RFC-0042 §D3.3), and `nros_cpp_init` additionally calls the weak
888 // `nros_app_register_backends()` board-override hook for RTOS targets
889 // where `.init_array` ctors do not run. No `#ifdef NROS_RMW_*` chain
890 // here, no CMake-driven fan-out — the user's
891 // `target_link_libraries(... NanoRos::Rmw::<name>)` is the only
892 // selector.
893 // Issue 1050 defect (3) — the RMW selector's BAKED rung. `NROS_ENTRY_RMW`
894 // is a target compile definition the entry gate bakes, exactly like
895 // `NROS_ENTRY_LOCATOR` / `NROS_ENTRY_DOMAIN_ID` above; undefined means the
896 // image names no backend and the registry must contain exactly one.
897 //
898 // This is what the paragraph above could not express. "The user's
899 // `target_link_libraries(... NanoRos::Rmw::<name>)` is the only selector"
900 // is true of the LINK and false of the REGISTRY: a hosted archive
901 // registers whatever it carries, from `.init_array`, before `main`.
902#ifdef NROS_ENTRY_RMW
903 const char* rmw = NROS_ENTRY_RMW;
904#else
905 const char* rmw = nullptr;
906#endif
907 nros_cpp_ret_t ret =
908 nros_cpp_init_rmw(rmw, locator, domain_id, session_name, nullptr, Node::global_storage());
909 if (ret == 0) {
910 Node::global_initialized() = true;
911 }
912 return Result(ret);
913}
914
915inline Result init_with_rmw(const char* rmw, const char* locator, uint8_t domain_id,
916 const char* session_name) {
917 // NROS_CPP_RET_INVALID_ARGUMENT = -3; see the 3-arg overload for why the
918 // value is duplicated here rather than included.
919 if (session_name == nullptr) {
920 return Result(-3);
921 }
922#ifdef NROS_ENTRY_LOCATOR
923 if (locator == nullptr) {
924 locator = NROS_ENTRY_LOCATOR;
925 }
926#endif
927#ifdef NROS_ENTRY_DOMAIN_ID
928 if (domain_id == 0) {
929 domain_id = static_cast<uint8_t>(NROS_ENTRY_DOMAIN_ID);
930 }
931#endif
932 // No `NROS_ENTRY_RMW` fallback here: an explicit argument that resolves to
933 // nullptr is the caller saying "no selector", and quietly substituting the
934 // bake would make this overload unable to express that.
935 nros_cpp_ret_t ret =
936 nros_cpp_init_rmw(rmw, locator, domain_id, session_name, nullptr, Node::global_storage());
937 if (ret == 0) {
938 Node::global_initialized() = true;
939 }
940 return Result(ret);
941}
942
943inline Result shutdown() {
944 if (!Node::global_initialized()) {
945 return Result::success();
946 }
947 nros_cpp_ret_t ret = nros_cpp_fini(Node::global_storage());
948 Node::global_initialized() = false;
949 return Result(ret);
950}
951
952// -- Phase 212.L.5 launch-aware init --
953//
954// Both `init_with_launch_auto` and `init_with_launch(path)` delegate to
955// the existing 3-arg `init` after resolving the launch overlay (today:
956// env vars only — see header docs for the follow-up plan). The session
957// name falls back to `"nros_cpp"` so existing callsites keep working.
958
959inline Result init_with_launch_auto(int argc, char** argv, const char* session_name) {
960 (void)argc;
961 (void)argv;
962 // TODO (Phase 212.L.5 follow-up):
963 // 1. If $NROS_RUNTIME_OVERLAY is set, read the JSON sidecar and
964 // fold its params/remaps/env into the init call.
965 // 2. Else walk <CARGO_MANIFEST_DIR>/launch/* and parse the XML
966 // in-process.
967 // For now the env overlay (NROS_LOCATOR / ROS_DOMAIN_ID consumed by
968 // the 2-arg `init`) is the only channel.
969 const char* name = (session_name != nullptr) ? session_name : "nros_cpp";
970 return init(nullptr, 0, name);
971}
972
973inline Result init_with_launch(const char* path, int argc, char** argv, const char* session_name) {
974 (void)argc;
975 (void)argv;
976 // NROS_CPP_RET_INVALID_ARGUMENT = -3 (mirrors the 3-arg init guard).
977 if (path == nullptr) {
978 return Result(-3);
979 }
980#if defined(NROS_CPP_STD) || (__STDC_HOSTED__ + 0)
981 // Verify the file exists so misspelled paths fail fast at init time
982 // instead of surfacing as a silently-empty overlay later.
983 if (FILE* f = std::fopen(path, "rb")) {
984 std::fclose(f);
985 } else {
987 }
988#endif
989 // TODO (Phase 212.L.5 follow-up): parse `path` as launch XML and
990 // fold params/remaps/env into the init call. Today the env overlay
991 // is the only channel.
992 const char* name = (session_name != nullptr) ? session_name : "nros_cpp";
993 return init(nullptr, 0, name);
994}
995
997inline bool ok() {
998 return Node::global_initialized();
999}
1000
1008inline Result create_node(Node& out, const char* name, const char* ns = nullptr) {
1009 if (!Node::global_initialized()) {
1011 }
1012 out.executor_handle_ = Node::global_storage();
1013 return Node::create(out, name, ns);
1014}
1015
1027inline Result create_node_on(Node& out, void* executor_handle, const char* name,
1028 const char* ns = nullptr) {
1029 if (executor_handle == nullptr) {
1031 }
1032 out.executor_handle_ = executor_handle;
1033 return Node::create(out, name, ns);
1034}
1035
1039inline Expected<Node> make_node(const char* name, const char* ns = nullptr) {
1040 Node n;
1041 Result r = create_node(n, name, ns);
1042 if (!r.ok()) return Expected<Node>::error(r);
1043 return Expected<Node>::ok(::std::move(n));
1044}
1045
1046// -- Executor::create_node implementation (requires full Node definition) --
1047
1048inline Result Executor::create_node(Node& out, const char* name, const char* ns) {
1049 if (!initialized_) return Result(ErrorCode::NotInitialized);
1050 out.executor_handle_ = storage_;
1051 return Node::create(out, name, ns);
1052}
1053
1054// -- Phase 104.C.9 — NodeBuilder ----------------------------------------
1055//
1056// Mirrors Rust's `Executor::node_builder(name).rmw(...).locator(...).
1057// domain_id(...).namespace(...).sched(...).build()` chain. The C++
1058// wrapper is value-typed and stack-allocated; it accumulates options
1059// into an inline `nros_cpp_node_options_t` and ships it to
1060// `nros_cpp_node_create_ex` on `.build()`.
1061//
1062// Usage:
1063// ```cpp
1064// nros::Node node;
1065// NROS_TRY(executor.node_builder("egress")
1066// .rmw("cyclonedds")
1067// .domain_id(0)
1068// .build(node));
1069// ```
1070
1072 public:
1073 NodeBuilder(void* executor_handle, const char* name)
1074 : executor_handle_(executor_handle), name_(name),
1075 options_(nros_cpp_node_get_default_options()) {}
1076
1081 NodeBuilder& rmw(const char* name) {
1082 copy_bounded(name, options_.rmw_name, &options_.rmw_name_len, NROS_CPP_RMW_NAME_LEN);
1083 return *this;
1084 }
1085
1088 NodeBuilder& locator(const char* loc) {
1089 copy_bounded(loc, options_.locator, &options_.locator_len, NROS_CPP_LOCATOR_LEN);
1090 return *this;
1091 }
1092
1095 NodeBuilder& domain_id(uint32_t id) {
1096 options_.domain_id_override = id;
1097 return *this;
1098 }
1099
1102 NodeBuilder& namespace_(const char* ns) {
1103 copy_bounded(ns, options_.namespace_, &options_.namespace_len, NROS_CPP_NAMESPACE_LEN);
1104 return *this;
1105 }
1106
1109 NodeBuilder& sched(uint8_t sc_id) {
1110 options_.sched_context_id = sc_id;
1111 return *this;
1112 }
1113
1115 Result build(Node& out) const {
1116 if (!executor_handle_) return Result(ErrorCode::NotInitialized);
1117 out.executor_handle_ = executor_handle_;
1118 nros_cpp_ret_t ret =
1119 nros_cpp_node_create_ex(executor_handle_, name_, &options_, &out.handle_);
1120 if (ret == 0) {
1121 out.initialized_ = true;
1122 }
1123 return Result(ret);
1124 }
1125
1126 private:
1127 static void copy_bounded(const char* src, uint8_t* dst, size_t* dst_len, size_t cap) {
1128 size_t n = 0;
1129 if (src != nullptr) {
1130 while (src[n] != '\0' && n < cap) {
1131 dst[n] = static_cast<uint8_t>(src[n]);
1132 ++n;
1133 }
1134 }
1135 // Zero out the tail so stale bytes don't leak across reuses.
1136 for (size_t i = n; i < cap; ++i) {
1137 dst[i] = 0;
1138 }
1139 *dst_len = n;
1140 }
1141
1142 void* executor_handle_;
1143 const char* name_;
1144 nros_cpp_node_options_t options_;
1145};
1146
1147inline NodeBuilder Executor::node_builder(const char* name) {
1148 return NodeBuilder(initialized_ ? handle() : nullptr, name);
1149}
1150
1151} // namespace nros
1152
1153#endif // NROS_CPP_NODE_HPP
NodeBuilder node_builder(const char *name)
Definition node.hpp:1147
void * handle()
Definition executor.hpp:401
Result create_node(Node &out, const char *name, const char *ns=nullptr)
Definition node.hpp:1048
Definition result.hpp:198
ErrorCode error() const
Definition result.hpp:221
bool ok() const
Definition result.hpp:214
Definition guard_condition.hpp:45
Definition node.hpp:1071
NodeBuilder & domain_id(uint32_t id)
Definition node.hpp:1095
NodeBuilder & sched(uint8_t sc_id)
Definition node.hpp:1109
NodeBuilder(void *executor_handle, const char *name)
Definition node.hpp:1073
NodeBuilder & rmw(const char *name)
Definition node.hpp:1081
NodeBuilder & locator(const char *loc)
Definition node.hpp:1088
NodeBuilder & namespace_(const char *ns)
Definition node.hpp:1102
Result build(Node &out) const
Materialize the Node.
Definition node.hpp:1115
Definition node.hpp:211
friend Result init(const char *locator, uint8_t domain_id)
Definition node.hpp:824
Time now() const
Definition node.hpp:302
Result create_subscription_in(const CallbackGroup &group, Subscription< M > &out, const char *topic, F callback, const QoS &qos=QoS::default_profile(), const SubscriptionOptions &options={})
Definition subscription.hpp:706
Result create_polling_action_server(PollingActionServer< A > &out, const char *action_name)
Result create_timer(Timer &out, const Clock &clock, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:583
Result create_client(Client< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition client.hpp:345
friend class ComponentNode
Definition node.hpp:783
Result create_action_server(ActionServer< A > &out, const char *action_name, const QoS &qos=QoS::services(), const ActionServerOptions &options={})
Definition action_server.hpp:466
friend class NodeBuilder
Definition node.hpp:782
const void * get_logger() const
Definition node.hpp:276
friend bool ok()
Check if the nros session is initialized.
Definition node.hpp:997
const nros_cpp_node_t * ffi_handle() const
Definition node.hpp:322
const char * get_namespace() const
Get the node namespace.
Definition node.hpp:243
CallbackGroup create_callback_group(const char *name)
Definition node.hpp:654
Result create_publisher_in(const CallbackGroup &, Publisher< M > &out, const char *topic, const QoS &qos=QoS::default_profile())
Definition node.hpp:715
friend Result spin()
Definition nros.hpp:157
Result create_subscription(Subscription< M > &out, const char *topic, const QoS &qos=QoS::default_profile())
Definition subscription.hpp:608
Result create_action_client(ActionClient< A > &out, const char *action_name, const QoS &qos=QoS::services())
Definition action_client.hpp:482
void * executor_handle() const
Definition node.hpp:346
friend Result create_node(Node &out, const char *name, const char *ns)
Definition node.hpp:1008
Clock * get_clock()
Definition node.hpp:291
friend class Executor
Definition node.hpp:781
Node(Node &&other)
Definition node.hpp:747
Result create_service(Service< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition service.hpp:246
const Clock * get_clock() const
Const overload of get_clock().
Definition node.hpp:293
~Node()
Destructor — releases node resources.
Definition node.hpp:739
Result create_publisher(Publisher< M > &out, const char *topic, const QoS &qos=QoS::default_profile())
Definition publisher.hpp:274
static Result create(Node &out, const char *name, const char *ns=nullptr)
Definition node.hpp:223
Result create_subscription_with_info(Subscription< M > &out, const char *topic, F callback, const QoS &qos=QoS::default_profile(), const SubscriptionOptions &options={})
Definition subscription.hpp:743
const char * serialization_format() const
Definition node.hpp:265
Node()
Default constructor — creates an uninitialized node.
Definition node.hpp:214
Result create_wall_timer(Timer &out, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:608
bool is_valid() const
Check if the node is initialized and valid.
Definition node.hpp:305
Result create_polling_action_client(PollingActionClient< A > &out, const char *action_name)
Phase 122.3.d.b — Create an L1 polling-mode action client.
friend void * global_handle()
Definition nros.hpp:63
friend Result spin_once(int32_t timeout_ms)
Definition nros.hpp:75
Result create_timer_oneshot(Timer &out, uint64_t delay_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:630
Result create_timer_in(const CallbackGroup &group, Timer &out, uint64_t period_ms, nros_cpp_timer_callback_t callback, void *context=nullptr)
Definition node.hpp:667
Result create_guard_condition(GuardCondition &out, nros_cpp_guard_callback_t callback, void *context=nullptr)
Definition node.hpp:727
void set_qos_overrides(const nros_cpp_qos_override_t *overrides, size_t len)
Definition node.hpp:334
friend Result shutdown()
Definition node.hpp:943
Result create_polling_subscription(PollingSubscription< M > &out, const char *topic, const QoS &qos=QoS::default_profile())
friend Result create_node_on(Node &out, void *executor_handle, const char *name, const char *ns)
Definition node.hpp:1027
const char * get_name() const
Get the node name.
Definition node.hpp:237
friend Result init_with_rmw(const char *rmw, const char *locator, uint8_t domain_id, const char *session_name)
Definition node.hpp:915
Node & operator=(Node &&other)
Definition node.hpp:754
Definition node.hpp:90
Definition node.hpp:89
Definition node.hpp:91
Definition publisher.hpp:48
Definition qos.hpp:173
static constexpr QoS default_profile()
Default profile: RELIABLE + VOLATILE + KEEP_LAST(10).
Definition qos.hpp:328
static constexpr QoS services()
Services profile: RELIABLE + VOLATILE + KEEP_LAST(10).
Definition qos.hpp:334
Definition result.hpp:90
static constexpr Result success()
Named constructors.
Definition result.hpp:112
bool ok() const
Returns true if the operation succeeded.
Definition result.hpp:100
Definition subscription.hpp:100
Definition timer.hpp:42
nros::Executor — drives transport I/O and dispatches callbacks.
int nros_cpp_ret_t
Definition future.hpp:21
nros::GuardCondition — cross-thread wake source.
Definition nros.hpp:55
Result init_with_rmw(const char *rmw, const char *locator=nullptr, uint8_t domain_id=0, const char *session_name="node")
Definition node.hpp:915
Result init_with_launch(const char *path, int argc=0, char **argv=nullptr, const char *session_name=nullptr)
Definition node.hpp:973
Expected< Node > make_node(const char *name, const char *ns=nullptr)
Definition node.hpp:1039
Result shutdown()
Definition node.hpp:943
bool ok()
Check if the nros session is initialized.
Definition node.hpp:997
constexpr uint8_t kDomainIdExplicitZero
Definition node.hpp:110
Result init_with_launch_auto(int argc=0, char **argv=nullptr, const char *session_name=nullptr)
Definition node.hpp:959
Result init(const char *locator=nullptr, uint8_t domain_id=0)
Definition node.hpp:824
Result create_node(Node &out, const char *name, const char *ns=nullptr)
Definition node.hpp:1008
Result create_node_on(Node &out, void *executor_handle, const char *name, const char *ns=nullptr)
Definition node.hpp:1027
nros::QoS — full DDS-shaped QoS settings (Phase 108.B.7).
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
nros::Timer — periodic callback driven by the executor.