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/future.hpp"
19
20#include "nros_cpp_ffi.h"
21
22// Phase 189.M3.3.f — `nros_cpp_service_client_register` is excluded from
23// cbindgen (its Rust signature uses `RawResponseCallback`, an external-crate
24// type alias). Declare it locally with a matching fn-ptr typedef.
25// (`nros_cpp_service_client_send_on_handle` takes no callback, so it comes from
26// the cbindgen header.)
27extern "C" {
28typedef void (*nros_cpp_service_response_callback_t)(const uint8_t* data, size_t len, void* ctx);
29
31 const char* service_name, const char* type_name,
32 const char* type_hash, nros_cpp_qos_t qos,
34 void* context, uint8_t sched_context,
35 size_t* out_handle_id);
36} // extern "C"
37
38namespace nros {
39
54template <typename S> class Client {
55 public:
56 using RequestType = typename S::Request;
57 using ResponseType = typename S::Response;
58
65
75 if (!initialized_) return Future<ResponseType>();
76
77 uint8_t req_buf[RequestType::SERIALIZED_SIZE_MAX];
78 size_t req_len = 0;
79 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
80 return Future<ResponseType>();
81 }
82
84 if (ret != 0) return Future<ResponseType>();
85
87 0 // slot 0 (single outstanding request)
88 );
89 }
90
101 if (!initialized_ || !executor_) return Result(ErrorCode::NotInitialized);
102 auto fut = send_request(req);
103 return fut.wait(executor_, timeout_ms, resp);
104 }
105
107 bool is_valid() const { return initialized_; }
108
122 int server_available() const {
123 if (!initialized_) return -1;
124 int out = -1;
126 nros_cpp_service_client_server_available(const_cast<uint8_t*>(storage_), &out);
127 if (ret != 0) return -1;
128 return out;
129 }
130
136 if (!initialized_ || !callback_mode_) return Result(ErrorCode::NotInitialized);
137 uint8_t req_buf[RequestType::SERIALIZED_SIZE_MAX];
138 size_t req_len = 0;
139 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
140 return Result(ErrorCode::Error);
141 }
142 return Result(
144 }
145
148 size_t handle_id() const { return handle_id_; }
149
156 if (initialized_ && !callback_mode_) {
158 }
159 initialized_ = false;
160 }
161
162 // Move semantics (non-copyable). Future-style relocation goes through the
163 // `nros_cpp_service_client_relocate` runtime call (Phase 84.C1). A
164 // callback-style client must NOT be moved after register — the arena holds
165 // `this` as the response trampoline context (M3.3.f).
167 : executor_(other.executor_), initialized_(other.initialized_), user_fn_(other.user_fn_),
168 user_fn_ctx_(other.user_fn_ctx_), user_ctx_(other.user_ctx_),
169 handle_id_(other.handle_id_), callback_mode_(other.callback_mode_) {
170 if (other.initialized_ && !other.callback_mode_) {
171 nros_cpp_service_client_relocate(other.storage_, storage_);
172 }
173 other.initialized_ = false;
174 }
175
177 if (this != &other) {
178 if (initialized_ && !callback_mode_) {
180 }
181 executor_ = other.executor_;
182 initialized_ = other.initialized_;
183 user_fn_ = other.user_fn_;
184 user_fn_ctx_ = other.user_fn_ctx_;
185 user_ctx_ = other.user_ctx_;
186 handle_id_ = other.handle_id_;
187 callback_mode_ = other.callback_mode_;
188 if (other.initialized_ && !other.callback_mode_) {
189 nros_cpp_service_client_relocate(other.storage_, storage_);
190 }
191 other.initialized_ = false;
192 }
193 return *this;
194 }
195
198 Client() : storage_(), executor_(nullptr), initialized_(false) {}
199
200 private:
201 Client(const Client&) = delete;
202 Client& operator=(const Client&) = delete;
203
204 friend class Node;
205
209 static void response_trampoline(const uint8_t* data, size_t len, void* ctx) {
210 auto* self = static_cast<Client*>(ctx);
211 if (self == nullptr) return;
213 if (ResponseType::ffi_deserialize(data, len, &response) != 0) return;
214 if (self->user_fn_ != nullptr) {
215 self->user_fn_(response);
216 } else if (self->user_fn_ctx_ != nullptr) {
217 self->user_fn_ctx_(response, self->user_ctx_);
218 }
219 }
220
221 alignas(8) uint8_t storage_[NROS_SERVICE_CLIENT_SIZE];
222 void* executor_;
223 bool initialized_;
224 // Callback-style state (Phase 189.M3.3.f); unused in future mode.
225 TypedResponseFn user_fn_ = nullptr;
226 TypedResponseFnWithCtx user_fn_ctx_ = nullptr;
227 void* user_ctx_ = nullptr;
228 size_t handle_id_ = static_cast<size_t>(-1);
229 bool callback_mode_ = false;
230};
231
232} // namespace nros
233
234// Phase 84.G8: out-of-line definition of Node::create_client<S>().
235#include "nros/node.hpp"
236
237namespace nros {
238
239template <typename S>
241 if (!initialized_) return Result(ErrorCode::NotInitialized);
243 ffi_qos.reliability = static_cast<nros_cpp_qos_reliability_t>(qos.reliability_raw());
244 ffi_qos.durability = static_cast<nros_cpp_qos_durability_t>(qos.durability_raw());
245 ffi_qos.history = static_cast<nros_cpp_qos_history_t>(qos.history_raw());
246 ffi_qos.liveliness_kind = static_cast<nros_cpp_qos_liveliness_t>(qos.liveliness_raw());
247 ffi_qos.depth = qos.depth();
248 ffi_qos.deadline_ms = qos.deadline_ms();
249 ffi_qos.lifespan_ms = qos.lifespan_ms();
250 ffi_qos.liveliness_lease_ms = qos.liveliness_lease_ms();
251 ffi_qos.avoid_ros_namespace_conventions = qos.avoid_ros_namespace_conventions() ? 1 : 0;
253 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos, out.storage_);
254 if (ret == 0) {
255 out.executor_ = executor_handle_;
256 out.initialized_ = true;
257 }
258 return Result(ret);
259}
260
261// Phase 189.M3.3.f — callback-style (arena-registered) client. The arena owns
262// the client + dispatches `out`'s response handler during spin_once; requests
263// go through `async_send_request`. `options.sched_context` is functional.
264template <typename S, typename F, typename>
266 const ClientOptions& options) {
267 if (!initialized_) return Result(ErrorCode::NotInitialized);
269 ffi_qos.reliability = static_cast<nros_cpp_qos_reliability_t>(qos.reliability_raw());
270 ffi_qos.durability = static_cast<nros_cpp_qos_durability_t>(qos.durability_raw());
271 ffi_qos.history = static_cast<nros_cpp_qos_history_t>(qos.history_raw());
272 ffi_qos.liveliness_kind = static_cast<nros_cpp_qos_liveliness_t>(qos.liveliness_raw());
273 ffi_qos.depth = qos.depth();
274 ffi_qos.deadline_ms = qos.deadline_ms();
275 ffi_qos.lifespan_ms = qos.lifespan_ms();
276 ffi_qos.liveliness_lease_ms = qos.liveliness_lease_ms();
277 ffi_qos.avoid_ros_namespace_conventions = qos.avoid_ros_namespace_conventions() ? 1 : 0;
278
279 out.user_fn_ = typename Client<S>::TypedResponseFn(callback);
280 out.user_fn_ctx_ = nullptr;
281 out.user_ctx_ = nullptr;
282
283 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
284 ? 0u
285 : static_cast<uint8_t>(options.sched_context);
286 size_t handle = static_cast<size_t>(-1);
288 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
290 &out, sched, &handle);
291 if (ret == 0) {
292 out.executor_ = executor_handle_;
293 out.handle_id_ = handle;
294 out.callback_mode_ = true;
295 out.initialized_ = true;
296 }
297 return Result(ret);
298}
299
300} // namespace nros
301
302#endif // NROS_CPP_CLIENT_HPP
Definition client.hpp:54
typename S::Response ResponseType
Definition client.hpp:57
Result async_send_request(const RequestType &req)
Definition client.hpp:135
Client()
Definition client.hpp:198
void(*)(const ResponseType &response, void *ctx) TypedResponseFnWithCtx
Definition client.hpp:64
Client & operator=(Client &&other)
Definition client.hpp:176
int server_available() const
Definition client.hpp:122
size_t handle_id() const
Definition client.hpp:148
typename S::Request RequestType
Definition client.hpp:56
~Client()
Definition client.hpp:155
Client(Client &&other)
Definition client.hpp:166
bool is_valid() const
Check if the client is initialized and valid.
Definition client.hpp:107
void(*)(const ResponseType &response) TypedResponseFn
Definition client.hpp:63
Future< ResponseType > send_request(const RequestType &req)
Definition client.hpp:74
Result call(const RequestType &req, ResponseType &resp, uint32_t timeout_ms=5000)
Definition client.hpp:100
Definition future.hpp:40
Result wait(void *executor_handle, uint32_t timeout_ms, T &out, uint32_t poll_ms=10)
Definition future.hpp:84
Definition node.hpp:158
Result create_client(Client< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition client.hpp:240
Definition qos.hpp:67
Definition result.hpp:52
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:28
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.
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.
Definition qos.hpp:41