|
nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
|
#include <client.hpp>
Public Types | |
| using | ConstSharedPtr = ::rclcpp::ClientHandle< S > |
| using | RequestType = typename S::Request |
| using | ResponseType = typename S::Response |
| using | SharedPtr = ::rclcpp::ClientHandle< S > |
| using | TypedResponseFn = void(*)(const ResponseType &response) |
| using | UniquePtr = ::rclcpp::ClientHandle< S > |
rclcpp::Client<S>::UniquePtr — see SharedPtr. | |
Public Member Functions | |
| Result | async_send_request (const RequestType &req) |
| Client () | |
| Client (Client &&other) | |
| ::rclcpp::QoS | get_request_publisher_actual_qos () const |
| ::rclcpp::QoS | get_response_subscription_actual_qos () const |
| const char * | get_service_name () const |
| size_t | handle_id () const |
Executor arena slot for the registration; SIZE_MAX until registered. | |
| bool | is_valid () const |
| Check if the client was registered. | |
| Client & | operator= (Client &&other) |
| ::rclcpp::ResultOf< bool > | service_is_ready () const |
| template<typename T = void> | |
| Result | wait_for_service () |
| Result | wait_for_service (uint32_t timeout_ms) |
| ~Client () | |
Friends | |
| class | ::rclcpp::Node |
Dispatch service client for a ROS 2 service — the rclcpp model.
A response handler is registered into the executor arena, which owns the RmwServiceClient and runs the handler during spin_once. The service type S must provide nested Request and Response types with TYPE_NAME, TYPE_HASH, SERIALIZED_SIZE_MAX, ffi_serialize(), and ffi_deserialize().
THIS OBJECT IS BOOKKEEPING PLUS ITS VERBS — phase-456 W9, amended 2026-09-28. The client, the reply buffer, the handler and its context are all the arena's; W3 made the arena's trampoline context the user's HANDLER rather than &out, so after registration nothing of the caller's is referenced and this object is freely movable.
What it does hold is what a REGISTRATION can be asked about and the arena cannot be asked for by an index alone: {initialized_, handle_id_, executor_, service_name_}. executor_ and handle_id_ are the pair that names the arena entry (issue 1437) — and they are also the argument list of nros_cpp_service_client_send_on_handle, which is why async_send_request needs nothing more. service_name_ is the phase-444 C++-side copy, because the runtime takes the name at create and drops it.
THE SAME PAIR IS NOW THE WHOLE VERB SET. 2026-09-28 added service_is_ready and wait_for_service, which W9 had to leave off — and it added no member to do it: both FFI entry points took caller storage and now take (storage, executor, handle_id) beside it, so {executor_, handle_id_} answers three verbs rather than one. The object is still bookkeeping over an arena entry; what changed is how much of the arena an index can be asked about.
Measured (phase-456 W3, re-measured W9), across examples/, tests/, book/ and packages/: a dispatch client has exactly ONE verb INVOKED on it, async_send_request, at one example site plus the move probe W3 added. That is why Client<S>::SharedPtr is a two-word rclcpp::ClientHandle<S> — a handle with verbs, not the empty keep-alive ServiceHandle<S> is, and not an Owned<Client<S>>, because the arena owns the entity and the caller does not. The census says what ported source REACHES FOR today; it is not the argument for which verbs a registration can answer, which is why the two discovery reads landed on the handle as well.
Usage:
| using rclcpp::Client< S >::ConstSharedPtr = ::rclcpp::ClientHandle<S> |
rclcpp::Client<S>::ConstSharedPtr — see SharedPtr. The same handle: async_send_request is const on it (the handle is two words the caller owns; the mutation is the arena's), so there is no mutable/const distinction to draw.
| using rclcpp::Client< S >::SharedPtr = ::rclcpp::ClientHandle<S> |
rclcpp::Client<S>::SharedPtr — phase-456 W9.
rclcpp::Client<S>::SharedPtr member_; is how ported source declares a client member, so this alias must exist on every target — which std::shared_ptr does not.
IT IS NOT A POINTER TO A Client<S>. A registered client is the arena's; what this names is rclcpp::ClientHandle<S> — two words, copyable, carrying exactly the one verb the census says a dispatch client is asked for. See client_handle.hpp.
| using rclcpp::Client< S >::TypedResponseFn = void (*)(const ResponseType& response) |
Phase 189.M3.3.f — typed response-handler signature for the callback-style client (rclcpp async dispatch). The handler runs during spin_once when a reply arrives for a request sent via async_send_request.
phase-456 W3 deleted the TypedResponseFnWithCtx sibling, for the reason stated on Service<S>::TypedServiceFn: the SFINAE guard on Node::create_client admits only a void(*)(const Response&), so no overload could set it and the branch reading it was unreachable.
| using rclcpp::Client< S >::UniquePtr = ::rclcpp::ClientHandle<S> |
rclcpp::Client<S>::UniquePtr — see SharedPtr.
|
inline |
Destructor — there is nothing to release.
The executor arena owns the client, the reply buffer and the handler. This destructor deliberately does not release them: it clears this object's own bookkeeping, and the registration lives until its node is destroyed or a ClientHandle releases it (cli_.reset(), issue 1667). Until phase-456 W9 the same destructor also freed a FUTURE-style client, behind an if (initialized_ && !callback_mode_); that half moved to rclcpp::PollClient<S>, where the condition is unconditional.
|
inline |
Default constructor – creates an unregistered service client. Use Node::create_client() to register one.
|
inline |
Phase 189.M3.3.f — THE ONE VERB. Send a request; the reply is delivered to the registered response handler during spin_once (no Future). Returns immediately after sending.
UPSTREAM PARITY: upstream's async_send_request(req) returns a std::shared_future<Response::SharedPtr> and its two-argument form takes a callback per REQUEST. Ours returns a Result and the handler is bound once, at registration — ledgered at cpp:Client::async_send_request, divergence / adopt. What changed in phase-456 W9 is only that the callback_mode_ check is gone with the flag: every rclcpp::Client<S> is a dispatch client now, and the future-style road is rclcpp::PollClient<S>.
|
inline |
The QoS the backend GRANTED this client's REQUEST endpoint — the publisher that sends calls. Issue 1437.
rclcpp::Client::get_request_publisher_actual_qos. ONE create_client builds TWO endpoints that negotiate against DIFFERENT peers, so this and get_response_subscription_actual_qos are two answers and neither stands for the other.
A policy the backend cannot report is an ABSENCE (ReliabilityPolicy::Unknown and friends), never the request echoed back — see Publisher::get_actual_qos.
phase-456 W9 — BOTH HALVES ANSWER, and this accessor is the WORKED EXAMPLE the two later closures copied. Issue 1437 gave a dispatch client executor_ beside handle_id_, and nros_cpp_service_client_get_actual_qos serves both roads by construction — storage for the owner, (executor, handle_id) for the arena — so the upstream no-argument spelling was reachable here with no weakening and no ledger row was ever owed.
W9 had to leave get_actual_qos off the dispatch Subscription<M> for want of exactly that: the type held only sched_handle_id_. Both ends of that gap are closed now, each by taking this shape rather than by changing a signature — the subscription carries its executor since phase-467 (2026-09-25, its ledger row deleted), and this class's own service_is_ready / wait_for_service since 2026-09-28.
|
inline |
The QoS the backend GRANTED this client's RESPONSE endpoint — the subscription that receives replies. Issue 1437; see get_request_publisher_actual_qos.
Read back the service name this client was created on — phase-444.
rclcpp's ClientBase::get_service_name, and the client half of "four
entity families, one accessor" (ledger rows cpp:Client::get_service_name and cpp:Service::get_service_name): Publisher and Subscription have had get_topic_name() all along, both action tiers gained get_action_name() in phase-417 W4.b, and the service pair was the one family with no accessor in any C++ tier.
The name lives C++-side in service_name_, for the reason ActionServer::get_action_name states: the runtime does NOT own it. nros_cpp_service_client_register takes service_name and drops it, so there is nothing FFI-side to hand back, and a borrowed const char* was rejected because create_client takes a pointer a hosted caller may well have obtained from a temporary.
Returns "" (never NULL) on an unregistered client, matching Publisher::get_topic_name and both action tiers.
phase-456 W9 split this class in two and the method answers on BOTH halves, unchanged — the same shape Service::get_service_name took under W5. Each half keeps its own service_name_, because the copy is made at create from the argument the caller passed and neither owner can recover it afterwards.
|
inline |
Executor arena slot for the registration; SIZE_MAX until registered.
|
inline |
Check if the client was registered.
|
inline |
|
inline |
Is a matching service server visible? — rclcpp::ClientBase:: service_is_ready, no arguments, as upstream's is.
ok(true) — at least one matching server is currently visible.ok(false) — none discovered yet.error(Unsupported) — the backend cannot answer (XRCE has no participant enumeration); fall back to a timed wait_for_service or assume reachability.error(<code>) — the probe itself failed.Never spins the executor, so it is safe inside a callback. The tri-state is the divergence from upstream's bare bool and is ledgered at cpp:Client::service_is_ready: RFC-0018 forbids exceptions, so a failed probe and a successful "no" must be distinguishable by return value.
This is the gap phase-456 W9 RECORDED, closed 2026-09-28. The split left this verb on the future-style half alone, because nros_cpp_service_client_server_available read the RmwServiceClient in CALLER storage and a dispatch client has none — so a porter holding a Client<S>::SharedPtr could not ask at all. The fix is the FFI's, not the signature's: that entry point now takes (storage, executor, handle_id), the shape nros_cpp_service_client_get_actual_qos has had since issue 1437, and this class already holds {executor_, handle_id_}. Inventing an Executor& parameter here was refused — a method that exists to adopt the upstream spelling cannot close a gap by leaving it.
|
inline |
REFUSED — wait_for_service() with no budget. phase-417 stage 3.
A member template rather than = delete for the C++14 reason given on Executor::spin_once(): a deleted function carries no message.
Block until a matching service server is discoverable — rclcpp::ClientBase::wait_for_service.
Spins the executor cooperatively while probing, so do NOT call it from inside a callback; use service_is_ready there. Returns ok when the server is visible, Timeout when the budget elapses, Unsupported at once when the backend cannot know (XRCE — issue 1686; a caller that may send blind proceeds).
The budget is REQUIRED — phase-417 stage 3, and the reason is RFC-0021 rather than convenience: this call drives a single-threaded executor, so a wait that never returns starves every other entity, and uint32_t has no value to port upstream's -1 to. The no-argument form is a compile error carrying NROS_RCLCPP_REFUSE_UNBOUNDED_WAIT, exactly as it is on rclcpp::PollClient<S> and rclcpp_action::Client<A> — one concept, now three sites, each with its own expected-failure TU.
Closed 2026-09-28 with service_is_ready; that method's note carries why the remedy was the FFI's argument list and not this one.
|
friend |