mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
Loading...
Searching...
No Matches
service_client_handler.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include <mrs_lib/coro/cancellation.hpp>
9#include <mrs_lib/coro/event.hpp>
11
12namespace mrs_lib
13{
14
15 // --------------------------------------------------------------
16 // | ServiceClientHandler |
17 // --------------------------------------------------------------
18
19 /* ServiceClientHandler() constructors //{ */
20
21 template <class ServiceType>
22 ServiceClientHandler<ServiceType>::ServiceClientHandler(rclcpp::Node::SharedPtr& node, const std::string& address, const rclcpp::QoS& qos)
23 : impl_(std::make_shared<Impl>(node, address, qos, node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive)))
24 {
25 }
26
27 template <class ServiceType>
31
32 template <class ServiceType>
33 ServiceClientHandler<ServiceType>::ServiceClientHandler(rclcpp::Node::SharedPtr& node, const std::string& address, const rclcpp::QoS& qos,
34 const rclcpp::CallbackGroup::SharedPtr& callback_group)
35 : impl_(std::make_shared<Impl>(node, address, qos, callback_group))
36 {
37 }
38
39 template <class ServiceType>
40 ServiceClientHandler<ServiceType>::ServiceClientHandler(rclcpp::Node::SharedPtr& node, const std::string& address,
41 const rclcpp::CallbackGroup::SharedPtr& callback_group)
42 : ServiceClientHandler(node, address, rclcpp::ServicesQoS(), callback_group)
43 {
44 }
45
46 //}
47
48 /* callSync(const ServiceType::Request& request, ServiceType::Response& response) //{ */
49
50 template <class ServiceType>
51 std::optional<std::shared_ptr<typename ServiceType::Response>>
52 ServiceClientHandler<ServiceType>::callSync(const std::shared_ptr<typename ServiceType::Request>& request)
53 {
54 if (!impl_)
55 {
56 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use callSync()!");
57 return std::nullopt;
58 }
59 return impl_->callSync(request);
60 }
61
62 //}
63
64 /* callAsync(const ServiceType::Request& request, ServiceType::Response& response) //{ */
65
66 template <class ServiceType>
67 std::optional<std::shared_future<std::shared_ptr<typename ServiceType::Response>>>
68 ServiceClientHandler<ServiceType>::callAsync(const std::shared_ptr<typename ServiceType::Request>& request)
69 {
70 if (!impl_)
71 {
72 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use callAsync()!");
73 return std::nullopt;
74 }
75 return impl_->callAsync(request);
76 }
77
78 //}
79
80 template <class ServiceType>
82 ServiceClientHandler<ServiceType>::callAwaitable(std::shared_ptr<typename ServiceType::Request> request)
83 {
84 if (!impl_)
85 {
86 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use callAwaitable()!");
87 co_return std::nullopt;
88 }
89
90 co_return co_await impl_->callAwaitable(request);
91 }
92
93 /* getServiceName() //{ */
94
95 template <class ServiceType>
97 {
98 if (!impl_)
99 {
100 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use getServiceName()!");
101 return {};
102 }
103 return impl_->getServiceName();
104 }
105
106 //}
107
108 /* waitForService() //{ */
109
110 template <class ServiceType>
111 template <typename RepT, typename RatioT>
112 bool ServiceClientHandler<ServiceType>::waitForService(std::chrono::duration<RepT, RatioT> timeout)
113 {
114 if (!impl_)
115 {
116 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use waitForService()!");
117 return false;
118 }
119 return impl_->waitForService(timeout);
120 }
121
122 //}
123
124 /* isServiceReady() //{ */
125 template <class ServiceType>
127 {
128 if (!impl_)
129 {
130 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use isServiceReady()!");
131 return false;
132 }
133 return impl_->isServiceReady();
134 }
135
136 //}
137
138 template <class ServiceType>
140 {
141 if (!impl_)
142 {
143 RCLCPP_ERROR(rclcpp::get_logger("ServiceClientHandler"), "Not initialized, cannot use prunePendingRequests()!");
144 return false;
145 }
146 return impl_->prunePendingRequests();
147 }
148
149
150 // --------------------------------------------------------------
151 // | ServiceClientHandler::Impl |
152 // --------------------------------------------------------------
153
154 /* class ServiceClientHandler::impl //{ */
155
159 template <class ServiceType>
160 class ServiceClientHandler<ServiceType>::Impl
161 {
162
163 public:
172 Impl(rclcpp::Node::SharedPtr& node, const std::string& address, const rclcpp::QoS& qos, const rclcpp::CallbackGroup::SharedPtr& callback_group)
173 : callback_group_(callback_group), service_client_(node->create_client<ServiceType>(address, qos, callback_group))
174 {
175 RCLCPP_INFO_STREAM(node->get_logger(), "Created client '" << address << "' -> '" << service_client_->get_service_name() << "'");
176 }
177
185 std::optional<std::shared_ptr<typename ServiceType::Response>> callSync(const std::shared_ptr<typename ServiceType::Request>& request)
186 {
187 /* always check if the service is ready before calling */
188 if (!service_client_->service_is_ready())
189 return std::nullopt;
190
191 /* the future done callback is being run in a separate thread under the default callback group of the node */
192 const auto future_msg = service_client_->async_send_request(request).future.share();
193
194 /* it is a good practice to check if the future object is not already invalid after the call */
195 /* if valid() is false, the future has UNDEFINED behavior */
196 if (!future_msg.valid())
197 return std::nullopt;
198
199 // wait for the future to become available and then return
200 return future_msg.get();
201 }
202
210 std::optional<std::shared_future<std::shared_ptr<typename ServiceType::Response>>> callAsync(const std::shared_ptr<typename ServiceType::Request>& request)
211 {
212 /* always check if the service is ready before calling */
213 if (!service_client_->service_is_ready())
214 return std::nullopt;
215
216 const auto future = service_client_->async_send_request(request).future.share();
217
218 /* it is a good practice to check if the future object is not already invalid after the call */
219 /* if valid() is false, the future has UNDEFINED behavior */
220 if (!future.valid())
221 return std::nullopt;
222
223 return future;
224 }
225
226 Task<std::optional<std::shared_ptr<typename ServiceType::Response>>> callAwaitable(const std::shared_ptr<typename ServiceType::Request>& request)
227 {
228 using Response = ServiceType::Response;
229 using Client = rclcpp::Client<ServiceType>;
230 using SharedFutureAndRequestId = Client::SharedFutureAndRequestId;
231 using StopTokenBehavior = coro::internal::LowLevelEventAwaitable::StopTokenBehavior;
232
233 struct StateData
234 {
235 std::mutex mutex{};
236 bool cancelled = false;
237
238 std::optional<SharedFutureAndRequestId> future_and_id{};
239 };
240
241 if (!service_client_->service_is_ready())
242 {
243 co_return std::nullopt;
244 }
245
246 auto [event, awaitable] = coro::make_event();
247
248 std::shared_ptr<StateData> state_data = std::make_shared<StateData>();
249
250 auto register_waker = [&event, client = service_client_, request, state_data]() {
251 std::lock_guard lock(state_data->mutex);
252 if (state_data->cancelled)
253 {
254 event.try_cancel();
255 return;
256 }
257 auto shared_event = std::make_shared<coro::Event>(std::move(event));
258 state_data->future_and_id =
259 client->async_send_request(request, [shared_event](std::shared_future<std::shared_ptr<Response>>) { shared_event->try_trigger(); });
260 };
261
262 auto low_level_awaitable = coro::internal::get_low_level_event_awaitable(std::move(awaitable));
263
264 {
265 // If cancellation is requested via stop token, this callback sets
266 // cancelled flag on the state and removes a the pending request.
267 // The flag is set to stop sending the request in case the stop token
268 // is triggered before the service is called.
269 std::stop_callback remove_request_if_canceled(co_await coro::get_task_stop_token(), [state_data, client_weak = std::weak_ptr(service_client_)]() {
270 std::lock_guard lock(state_data->mutex);
271 state_data->cancelled = true;
272 std::shared_ptr<Client> client = client_weak.lock();
273 if (client == nullptr || !state_data->future_and_id.has_value())
274 {
275 return;
276 }
277 client->remove_pending_request(state_data->future_and_id.value());
278 });
279
280 co_await std::move(low_level_awaitable).get_awaitable(StopTokenBehavior::ignore, register_waker);
281 }
282
283 {
284 std::lock_guard lock(state_data->mutex);
285 if (!state_data->future_and_id.has_value())
286 {
287 co_return std::nullopt;
288 }
289
290 auto future = state_data->future_and_id.value().future;
291 if (future.wait_for(std::chrono::nanoseconds(0)) == std::future_status::timeout)
292 {
293 co_return std::nullopt;
294 }
295
296 co_return std::shared_ptr<Response>(future.get());
297 }
298 }
299
305 std::string getServiceName() const
306 {
307 return service_client_->get_service_name();
308 }
309
317 template <typename RepT = int64_t, typename RatioT = std::milli>
318 bool waitForService(std::chrono::duration<RepT, RatioT> timeout)
319 {
320 return service_client_->wait_for_service(timeout);
321 }
322
328 bool isServiceReady() const
329 {
330 return service_client_->service_is_ready();
331 }
332
338 size_t prunePendingRequests() const
339 {
340 return service_client_->prune_pending_requests();
341 }
342
343 private:
344 rclcpp::CallbackGroup::SharedPtr callback_group_;
345 typename rclcpp::Client<ServiceType>::SharedPtr service_client_;
346 };
347
348 //}
349
350} // namespace mrs_lib
implementation of the service client handler
Definition service_client_handler.hpp:161
size_t prunePendingRequests() const
Clean all pending requests.
Definition service_client_handler.hpp:338
std::optional< std::shared_future< std::shared_ptr< typename ServiceType::Response > > > callAsync(const std::shared_ptr< typename ServiceType::Request > &request)
asynchronous service call
Definition service_client_handler.hpp:210
std::optional< std::shared_ptr< typename ServiceType::Response > > callSync(const std::shared_ptr< typename ServiceType::Request > &request)
"classic" synchronous service call
Definition service_client_handler.hpp:185
Impl(rclcpp::Node::SharedPtr &node, const std::string &address, const rclcpp::QoS &qos, const rclcpp::CallbackGroup::SharedPtr &callback_group)
constructor
Definition service_client_handler.hpp:172
std::string getServiceName() const
Returns the name of the service this client connects to.
Definition service_client_handler.hpp:305
bool waitForService(std::chrono::duration< RepT, RatioT > timeout)
Waits for the service to be available.
Definition service_client_handler.hpp:318
bool isServiceReady() const
Checks if the service is available.
Definition service_client_handler.hpp:328
user wrapper of the service client handler implementation
Definition service_client_handler.h:26
ServiceClientHandler()
Default constructor to avoid having to use pointers.
Definition service_client_handler.hpp:28
std::optional< std::shared_ptr< typename ServiceType::Response > > callSync(const std::shared_ptr< typename ServiceType::Request > &request)
Synchronous (blocking) call of the service.
Definition service_client_handler.hpp:52
std::string getServiceName() const
Returns the name of the service this client connects to.
Definition service_client_handler.hpp:96
bool waitForService(std::chrono::duration< RepT, RatioT > timeout=std::chrono::seconds(1))
Waits for the service to be available.
Definition service_client_handler.hpp:112
std::optional< std::shared_future< std::shared_ptr< typename ServiceType::Response > > > callAsync(const std::shared_ptr< typename ServiceType::Request > &request)
Asynchronous (non-blocking) call of the service.
Definition service_client_handler.hpp:68
size_t prunePendingRequests()
Clean all pending requests.
Definition service_client_handler.hpp:139
bool isServiceReady() const
Checks if the service is available.
Definition service_client_handler.hpp:126
Task< std::optional< std::shared_ptr< typename ServiceType::Response > > > callAwaitable(std::shared_ptr< typename ServiceType::Request > request)
Awaitable call of the service.
Definition service_client_handler.hpp:82
Task type for creating coroutines.
Definition task.hpp:268
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
Defines ServiceClientHandler and related convenience classes for upgrading the ROS service client.