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: Service client class
2// Freestanding C++ -- no exceptions, no STL required
3
10#ifndef NROS_CPP_CLIENT_HPP
11#define NROS_CPP_CLIENT_HPP
12
13#include <cstdint>
14#include <cstddef>
15
16#include "nros/config.hpp"
17#include "nros/result.hpp"
18#include "nros/size_bound.hpp" // nros::rx_buffer_capacity<M> — the receive-buffer size
19#include "nros/future.hpp"
20
21#include "nros_cpp_ffi.h"
22
23// Phase 189.M3.3.f — `nros_cpp_service_client_register` is excluded from
24// cbindgen (its Rust signature uses `RawResponseCallback`, an external-crate
25// type alias). Declare it locally with a matching fn-ptr typedef.
26// (`nros_cpp_service_client_send_on_handle` takes no callback, so it comes from
27// the cbindgen header.)
28extern "C" {
29typedef void (*nros_cpp_service_response_callback_t)(const uint8_t* data, size_t len, void* ctx);
30
32 const char* service_name, const char* type_name,
33 const char* type_hash, nros_cpp_qos_t qos,
35 void* context, uint8_t sched_context,
36 size_t* out_handle_id);
37} // extern "C"
38
39namespace nros {
40
55template <typename S> class Client {
56 public:
57 using RequestType = typename S::Request;
58 using ResponseType = typename S::Response;
59
64 using TypedResponseFn = void (*)(const ResponseType& response);
65 using TypedResponseFnWithCtx = void (*)(const ResponseType& response, void* ctx);
66
76 return send_request_sized<::nros::rx_buffer_capacity<ResponseType>::value>(req);
77 }
78
88 template <size_t RespCap>
91 if (!initialized_) return Fut();
92
93 uint8_t req_buf[::nros::detail::buffer_bounds<RequestType>::tx];
94 size_t req_len = 0;
95 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
96 return Fut();
97 }
98
99 nros_cpp_ret_t ret = nros_cpp_service_client_send_request(storage_, req_buf, req_len);
100 if (ret != 0) return Fut();
101
102 return Fut(storage_, &nros_cpp_service_client_take_response,
103 0 // slot 0 (single outstanding request)
104 );
105 }
106
116 Result call(const RequestType& req, ResponseType& resp, uint32_t timeout_ms = 5000) {
117 return call_sized<::nros::rx_buffer_capacity<ResponseType>::value>(req, resp, timeout_ms);
118 }
119
121 template <size_t RespCap>
122 Result call_sized(const RequestType& req, ResponseType& resp, uint32_t timeout_ms = 5000) {
123 if (!initialized_ || !executor_) return Result(ErrorCode::NotInitialized);
124 auto fut = send_request_sized<RespCap>(req);
125 return fut.wait(executor_, timeout_ms, resp);
126 }
127
146 Result call_polling(const RequestType& req, ResponseType& resp, uint32_t timeout_ms = 100) {
147 return call_polling_sized<::nros::rx_buffer_capacity<ResponseType>::value>(req, resp,
148 timeout_ms);
149 }
150
154 template <size_t RespCap>
156 uint32_t timeout_ms = 100) {
157 if (!initialized_) return Result(ErrorCode::NotInitialized);
158 uint8_t req_buf[::nros::detail::buffer_bounds<RequestType>::tx];
159 size_t req_len = 0;
160 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
161 return Result(ErrorCode::Error);
162 }
163 uint8_t resp_buf[RespCap];
164 size_t resp_len = 0;
165 nros_cpp_ret_t ret = nros_cpp_service_client_call_raw(
166 storage_, req_buf, req_len, resp_buf, sizeof(resp_buf), &resp_len, timeout_ms);
167 if (ret != 0) return Result(ret);
168 if (resp_len == 0) return Result(ErrorCode::Timeout);
169 if (ResponseType::ffi_deserialize(resp_buf, resp_len, &resp) != 0) {
170 return Result(ErrorCode::Error);
171 }
172 return Result::success();
173 }
174
176 bool is_valid() const { return initialized_; }
177
193 if (!initialized_) return Expected<bool>::error(ErrorCode::NotInitialized);
194 int out = -1;
195 nros_cpp_ret_t ret =
196 nros_cpp_service_client_server_available(const_cast<uint8_t*>(storage_), &out);
197 // A failed CALL and a backend that cannot ANSWER are different facts,
198 // and the old `int` form reported both as `-1`. Keep them apart.
199 if (ret != 0) return Expected<bool>::error(static_cast<ErrorCode>(ret));
201 return Expected<bool>::ok(out != 0);
202 }
203
209 [[deprecated("Client::server_available is deprecated; use "
210 "Client::service_is_ready, which returns Expected<bool>")]] int
212 auto r = service_is_ready();
213 if (!r.ok()) return -1;
214 return r.value() ? 1 : 0;
215 }
216
231 Result wait_for_service(uint32_t timeout_ms = 5000) {
232 if (!initialized_) return Result(ErrorCode::NotInitialized);
233 return Result(nros_cpp_service_client_wait_for_service(storage_, executor_, timeout_ms));
234 }
235
241 if (!initialized_ || !callback_mode_) return Result(ErrorCode::NotInitialized);
242 uint8_t req_buf[::nros::detail::buffer_bounds<RequestType>::tx];
243 size_t req_len = 0;
244 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
245 return Result(ErrorCode::Error);
246 }
247 return Result(
248 nros_cpp_service_client_send_on_handle(executor_, handle_id_, req_buf, req_len));
249 }
250
253 size_t handle_id() const { return handle_id_; }
254
261 if (initialized_ && !callback_mode_) {
262 nros_cpp_service_client_destroy(storage_);
263 }
264 initialized_ = false;
265 }
266
267 // Move semantics (non-copyable). Future-style relocation goes through the
268 // `nros_cpp_service_client_relocate` runtime call (Phase 84.C1). A
269 // callback-style client must NOT be moved after register — the arena holds
270 // `this` as the response trampoline context (M3.3.f).
271 Client(Client&& other)
272 : executor_(other.executor_), initialized_(other.initialized_), user_fn_(other.user_fn_),
273 user_fn_ctx_(other.user_fn_ctx_), user_ctx_(other.user_ctx_),
274 handle_id_(other.handle_id_), callback_mode_(other.callback_mode_) {
275 if (other.initialized_ && !other.callback_mode_) {
276 nros_cpp_service_client_relocate(other.storage_, storage_);
277 }
278 other.initialized_ = false;
279 }
280
282 if (this != &other) {
283 if (initialized_ && !callback_mode_) {
284 nros_cpp_service_client_destroy(storage_);
285 }
286 executor_ = other.executor_;
287 initialized_ = other.initialized_;
288 user_fn_ = other.user_fn_;
289 user_fn_ctx_ = other.user_fn_ctx_;
290 user_ctx_ = other.user_ctx_;
291 handle_id_ = other.handle_id_;
292 callback_mode_ = other.callback_mode_;
293 if (other.initialized_ && !other.callback_mode_) {
294 nros_cpp_service_client_relocate(other.storage_, storage_);
295 }
296 other.initialized_ = false;
297 }
298 return *this;
299 }
300
303 Client() : storage_(), executor_(nullptr), initialized_(false) {}
304
305 private:
306 Client(const Client&) = delete;
307 Client& operator=(const Client&) = delete;
308
309 friend class Node;
310
314 static void response_trampoline(const uint8_t* data, size_t len, void* ctx) {
315 auto* self = static_cast<Client*>(ctx);
316 if (self == nullptr) return;
317 ResponseType response;
318 if (ResponseType::ffi_deserialize(data, len, &response) != 0) return;
319 if (self->user_fn_ != nullptr) {
320 self->user_fn_(response);
321 } else if (self->user_fn_ctx_ != nullptr) {
322 self->user_fn_ctx_(response, self->user_ctx_);
323 }
324 }
325
326 alignas(8) uint8_t storage_[NROS_SERVICE_CLIENT_SIZE];
327 void* executor_;
328 bool initialized_;
329 // Callback-style state (Phase 189.M3.3.f); unused in future mode.
330 TypedResponseFn user_fn_ = nullptr;
331 TypedResponseFnWithCtx user_fn_ctx_ = nullptr;
332 void* user_ctx_ = nullptr;
333 size_t handle_id_ = static_cast<size_t>(-1);
334 bool callback_mode_ = false;
335};
336
337} // namespace nros
338
339// Phase 84.G8: out-of-line definition of Node::create_client<S>().
340#include "nros/node.hpp"
341
342namespace nros {
343
344template <typename S>
345Result Node::create_client(Client<S>& out, const char* service_name, const QoS& qos) {
346 if (!initialized_) return Result(ErrorCode::NotInitialized);
347 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
348 nros_cpp_ret_t ret = nros_cpp_service_client_create(
349 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos, out.storage_);
350 if (ret == 0) {
351 out.executor_ = executor_handle_;
352 out.initialized_ = true;
353 }
354 return Result(ret);
355}
356
357// Phase 189.M3.3.f — callback-style (arena-registered) client. The arena owns
358// the client + dispatches `out`'s response handler during spin_once; requests
359// go through `async_send_request`. `options.sched_context` is functional.
360template <typename S, typename F, typename>
361Result Node::create_client(Client<S>& out, const char* service_name, F callback, const QoS& qos,
362 const ClientOptions& options) {
363 if (!initialized_) return Result(ErrorCode::NotInitialized);
364 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
365
366 out.user_fn_ = typename Client<S>::TypedResponseFn(callback);
367 out.user_fn_ctx_ = nullptr;
368 out.user_ctx_ = nullptr;
369
370 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
371 ? 0u
372 : static_cast<uint8_t>(options.sched_context);
373 size_t handle = static_cast<size_t>(-1);
375 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
377 &out, sched, &handle);
378 if (ret == 0) {
379 out.executor_ = executor_handle_;
380 out.handle_id_ = handle;
381 out.callback_mode_ = true;
382 out.initialized_ = true;
383 }
384 return Result(ret);
385}
386
387} // namespace nros
388
389#endif // NROS_CPP_CLIENT_HPP
Definition client.hpp:55
typename S::Response ResponseType
Definition client.hpp:58
Result async_send_request(const RequestType &req)
Definition client.hpp:240
Client()
Definition client.hpp:303
void(*)(const ResponseType &response, void *ctx) TypedResponseFnWithCtx
Definition client.hpp:65
Client & operator=(Client &&other)
Definition client.hpp:281
int server_available() const
Definition client.hpp:211
Result call_sized(const RequestType &req, ResponseType &resp, uint32_t timeout_ms=5000)
call with the REPLY buffer sized by the caller (issue 0964).
Definition client.hpp:122
size_t handle_id() const
Definition client.hpp:253
Result call_polling_sized(const RequestType &req, ResponseType &resp, uint32_t timeout_ms=100)
Definition client.hpp:155
typename S::Request RequestType
Definition client.hpp:57
~Client()
Definition client.hpp:260
Expected< bool > service_is_ready() const
Definition client.hpp:192
Future< ResponseType, RespCap > send_request_sized(const RequestType &req)
Definition client.hpp:89
Client(Client &&other)
Definition client.hpp:271
bool is_valid() const
Check if the client is initialized and valid.
Definition client.hpp:176
void(*)(const ResponseType &response) TypedResponseFn
Definition client.hpp:64
Future< ResponseType > send_request(const RequestType &req)
Definition client.hpp:75
Result call(const RequestType &req, ResponseType &resp, uint32_t timeout_ms=5000)
Definition client.hpp:116
Result call_polling(const RequestType &req, ResponseType &resp, uint32_t timeout_ms=100)
Definition client.hpp:146
Result wait_for_service(uint32_t timeout_ms=5000)
Definition client.hpp:231
Definition result.hpp:198
ErrorCode error() const
Definition result.hpp:221
bool ok() const
Definition result.hpp:214
Definition future.hpp:49
Definition node.hpp:211
Result create_client(Client< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition client.hpp:345
Definition qos.hpp:173
Definition result.hpp:90
static constexpr Result success()
Named constructors.
Definition result.hpp:112
nros_cpp_ret_t nros_cpp_service_client_register(const nros_cpp_node_t *node, const char *service_name, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_service_response_callback_t callback, void *context, uint8_t sched_context, size_t *out_handle_id)
void(* nros_cpp_service_response_callback_t)(const uint8_t *data, size_t len, void *ctx)
Definition client.hpp:29
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
ErrorCode
Definition result.hpp:30
@ Error
Generic failure not covered by a more specific code.
@ Unsupported
Operation not implemented by the active backend.
@ Timeout
Operation deadline elapsed before completion.
nros::Node and global session helpers.
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
Definition qos.hpp:54