nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
client.hpp
Go to the documentation of this file.
1// nros-cpp: Action client class
2// Freestanding C++ — no exceptions, no STL required
3
10#ifndef NROS_RCLCPP_ACTION_CLIENT_HPP
11#define NROS_RCLCPP_ACTION_CLIENT_HPP
12
13#include <cstdint>
14#include <cstddef>
15#include <string.h>
16
18#include "rclcpp/detail/entity_name.hpp" // phase-444 — the one entity-name copy
19#include "rclcpp/logging.hpp" // phase-417 stage 3 — NROS_RCLCPP_REFUSE_* + rclcpp::detail::refuse
20#include "rclcpp/result.hpp"
21#include "rclcpp/size_bound.hpp" // rclcpp::rx_buffer_capacity<M> — the receive-buffer size
22#include "rclcpp/future.hpp"
23#include "rclcpp/stream.hpp"
24// Issue 0796 — `CancelReturnCode` (the `action_msgs/srv/CancelGoal` RPC status
25// `cancel_goal` resolves to) is declared beside its per-goal sibling
26// `CancelResponse` in action_server.hpp, which is also where
27// polling_action_server.hpp reaches for them.
29
30// Phase 118.D: FFI declarations sourced from cbindgen-generated
31// `nros_cpp_ffi.h`. cbindgen renders Rust `*const [u8; 16]` /
32// `*mut [u8; 16]` parameters as C++ pointer-to-array
33// (`const uint8_t (*goal_id)[16]`); member-function bodies below
34// take `goal_id` as a 16-byte array (decays to `uint8_t*`) and
35// `reinterpret_cast` at the call site to bridge the two ABI
36// equivalent shapes.
37#include "rclcpp/detail/nros_cpp_ffi.h"
38
39extern "C" {
41 const uint8_t goal_id[16],
42 void* ctx);
43typedef void (*nros_cpp_action_client_feedback_callback_t)(const uint8_t goal_id[16],
44 const uint8_t* data, size_t len,
45 void* ctx);
46typedef void (*nros_cpp_action_client_result_callback_t)(const uint8_t goal_id[16], int32_t status,
47 const uint8_t* data, size_t len,
48 void* ctx);
49
51 void* handle, nros_cpp_action_client_goal_response_callback_t goal_response,
53 nros_cpp_action_client_result_callback_t result, void* context);
54} // extern "C"
55
63namespace rclcpp {
64class Node;
65}
66
67// ============================================================================
68// `rclcpp_action::Client<A>` -- DEFINED here (RFC-0089: rclcpp_action:: is the home)
69// ============================================================================
70//
71// phase-428: the definition moved from `nros::ActionClient<A>` to
72// `rclcpp_action::Client<A>` (the `nros::` alias went in phase-483 W1). A RENAME
73// as well as a move -- upstream's namespace already says "action", so the type
74// is `Client`, not `ActionClient`. Note the two `Client`s do not collide:
75// `rclcpp::Client<S>` is the SERVICE client, in a different namespace.
76namespace rclcpp_action {
77
95template <typename A> class Client {
96 public:
97 using GoalType = typename A::Goal;
98 using ResultType = typename A::Result;
99 using FeedbackType = typename A::Feedback;
100
105 struct GoalAccept {
111
112 // Receive-buffer size for `Future<GoalAccept>`; the payload is 17 bytes
113 // (16-byte goal_id + 1 accepted byte), rounded up.
114 static const size_t SERIALIZED_SIZE_MAX = 32;
115
116 // Issue 0329 — the 17-byte wire layout is owned Rust-side
117 // (`nros_cpp_action_goal_accept_decode`, beside its producer); the header
118 // no longer hand-decodes it, so the two cannot drift.
119 static int ffi_deserialize(const uint8_t* data, size_t len, GoalAccept* out) {
120 if (!out) return -1;
121 return nros_cpp_action_goal_accept_decode(data, len, &out->goal_id, &out->accepted) ==
123 ? 0
124 : -1;
125 }
126 };
127
151 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
152 return ::rclcpp::Result(
154 }
155
158 static_assert(::rclcpp::detail::refuse<T>::value, NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT);
159 return ::rclcpp::Result(::rclcpp::ErrorCode::Unsupported);
160 }
161
176 if (!initialized_) return false;
178 const_cast<void*>(static_cast<const void*>(storage_)));
179 }
180
187 const char* get_action_name() const { return initialized_ ? action_name_ : ""; }
188
203 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
204
205 uint8_t buf[::rclcpp::detail::buffer_bounds<GoalType>::tx];
206 size_t len = 0;
207 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
208 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
209 }
210 return ::rclcpp::Result(nros_cpp_action_client_send_goal(
211 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id)));
212 }
213
217 return send_goal(goal, goal_id.data());
218 }
219
231
233 ::rclcpp::Result get_result(const ::rclcpp_action::GoalUUID& goal_id, ResultType& result) {
235 result);
236 }
237
240 template <size_t Cap>
242 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
243
244 uint8_t buf[Cap];
245 size_t len = 0;
247 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id), buf, sizeof(buf),
248 &len);
249 if (ret != 0) return ::rclcpp::Result(ret);
250
251 if (ResultType::ffi_deserialize(buf, len, &result) != 0) {
252 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
253 }
254 return ::rclcpp::Result::success();
255 }
256
257 // =================================================================
258 // Future-based API — non-blocking, polled via Future<T>
259 // =================================================================
260
278 if (!initialized_) return ::rclcpp::Future<GoalAccept>();
279
280 uint8_t buf[::rclcpp::detail::buffer_bounds<GoalType>::tx];
281 size_t len = 0;
282 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
283 return ::rclcpp::Future<GoalAccept>();
284 }
285
286 uint8_t goal_id[16];
288 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id));
289 if (ret != 0) return ::rclcpp::Future<GoalAccept>();
290
291 return ::rclcpp::Future<GoalAccept>(storage_,
293 0 // slot 0 (single outstanding goal request)
294 );
295 }
296
316
318 ::rclcpp::Future<ResultType> get_result_future(const ::rclcpp_action::GoalUUID& goal_id) {
320 goal_id.data());
321 }
322
328 template <size_t Cap>
331 if (!initialized_) return Fut();
332
334 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id));
335 if (ret != 0) return Fut();
336
338 0 // slot 0 (single outstanding result request)
339 );
340 }
341
353
357 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
358
359 uint8_t buf[Cap];
360 size_t len = 0;
362 nros_cpp_action_client_try_recv_feedback(storage_, buf, sizeof(buf), &len);
363 if (ret != 0) return ::rclcpp::Result(ret);
364 if (len == 0) return ::rclcpp::Result(::rclcpp::ErrorCode::TryAgain);
365 if (FeedbackType::ffi_deserialize(buf, len, &feedback) != 0) {
366 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
367 }
368 return ::rclcpp::Result::success();
369 }
370
392 if (initialized_ && !feedback_stream_.is_valid()) {
393 feedback_stream_.bind(storage_, &nros_cpp_action_client_try_recv_feedback);
394 }
395 return feedback_stream_;
396 }
397
398 const ::rclcpp::Stream<FeedbackType>& feedback_stream() const { return feedback_stream_; }
399
400 // =================================================================
401 // Async (non-blocking) API — callbacks invoked during spin_once()
402 // =================================================================
403
412 void (*goal_response)(bool accepted, const uint8_t goal_id[16], void* ctx);
414 void (*feedback)(const uint8_t goal_id[16], const uint8_t* data, size_t len, void* ctx);
416 void (*result)(const uint8_t goal_id[16], int32_t status, const uint8_t* data, size_t len,
417 void* ctx);
419 void* context;
420
422 };
423
434 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
435
436 uint8_t buf[::rclcpp::detail::buffer_bounds<GoalType>::tx];
437 size_t len = 0;
438 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
439 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
440 }
441 return ::rclcpp::Result(nros_cpp_action_client_send_goal_async(
442 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id)));
443 }
444
450
466 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
467 return ::rclcpp::Result(nros_cpp_action_client_cancel_goal(
468 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
469 }
470
472 ::rclcpp::Result cancel_goal(const ::rclcpp_action::GoalUUID& goal_id) {
473 return cancel_goal(goal_id.data());
474 }
475
482 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
483 int8_t code = 0;
485 if (ret != 0) return ::rclcpp::Result(ret);
486 out = static_cast<::rclcpp_action::CancelReturnCode>(code);
487 return ::rclcpp::Result::success();
488 }
489
498 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
499 return ::rclcpp::Result(nros_cpp_action_client_get_result_async(
500 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
501 }
502
504 ::rclcpp::Result get_result_async(const ::rclcpp_action::GoalUUID& goal_id) {
505 return get_result_async(goal_id.data());
506 }
507
521 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
522 return ::rclcpp::Result(nros_cpp_action_client_set_callbacks(
523 storage_, options.goal_response, options.feedback, options.result, options.context));
524 }
525
531 void poll() {
532 if (!initialized_) return;
534 }
535
537 bool is_valid() const { return initialized_; }
538
550 if (initialized_) {
552 initialized_ = false;
553 }
554 feedback_stream_ = ::rclcpp::Stream<FeedbackType>();
555 }
556
557 // Move semantics (non-copyable). Relocation goes through the
558 // `nros_cpp_action_client_relocate` runtime call (Phase 84.C1), which also
559 // RE-POINTS the arena entry's callback `context` at the new `storage_`
560 // (issue 1496 — before that, a moved-from temporary left the entry naming
561 // dead bytes). The feedback stream is rebound to the new storage afterwards.
563 : executor_(other.executor_), initialized_(other.initialized_), action_name_{} {
564 ::memcpy(action_name_, other.action_name_, sizeof(action_name_));
565 if (other.initialized_) {
566 nros_cpp_action_client_relocate(other.storage_, storage_);
567 other.initialized_ = false;
568 }
569 other.feedback_stream_ = ::rclcpp::Stream<FeedbackType>();
570 }
571
573 if (this != &other) {
574 if (initialized_) {
576 feedback_stream_ = ::rclcpp::Stream<FeedbackType>();
577 }
578 executor_ = other.executor_;
579 initialized_ = other.initialized_;
580 ::memcpy(action_name_, other.action_name_, sizeof(action_name_));
581 if (other.initialized_) {
582 nros_cpp_action_client_relocate(other.storage_, storage_);
583 other.initialized_ = false;
584 }
585 other.feedback_stream_ = ::rclcpp::Stream<FeedbackType>();
586 }
587 return *this;
588 }
589
592 Client() : executor_(nullptr), initialized_(false), action_name_{} {}
593
594 private:
595 Client(const Client&) = delete;
596 Client& operator=(const Client&) = delete;
597
598 friend class ::rclcpp::Node;
599
600 alignas(8) uint8_t storage_[NROS_CPP_ACTION_CLIENT_STORAGE_SIZE];
601 void* executor_; // Stashed executor handle (Phase 82) for blocking helpers
602 bool initialized_;
603 ::rclcpp::Stream<FeedbackType> feedback_stream_;
604 // Phase 87.6 put a `char action_name_[256]` here for an accessor that was
605 // never written, and an earlier phase-417 W4.b wave deleted it as dead
606 // state. W4.b's second half writes `get_action_name()`, so it is back and
607 // it is read — see the note on `Server<A>::get_action_name`.
608 char action_name_[::rclcpp_action::ACTION_NAME_MAX];
609};
610
611} // namespace rclcpp_action
612
613// Phase 84.G8: out-of-line definition of Node::create_action_client<A>().
614#include "rclcpp/node.hpp"
615
616namespace rclcpp {} // namespace rclcpp
617
618namespace rclcpp {
619template <typename A>
621 const ::rclcpp::QoS& qos) {
622 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
623 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
624 const nros_cpp_action_hashes_t hashes = ::rclcpp::detail::action_endpoint_hashes<A>();
626 &handle_, action_name, A::TYPE_NAME, A::Goal::TYPE_HASH, &hashes, ffi_qos, out.storage_);
627 if (ret == 0) {
628 out.executor_ = executor_handle_;
629 // phase-417 W4.b — remember the name for `get_action_name()`, truncating
630 // at `rclcpp_action::ACTION_NAME_MAX` exactly as the server and the polling tiers do.
631 ::rclcpp::detail::assign_entity_name(out.action_name_, action_name);
632 out.initialized_ = true;
633 }
634 return Result(ret);
635}
636} // namespace rclcpp
637
638namespace rclcpp {} // namespace rclcpp
639
640#endif // NROS_RCLCPP_ACTION_CLIENT_HPP
Definition client.hpp:129
Result create_action_client(::rclcpp_action::Client< A > &out, const char *action_name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Definition client.hpp:620
Definition result.hpp:216
bool is_valid() const
Check if the stream is connected to a valid source.
Definition stream.hpp:138
Definition client.hpp:95
::rclcpp::Result send_goal(const GoalType &goal, uint8_t goal_id[16])
Definition client.hpp:202
bool is_valid() const
Check if the action client is initialized and valid.
Definition client.hpp:537
::rclcpp::Stream< FeedbackType > & feedback_stream()
Definition client.hpp:391
::rclcpp::Result send_goal_async(const GoalType &goal, uint8_t goal_id[16])
Definition client.hpp:433
void poll()
Definition client.hpp:531
::rclcpp::Result wait_for_action_server(uint32_t timeout_ms)
Definition client.hpp:150
typename A::Result ResultType
Definition client.hpp:98
::rclcpp::Result get_result_async(const uint8_t goal_id[16])
Definition client.hpp:497
Client()
Definition client.hpp:592
::rclcpp::Result send_goal(const GoalType &goal, ::rclcpp_action::GoalUUID &goal_id)
Definition client.hpp:216
::rclcpp::Result get_result_async(const ::rclcpp_action::GoalUUID &goal_id)
get_result_async taking a GoalUUID value (phase-417 W4.b).
Definition client.hpp:504
Client & operator=(Client &&other)
Definition client.hpp:572
Client(Client &&other)
Definition client.hpp:562
::rclcpp::Result try_recv_cancel_response(::rclcpp_action::CancelReturnCode &out)
Definition client.hpp:481
typename A::Feedback FeedbackType
Definition client.hpp:99
bool action_server_is_ready() const
Definition client.hpp:175
::rclcpp::Result try_recv_feedback(FeedbackType &feedback)
Definition client.hpp:350
::rclcpp::Result get_result(const uint8_t goal_id[16], ResultType &result)
Definition client.hpp:228
::rclcpp::Future< ResultType > get_result_future(const uint8_t goal_id[16])
Definition client.hpp:313
::rclcpp::Result cancel_goal(const ::rclcpp_action::GoalUUID &goal_id)
cancel_goal taking a GoalUUID value (phase-417 W4.b).
Definition client.hpp:472
::rclcpp::Result wait_for_action_server()
REFUSED — wait_for_action_server() with no budget.
Definition client.hpp:157
::rclcpp::Future< GoalAccept > send_goal_future(const GoalType &goal)
Definition client.hpp:277
::rclcpp::Result get_result(const ::rclcpp_action::GoalUUID &goal_id, ResultType &result)
get_result taking a GoalUUID value (phase-417 W4.b).
Definition client.hpp:233
::rclcpp::Result set_callbacks(const SendGoalOptions &options)
Definition client.hpp:520
::rclcpp::Result try_recv_feedback_sized(FeedbackType &feedback)
Definition client.hpp:356
const ::rclcpp::Stream< FeedbackType > & feedback_stream() const
Definition client.hpp:398
::rclcpp::Result send_goal_async(const GoalType &goal, ::rclcpp_action::GoalUUID &goal_id)
Definition client.hpp:447
~Client()
Definition client.hpp:549
const char * get_action_name() const
Definition client.hpp:187
::rclcpp::Result get_result_sized(const uint8_t goal_id[16], ResultType &result)
Definition client.hpp:241
typename A::Goal GoalType
Definition client.hpp:97
::rclcpp::Future< ResultType, Cap > get_result_future_sized(const uint8_t goal_id[16])
Definition client.hpp:329
::rclcpp::Future< ResultType > get_result_future(const ::rclcpp_action::GoalUUID &goal_id)
get_result_future taking a GoalUUID value (phase-417 W4.b).
Definition client.hpp:318
::rclcpp::Result cancel_goal(const uint8_t goal_id[16])
Definition client.hpp:465
Inline storage-size macros for opaque entity buffers.
rclcpp::Future<T> — single-shot deferred result.
int nros_cpp_ret_t
Definition future.hpp:21
Definition node.hpp:199
static const size_t ACTION_NAME_MAX
Definition server.hpp:189
CancelReturnCode
Definition server.hpp:253
Definition rclcpp.hpp:75
ResultOf< void > Result
Definition result.hpp:246
@ Error
Generic failure not covered by a more specific code.
@ Unsupported
Operation not implemented by the active backend.
@ TryAgain
Transient — no data ready yet (non-blocking take). Retry later.
rclcpp::Node and global session helpers.
nros_cpp_ret_t nros_cpp_action_client_set_callbacks(void *handle, nros_cpp_action_client_goal_response_callback_t goal_response, nros_cpp_action_client_feedback_callback_t feedback, nros_cpp_action_client_result_callback_t result, void *context)
void(* nros_cpp_action_client_goal_response_callback_t)(bool accepted, const uint8_t goal_id[16], void *ctx)
Definition client.hpp:40
void(* nros_cpp_action_client_feedback_callback_t)(const uint8_t goal_id[16], const uint8_t *data, size_t len, void *ctx)
Definition client.hpp:43
void(* nros_cpp_action_client_result_callback_t)(const uint8_t goal_id[16], int32_t status, const uint8_t *data, size_t len, void *ctx)
Definition client.hpp:46
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
rclcpp::Stream<T> — multi-shot message receiver.
Definition qos.hpp:81
Definition client.hpp:105
static int ffi_deserialize(const uint8_t *data, size_t len, GoalAccept *out)
Definition client.hpp:119
static const size_t SERIALIZED_SIZE_MAX
Definition client.hpp:114
uint8_t goal_id[16]
Definition client.hpp:109
bool accepted
Definition client.hpp:110
SendGoalOptions()
Definition client.hpp:421
void * context
User context pointer passed to all callbacks.
Definition client.hpp:419
void(* result)(const uint8_t goal_id[16], int32_t status, const uint8_t *data, size_t len, void *ctx)
Called when the result is received.
Definition client.hpp:416
void(* feedback)(const uint8_t goal_id[16], const uint8_t *data, size_t len, void *ctx)
Called when feedback is received for the goal.
Definition client.hpp:414
void(* goal_response)(bool accepted, const uint8_t goal_id[16], void *ctx)
Called when the server accepts or rejects the goal.
Definition client.hpp:412
Definition server.hpp:124
uint8_t * data()
Pointer to the 16 bytes — what the raw-array overloads and the FFI take.
Definition server.hpp:142