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/future.hpp"
20#include "nros/stream.hpp"
21
22// Phase 118.D: FFI declarations sourced from cbindgen-generated
23// `nros_cpp_ffi.h`. cbindgen renders Rust `*const [u8; 16]` /
24// `*mut [u8; 16]` parameters as C++ pointer-to-array
25// (`const uint8_t (*goal_id)[16]`); member-function bodies below
26// take `goal_id` as a 16-byte array (decays to `uint8_t*`) and
27// `reinterpret_cast` at the call site to bridge the two ABI
28// equivalent shapes.
29#include "nros_cpp_ffi.h"
30
31extern "C" {
33 const uint8_t goal_id[16],
34 void* ctx);
35typedef void (*nros_cpp_action_client_feedback_callback_t)(const uint8_t goal_id[16],
36 const uint8_t* data, size_t len,
37 void* ctx);
38typedef void (*nros_cpp_action_client_result_callback_t)(const uint8_t goal_id[16], int32_t status,
39 const uint8_t* data, size_t len,
40 void* ctx);
41
43 void* handle, nros_cpp_action_client_goal_response_callback_t goal_response,
45 nros_cpp_action_client_result_callback_t result, void* context);
46} // extern "C"
47
48namespace nros {
49
67template <typename A> class ActionClient {
68 public:
69 using GoalType = typename A::Goal;
70 using ResultType = typename A::Result;
71 using FeedbackType = typename A::Feedback;
72
77 struct GoalAccept {
78 uint8_t goal_id[16];
80
81 static const size_t SERIALIZED_SIZE_MAX = 32;
82 static int ffi_deserialize(const uint8_t* data, size_t len, GoalAccept* out) {
83 if (!out || len < 17) return -1;
84 for (int i = 0; i < 16; ++i)
85 out->goal_id[i] = data[i];
86 out->accepted = data[16] != 0;
87 return 0;
88 }
89 };
90
99 Result send_goal(const GoalType& goal, uint8_t goal_id[16]) {
100 if (!initialized_) return Result(ErrorCode::NotInitialized);
101
102 uint8_t buf[GoalType::SERIALIZED_SIZE_MAX];
103 size_t len = 0;
104 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
105 return Result(ErrorCode::Error);
106 }
107 return Result(nros_cpp_action_client_send_goal(storage_, buf, len,
108 reinterpret_cast<uint8_t(*)[16]>(goal_id)));
109 }
110
119 Result get_result(const uint8_t goal_id[16], ResultType& result) {
120 if (!initialized_) return Result(ErrorCode::NotInitialized);
121
122 uint8_t buf[ResultType::SERIALIZED_SIZE_MAX];
123 size_t len = 0;
124 nros_cpp_ret_t ret = nros_cpp_action_client_get_result(
125 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id), buf, sizeof(buf),
126 &len);
127 if (ret != 0) return Result(ret);
128
129 if (ResultType::ffi_deserialize(buf, len, &result) != 0) {
130 return Result(ErrorCode::Error);
131 }
132 return Result::success();
133 }
134
135 // =================================================================
136 // Future-based API — non-blocking, polled via Future<T>
137 // =================================================================
138
156 if (!initialized_) return Future<GoalAccept>();
157
158 uint8_t buf[GoalType::SERIALIZED_SIZE_MAX];
159 size_t len = 0;
160 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
161 return Future<GoalAccept>();
162 }
163
164 uint8_t goal_id[16];
166 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id));
167 if (ret != 0) return Future<GoalAccept>();
168
170 0 // slot 0 (single outstanding goal request)
171 );
172 }
173
191 if (!initialized_) return Future<ResultType>();
192
194 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id));
195 if (ret != 0) return Future<ResultType>();
196
198 0 // slot 0 (single outstanding result request)
199 );
200 }
201
211 if (!initialized_) return Result(ErrorCode::NotInitialized);
212
213 uint8_t buf[FeedbackType::SERIALIZED_SIZE_MAX];
214 size_t len = 0;
216 nros_cpp_action_client_try_recv_feedback(storage_, buf, sizeof(buf), &len);
217 if (ret != 0) return Result(ret);
218 if (len == 0) return Result(ErrorCode::TryAgain);
219 if (FeedbackType::ffi_deserialize(buf, len, &feedback) != 0) {
220 return Result(ErrorCode::Error);
221 }
222 return Result::success();
223 }
224
246 if (initialized_ && !feedback_stream_.is_valid()) {
247 feedback_stream_.bind(storage_, &nros_cpp_action_client_try_recv_feedback);
248 }
249 return feedback_stream_;
250 }
251
252 const Stream<FeedbackType>& feedback_stream() const { return feedback_stream_; }
253
254 // =================================================================
255 // Async (non-blocking) API — callbacks invoked during spin_once()
256 // =================================================================
257
265 void (*goal_response)(bool accepted, const uint8_t goal_id[16], void* ctx);
267 void (*feedback)(const uint8_t goal_id[16], const uint8_t* data, size_t len, void* ctx);
269 void (*result)(const uint8_t goal_id[16], int32_t status, const uint8_t* data, size_t len,
270 void* ctx);
272 void* context;
273
275 };
276
287 if (!initialized_) return Result(ErrorCode::NotInitialized);
288
289 uint8_t buf[GoalType::SERIALIZED_SIZE_MAX];
290 size_t len = 0;
291 if (GoalType::ffi_serialize(&goal, buf, sizeof(buf), &len) != 0) {
292 return Result(ErrorCode::Error);
293 }
295 storage_, buf, len, reinterpret_cast<uint8_t(*)[16]>(goal_id)));
296 }
297
305 Result get_result_async(const uint8_t goal_id[16]) {
306 if (!initialized_) return Result(ErrorCode::NotInitialized);
308 storage_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
309 }
310
317 if (!initialized_) return Result(ErrorCode::NotInitialized);
319 storage_, options.goal_response, options.feedback, options.result, options.context));
320 }
321
327 void poll() {
328 if (!initialized_) return;
330 }
331
333 bool is_valid() const { return initialized_; }
334
337 if (initialized_) {
339 initialized_ = false;
340 }
341 feedback_stream_ = Stream<FeedbackType>();
342 }
343
344 // Move semantics (non-copyable). Relocation goes through the
345 // `nros_cpp_action_client_relocate` runtime call (Phase 84.C1).
346 // The feedback stream is rebound to the new storage afterwards.
348 : executor_(other.executor_), initialized_(other.initialized_) {
349 if (other.initialized_) {
350 nros_cpp_action_client_relocate(other.storage_, storage_);
351 other.initialized_ = false;
352 }
353 other.feedback_stream_ = Stream<FeedbackType>();
354 }
355
357 if (this != &other) {
358 if (initialized_) {
360 feedback_stream_ = Stream<FeedbackType>();
361 }
362 executor_ = other.executor_;
363 initialized_ = other.initialized_;
364 if (other.initialized_) {
365 nros_cpp_action_client_relocate(other.storage_, storage_);
366 other.initialized_ = false;
367 }
368 other.feedback_stream_ = Stream<FeedbackType>();
369 }
370 return *this;
371 }
372
375 ActionClient() : executor_(nullptr), initialized_(false) {}
376
377 private:
378 ActionClient(const ActionClient&) = delete;
379 ActionClient& operator=(const ActionClient&) = delete;
380
381 friend class Node;
382
383 alignas(8) uint8_t storage_[NROS_CPP_ACTION_CLIENT_STORAGE_SIZE];
384 void* executor_; // Stashed executor handle (Phase 82) for blocking helpers
385 bool initialized_;
386 Stream<FeedbackType> feedback_stream_;
387 // Phase 87.6: action name buffer moved C++-side.
388 char action_name_[256] = {};
389};
390
391} // namespace nros
392
393// Phase 84.G8: out-of-line definition of Node::create_action_client<A>().
394#include "nros/node.hpp"
395
396namespace nros {
397
398template <typename A>
400 if (!initialized_) return Result(ErrorCode::NotInitialized);
402 ffi_qos.reliability = static_cast<nros_cpp_qos_reliability_t>(qos.reliability_raw());
403 ffi_qos.durability = static_cast<nros_cpp_qos_durability_t>(qos.durability_raw());
404 ffi_qos.history = static_cast<nros_cpp_qos_history_t>(qos.history_raw());
405 ffi_qos.liveliness_kind = static_cast<nros_cpp_qos_liveliness_t>(qos.liveliness_raw());
406 ffi_qos.depth = qos.depth();
407 ffi_qos.deadline_ms = qos.deadline_ms();
408 ffi_qos.lifespan_ms = qos.lifespan_ms();
409 ffi_qos.liveliness_lease_ms = qos.liveliness_lease_ms();
410 ffi_qos.avoid_ros_namespace_conventions = qos.avoid_ros_namespace_conventions() ? 1 : 0;
412 A::Goal::TYPE_HASH, ffi_qos, out.storage_);
413 if (ret == 0) {
414 // Phase 87.6: action_name buffer lives C++-side now.
415 size_t name_len = 0;
416 while (action_name[name_len] != '\0' && name_len + 1 < sizeof(out.action_name_)) {
417 out.action_name_[name_len] = action_name[name_len];
418 ++name_len;
419 }
420 out.action_name_[name_len] = '\0';
421 out.executor_ = executor_handle_;
422 out.initialized_ = true;
423 }
424 return Result(ret);
425}
426
427} // namespace nros
428
429#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:32
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:35
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:38
Definition action_client.hpp:67
Result get_result(const uint8_t goal_id[16], ResultType &result)
Definition action_client.hpp:119
Future< GoalAccept > send_goal_future(const GoalType &goal)
Definition action_client.hpp:155
Future< ResultType > get_result_future(const uint8_t goal_id[16])
Definition action_client.hpp:190
Result get_result_async(const uint8_t goal_id[16])
Definition action_client.hpp:305
ActionClient()
Definition action_client.hpp:375
ActionClient & operator=(ActionClient &&other)
Definition action_client.hpp:356
bool is_valid() const
Check if the action client is initialized and valid.
Definition action_client.hpp:333
const Stream< FeedbackType > & feedback_stream() const
Definition action_client.hpp:252
Stream< FeedbackType > & feedback_stream()
Definition action_client.hpp:245
typename A::Result ResultType
Definition action_client.hpp:70
Result try_recv_feedback(FeedbackType &feedback)
Definition action_client.hpp:210
typename A::Goal GoalType
Definition action_client.hpp:69
Result send_goal_async(const GoalType &goal, uint8_t goal_id[16])
Definition action_client.hpp:286
~ActionClient()
Destructor — releases action client resources.
Definition action_client.hpp:336
Result set_callbacks(const SendGoalOptions &options)
Definition action_client.hpp:316
ActionClient(ActionClient &&other)
Definition action_client.hpp:347
Result send_goal(const GoalType &goal, uint8_t goal_id[16])
Definition action_client.hpp:99
typename A::Feedback FeedbackType
Definition action_client.hpp:71
void poll()
Definition action_client.hpp:327
Definition future.hpp:40
Definition node.hpp:158
Result create_action_client(ActionClient< A > &out, const char *action_name, const QoS &qos=QoS::services())
Definition action_client.hpp:399
Definition qos.hpp:67
Definition result.hpp:52
static constexpr Result success()
Named constructors.
Definition result.hpp:74
bool is_valid() const
Check if the stream is connected to a valid source.
Definition stream.hpp:105
Inline storage-size macros for opaque entity buffers.
nros::Future<T> — single-shot deferred result.
int nros_cpp_ret_t
Definition future.hpp:20
Definition nros.hpp:42
@ 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_cpp_qos_history_t
Definition qos.hpp:31
nros_cpp_qos_liveliness_t
Definition qos.hpp:35
nros_cpp_qos_durability_t
Definition qos.hpp:27
nros_cpp_qos_reliability_t
Definition qos.hpp:23
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
nros::Stream<T> — multi-shot message receiver.
Definition action_client.hpp:77
static const size_t SERIALIZED_SIZE_MAX
Definition action_client.hpp:81
bool accepted
Definition action_client.hpp:79
uint8_t goal_id[16]
Definition action_client.hpp:78
static int ffi_deserialize(const uint8_t *data, size_t len, GoalAccept *out)
Definition action_client.hpp:82
Definition action_client.hpp:263
void * context
User context pointer passed to all callbacks.
Definition action_client.hpp:272
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:265
SendGoalOptions()
Definition action_client.hpp:274
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:269
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:267
Definition qos.hpp:41