nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
subscription.hpp
Go to the documentation of this file.
1// nros-cpp: Subscription class
2// Freestanding C++ — no exceptions, no STL required
3
10#ifndef NROS_CPP_SUBSCRIPTION_HPP
11#define NROS_CPP_SUBSCRIPTION_HPP
12
13#include <cstdint>
14#include <cstddef>
15#include <string.h> // memcpy — `<cstring>` isn't in Zephyr's minimal libcpp
16
17#include "nros/config.hpp"
18#include "nros/result.hpp"
19#include "nros/size_bound.hpp" // nros::rx_buffer_capacity<M> — the receive-buffer size
20// RFC-0088 D5 — NROS_CPP_ASSERT_MESSAGE_FORMAT, expanded in the creators below.
22#include "nros/stream.hpp"
23
24#include "nros_cpp_ffi.h"
25
26// Phase 189.M3.x — `nros_cpp_subscription_register` is excluded from cbindgen
27// (its Rust signature uses `RawSubscriptionCallback`, an external-crate type
28// alias cbindgen names without defining). Declare it locally with a plain
29// function-pointer typedef matching the ABI (`void(data, len, ctx)`), mirroring
30// the service.hpp callback-register treatment.
31extern "C" {
32typedef void (*nros_cpp_subscription_message_callback_t)(const uint8_t* data, size_t len,
33 void* ctx);
34
35// phase-402: the trailing `sched_context` / `callback_group` arguments moved
36// into `nros_cpp_subscription_options_t` (defined by the cbindgen output above);
37// a NULL `options` is all-defaults, i.e. the pre-phase-402 behaviour.
38nros_cpp_ret_t nros_cpp_subscription_register(const nros_cpp_node_t* node, const char* topic,
39 const char* type_name, const char* type_hash,
42 void* context, size_t* out_handle_id,
43 const nros_cpp_subscription_options_t* options);
44
45// Phase 189.M3.4 — callback-style register that also delivers the sample's wire
46// attachment (5-arg trampoline). Same cbindgen-exclusion reason as above.
47typedef void (*nros_cpp_subscription_message_info_callback_t)(const uint8_t* data, size_t len,
48 const uint8_t* attachment,
49 size_t attachment_len, void* ctx);
50
52 const nros_cpp_node_t* node, const char* topic, const char* type_name, const char* type_hash,
54 size_t* out_handle_id, const nros_cpp_subscription_options_t* options);
55// Phase 269 W3 — callback-style subscription that delivers the sample's E2E
56// integrity status alongside the CDR bytes. Same cbindgen-exclusion reason as
57// above (takes `RawSubscriptionSafetyCallback`, an external nros-node alias
58// gated on the `safety-e2e` feature). The 6-arg trampoline unpacks the three
59// integrity scalars; `subscription.hpp` repacks them into
60// `nros_cpp_integrity_status_t` for the typed user handler. Gated on the
61// `NANO_ROS_SAFETY_E2E` build feature (lowered from `[system].features =
62// ["safety"]` via `NanoRosCapabilities.cmake`).
63#if defined(NANO_ROS_SAFETY_E2E)
64typedef void (*nros_cpp_subscription_validated_callback_t)(const uint8_t* data, size_t len,
65 int64_t gap, bool duplicate,
66 int8_t crc_valid, void* ctx);
67
68nros_cpp_ret_t nros_cpp_subscription_register_validated(
69 const nros_cpp_node_t* node, const char* topic, const char* type_name, const char* type_hash,
70 nros_cpp_qos_t qos, nros_cpp_subscription_validated_callback_t callback, void* context,
71 size_t* out_handle_id, const nros_cpp_subscription_options_t* options);
72#endif // NANO_ROS_SAFETY_E2E
73} // extern "C"
74
75namespace nros {
76
80static constexpr size_t SUBSCRIPTION_TOPIC_NAME_MAX = 256;
81
100template <typename M> class Subscription {
101 public:
105 using TypedSubscriptionFn = void (*)(const M& msg);
106 using TypedSubscriptionFnWithCtx = void (*)(const M& msg, void* ctx);
107 // Phase 189.M3.4 — callback-with-attachment handler (`bridge_origin` etc.).
108 using TypedSubscriptionInfoFn = void (*)(const M& msg, const uint8_t* attachment,
109 size_t attachment_len);
110 // Phase 269 W3 — callback-with-integrity handler: receives the deserialized
111 // message plus the sample's E2E CRC/sequence status. Requires
112 // `NANO_ROS_SAFETY_E2E` (lowered from `[system].features = ["safety"]`).
113#if defined(NANO_ROS_SAFETY_E2E)
114 using TypedSubscriptionSafetyFn = void (*)(const M& msg,
115 const nros_cpp_integrity_status_t& integrity);
116#endif // NANO_ROS_SAFETY_E2E
117
128 Result take(M& msg) { return take_sized<::nros::rx_buffer_capacity<M>::value>(msg); }
129
138 template <size_t Cap> Result take_sized(M& msg) {
139 if (!initialized_) return Result(ErrorCode::NotInitialized);
140 uint8_t buf[Cap];
141 size_t len = 0;
142 nros_cpp_ret_t ret =
143 nros_cpp_subscription_take_serialized(storage_, buf, sizeof(buf), &len);
144 if (ret != 0) return Result(ret);
145 if (len == 0) return Result(ErrorCode::TryAgain);
146 if (M::ffi_deserialize(buf, len, &msg) != 0) return Result(ErrorCode::Error);
147 return Result::success();
148 }
149
165 Result take_validated(M& msg, nros_cpp_integrity_status_t& status) {
166 return take_validated_sized<::nros::rx_buffer_capacity<M>::value>(msg, status);
167 }
168
171 template <size_t Cap> Result take_validated_sized(M& msg, nros_cpp_integrity_status_t& status) {
172 if (!initialized_) return Result(ErrorCode::NotInitialized);
173 uint8_t buf[Cap];
174 size_t len = 0;
175 nros_cpp_ret_t ret =
176 nros_cpp_subscription_take_validated(storage_, buf, sizeof(buf), &len, &status);
177 if (ret != 0) return Result(ret);
178 if (len == 0) return Result(ErrorCode::TryAgain);
179 if (M::ffi_deserialize(buf, len, &msg) != 0) return Result(ErrorCode::Error);
180 return Result::success();
181 }
182
193 Result take_serialized(uint8_t* buf, size_t capacity, size_t& out_len) {
194 if (!initialized_) {
195 out_len = 0;
197 }
198 nros_cpp_ret_t ret =
199 nros_cpp_subscription_take_serialized(storage_, buf, capacity, &out_len);
200 if (ret != 0) return Result(ret);
201 if (out_len == 0) return Result(ErrorCode::TryAgain);
202 return Result::success();
203 }
204
220 Result take_serialized_with_attachment(uint8_t* buf, size_t capacity, size_t& out_len,
221 uint8_t* att, size_t att_capacity, size_t& out_att_len) {
222 if (!initialized_) {
223 out_len = 0;
224 out_att_len = 0;
226 }
227 nros_cpp_ret_t ret = nros_cpp_subscription_take_serialized_with_attachment(
228 storage_, buf, capacity, &out_len, att, att_capacity, &out_att_len);
229 if (ret != 0) return Result(ret);
230 if (out_len == 0) return Result(ErrorCode::TryAgain);
231 return Result::success();
232 }
233
234 // ====================================================================
235 // Phase 124.A.7 — zero-copy receive (borrow / release)
236 // ====================================================================
237
240 class View {
241 public:
242 View() : sub_(nullptr), buf_(nullptr), len_(0), token_(nullptr) {}
243 View(View&& o) : sub_(o.sub_), buf_(o.buf_), len_(o.len_), token_(o.token_) {
244 o.sub_ = nullptr;
245 o.token_ = nullptr;
246 }
248 if (this != &o) {
249 release();
250 sub_ = o.sub_;
251 buf_ = o.buf_;
252 len_ = o.len_;
253 token_ = o.token_;
254 o.sub_ = nullptr;
255 o.token_ = nullptr;
256 }
257 return *this;
258 }
259 View(const View&) = delete;
260 View& operator=(const View&) = delete;
261 ~View() { release(); }
262
263 const uint8_t* data() const { return buf_; }
264 size_t size() const { return len_; }
265 bool empty() const { return token_ == nullptr; }
266
268 View(void* sub, const uint8_t* buf, size_t len, void* token)
269 : sub_(sub), buf_(buf), len_(len), token_(token) {}
270
271 private:
272 void release() {
273 if (token_ && sub_) {
274 nros_cpp_subscription_release(sub_, token_);
275 token_ = nullptr;
276 }
277 }
278
279 void* sub_;
280 const uint8_t* buf_;
281 size_t len_;
282 void* token_;
283 };
284
289 if (!initialized_) return Expected<View>::error(Result(ErrorCode::NotInitialized));
290 const uint8_t* buf = nullptr;
291 size_t len = 0;
292 void* token = nullptr;
293 int32_t rc = nros_cpp_subscription_borrow(storage_, &buf, &len, &token);
294 if (rc < 0) return Expected<View>::error(Result(rc));
295 if (rc == 0) return Expected<View>::ok(View{});
296 return Expected<View>::ok(View{storage_, buf, len, token});
297 }
298
311 Result take_sequence(uint8_t* buf, size_t per_msg_cap, size_t max_msgs, size_t* out_lens,
312 size_t& out_count) {
313 if (!initialized_) {
314 out_count = 0;
316 }
317 nros_cpp_ret_t ret = nros_cpp_subscription_take_sequence(storage_, buf, per_msg_cap,
318 max_msgs, out_lens, &out_count);
319 if (ret != 0) return Result(ret);
320 return Result::success();
321 }
322
323 // ====================================================================
324 // DEPRECATED spellings — phase-379 W6 decision 1 (2026-09-03)
325 // ====================================================================
326 //
327 // `take` -> `take`, `_raw` -> `_serialized`. rcl (`rcl_take`,
328 // `rcl_take_serialized_message`), rclcpp (`Subscription::take`,
329 // `take_serialized`) and our OWN RMW vtable one layer down (`take`,
330 // `take_sequence`) all spell the non-blocking consuming receive that way.
331 // Only this user-facing layer said `take` — Rust-channel vocabulary
332 // that reads as a different contract to a ROS 2 user, when both forms are
333 // non-blocking and both report emptiness without failing.
334 //
335 // Header-only forwarders, so there is no ABI cost and the new name is the
336 // only one a reader of this class sees first. Scheduled for removal.
337
339 [[deprecated("Subscription::try_recv is deprecated; use Subscription::take")]] Result
340 try_recv(M& msg) {
341 return take(msg);
342 }
343
349 template <size_t Cap>
350 [[deprecated("Subscription::try_recv_sized is deprecated; use "
351 "Subscription::take_sized")]] Result
353 return take_sized<Cap>(msg);
354 }
355
357 template <size_t Cap>
358 [[deprecated("Subscription::try_recv_validated_sized is deprecated; use "
359 "Subscription::take_validated_sized")]] Result
360 try_recv_validated_sized(M& msg, nros_cpp_integrity_status_t& status) {
361 return take_validated_sized<Cap>(msg, status);
362 }
363
365 [[deprecated("Subscription::try_recv_validated is deprecated; use "
366 "Subscription::take_validated")]] Result
367 try_recv_validated(M& msg, nros_cpp_integrity_status_t& status) {
368 return take_validated(msg, status);
369 }
370
372 [[deprecated("Subscription::try_recv_raw is deprecated; use "
373 "Subscription::take_serialized")]] Result
374 try_recv_raw(uint8_t* buf, size_t capacity, size_t& out_len) {
375 return take_serialized(buf, capacity, out_len);
376 }
377
379 [[deprecated("Subscription::try_recv_raw_with_attachment is deprecated; use "
380 "Subscription::take_serialized_with_attachment")]] Result
381 try_recv_raw_with_attachment(uint8_t* buf, size_t capacity, size_t& out_len, uint8_t* att,
382 size_t att_capacity, size_t& out_att_len) {
383 return take_serialized_with_attachment(buf, capacity, out_len, att, att_capacity,
384 out_att_len);
385 }
386
388 [[deprecated("Subscription::try_recv_sequence is deprecated; use "
389 "Subscription::take_sequence")]] Result
390 try_recv_sequence(uint8_t* buf, size_t per_msg_cap, size_t max_msgs, size_t* out_lens,
391 size_t& out_count) {
392 return take_sequence(buf, per_msg_cap, max_msgs, out_lens, out_count);
393 }
394
396 const char* get_topic_name() const { return initialized_ ? topic_name_ : ""; }
397
406 if (initialized_ && !stream_.is_valid()) {
407 stream_.bind(storage_, &nros_cpp_subscription_take_serialized);
408 }
409 return stream_;
410 }
411
412 const Stream<M>& stream() const { return stream_; }
413
415 bool is_valid() const { return initialized_; }
416
424 if (initialized_ && !callback_mode_) {
425 nros_cpp_subscription_destroy(storage_);
426 }
427 initialized_ = false;
428 }
429
430 // Move semantics (non-copyable). Relocation goes through the
431 // `nros_cpp_subscription_relocate` runtime call (Phase 84.C1);
432 // the `stream_` is rebound to the new storage afterwards.
433 // A callback-style subscription must NOT be moved after register — the
434 // executor arena holds `this` as the trampoline context (Phase 189.M3.x);
435 // the move only transfers bookkeeping and leaves that pointer stale. The
436 // poll-style relocation path is unchanged.
437 Subscription(Subscription&& other) : initialized_(other.initialized_) {
438 user_fn_ = other.user_fn_;
439 user_fn_ctx_ = other.user_fn_ctx_;
440 user_ctx_ = other.user_ctx_;
441 callback_mode_ = other.callback_mode_;
442 sched_handle_id_ = other.sched_handle_id_;
443 if (other.initialized_ && !other.callback_mode_) {
444 nros_cpp_subscription_relocate(other.storage_, storage_);
445 ::memcpy(topic_name_, other.topic_name_, sizeof(topic_name_));
446 stream_.bind(storage_, &nros_cpp_subscription_take_serialized);
447 }
448 other.initialized_ = false;
449 other.stream_ = Stream<M>();
450 }
451
453 if (this != &other) {
454 if (initialized_ && !callback_mode_) {
455 nros_cpp_subscription_destroy(storage_);
456 stream_ = Stream<M>();
457 }
458 initialized_ = other.initialized_;
459 user_fn_ = other.user_fn_;
460 user_fn_ctx_ = other.user_fn_ctx_;
461 user_ctx_ = other.user_ctx_;
462 callback_mode_ = other.callback_mode_;
463 sched_handle_id_ = other.sched_handle_id_;
464 if (other.initialized_ && !other.callback_mode_) {
465 nros_cpp_subscription_relocate(other.storage_, storage_);
466 ::memcpy(topic_name_, other.topic_name_, sizeof(topic_name_));
467 stream_.bind(storage_, &nros_cpp_subscription_take_serialized);
468 }
469 other.initialized_ = false;
470 other.stream_ = Stream<M>();
471 }
472 return *this;
473 }
474
477 Subscription() : storage_(), topic_name_{}, initialized_(false), stream_() {}
478
489 bool has_sched_handle() const { return sched_handle_id_ != static_cast<size_t>(-1); }
490 size_t sched_handle_id() const { return sched_handle_id_; }
491
492 // ====================================================================
493 // Phase 108 — status events
494 // ====================================================================
495
500 Result on_liveliness_changed(nros_cpp_liveliness_changed_cb_t cb,
501 void* user_context = nullptr) {
502 if (!initialized_) return Result(ErrorCode::NotInitialized);
503 return Result(nros_cpp_subscription_set_liveliness_changed(storage_, cb, user_context));
504 }
505
507 Result on_requested_deadline_missed(uint32_t deadline_ms, nros_cpp_subscriber_count_cb_t cb,
508 void* user_context = nullptr) {
509 if (!initialized_) return Result(ErrorCode::NotInitialized);
510 return Result(nros_cpp_subscription_set_requested_deadline_missed(storage_, deadline_ms, cb,
511 user_context));
512 }
513
515 Result on_message_lost(nros_cpp_subscriber_count_cb_t cb, void* user_context = nullptr) {
516 if (!initialized_) return Result(ErrorCode::NotInitialized);
517 return Result(nros_cpp_subscription_set_message_lost(storage_, cb, user_context));
518 }
519
520 private:
521 Subscription(const Subscription&) = delete;
522 Subscription& operator=(const Subscription&) = delete;
523
524 friend class Node;
525
529 static void message_trampoline(const uint8_t* data, size_t len, void* ctx) {
530 auto* self = static_cast<Subscription*>(ctx);
531 if (self == nullptr) return;
532 M msg;
533 if (M::ffi_deserialize(data, len, &msg) != 0) return;
534 if (self->user_fn_ != nullptr) {
535 self->user_fn_(msg);
536 } else if (self->user_fn_ctx_ != nullptr) {
537 self->user_fn_ctx_(msg, self->user_ctx_);
538 }
539 }
540
544 static void message_info_trampoline(const uint8_t* data, size_t len, const uint8_t* attachment,
545 size_t attachment_len, void* ctx) {
546 auto* self = static_cast<Subscription*>(ctx);
547 if (self == nullptr) return;
548 M msg;
549 if (M::ffi_deserialize(data, len, &msg) != 0) return;
550 if (self->user_fn_info_ != nullptr) {
551 self->user_fn_info_(msg, attachment, attachment_len);
552 }
553 }
554
555#if defined(NANO_ROS_SAFETY_E2E)
561 static void message_safety_trampoline(const uint8_t* data, size_t len, int64_t gap,
562 bool duplicate, int8_t crc_valid, void* ctx) {
563 auto* self = static_cast<Subscription*>(ctx);
564 if (self == nullptr) return;
565 M msg;
566 if (M::ffi_deserialize(data, len, &msg) != 0) return;
567 if (self->user_fn_safety_ != nullptr) {
568 nros_cpp_integrity_status_t status;
569 status.gap = gap;
570 status.duplicate = duplicate;
571 status.crc_valid = crc_valid;
572 self->user_fn_safety_(msg, status);
573 }
574 }
575#endif // NANO_ROS_SAFETY_E2E
576
577 alignas(8) uint8_t storage_[NROS_SUBSCRIBER_SIZE];
578 char topic_name_[SUBSCRIPTION_TOPIC_NAME_MAX];
579 bool initialized_;
580 Stream<M> stream_;
581 // Phase 189.M3.1 — executor HandleId for sched-context binding, or
582 // SIZE_MAX (the default) when no bindable handle exists. The poll-style
583 // thin-wrapper create path leaves this unset (see has_sched_handle());
584 // the callback-style create (Phase 189.M3.x) stores the real arena handle.
585 size_t sched_handle_id_ = static_cast<size_t>(-1);
586 // Callback-style state (Phase 189.M3.x); unused in poll mode. The executor
587 // arena owns the subscriber + dispatches `message_trampoline` during spin.
588 TypedSubscriptionFn user_fn_ = nullptr;
589 TypedSubscriptionFnWithCtx user_fn_ctx_ = nullptr;
590 TypedSubscriptionInfoFn user_fn_info_ = nullptr;
591 void* user_ctx_ = nullptr;
592 bool callback_mode_ = false;
593#if defined(NANO_ROS_SAFETY_E2E)
594 // Phase 269 W3 — handler for the integrity-carrying callback path; nullptr
595 // when not using `create_subscription_with_safety`.
596 TypedSubscriptionSafetyFn user_fn_safety_ = nullptr;
597#endif // NANO_ROS_SAFETY_E2E
598};
599
600} // namespace nros
601
602// Phase 84.G8: out-of-line definition of Node::create_subscription<M>().
603#include "nros/node.hpp"
604
605namespace nros {
606
607template <typename M>
608Result Node::create_subscription(Subscription<M>& out, const char* topic, const QoS& qos) {
609 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
610 // message the linked backend cannot encode never reaches the wire.
612 if (!initialized_) return Result(ErrorCode::NotInitialized);
613 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
614 nros_cpp_ret_t ret = nros_cpp_subscription_create(&handle_, topic, M::TYPE_NAME, M::TYPE_HASH,
615 ffi_qos, out.storage_);
616 if (ret == 0) {
617 // Phase 87.6: topic name lives C++-side now.
618 size_t topic_len = 0;
619 while (topic[topic_len] != '\0' && topic_len + 1 < sizeof(out.topic_name_)) {
620 out.topic_name_[topic_len] = topic[topic_len];
621 ++topic_len;
622 }
623 out.topic_name_[topic_len] = '\0';
624 out.initialized_ = true;
625 }
626 return Result(ret);
627}
628
641template <typename M>
642Result Node::create_subscription(Subscription<M>& out, const char* topic, const QoS& qos,
643 const SubscriptionOptions& options) {
644 Result r = create_subscription<M>(out, topic, qos);
645 if (!r.ok()) return r;
646
647 // TODO(M3.4): honour options.message_info via the with-info arena path.
648
649 if (options.sched_context != SCHED_CONTEXT_UNSET && out.has_sched_handle()) {
650 nros_cpp_ret_t bind = nros_cpp_bind_handle_to_sched_context(
651 executor_handle_, out.sched_handle_id(), static_cast<uint8_t>(options.sched_context));
652 if (bind != 0) {
653 // Roll back so the caller doesn't observe a half-configured
654 // entity. Destructor-on-out would also fire, but explicit
655 // teardown keeps the returned error authoritative.
656 nros_cpp_subscription_destroy(out.storage_);
657 out.initialized_ = false;
658 return Result(bind);
659 }
660 }
661 return Result::success();
662}
663
664// Phase 189.M3.x — callback-style (arena-registered) subscription. The arena
665// owns the subscriber + dispatches `out`'s message handler during spin_once, so
666// the handle is real and `options.sched_context` is functional. Mirrors the
667// callback-style `create_service` one entity over.
668template <typename M, typename F, typename>
669Result Node::create_subscription(Subscription<M>& out, const char* topic, F callback,
670 const QoS& qos, const SubscriptionOptions& options) {
671 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
672 // message the linked backend cannot encode never reaches the wire.
674 if (!initialized_) return Result(ErrorCode::NotInitialized);
675 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
676
677 // Store the user handler (compile error if F isn't convertible to the
678 // plain-fn-ptr handler type).
679 out.user_fn_ = typename Subscription<M>::TypedSubscriptionFn(callback);
680 out.user_fn_ctx_ = nullptr;
681 out.user_ctx_ = nullptr;
682
683 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
684 ? 0u
685 : static_cast<uint8_t>(options.sched_context);
686 size_t handle = static_cast<size_t>(-1);
687 // phase-402: `sched_context` is a FIELD now. `callback_group` stays unset,
688 // i.e. the default group.
689 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
690 ffi_options.sched_context = sched;
692 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos, &Subscription<M>::message_trampoline,
693 &out, &handle, &ffi_options);
694 if (ret == 0) {
695 out.sched_handle_id_ = handle;
696 out.callback_mode_ = true;
697 out.initialized_ = true;
698 }
699 return Result(ret);
700}
701
702// Phase 273 (RFC-0047) — callback-style subscription **in** a named callback group.
703// Mirrors create_subscription (callback-style) exactly but passes group.get_name()
704// as `callback_group` so the executor binds the slot via group_sched_table.
705template <typename M, typename F, typename>
706Result Node::create_subscription_in(const CallbackGroup& group, Subscription<M>& out,
707 const char* topic, F callback, const QoS& qos,
708 const SubscriptionOptions& options) {
709 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
710 // message the linked backend cannot encode never reaches the wire.
712 if (!initialized_) return Result(ErrorCode::NotInitialized);
713 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
714
715 out.user_fn_ = typename Subscription<M>::TypedSubscriptionFn(callback);
716 out.user_fn_ctx_ = nullptr;
717 out.user_ctx_ = nullptr;
718
719 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
720 ? 0u
721 : static_cast<uint8_t>(options.sched_context);
722 size_t handle = static_cast<size_t>(-1);
723 // phase-402: both the sched slot and the Phase 273 group name are FIELDS now.
724 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
725 ffi_options.sched_context = sched;
726 ffi_options.callback_group = group.get_name();
728 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos, &Subscription<M>::message_trampoline,
729 &out, &handle, &ffi_options);
730 if (ret == 0) {
731 out.sched_handle_id_ = handle;
732 out.callback_mode_ = true;
733 out.initialized_ = true;
734 }
735 return Result(ret);
736}
737
738// Phase 189.M3.4 — callback-style subscription that delivers the wire attachment.
739// Mirrors the callback `create_subscription` one step over, but stores the
740// `(const M&, attachment, att_len)` handler + registers via the with-info arena
741// path so the trampoline receives the attachment.
742template <typename M, typename F, typename>
744 const QoS& qos, const SubscriptionOptions& options) {
745 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
746 // message the linked backend cannot encode never reaches the wire.
748 if (!initialized_) return Result(ErrorCode::NotInitialized);
749 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
750
751 out.user_fn_info_ = typename Subscription<M>::TypedSubscriptionInfoFn(callback);
752 out.user_fn_ = nullptr;
753 out.user_fn_ctx_ = nullptr;
754 out.user_ctx_ = nullptr;
755
756 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
757 ? 0u
758 : static_cast<uint8_t>(options.sched_context);
759 size_t handle = static_cast<size_t>(-1);
760 // phase-402: `sched_context` is a FIELD now.
761 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
762 ffi_options.sched_context = sched;
764 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos,
765 &Subscription<M>::message_info_trampoline, &out, &handle, &ffi_options);
766 if (ret == 0) {
767 out.sched_handle_id_ = handle;
768 out.callback_mode_ = true;
769 out.initialized_ = true;
770 }
771 return Result(ret);
772}
773
777template <typename M>
778inline Expected<Subscription<M>> create_subscription(Node& node, const char* topic,
779 const QoS& qos = QoS::default_profile()) {
781 Result r = node.create_subscription<M>(s, topic, qos);
782 if (!r.ok()) return Expected<Subscription<M>>::error(r);
783 return Expected<Subscription<M>>::ok(std::move(s));
784}
785
786#if defined(NANO_ROS_SAFETY_E2E)
787// Phase 269 W3 — out-of-line definition of Node::create_subscription_with_safety.
788// Mirrors `create_subscription_with_info` one overload over, but routes through
789// `nros_cpp_subscription_register_validated` so the arena dispatches the
790// `message_safety_trampoline` on each new sample.
791template <typename M, typename F, typename>
792Result Node::create_subscription_with_safety(Subscription<M>& out, const char* topic, F callback,
793 const QoS& qos, const SubscriptionOptions& options) {
794 // RFC-0088 D5 — one image, one backend, one encoding. Compile-time, so a
795 // message the linked backend cannot encode never reaches the wire.
797 if (!initialized_) return Result(ErrorCode::NotInitialized);
798 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
799
800 out.user_fn_safety_ = typename Subscription<M>::TypedSubscriptionSafetyFn(callback);
801 out.user_fn_ = nullptr;
802 out.user_fn_ctx_ = nullptr;
803 out.user_fn_info_ = nullptr;
804 out.user_ctx_ = nullptr;
805
806 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
807 ? 0u
808 : static_cast<uint8_t>(options.sched_context);
809 size_t handle = static_cast<size_t>(-1);
810 // phase-402: `sched_context` is a FIELD now.
811 nros_cpp_subscription_options_t ffi_options = nros_cpp_subscription_default_options();
812 ffi_options.sched_context = sched;
813 nros_cpp_ret_t ret = nros_cpp_subscription_register_validated(
814 &handle_, topic, M::TYPE_NAME, M::TYPE_HASH, ffi_qos,
815 &Subscription<M>::message_safety_trampoline, &out, &handle, &ffi_options);
816 if (ret == 0) {
817 out.sched_handle_id_ = handle;
818 out.callback_mode_ = true;
819 out.initialized_ = true;
820 }
821 return Result(ret);
822}
823#endif // NANO_ROS_SAFETY_E2E
824
825} // namespace nros
826
827#endif // NROS_CPP_SUBSCRIPTION_HPP
Definition result.hpp:198
ErrorCode error() const
Definition result.hpp:221
bool ok() const
Definition result.hpp:214
Definition node.hpp:211
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_subscription(Subscription< M > &out, const char *topic, const QoS &qos=QoS::default_profile())
Definition subscription.hpp:608
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
Definition qos.hpp:173
static constexpr QoS default_profile()
Default profile: RELIABLE + VOLATILE + KEEP_LAST(10).
Definition qos.hpp:328
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 stream.hpp:43
bool is_valid() const
Check if the stream is connected to a valid source.
Definition stream.hpp:119
Definition subscription.hpp:240
View & operator=(View &&o)
Definition subscription.hpp:247
View(const View &)=delete
View(View &&o)
Definition subscription.hpp:243
View()
Definition subscription.hpp:242
const uint8_t * data() const
Definition subscription.hpp:263
bool empty() const
Definition subscription.hpp:265
size_t size() const
Definition subscription.hpp:264
View(void *sub, const uint8_t *buf, size_t len, void *token)
Internal constructor — callers use Subscription::try_borrow().
Definition subscription.hpp:268
View & operator=(const View &)=delete
~View()
Definition subscription.hpp:261
Definition subscription.hpp:100
void(*)(const M &msg, const uint8_t *attachment, size_t attachment_len) TypedSubscriptionInfoFn
Definition subscription.hpp:109
Result on_liveliness_changed(nros_cpp_liveliness_changed_cb_t cb, void *user_context=nullptr)
Definition subscription.hpp:500
Result take_serialized(uint8_t *buf, size_t capacity, size_t &out_len)
Definition subscription.hpp:193
const char * get_topic_name() const
Get the topic name.
Definition subscription.hpp:396
Result on_requested_deadline_missed(uint32_t deadline_ms, nros_cpp_subscriber_count_cb_t cb, void *user_context=nullptr)
Register a callback for requested-deadline-missed events.
Definition subscription.hpp:507
Result try_recv_validated_sized(M &msg, nros_cpp_integrity_status_t &status)
Definition subscription.hpp:360
Result take_sequence(uint8_t *buf, size_t per_msg_cap, size_t max_msgs, size_t *out_lens, size_t &out_count)
Definition subscription.hpp:311
Result try_recv_sequence(uint8_t *buf, size_t per_msg_cap, size_t max_msgs, size_t *out_lens, size_t &out_count)
Definition subscription.hpp:390
Subscription(Subscription &&other)
Definition subscription.hpp:437
Subscription()
Definition subscription.hpp:477
Result try_recv_sized(M &msg)
Definition subscription.hpp:352
~Subscription()
Definition subscription.hpp:423
Expected< View > try_borrow()
Definition subscription.hpp:288
const Stream< M > & stream() const
Definition subscription.hpp:412
Stream< M > & stream()
Definition subscription.hpp:405
size_t sched_handle_id() const
Definition subscription.hpp:490
bool has_sched_handle() const
Definition subscription.hpp:489
Result take_serialized_with_attachment(uint8_t *buf, size_t capacity, size_t &out_len, uint8_t *att, size_t att_capacity, size_t &out_att_len)
Definition subscription.hpp:220
Result try_recv(M &msg)
Definition subscription.hpp:340
Result on_message_lost(nros_cpp_subscriber_count_cb_t cb, void *user_context=nullptr)
Register a callback for message-lost events.
Definition subscription.hpp:515
Result try_recv_raw(uint8_t *buf, size_t capacity, size_t &out_len)
Definition subscription.hpp:374
void(*)(const M &msg, void *ctx) TypedSubscriptionFnWithCtx
Definition subscription.hpp:106
bool is_valid() const
Check if the subscription is initialized and valid.
Definition subscription.hpp:415
Result take(M &msg)
Definition subscription.hpp:128
Subscription & operator=(Subscription &&other)
Definition subscription.hpp:452
Result take_sized(M &msg)
Definition subscription.hpp:138
Result take_validated_sized(M &msg, nros_cpp_integrity_status_t &status)
Definition subscription.hpp:171
Result try_recv_raw_with_attachment(uint8_t *buf, size_t capacity, size_t &out_len, uint8_t *att, size_t att_capacity, size_t &out_att_len)
Definition subscription.hpp:381
Result take_validated(M &msg, nros_cpp_integrity_status_t &status)
Definition subscription.hpp:165
void(*)(const M &msg) TypedSubscriptionFn
Definition subscription.hpp:105
Result try_recv_validated(M &msg, nros_cpp_integrity_status_t &status)
Definition subscription.hpp:367
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:21
Definition nros.hpp:55
bool ok()
Check if the nros session is initialized.
Definition node.hpp:997
static constexpr size_t SUBSCRIPTION_TOPIC_NAME_MAX
Definition subscription.hpp:80
@ Error
Generic failure not covered by a more specific code.
@ TryAgain
Transient — no data ready yet (non-blocking take). Retry later.
nros::Node and global session helpers.
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
nros::format_of<M> — a message type's serialization format; nros::linked_format() — the linked backen...
#define NROS_CPP_ASSERT_MESSAGE_FORMAT(M)
Definition serialization_format.hpp:97
nros::Stream<T> — multi-shot message receiver.
Definition qos.hpp:54
nros_cpp_ret_t nros_cpp_subscription_register(const nros_cpp_node_t *node, const char *topic, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_subscription_message_callback_t callback, void *context, size_t *out_handle_id, const nros_cpp_subscription_options_t *options)
void(* nros_cpp_subscription_message_info_callback_t)(const uint8_t *data, size_t len, const uint8_t *attachment, size_t attachment_len, void *ctx)
Definition subscription.hpp:47
nros_cpp_ret_t nros_cpp_subscription_register_with_info(const nros_cpp_node_t *node, const char *topic, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_subscription_message_info_callback_t callback, void *context, size_t *out_handle_id, const nros_cpp_subscription_options_t *options)
void(* nros_cpp_subscription_message_callback_t)(const uint8_t *data, size_t len, void *ctx)
Definition subscription.hpp:32