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
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
32// issue 1437 — `get_actual_qos()` returns a `rclcpp::QoS` BY VALUE from an
33// inline body, so the complete type must be here, not only by the time
34// `rclcpp/node.hpp` is pulled in below.
35//
36// AFTER `nros_cpp_ffi.h`, never before: `qos.hpp` defines the four
37// `nros_cpp_qos_*_t` enums ITSELF under `#ifndef NROS_CPP_FFI_H`, so
38// reaching it first makes the cbindgen header a REDEFINITION of all four.
39#include "rclcpp/qos.hpp"
40
41// Phase 189.M3.3.f — `nros_cpp_service_client_register` is excluded from
42// cbindgen (its Rust signature uses `RawResponseCallback`, an external-crate
43// type alias). Declare it locally with a matching fn-ptr typedef.
44// (`nros_cpp_service_client_send_on_handle` takes no callback, so it comes from
45// the cbindgen header.)
46extern "C" {
47typedef void (*nros_cpp_service_response_callback_t)(const uint8_t* data, size_t len, void* ctx);
48
50 const char* service_name, const char* type_name,
51 const char* type_hash, nros_cpp_qos_t qos,
53 void* context, uint8_t sched_context,
54 size_t* out_handle_id);
55} // extern "C"
56
64namespace rclcpp {
65class Node;
66}
67
68// ============================================================================
69// `rclcpp::Client<S>` -- DEFINED here (RFC-0089: rclcpp:: is the home)
70// ============================================================================
71//
72// phase-428: the definition moved from `nros::` to `rclcpp::` (the `nros::`
73// alias went in phase-483 W1). The nested `SharedPtr` / `ConstSharedPtr` / `UniquePtr`
74// aliases live on the class itself, so the rclcpp way of indexing types
75// (`rclcpp::Client<S>::SharedPtr`) resolves with no wrapper in between.
76namespace rclcpp {
77
129template <typename S> class Client {
130 public:
149
150 using RequestType = typename S::Request;
151 using ResponseType = typename S::Response;
152
162 using TypedResponseFn = void (*)(const ResponseType& response);
163
177 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
178 uint8_t req_buf[::rclcpp::detail::buffer_bounds<RequestType>::tx];
179 size_t req_len = 0;
180 if (RequestType::ffi_serialize(&req, req_buf, sizeof(req_buf), &req_len) != 0) {
182 }
183 return Result(
184 nros_cpp_service_client_send_on_handle(executor_, handle_id_, req_buf, req_len));
185 }
186
188 bool is_valid() const { return initialized_; }
189
214 const char* get_service_name() const { return initialized_ ? service_name_ : ""; }
215
242 ::rclcpp::QoS get_request_publisher_actual_qos() const { return actual_qos_half(true); }
243
247 ::rclcpp::QoS get_response_subscription_actual_qos() const { return actual_qos_half(false); }
248
275 if (!initialized_)
276 return ::rclcpp::ResultOf<bool>::error(::rclcpp::ErrorCode::NotInitialized);
277 int out = -1;
278 nros_cpp_ret_t ret =
279 nros_cpp_service_client_server_available(nullptr, executor_, handle_id_, &out);
280 // A failed CALL and a backend that cannot ANSWER are different facts;
281 // the retired `int` form reported both as `-1`. Keep them apart.
282 if (ret != 0) return ::rclcpp::ResultOf<bool>::error(static_cast<::rclcpp::ErrorCode>(ret));
283 if (out < 0) return ::rclcpp::ResultOf<bool>::error(::rclcpp::ErrorCode::Unsupported);
284 return ::rclcpp::ResultOf<bool>::ok(out != 0);
285 }
286
306 Result wait_for_service(uint32_t timeout_ms) {
307 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
308 return Result(
309 nros_cpp_service_client_wait_for_service(nullptr, executor_, handle_id_, timeout_ms));
310 }
311
316 template <typename T = void> Result wait_for_service() {
317 static_assert(::rclcpp::detail::refuse<T>::value, NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT);
319 }
320
322 size_t handle_id() const { return handle_id_; }
323
333 ~Client() { initialized_ = false; }
334
335 // Move semantics (non-copyable). Bookkeeping only — there is no storage to
336 // relocate, so `nros_cpp_service_client_relocate` is not called here.
337 //
338 // phase-456 W3 — a callback-style client is MOVABLE, and the warning that
339 // used to stand here is gone with its subject. It said the arena holds
340 // `this` as the response trampoline context; the arena holds the user's
341 // HANDLER now, so nothing of the caller's is referenced after registration
342 // and there is no pointer a move could leave stale. What survives the move
343 // is `{executor_, handle_id_}`, which is what `async_send_request` needs and
344 // all it needs.
345 Client(Client&& other)
346 : executor_(other.executor_), initialized_(other.initialized_),
347 handle_id_(other.handle_id_), service_name_{} {
348 ::rclcpp::detail::assign_entity_name(service_name_, other.service_name_);
349 other.initialized_ = false;
350 }
351
353 if (this != &other) {
354 executor_ = other.executor_;
355 initialized_ = other.initialized_;
356 handle_id_ = other.handle_id_;
357 ::rclcpp::detail::assign_entity_name(service_name_, other.service_name_);
358 other.initialized_ = false;
359 }
360 return *this;
361 }
362
365 Client() : executor_(nullptr), initialized_(false), service_name_{} {}
366
367 private:
368 Client(const Client&) = delete;
369 Client& operator=(const Client&) = delete;
370
371 friend class ::rclcpp::Node;
372
379 static void response_trampoline(const uint8_t* data, size_t len, void* ctx) {
380 const TypedResponseFn user_fn = ::rclcpp::detail::fn_from_context<TypedResponseFn>(ctx);
381 if (user_fn == nullptr) return;
382 ResponseType response;
383 if (ResponseType::ffi_deserialize(data, len, &response) != 0) return;
384 user_fn(response);
385 }
386
395 ::rclcpp::QoS actual_qos_half(bool request) const {
396 nros_cpp_qos_t req{};
397 nros_cpp_qos_t resp{};
398 if (!initialized_) return ::rclcpp::detail::qos_all_unknown();
399 if (nros_cpp_service_client_get_actual_qos(nullptr, executor_, handle_id_, &req, &resp) !=
400 0) {
401 return ::rclcpp::detail::qos_all_unknown();
402 }
403 return ::rclcpp::detail::qos_from_ffi(request ? req : resp);
404 }
405
406 // Callback-style BOOKKEEPING (Phase 189.M3.3.f). The handler itself is not
407 // here — it lives in the arena (phase-456 W3) — and neither is the client,
408 // so what remains is the caller's own record of a registration that refers
409 // back to nothing of the caller's. `{executor_, handle_id_}` is also the
410 // send road, which is why this object has a verb where `Service<S>` has
411 // none.
412 void* executor_;
413 bool initialized_;
414 size_t handle_id_ = static_cast<size_t>(-1);
418 char service_name_[::rclcpp::SERVICE_NAME_MAX];
419};
420
421} // namespace rclcpp
422
423// Phase 84.G8: out-of-line definition of Node::create_client<S>().
424#include "rclcpp/node.hpp"
425
426namespace rclcpp {
427
428// Phase 189.M3.3.f — callback-style (arena-registered) client. The arena owns
429// the client AND the response handler, and dispatches it during spin_once;
430// requests go through `async_send_request`, which needs only `out`'s own
431// `{executor_, handle_id_}`. `options.sched_context` is functional.
432} // namespace rclcpp
433
434namespace rclcpp {
435template <typename S, typename F, typename>
436Result Node::create_client(Client<S>& out, const char* service_name, F callback,
437 const ::rclcpp::QoS& qos, const ::rclcpp::ClientOptions& options) {
438 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
439 nros_cpp_qos_t ffi_qos = ::rclcpp::detail::qos_to_ffi(qos);
440
441 // The user handler becomes the ARENA's context (phase-456 W3); the
442 // conversion is still the compile error for a non-convertible `F`.
443 const typename Client<S>::TypedResponseFn user_fn =
444 typename Client<S>::TypedResponseFn(callback);
445
446 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
447 ? 0u
448 : static_cast<uint8_t>(options.sched_context);
449 size_t handle = static_cast<size_t>(-1);
451 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
452 reinterpret_cast<nros_cpp_service_response_callback_t>(&Client<S>::response_trampoline),
453 ::rclcpp::detail::fn_to_context(user_fn), sched, &handle);
454 if (ret == 0) {
455 out.executor_ = executor_handle_;
456 out.handle_id_ = handle;
457 // phase-444 — see `rclcpp::PollClient`'s overload, which does the same.
458 ::rclcpp::detail::assign_entity_name(out.service_name_, service_name);
459 out.initialized_ = true;
460 }
461 return Result(ret);
462}
463} // namespace rclcpp
464
465namespace rclcpp {} // namespace rclcpp
466
467#endif // NROS_RCLCPP_CLIENT_HPP
Definition node.hpp:218
Definition client.hpp:129
::rclcpp::QoS get_request_publisher_actual_qos() const
Definition client.hpp:242
void(*)(const ResponseType &response) TypedResponseFn
Definition client.hpp:162
Result wait_for_service(uint32_t timeout_ms)
Definition client.hpp:306
::rclcpp::ResultOf< bool > service_is_ready() const
Definition client.hpp:274
Result async_send_request(const RequestType &req)
Definition client.hpp:176
typename S::Request RequestType
Definition client.hpp:150
bool is_valid() const
Check if the client was registered.
Definition client.hpp:188
const char * get_service_name() const
Definition client.hpp:214
::rclcpp::QoS get_response_subscription_actual_qos() const
Definition client.hpp:247
~Client()
Definition client.hpp:333
Client()
Definition client.hpp:365
Result wait_for_service()
Definition client.hpp:316
Client(Client &&other)
Definition client.hpp:345
Client & operator=(Client &&other)
Definition client.hpp:352
size_t handle_id() const
Executor arena slot for the registration; SIZE_MAX until registered.
Definition client.hpp:322
typename S::Response ResponseType
Definition client.hpp:151
::rclcpp::Owned<::rclcpp::PollClient< S > > create_client(const char *name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services())
Definition qos.hpp:237
Definition result.hpp:216
Definition result.hpp:331
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:21
Definition rclcpp.hpp:75
ResultOf< void > Result
Definition result.hpp:246
ErrorCode
Definition result.hpp:113
@ Error
Generic failure not covered by a more specific code.
@ Unsupported
Operation not implemented by the active backend.
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)
void(* nros_cpp_service_response_callback_t)(const uint8_t *data, size_t len, void *ctx)
Definition client.hpp:47
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
Definition qos.hpp:81