SMACC2
Loading...
Searching...
No Matches
cp_action_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_action/rclcpp_action.hpp>
28
29namespace smacc2
30{
31namespace client_core_components
32{
33using namespace smacc2::default_events;
34
35template <typename ActionType>
37{
38public:
39 // Type aliases
40 using ActionClient = rclcpp_action::Client<ActionType>;
41 using Goal = typename ActionType::Goal;
42 using Feedback = typename ActionType::Feedback;
43 using Result = typename ActionType::Result;
44 using GoalHandle = rclcpp_action::ClientGoalHandle<ActionType>;
45 using WrappedResult = typename GoalHandle::WrappedResult;
46 using SendGoalOptions = typename ActionClient::SendGoalOptions;
48 std::function<void(std::shared_future<typename GoalHandle::SharedPtr>)>;
49 using FeedbackCallback = typename GoalHandle::FeedbackCallback;
50 using ResultCallback = typename GoalHandle::ResultCallback;
51
52 // Configuration options
53 std::optional<std::string> actionServerName;
54 std::optional<std::chrono::milliseconds> serverTimeout;
55
56 // SMACC2 Signals for component communication
63
64 // Event posting functions (set during orthogonal allocation)
65 std::function<void(const WrappedResult &)> postSuccessEvent;
66 std::function<void(const WrappedResult &)> postAbortedEvent;
67 std::function<void(const WrappedResult &)> postCancelledEvent;
68 std::function<void(const Feedback &)> postFeedbackEvent;
69
70 // Constructor
71 CpActionClient() = default;
72
74
75 virtual ~CpActionClient() = default;
76
77 // Public API
78 std::shared_future<typename GoalHandle::SharedPtr> sendGoal(
79 Goal & goal, typename smacc2::SmaccSignal<void(const WrappedResult &)>::WeakPtr resultCallback =
80 typename smacc2::SmaccSignal<void(const WrappedResult &)>::WeakPtr())
81 {
82 std::lock_guard<std::mutex> lock(actionMutex_);
83
84 if (client_ == nullptr)
85 {
86 RCLCPP_ERROR_STREAM(
87 getLogger(), "[" << this->getName()
88 << "] Cannot send goal: action client not initialized (was the "
89 "component created with an action server name?)");
90 return std::shared_future<typename GoalHandle::SharedPtr>();
91 }
92
93 SendGoalOptions options;
94
95 // Set up feedback callback
96 options.feedback_callback = feedbackCallback_;
97
98 // Goal acceptance/rejection notification. Without this a rejected goal is
99 // silent: the result callback never fires and no signal is emitted.
100 options.goal_response_callback = [this](typename GoalHandle::SharedPtr goalHandle)
101 {
102 if (goalHandle != nullptr)
103 {
104 RCLCPP_INFO_STREAM(getLogger(), "[" << this->getName() << "] Goal accepted by server");
106 }
107 else
108 {
109 RCLCPP_ERROR_STREAM(getLogger(), "[" << this->getName() << "] Goal rejected by server");
111 }
112 };
113
114 // Set up result callback
115 options.result_callback = [this, resultCallback](const WrappedResult & result)
116 {
117 std::lock_guard<std::mutex> lock(actionMutex_);
118
119 RCLCPP_INFO_STREAM(
120 getLogger(), "[" << this->getName() << "] Action result callback, goal id: "
121 << rclcpp_action::to_string(result.goal_id));
122
123 auto resultCallbackPtr = resultCallback.lock();
124 if (resultCallbackPtr != nullptr)
125 {
126 RCLCPP_INFO_STREAM(getLogger(), "[" << this->getName() << "] Calling user result callback");
127 (*resultCallbackPtr)(result);
128 }
129 else
130 {
131 RCLCPP_INFO_STREAM(
132 getLogger(), "[" << this->getName() << "] Using default result handling");
133 this->onResult(result);
134 }
135 };
136
137 RCLCPP_INFO_STREAM(
138 getLogger(),
139 "[" << this->getName() << "] Sending goal to action server: " << (long)client_.get());
140
141 auto goalFuture = client_->async_send_goal(goal, options);
142 lastRequest_ = goalFuture;
143
144 return goalFuture;
145 }
146
148 {
149 std::lock_guard<std::mutex> lock(actionMutex_);
150
151 if (client_ != nullptr && lastRequest_ && lastRequest_->valid())
152 {
153 RCLCPP_INFO_STREAM(getLogger(), "[" << this->getName() << "] Cancelling current goal");
154
155 auto cancelFuture = client_->async_cancel_all_goals();
156 lastCancelResponse_ = cancelFuture;
157 return true;
158 }
159 else
160 {
161 RCLCPP_WARN_STREAM(getLogger(), "[" << this->getName() << "] No active goal to cancel");
162 return false;
163 }
164 }
165
166 bool isServerReady() const { return client_ && client_->action_server_is_ready(); }
167
169 {
170 if (client_)
171 {
172 RCLCPP_INFO_STREAM(
173 getLogger(),
174 "[" << this->getName() << "] Waiting for action server: " << *actionServerName);
175 client_->wait_for_action_server();
176 }
177 }
178
179 // Component lifecycle
180 void onInitialize() override
181 {
182 if (!actionServerName)
183 {
184 RCLCPP_ERROR_STREAM(getLogger(), "[" << this->getName() << "] Action server name not set!");
185 return;
186 }
187
188 RCLCPP_INFO_STREAM(
189 getLogger(),
190 "[" << this->getName() << "] Initializing action client for: " << *actionServerName);
191
192 client_ = rclcpp_action::create_client<ActionType>(getNode(), *actionServerName);
193 RCLCPP_INFO_STREAM(
194 getLogger(),
195 "[" << this->getName() << "] DONE Initializing action client for: " << *actionServerName);
196
197 // Set up feedback callback
198 feedbackCallback_ = [this](auto goalHandle, auto feedback)
199 { this->onFeedback(goalHandle, feedback); };
200 }
201
202 template <typename TOrthogonal, typename TSourceObject>
204 {
205 // Event source type is THIS COMPONENT, not the owning client: the event
206 // payload type is TSource::WrappedResult, and typing it on the client would
207 // wrongly assume the client's WrappedResult typedef matches this component's
208 // action - which breaks as soon as a client owns action clients of more than
209 // one action type. Nothing in-tree listens to client-typed events from this
210 // component (domain interfaces like CpNav2ActionInterface post those).
211 postSuccessEvent = [this](const WrappedResult & result)
212 { this->postResultEvent<EvActionSucceeded<CpActionClient<ActionType>, TOrthogonal>>(result); };
213
214 postAbortedEvent = [this](const WrappedResult & result)
215 { this->postResultEvent<EvActionAborted<CpActionClient<ActionType>, TOrthogonal>>(result); };
216
217 postCancelledEvent = [this](const WrappedResult & result)
218 { this->postResultEvent<EvActionCancelled<CpActionClient<ActionType>, TOrthogonal>>(result); };
219
220 postFeedbackEvent = [this](const Feedback & feedback)
221 {
222 auto actionFeedbackEvent = new EvActionFeedback<Feedback, TOrthogonal>();
223 actionFeedbackEvent->feedbackMessage = feedback;
224 this->postEvent(actionFeedbackEvent);
225 RCLCPP_DEBUG(getLogger(), "[%s] FEEDBACK EVENT", this->getName().c_str());
226 };
227 }
228
229 // Signal connection methods
230 template <typename T>
231 smacc2::SmaccSignalConnection onSucceeded(void (T::*callback)(const WrappedResult &), T * object)
232 {
233 return this->getStateMachine()->createSignalConnection(onActionSucceeded_, callback, object);
234 }
235
236 template <typename T>
237 smacc2::SmaccSignalConnection onAborted(void (T::*callback)(const WrappedResult &), T * object)
238 {
239 return this->getStateMachine()->createSignalConnection(onActionAborted_, callback, object);
240 }
241
242 template <typename T>
243 smacc2::SmaccSignalConnection onCancelled(void (T::*callback)(const WrappedResult &), T * object)
244 {
245 return this->getStateMachine()->createSignalConnection(onActionCancelled_, callback, object);
246 }
247
248 template <typename T>
249 smacc2::SmaccSignalConnection onFeedback(void (T::*callback)(const Feedback &), T * object)
250 {
251 return this->getStateMachine()->createSignalConnection(onActionFeedback_, callback, object);
252 }
253
254 // Access to underlying client for advanced usage
255 std::shared_ptr<ActionClient> getActionClient() const { return client_; }
256
257private:
258 std::shared_ptr<ActionClient> client_ = nullptr;
259 std::optional<std::shared_future<typename GoalHandle::SharedPtr>> lastRequest_;
260 std::optional<
261 std::shared_future<typename rclcpp_action::Client<ActionType>::CancelResponse::SharedPtr>>
264 std::mutex actionMutex_;
265
267 typename GoalHandle::SharedPtr /* goalHandle */,
268 const std::shared_ptr<const Feedback> feedback_msg)
269 {
270 onActionFeedback_(*feedback_msg);
271 postFeedbackEvent(*feedback_msg);
272 }
273
274 void onResult(const WrappedResult & result_msg)
275 {
276 const auto & resultType = result_msg.code;
277
278 RCLCPP_INFO_STREAM(
279 getLogger(), "[" << this->getName() << "] Action result ["
280 << rclcpp_action::to_string(result_msg.goal_id) << "]: " << (int)resultType);
281
282 if (resultType == rclcpp_action::ResultCode::SUCCEEDED)
283 {
284 RCLCPP_INFO(getLogger(), "[%s] Action result: Success", this->getName().c_str());
285 onActionSucceeded_(result_msg);
286 postSuccessEvent(result_msg);
287 }
288 else if (resultType == rclcpp_action::ResultCode::ABORTED)
289 {
290 RCLCPP_INFO(getLogger(), "[%s] Action result: Aborted", this->getName().c_str());
291 onActionAborted_(result_msg);
292 postAbortedEvent(result_msg);
293 }
294 else if (resultType == rclcpp_action::ResultCode::CANCELED)
295 {
296 RCLCPP_INFO(getLogger(), "[%s] Action result: Cancelled", this->getName().c_str());
297 onActionCancelled_(result_msg);
298 postCancelledEvent(result_msg);
299 }
300 else
301 {
302 RCLCPP_WARN(
303 getLogger(), "[%s] Action result: Unhandled type: %d", this->getName().c_str(),
304 (int)resultType);
305 }
306 }
307
308 template <typename EvType>
309 void postResultEvent(const WrappedResult & result)
310 {
311 auto * ev = new EvType();
312 ev->resultMessage = result;
313 RCLCPP_INFO(
314 getLogger(), "[%s] Posting event: %s", this->getName().c_str(),
315 smacc2::demangleSymbol(typeid(ev).name()).c_str());
316 this->postEvent(ev);
317 }
318};
319
320} // namespace client_core_components
321} // namespace smacc2
ISmaccStateMachine * getStateMachine()
virtual std::string getName() const
rclcpp::Logger getLogger() const
rclcpp::Node::SharedPtr getNode()
smacc2::SmaccSignalConnection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)
std::function< void(std::shared_future< typename GoalHandle::SharedPtr >)> GoalResponseCallback
typename ActionClient::SendGoalOptions SendGoalOptions
std::function< void(const WrappedResult &)> postCancelledEvent
typename GoalHandle::WrappedResult WrappedResult
std::optional< std::shared_future< typename GoalHandle::SharedPtr > > lastRequest_
void onResult(const WrappedResult &result_msg)
smacc2::SmaccSignalConnection onSucceeded(void(T::*callback)(const WrappedResult &), T *object)
CpActionClient(const std::string &actionServerName)
std::shared_future< typename GoalHandle::SharedPtr > sendGoal(Goal &goal, typename smacc2::SmaccSignal< void(const WrappedResult &)>::WeakPtr resultCallback=typename smacc2::SmaccSignal< void(const WrappedResult &)>::WeakPtr())
smacc2::SmaccSignalConnection onFeedback(void(T::*callback)(const Feedback &), T *object)
smacc2::SmaccSignal< void(const WrappedResult &)> onActionCancelled_
smacc2::SmaccSignal< void(const Feedback &)> onActionFeedback_
std::function< void(const WrappedResult &)> postSuccessEvent
void postResultEvent(const WrappedResult &result)
typename GoalHandle::FeedbackCallback FeedbackCallback
smacc2::SmaccSignalConnection onAborted(void(T::*callback)(const WrappedResult &), T *object)
std::optional< std::chrono::milliseconds > serverTimeout
rclcpp_action::Client< ActionType > ActionClient
smacc2::SmaccSignalConnection onCancelled(void(T::*callback)(const WrappedResult &), T *object)
smacc2::SmaccSignal< void(const WrappedResult &)> onActionSucceeded_
void onFeedback(typename GoalHandle::SharedPtr, const std::shared_ptr< const Feedback > feedback_msg)
std::shared_ptr< ActionClient > getActionClient() const
std::function< void(const WrappedResult &)> postAbortedEvent
smacc2::SmaccSignal< void(const WrappedResult &)> onActionAborted_
std::function< void(const Feedback &)> postFeedbackEvent
typename GoalHandle::ResultCallback ResultCallback
rclcpp_action::ClientGoalHandle< ActionType > GoalHandle
std::optional< std::shared_future< typename rclcpp_action::Client< ActionType >::CancelResponse::SharedPtr > > lastCancelResponse_
std::string demangleSymbol()
boost::signals2::connection SmaccSignalConnection