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: the DISPATCH service client
2// Freestanding C++ -- no exceptions, no STL required
3
15
16#ifndef NROS_RCLCPP_CLIENT_HPP
17#define NROS_RCLCPP_CLIENT_HPP
18
19#include <cstdint>
20#include <cstddef>
21
22#include "rclcpp/detail/callback_context.hpp" // phase-456 W3 — the handler IS the arena context
23#include "rclcpp/client_handle.hpp" // phase-456 W9 — what `Client<S>::SharedPtr` IS
25#include "rclcpp/detail/entity_name.hpp" // phase-444 — the one entity-name copy
26#include "rclcpp/logging.hpp" // phase-417 stage 3 — NROS_RCLCPP_REFUSE_* + rclcpp::detail::refuse
27#include "rclcpp/result.hpp"
28#include "rclcpp/size_bound.hpp" // rclcpp::detail::buffer_bounds<M>::tx — the request scratch bound
29
30#include "rclcpp/detail/nros_cpp_ffi.h"
31#include "rclcpp/detail/driven_future.hpp" // issue 2055 — `Client<S>::SharedFuture`
32
33// issue 1437 — `get_actual_qos()` returns a `rclcpp::QoS` BY VALUE from an
34// inline body, so the complete type must be here, not only by the time
35// `rclcpp/node.hpp` is pulled in below.
36//
37// AFTER `nros_cpp_ffi.h`, never before: `qos.hpp` defines the four
38// `nros_cpp_qos_*_t` enums ITSELF under `#ifndef NROS_CPP_FFI_H`, so
39// reaching it first makes the cbindgen header a REDEFINITION of all four.
40#include "rclcpp/qos.hpp"
41
42// Phase 189.M3.3.f — `nros_cpp_service_client_register` is excluded from
43// cbindgen (its Rust signature uses `RawResponseCallback`, an external-crate
44// type alias). Declare it locally with a matching fn-ptr typedef.
45// (`nros_cpp_service_client_send_on_handle` takes no callback, so it comes from
46// the cbindgen header.)
47extern "C" {
48typedef void (*nros_cpp_service_response_callback_t)(const uint8_t* data, size_t len, void* ctx);
49
51 const char* service_name, const char* type_name,
52 const char* type_hash, nros_cpp_qos_t qos,
54 void* context, uint8_t sched_context,
55 size_t* out_handle_id);
56} // extern "C"
57
65namespace rclcpp {
66class Node;
67}
68
69// ============================================================================
70// `rclcpp::Client<S>` -- DEFINED here (RFC-0089: rclcpp:: is the home)
71// ============================================================================
72//
73// phase-428: the definition moved from `nros::` to `rclcpp::` (the `nros::`
74// alias went in phase-483 W1). The nested `SharedPtr` / `ConstSharedPtr` / `UniquePtr`
75// aliases live on the class itself, so the rclcpp way of indexing types
76// (`rclcpp::Client<S>::SharedPtr`) resolves with no wrapper in between.
77namespace rclcpp {
78
130template <typename S> class Client {
131 public:
150
151 using RequestType = typename S::Request;
152 using ResponseType = typename S::Response;
153
154#if defined(NROS_CPP_HAS_STD_PROMISE) // hosted-family: shared-future-interop
159 using SharedRequest = ::std::shared_ptr<RequestType>;
160 using SharedResponse = ::std::shared_ptr<ResponseType>;
161 using SharedFuture = ::rclcpp::detail::DrivenSharedFuture<SharedResponse>;
162#endif
163
173 using TypedResponseFn = void (*)(const ResponseType& response);
174
188 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
189 uint8_t req_buf[::rclcpp::detail::buffer_bounds<RequestType>::tx];
190 size_t req_len = 0;
191 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
193 }
194 return Result(
195 nros_cpp_service_client_send_on_handle(executor_, handle_id_, req_buf, req_len));
196 }
197
199 bool is_valid() const { return initialized_; }
200
225 const char* get_service_name() const { return initialized_ ? service_name_ : ""; }
226
253 ::rclcpp::QoS get_request_publisher_actual_qos() const { return actual_qos_half(true); }
254
258 ::rclcpp::QoS get_response_subscription_actual_qos() const { return actual_qos_half(false); }
259
286 if (!initialized_)
287 return ::rclcpp::ResultOf<bool>::error(::rclcpp::ErrorCode::NotInitialized);
288 int32_t out = -1;
289 nros_cpp_ret_t ret =
290 nros_cpp_service_client_server_available(nullptr, executor_, handle_id_, &out);
291 // A failed CALL and a backend that cannot ANSWER are different facts;
292 // the retired `int` form reported both as `-1`. Keep them apart.
293 if (ret != 0) return ::rclcpp::ResultOf<bool>::error(static_cast<::rclcpp::ErrorCode>(ret));
294 if (out < 0) return ::rclcpp::ResultOf<bool>::error(::rclcpp::ErrorCode::Unsupported);
295 return ::rclcpp::ResultOf<bool>::ok(out != 0);
296 }
297
317 Result wait_for_service(uint32_t timeout_ms) {
318 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
319 return Result(
320 nros_cpp_service_client_wait_for_service(nullptr, executor_, handle_id_, timeout_ms));
321 }
322
327 template <typename T = void> Result wait_for_service() {
328 static_assert(::rclcpp::detail::refuse<T>::value, NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT);
330 }
331
332#ifdef NROS_CPP_HAS_STD_CHRONO // hosted-family: chrono-interop
338 template <typename Rep, typename Period>
339 Result wait_for_service(::std::chrono::duration<Rep, Period> timeout) {
340 const int64_t ms = ::rclcpp::detail::chrono_wait_budget_ms(timeout);
341 if (ms < 0) {
342 NROS_RCLCPP_SAY_REFUSED(NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT_RUNTIME);
344 }
345 return wait_for_service(static_cast<uint32_t>(ms));
346 }
347#endif // NROS_CPP_HAS_STD_CHRONO
348
350 size_t handle_id() const { return handle_id_; }
351
361 ~Client() { initialized_ = false; }
362
363 // Move semantics (non-copyable). Bookkeeping only — there is no storage to
364 // relocate, so `nros_cpp_service_client_relocate` is not called here.
365 //
366 // phase-456 W3 — a callback-style client is MOVABLE, and the warning that
367 // used to stand here is gone with its subject. It said the arena holds
368 // `this` as the response trampoline context; the arena holds the user's
369 // HANDLER now, so nothing of the caller's is referenced after registration
370 // and there is no pointer a move could leave stale. What survives the move
371 // is `{executor_, handle_id_}`, which is what `async_send_request` needs and
372 // all it needs.
373 Client(Client&& other)
374 : executor_(other.executor_), initialized_(other.initialized_),
375 handle_id_(other.handle_id_), service_name_{} {
376 ::rclcpp::detail::assign_entity_name(service_name_, other.service_name_);
377 other.initialized_ = false;
378 }
379
381 if (this != &other) {
382 executor_ = other.executor_;
383 initialized_ = other.initialized_;
384 handle_id_ = other.handle_id_;
385 ::rclcpp::detail::assign_entity_name(service_name_, other.service_name_);
386 other.initialized_ = false;
387 }
388 return *this;
389 }
390
393 Client() : executor_(nullptr), initialized_(false), service_name_{} {}
394
395 private:
396 Client(const Client&) = delete;
397 Client& operator=(const Client&) = delete;
398
399 friend class ::rclcpp::Node;
400
407 static void response_trampoline(const uint8_t* data, size_t len, void* ctx) {
408 const TypedResponseFn user_fn = ::rclcpp::detail::fn_from_context<TypedResponseFn>(ctx);
409 if (user_fn == nullptr) return;
410 ResponseType response;
411 if (ResponseType::ffi_deserialize(data, len, &response) != 0) return;
412 user_fn(response);
413 }
414
423 ::rclcpp::QoS actual_qos_half(bool request) const {
424 nros_cpp_qos_t req{};
425 nros_cpp_qos_t resp{};
426 if (!initialized_) return ::rclcpp::detail::qos_all_unknown();
427 if (nros_cpp_service_client_get_actual_qos(nullptr, executor_, handle_id_, &req, &resp) !=
428 0) {
429 return ::rclcpp::detail::qos_all_unknown();
430 }
431 return ::rclcpp::detail::qos_from_ffi(request ? req : resp);
432 }
433
434 // Callback-style BOOKKEEPING (Phase 189.M3.3.f). The handler itself is not
435 // here — it lives in the arena (phase-456 W3) — and neither is the client,
436 // so what remains is the caller's own record of a registration that refers
437 // back to nothing of the caller's. `{executor_, handle_id_}` is also the
438 // send road, which is why this object has a verb where `Service<S>` has
439 // none.
440 void* executor_;
441 bool initialized_;
442 size_t handle_id_ = static_cast<size_t>(-1);
446 char service_name_[::rclcpp::SERVICE_NAME_MAX];
447};
448
449} // namespace rclcpp
450
451// Phase 84.G8: out-of-line definition of Node::create_client<S>().
452#include "rclcpp/node.hpp"
453
454namespace rclcpp {
455
456// Phase 189.M3.3.f — callback-style (arena-registered) client. The arena owns
457// the client AND the response handler, and dispatches it during spin_once;
458// requests go through `async_send_request`, which needs only `out`'s own
459// `{executor_, handle_id_}`. `options.sched_context` is functional.
460} // namespace rclcpp
461
462namespace rclcpp {
463template <typename S, typename F, typename>
464Result Node::create_client(Client<S>& out, const char* service_name, F callback,
465 const ::rclcpp::QoS& qos, const ::rclcpp::ClientOptions& options) {
466 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
468
469 // The user handler becomes the ARENA's context (phase-456 W3); the
470 // conversion is still the compile error for a non-convertible `F`.
471 const typename Client<S>::TypedResponseFn user_fn =
472 typename Client<S>::TypedResponseFn(callback);
473
474 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
475 ? 0u
476 : static_cast<uint8_t>(options.sched_context);
477 size_t handle = static_cast<size_t>(-1);
479 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
480 reinterpret_cast<nros_cpp_service_response_callback_t>(&Client<S>::response_trampoline),
481 ::rclcpp::detail::fn_to_context(user_fn), sched, &handle);
482 if (ret == 0) {
483 out.executor_ = executor_handle_;
484 out.handle_id_ = handle;
485 // phase-444 — see `rclcpp::PollClient`'s overload, which does the same.
486 ::rclcpp::detail::assign_entity_name(out.service_name_, service_name);
487 out.initialized_ = true;
488 }
489 return Result(ret);
490}
491} // namespace rclcpp
492
493namespace rclcpp {} // namespace rclcpp
494
495#endif // NROS_RCLCPP_CLIENT_HPP
Definition client.hpp:130
void(*)(const ResponseType &response) TypedResponseFn
Definition client.hpp:173
typename S::Request RequestType
Definition client.hpp:151
::rclcpp::ClientHandle< S > UniquePtr
rclcpp::Client<S>::UniquePtr — see SharedPtr.
Definition client.hpp:149
Client(Client &&other)
Definition client.hpp:373
::rclcpp::ClientHandle< S > SharedPtr
Definition client.hpp:142
~Client()
Definition client.hpp:361
Result async_send_request(const RequestType &req)
Definition client.hpp:187
::rclcpp::QoS get_request_publisher_actual_qos() const
Definition client.hpp:253
::rclcpp::ResultOf< bool > service_is_ready() const
Definition client.hpp:285
typename S::Response ResponseType
Definition client.hpp:152
Client()
Definition client.hpp:393
const char * get_service_name() const
Definition client.hpp:225
Result wait_for_service()
Definition client.hpp:327
Client & operator=(Client &&other)
Definition client.hpp:380
::rclcpp::ClientHandle< S > ConstSharedPtr
Definition client.hpp:147
::rclcpp::QoS get_response_subscription_actual_qos() const
Definition client.hpp:258
Result wait_for_service(uint32_t timeout_ms)
Definition client.hpp:317
bool is_valid() const
Check if the client was registered.
Definition client.hpp:199
size_t handle_id() const
Executor arena slot for the registration; SIZE_MAX until registered.
Definition client.hpp:350
Definition node.hpp:222
Definition node.hpp:197
Definition node.hpp:845
::rclcpp::Owned<::rclcpp::PollClient< S > > create_client(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Definition qos.hpp:276
Definition result.hpp:332
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:25
constexpr nros_cpp_qos_t qos_to_ffi(const QoS &qos)
Definition qos.hpp:719
Definition rclcpp.hpp:75
ErrorCode
Definition result.hpp:114
@ Error
Generic failure not covered by a more specific code.
Definition result.hpp:118
@ NotInitialized
Definition result.hpp:131
@ Unsupported
Operation not implemented by the active backend.
Definition result.hpp:152
ResultOf< void > Result
Definition result.hpp:247
rclcpp::Node and global session helpers.
rclcpp::QoS — full DDS-shaped QoS settings (Phase 108.B.7).
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)
**auto handle
Definition client.hpp:127
void(* nros_cpp_service_response_callback_t)(const uint8_t *data, size_t len, void *ctx)
Definition client.hpp:48
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
Definition qos.hpp:84