SMACC2
Loading...
Searching...
No Matches
cp_service_client.hpp
Go to the documentation of this file.
1// Copyright 2024 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <smacc2/component.hpp>
21
22#include <chrono>
23#include <functional>
24#include <future>
25#include <mutex>
26#include <optional>
27#include <rclcpp/rclcpp.hpp>
28
29namespace smacc2
30{
31namespace client_core_components
32{
33using namespace smacc2::default_events;
34
66template <typename ServiceType>
68{
69public:
70 // Type aliases for cleaner code
71 using Client = rclcpp::Client<ServiceType>;
72 using Request = typename ServiceType::Request;
73 using Response = typename ServiceType::Response;
74 using SharedRequest = typename Request::SharedPtr;
75 using SharedResponse = typename Response::SharedPtr;
76 using SharedFuture = typename rclcpp::Client<ServiceType>::SharedFuture;
77
78 // Configuration options (set before onInitialize() or via constructor)
79 std::optional<std::string> serviceName;
80 std::optional<std::chrono::milliseconds> serviceTimeout;
81
82 // SMACC2 Signals for component communication
83 // These signals are emitted when service events occur, allowing other
84 // components and behaviors to react to service responses
88
89 // Event posting functions (configured during component initialization)
90 // These lambdas are set up with proper template parameters to post
91 // type-safe SMACC2 events to the state machine
92 std::function<void(const SharedResponse &)> postResponseEvent;
93 std::function<void()> postRequestSentEvent;
94 std::function<void()> postFailureEvent;
95
99 CpServiceClient() = default;
100
106
112 CpServiceClient(const std::string & serviceName, std::chrono::milliseconds serviceTimeout)
114 {
115 }
116
117 virtual ~CpServiceClient() = default;
118
129 {
130 std::lock_guard<std::mutex> lock(serviceMutex_);
131
132 if (!client_)
133 {
134 RCLCPP_ERROR_STREAM(
135 getLogger(), "[" << this->getName() << "] Service client not initialized!");
136 throw std::runtime_error("Service client not initialized");
137 }
138
139 if (!client_->service_is_ready())
140 {
141 RCLCPP_WARN_STREAM(
142 getLogger(), "[" << this->getName() << "] Service not ready: " << *serviceName);
143 }
144
145 RCLCPP_INFO_STREAM(
146 getLogger(), "[" << this->getName() << "] Sending service request to: " << *serviceName);
147
148 // Send the async request
149 auto future = client_->async_send_request(request);
150 lastRequest_ = future;
151
152 // Emit signal that request was sent
155
156 return future;
157 }
158
171 {
172 auto future = sendRequest(request);
173
174 // Wait for response with timeout
175 std::future_status status;
176 if (serviceTimeout)
177 {
178 status = future.wait_for(*serviceTimeout);
179 if (status == std::future_status::timeout)
180 {
181 RCLCPP_ERROR_STREAM(
182 getLogger(), "[" << this->getName() << "] Service call timed out: " << *serviceName);
185 throw std::runtime_error("Service call timeout");
186 }
187 }
188 else
189 {
190 future.wait();
191 }
192
193 // Get the response
194 try
195 {
196 auto response = future.get();
197
198 RCLCPP_INFO_STREAM(
199 getLogger(),
200 "[" << this->getName() << "] Service response received from: " << *serviceName);
201
202 // Emit signals and events
203 onServiceResponse_(response);
205
206 return response;
207 }
208 catch (const std::exception & e)
209 {
210 RCLCPP_ERROR_STREAM(
211 getLogger(),
212 "[" << this->getName() << "] Service call failed: " << *serviceName << " - " << e.what());
215 throw;
216 }
217 }
218
223 bool isServiceReady() const { return client_ && client_->service_is_ready(); }
224
232 {
233 if (client_)
234 {
235 RCLCPP_INFO_STREAM(
236 getLogger(), "[" << this->getName() << "] Waiting for service: " << *serviceName);
237
238 if (serviceTimeout)
239 {
240 auto timeout = std::chrono::duration_cast<std::chrono::nanoseconds>(*serviceTimeout);
241 if (!client_->wait_for_service(timeout))
242 {
243 RCLCPP_WARN_STREAM(
244 getLogger(), "[" << this->getName() << "] Service wait timed out: " << *serviceName);
245 }
246 }
247 else
248 {
249 client_->wait_for_service();
250 }
251
252 RCLCPP_INFO_STREAM(
253 getLogger(), "[" << this->getName() << "] Service ready: " << *serviceName);
254 }
255 }
256
263 void onInitialize() override
264 {
265 if (!serviceName)
266 {
267 RCLCPP_ERROR_STREAM(getLogger(), "[" << this->getName() << "] Service name not set!");
268 return;
269 }
270
271 RCLCPP_INFO_STREAM(
272 getLogger(), "[" << this->getName() << "] Initializing service client for: " << *serviceName);
273
274 client_ = getNode()->create_client<ServiceType>(*serviceName);
275
276 RCLCPP_INFO_STREAM(
277 getLogger(),
278 "[" << this->getName() << "] DONE Initializing service client for: " << *serviceName);
279 }
280
291 template <typename TOrthogonal, typename TSourceObject>
293 {
294 // Set up event posting functions with proper template parameters
295 postResponseEvent = [this](const SharedResponse & response)
296 {
298 ev->response = *response;
299 this->postEvent(ev);
300 RCLCPP_DEBUG(getLogger(), "[%s] SERVICE RESPONSE EVENT", this->getName().c_str());
301 };
302
303 postRequestSentEvent = [this]()
304 {
306 this->postEvent(ev);
307 RCLCPP_DEBUG(getLogger(), "[%s] SERVICE REQUEST SENT EVENT", this->getName().c_str());
308 };
309
310 postFailureEvent = [this]()
311 {
313 this->postEvent(ev);
314 RCLCPP_DEBUG(getLogger(), "[%s] SERVICE FAILURE EVENT", this->getName().c_str());
315 };
316 }
317
329 template <typename T>
330 boost::signals2::connection onResponse(void (T::*callback)(const SharedResponse &), T * object)
331 {
332 return this->getStateMachine()->createSignalConnection(onServiceResponse_, callback, object);
333 }
334
343 template <typename T>
344 boost::signals2::connection onRequestSent(void (T::*callback)(), T * object)
345 {
346 return this->getStateMachine()->createSignalConnection(onServiceRequestSent_, callback, object);
347 }
348
357 template <typename T>
358 boost::signals2::connection onFailure(void (T::*callback)(), T * object)
359 {
360 return this->getStateMachine()->createSignalConnection(onServiceFailure_, callback, object);
361 }
362
370 std::shared_ptr<Client> getServiceClient() const { return client_; }
371
372private:
373 std::shared_ptr<Client> client_ = nullptr;
374 std::optional<SharedFuture> lastRequest_;
375 std::mutex serviceMutex_;
376};
377
378} // namespace client_core_components
379} // namespace smacc2
ISmaccStateMachine * getStateMachine()
virtual std::string getName() const
rclcpp::Logger getLogger() const
rclcpp::Node::SharedPtr getNode()
boost::signals2::connection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)
Generic ROS2 service client component for SMACC2.
void onComponentInitialization()
Configure event posting with proper template parameters.
std::shared_ptr< Client > getServiceClient() const
Get the underlying ROS2 service client.
void onInitialize() override
Component initialization - creates the ROS2 service client.
bool isServiceReady() const
Check if the service server is ready.
smacc2::SmaccSignal< void(const SharedResponse &)> onServiceResponse_
SharedResponse sendRequestSync(SharedRequest request)
Send a service request synchronously (blocking)
SharedFuture sendRequest(SharedRequest request)
Send a service request asynchronously.
std::function< void(const SharedResponse &)> postResponseEvent
CpServiceClient()=default
Default constructor.
boost::signals2::connection onRequestSent(void(T::*callback)(), T *object)
Helper method to connect a callback to the request sent signal.
CpServiceClient(const std::string &serviceName, std::chrono::milliseconds serviceTimeout)
Constructor with service name and timeout.
void waitForService()
Wait for the service to become available.
CpServiceClient(const std::string &serviceName)
Constructor with service name.
typename rclcpp::Client< ServiceType >::SharedFuture SharedFuture
std::optional< std::chrono::milliseconds > serviceTimeout
boost::signals2::connection onResponse(void(T::*callback)(const SharedResponse &), T *object)
Helper method to connect a callback to the response signal.
boost::signals2::connection onFailure(void(T::*callback)(), T *object)
Helper method to connect a callback to the failure signal.