nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
action_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_CPP_ACTION_CLIENT_HPP
11#define NROS_CPP_ACTION_CLIENT_HPP
12
13#include <cstdint>
14#include <cstddef>
15#include <string.h>
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#include "nros/future.hpp"
21#include "nros/stream.hpp"
22// Issue 0796 — `CancelReturnCode` (the `action_msgs/srv/CancelGoal` RPC status
23// `cancel_goal` resolves to) is declared beside its per-goal sibling
24// `CancelResponse` in action_server.hpp, which is also where
25// polling_action_server.hpp reaches for them.
27
28// Phase 118.D: FFI declarations sourced from cbindgen-generated
29// `nros_cpp_ffi.h`. cbindgen renders Rust `*const [u8; 16]` /
30// `*mut [u8; 16]` parameters as C++ pointer-to-array
31// (`const uint8_t (*goal_id)[16]`); member-function bodies below
32// take `goal_id` as a 16-byte array (decays to `uint8_t*`) and
33// `reinterpret_cast` at the call site to bridge the two ABI
34// equivalent shapes.
35#include "nros_cpp_ffi.h"
36
37extern "C" {
39 const uint8_t goal_id[16],
40 void* ctx);
41typedef void (*nros_cpp_action_client_feedback_callback_t)(const uint8_t goal_id[16],
42 const uint8_t* data, size_t len,
43 void* ctx);
44typedef void (*nros_cpp_action_client_result_callback_t)(const uint8_t goal_id[16], int32_t status,
45 const uint8_t* data, size_t len,
46 void* ctx);
47
49 void* handle, nros_cpp_action_client_goal_response_callback_t goal_response,
51 nros_cpp_action_client_result_callback_t result, void* context);
52} // extern "C"
53
54namespace nros {
55
73template <typename A> class ActionClient {
74 public:
75 using GoalType = typename A::Goal;
76 using ResultType = typename A::Result;
77 using FeedbackType = typename A::Feedback;
78
83 struct GoalAccept {
84 uint8_t goal_id[16];
86
87 // Receive-buffer size for `Future<GoalAccept>`; the payload is 17 bytes
88 // (16-byte goal_id + 1 accepted byte), rounded up.
89 static const size_t SERIALIZED_SIZE_MAX = 32;
90
91 // Issue 0329 — the 17-byte wire layout is owned Rust-side
92 // (`nros_cpp_action_goal_accept_decode`, beside its producer); the header
93 // no longer hand-decodes it, so the two cannot drift.
94 static int ffi_deserialize(const uint8_t* data, size_t len, GoalAccept* out) {
95 if (!out) return -1;
96 return nros_cpp_action_goal_accept_decode(data, len, &out->goal_id, &out->accepted) ==
97 NROS_CPP_RET_OK
98 ? 0
99 : -1;
100 }
101 };
102
120 Result wait_for_action_server(uint32_t timeout_ms = 5000) {
121 if (!initialized_) return Result(ErrorCode::NotInitialized);
122 return Result(nros_cpp_action_client_wait_for_action_server(storage_, timeout_ms));
123 }
124
125 Result send_goal(const GoalType& goal, uint8_t goal_id[16]) {
126 if (!initialized_) return Result(ErrorCode::NotInitialized);
127
128 uint8_t buf[::nros::detail::buffer_bounds<GoalType>::tx];
129 size_t len = 0;
130 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
131 return Result(ErrorCode::Error);
132 }
133 return Result(nros_cpp_action_client_send_goal(storage_, buf, len,
134 reinterpret_cast<uint8_t(*)[16]>(goal_id)));
135 }
136
145 Result get_result(const uint8_t goal_id[16], ResultType& result) {
147 }
148
151 template <size_t Cap> Result get_result_sized(const uint8_t goal_id[16], ResultType& result) {
152 if (!initialized_) return Result(ErrorCode::NotInitialized);
153
154 uint8_t buf[Cap];
155 size_t len = 0;
157 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id), buf, sizeof(buf),
158 &len);
159 if (ret != 0) return Result(ret);
160
161 if (ResultType::ffi_deserialize(buf, len, &result) != 0) {
162 return Result(ErrorCode::Error);
163 }
164 return Result::success();
165 }
166
167 // =================================================================
168 // Future-based API — non-blocking, polled via Future<T>
169 // =================================================================
170
188 if (!initialized_) return Future<GoalAccept>();
189
190 uint8_t buf[::nros::detail::buffer_bounds<GoalType>::tx];
191 size_t len = 0;
192 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
193 return Future<GoalAccept>();
194 }
195
196 uint8_t goal_id[16];
198 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id));
199 if (ret != 0) return Future<GoalAccept>();
200
202 0 // slot 0 (single outstanding goal request)
203 );
204 }
205
225
231 template <size_t Cap>
234 if (!initialized_) return Fut();
235
237 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id));
238 if (ret != 0) return Fut();
239
241 0 // slot 0 (single outstanding result request)
242 );
243 }
244
256
259 template <size_t Cap> Result try_recv_feedback_sized(FeedbackType& feedback) {
260 if (!initialized_) return Result(ErrorCode::NotInitialized);
261
262 uint8_t buf[Cap];
263 size_t len = 0;
265 nros_cpp_action_client_try_recv_feedback(storage_, buf, sizeof(buf), &len);
266 if (ret != 0) return Result(ret);
267 if (len == 0) return Result(ErrorCode::TryAgain);
268 if (FeedbackType::ffi_deserialize(buf, len, &feedback) != 0) {
269 return Result(ErrorCode::Error);
270 }
271 return Result::success();
272 }
273
295 if (initialized_ && !feedback_stream_.is_valid()) {
296 feedback_stream_.bind(storage_, &nros_cpp_action_client_try_recv_feedback);
297 }
298 return feedback_stream_;
299 }
300
301 const Stream<FeedbackType>& feedback_stream() const { return feedback_stream_; }
302
303 // =================================================================
304 // Async (non-blocking) API — callbacks invoked during spin_once()
305 // =================================================================
306
314 void (*goal_response)(bool accepted, const uint8_t goal_id[16], void* ctx);
316 void (*feedback)(const uint8_t goal_id[16], const uint8_t* data, size_t len, void* ctx);
318 void (*result)(const uint8_t goal_id[16], int32_t status, const uint8_t* data, size_t len,
319 void* ctx);
321 void* context;
322
324 };
325
336 if (!initialized_) return Result(ErrorCode::NotInitialized);
337
338 uint8_t buf[::nros::detail::buffer_bounds<GoalType>::tx];
339 size_t len = 0;
340 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
341 return Result(ErrorCode::Error);
342 }
344 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id)));
345 }
346
361 Result cancel_goal(const uint8_t goal_id[16]) {
362 if (!initialized_) return Result(ErrorCode::NotInitialized);
364 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
365 }
366
373 if (!initialized_) return Result(ErrorCode::NotInitialized);
374 int8_t code = 0;
376 if (ret != 0) return Result(ret);
377 out = static_cast<CancelReturnCode>(code);
378 return Result::success();
379 }
380
388 Result get_result_async(const uint8_t goal_id[16]) {
389 if (!initialized_) return Result(ErrorCode::NotInitialized);
391 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
392 }
393
400 if (!initialized_) return Result(ErrorCode::NotInitialized);
402 storage_, options.goal_response, options.feedback, options.result, options.context));
403 }
404
410 void poll() {
411 if (!initialized_) return;
413 }
414
416 bool is_valid() const { return initialized_; }
417
420 if (initialized_) {
422 initialized_ = false;
423 }
424 feedback_stream_ = Stream<FeedbackType>();
425 }
426
427 // Move semantics (non-copyable). Relocation goes through the
428 // `nros_cpp_action_client_relocate` runtime call (Phase 84.C1).
429 // The feedback stream is rebound to the new storage afterwards.
431 : executor_(other.executor_), initialized_(other.initialized_) {
432 if (other.initialized_) {
433 nros_cpp_action_client_relocate(other.storage_, storage_);
434 other.initialized_ = false;
435 }
436 other.feedback_stream_ = Stream<FeedbackType>();
437 }
438
440 if (this != &other) {
441 if (initialized_) {
443 feedback_stream_ = Stream<FeedbackType>();
444 }
445 executor_ = other.executor_;
446 initialized_ = other.initialized_;
447 if (other.initialized_) {
448 nros_cpp_action_client_relocate(other.storage_, storage_);
449 other.initialized_ = false;
450 }
451 other.feedback_stream_ = Stream<FeedbackType>();
452 }
453 return *this;
454 }
455
458 ActionClient() : executor_(nullptr), initialized_(false) {}
459
460 private:
461 ActionClient(const ActionClient&) = delete;
462 ActionClient& operator=(const ActionClient&) = delete;
463
464 friend class Node;
465
466 alignas(8) uint8_t storage_[NROS_CPP_ACTION_CLIENT_STORAGE_SIZE];
467 void* executor_; // Stashed executor handle (Phase 82) for blocking helpers
468 bool initialized_;
469 Stream<FeedbackType> feedback_stream_;
470 // Phase 87.6: action name buffer moved C++-side.
471 char action_name_[256] = {};
472};
473
474} // namespace nros
475
476// Phase 84.G8: out-of-line definition of Node::create_action_client<A>().
477#include "nros/node.hpp"
478
479namespace nros {
480
481template <typename A>
482Result Node::create_action_client(ActionClient<A>& out, const char* action_name, const QoS& qos) {
483 if (!initialized_) return Result(ErrorCode::NotInitialized);
484 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
485 nros_cpp_ret_t ret = nros_cpp_action_client_create(&handle_, action_name, A::TYPE_NAME,
486 A::Goal::TYPE_HASH, ffi_qos, out.storage_);
487 if (ret == 0) {
488 // Phase 87.6: action_name buffer lives C++-side now.
489 size_t name_len = 0;
490 while (action_name[name_len] != '\0' && name_len + 1 < sizeof(out.action_name_)) {
491 out.action_name_[name_len] = action_name[name_len];
492 ++name_len;
493 }
494 out.action_name_[name_len] = '\0';
495 out.executor_ = executor_handle_;
496 out.initialized_ = true;
497 }
498 return Result(ret);
499}
500
501} // namespace nros
502
503#endif // NROS_CPP_ACTION_CLIENT_HPP
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 action_client.hpp:38
void(* nros_cpp_action_client_feedback_callback_t)(const uint8_t goal_id[16], const uint8_t *data, size_t len, void *ctx)
Definition action_client.hpp:41
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 action_client.hpp:44
nros::ActionServer<A> — typed action server.
Definition action_client.hpp:73
Result get_result(const uint8_t goal_id[16], ResultType &result)
Definition action_client.hpp:145
Future< GoalAccept > send_goal_future(const GoalType &goal)
Definition action_client.hpp:187
Result wait_for_action_server(uint32_t timeout_ms=5000)
Definition action_client.hpp:120
Future< ResultType > get_result_future(const uint8_t goal_id[16])
Definition action_client.hpp:222
Result get_result_async(const uint8_t goal_id[16])
Definition action_client.hpp:388
ActionClient()
Definition action_client.hpp:458
ActionClient & operator=(ActionClient &&other)
Definition action_client.hpp:439
bool is_valid() const
Check if the action client is initialized and valid.
Definition action_client.hpp:416
const Stream< FeedbackType > & feedback_stream() const
Definition action_client.hpp:301
Stream< FeedbackType > & feedback_stream()
Definition action_client.hpp:294
typename A::Result ResultType
Definition action_client.hpp:76
Result try_recv_feedback(FeedbackType &feedback)
Definition action_client.hpp:253
Future< ResultType, Cap > get_result_future_sized(const uint8_t goal_id[16])
Definition action_client.hpp:232
typename A::Goal GoalType
Definition action_client.hpp:75
Result send_goal_async(const GoalType &goal, uint8_t goal_id[16])
Definition action_client.hpp:335
Result try_recv_cancel_response(CancelReturnCode &out)
Definition action_client.hpp:372
~ActionClient()
Destructor — releases action client resources.
Definition action_client.hpp:419
Result set_callbacks(const SendGoalOptions &options)
Definition action_client.hpp:399
ActionClient(ActionClient &&other)
Definition action_client.hpp:430
Result send_goal(const GoalType &goal, uint8_t goal_id[16])
Definition action_client.hpp:125
Result try_recv_feedback_sized(FeedbackType &feedback)
Definition action_client.hpp:259
typename A::Feedback FeedbackType
Definition action_client.hpp:77
Result get_result_sized(const uint8_t goal_id[16], ResultType &result)
Definition action_client.hpp:151
void poll()
Definition action_client.hpp:410
Result cancel_goal(const uint8_t goal_id[16])
Definition action_client.hpp:361
Definition future.hpp:49
Definition node.hpp:211
Result create_action_client(ActionClient< A > &out, const char *action_name, const QoS &qos=QoS::services())
Definition action_client.hpp:482
Definition qos.hpp:173
Definition result.hpp:90
static constexpr Result success()
Named constructors.
Definition result.hpp:112
Definition stream.hpp:43
bool is_valid() const
Check if the stream is connected to a valid source.
Definition stream.hpp:119
Inline storage-size macros for opaque entity buffers.
nros::Future<T> — single-shot deferred result.
int nros_cpp_ret_t
Definition future.hpp:21
Definition nros.hpp:55
@ Error
Generic failure not covered by a more specific code.
@ TryAgain
Transient — no data ready yet (non-blocking take). Retry later.
CancelReturnCode
Definition action_server.hpp:83
nros::Node and global session helpers.
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
nros::Stream<T> — multi-shot message receiver.
Definition action_client.hpp:83
static const size_t SERIALIZED_SIZE_MAX
Definition action_client.hpp:89
bool accepted
Definition action_client.hpp:85
uint8_t goal_id[16]
Definition action_client.hpp:84
static int ffi_deserialize(const uint8_t *data, size_t len, GoalAccept *out)
Definition action_client.hpp:94
Definition action_client.hpp:312
void * context
User context pointer passed to all callbacks.
Definition action_client.hpp:321
void(* goal_response)(bool accepted, const uint8_t goal_id[16], void *ctx)
Called when the server accepts or rejects the goal.
Definition action_client.hpp:314
SendGoalOptions()
Definition action_client.hpp:323
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 action_client.hpp:318
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 action_client.hpp:316
Definition qos.hpp:54