AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
action_client.hpp
Go to the documentation of this file.
1// Copyright 2024 Robin Müller
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// https://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 <chrono>
18
19#include "action_msgs/srv/cancel_goal.hpp"
20#include "rclcpp/rclcpp.hpp"
21#include "rclcpp_action/rclcpp_action.hpp"
22
24{
25
33enum class ActionGoalStatus : uint8_t
34{
35 REJECTED = 0,
36 RUNNING,
38};
39
44template <typename ActionT>
46{
47public:
48 using Goal = typename ActionT::Goal;
49 using SendGoalOptions = typename rclcpp_action::Client<ActionT>::SendGoalOptions;
50 using Feedback = typename ActionT::Feedback;
51 using Result = typename ActionT::Result;
52 using ClientGoalHandle = rclcpp_action::ClientGoalHandle<ActionT>;
53 using WrappedResultSharedPtr = std::shared_ptr<typename ClientGoalHandle::WrappedResult>;
54 using ResultFuture = std::shared_future<WrappedResultSharedPtr>;
55
61 ActionClientWrapper(rclcpp::Node& node, const std::string& action_name);
62
74 ResultFuture syncSendGoal(const Goal& goal = Goal{}, const SendGoalOptions& options = SendGoalOptions{},
75 const std::chrono::seconds server_timeout = std::chrono::seconds{ 3 },
76 const std::chrono::seconds response_timeout = std::chrono::seconds{ 3 });
77
87 bool syncCancelLastGoal(const std::chrono::seconds response_timeout = std::chrono::seconds{ 3 });
88
96 static ActionGoalStatus getGoalStatus(const ResultFuture& future);
97
103 typename ClientGoalHandle::SharedPtr getActiveGoalHandle();
104
109 std::shared_ptr<const Feedback> getFeedback();
110
111private:
112 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_ptr_;
113 const rclcpp::Logger logger_;
114 const std::string action_name_;
115 typename rclcpp_action::Client<ActionT>::SharedPtr client_ptr_;
116 typename ClientGoalHandle::SharedPtr goal_handle_ptr_;
117 std::shared_ptr<const Feedback> feedback_ptr_;
118};
119
120template <typename ActionT>
121ActionClientWrapper<ActionT>::ActionClientWrapper(rclcpp::Node& node, const std::string& action_name)
122 : node_base_interface_ptr_{ node.get_node_base_interface() }
123 , logger_{ node.get_logger() }
124 , action_name_{ action_name }
125{
126 client_ptr_ = rclcpp_action::create_client<ActionT>(&node, action_name_);
127}
128
129template <typename ActionT>
132 const std::chrono::seconds server_timeout,
133 const std::chrono::seconds response_timeout)
134{
135 auto promise_ptr = std::make_shared<std::promise<WrappedResultSharedPtr>>();
136 if (!client_ptr_->wait_for_action_server(server_timeout))
137 {
138 throw std::runtime_error("Action server '" + action_name_ + "' is not available");
139 }
140
141 SendGoalOptions _options;
142 _options.goal_response_callback = options.goal_response_callback;
143 _options.feedback_callback =
144 [this, feedback_callback = options.feedback_callback](typename ClientGoalHandle::SharedPtr client_goal_handle,
145 const std::shared_ptr<const Feedback> feedback) {
146 feedback_ptr_ = feedback;
147 if (feedback_callback)
148 {
149 feedback_callback(client_goal_handle, feedback);
150 }
151 };
152 _options.result_callback = [this, promise_ptr, result_callback = options.result_callback](
153 const typename ClientGoalHandle::WrappedResult& wr) {
154 if (result_callback)
155 {
156 result_callback(wr);
157 }
158 promise_ptr->set_value(std::make_shared<typename ClientGoalHandle::WrappedResult>(wr));
159 goal_handle_ptr_ = nullptr; // Reset active goal handle
160 };
161
162 auto goal_response_future = client_ptr_->async_send_goal(goal, _options);
163
164 switch (rclcpp::spin_until_future_complete(node_base_interface_ptr_, goal_response_future, response_timeout))
165 {
166 case rclcpp::FutureReturnCode::SUCCESS:
167 break;
168 case rclcpp::FutureReturnCode::TIMEOUT:
169 throw std::runtime_error("No goal response due to timeout");
170 case rclcpp::FutureReturnCode::INTERRUPTED:
171 throw std::runtime_error("No goal response due to interruption");
172 }
173
174 auto client_goal_handle_ptr = goal_response_future.get();
175 if (!client_goal_handle_ptr)
176 {
177 promise_ptr->set_value(nullptr);
178 return promise_ptr->get_future();
179 }
180
181 goal_handle_ptr_ = client_goal_handle_ptr;
182 return promise_ptr->get_future();
183}
184
185template <typename ActionT>
186bool ActionClientWrapper<ActionT>::syncCancelLastGoal(const std::chrono::seconds response_timeout)
187{
188 if (!goal_handle_ptr_)
189 {
190 throw std::runtime_error("Cannot cancel goal because goal_handle_ptr_ is nullptr");
191 }
192
193 // Send request and await response
194 auto cancel_response_future = client_ptr_->async_cancel_goal(goal_handle_ptr_);
195 switch (rclcpp::spin_until_future_complete(node_base_interface_ptr_, cancel_response_future, response_timeout))
196 {
197 case rclcpp::FutureReturnCode::SUCCESS:
198 break;
199 case rclcpp::FutureReturnCode::TIMEOUT:
200 throw std::runtime_error("No cancel response due to timeout");
201 case rclcpp::FutureReturnCode::INTERRUPTED:
202 throw std::runtime_error("No cancel response due to interruption");
203 }
204
205 // Evaluate the response message
206 switch (cancel_response_future.get()->return_code)
207 {
208 case action_msgs::srv::CancelGoal::Response::ERROR_NONE:
209 return true;
210 case action_msgs::srv::CancelGoal::Response::ERROR_REJECTED:
211 break;
212 case action_msgs::srv::CancelGoal::Response::ERROR_UNKNOWN_GOAL_ID:
213 RCLCPP_WARN(logger_, "Cancel response ERROR_UNKNOWN_GOAL_ID");
214 break;
215 case action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED:
216 RCLCPP_WARN(logger_, "Cancel response ERROR_GOAL_TERMINATED");
217 break;
218 }
219 return false;
220}
221
222template <typename ActionT>
224{
225 if (!future.valid())
226 {
227 throw std::runtime_error("ResultFuture is not valid");
228 }
229 if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
230 {
231 if (!future.get())
232 {
234 }
236 }
238}
239
240template <typename ActionT>
245
246template <typename ActionT>
247std::shared_ptr<const typename ActionClientWrapper<ActionT>::Feedback> ActionClientWrapper<ActionT>::getFeedback()
248{
249 return feedback_ptr_;
250}
251
252} // namespace auto_apms_core
Convenience wrapper for a rclcpp_action::Client that introduces synchronous goal handling functions.
bool syncCancelLastGoal(const std::chrono::seconds response_timeout=std::chrono::seconds{ 3 })
Request to cancel the most recent goal.
std::shared_future< WrappedResultSharedPtr > ResultFuture
std::shared_ptr< typename ClientGoalHandle::WrappedResult > WrappedResultSharedPtr
typename rclcpp_action::Client< ActionT >::SendGoalOptions SendGoalOptions
ResultFuture syncSendGoal(const Goal &goal=Goal{}, const SendGoalOptions &options=SendGoalOptions{}, const std::chrono::seconds server_timeout=std::chrono::seconds{ 3 }, const std::chrono::seconds response_timeout=std::chrono::seconds{ 3 })
Send a goal to the action and synchronously wait for the response.
std::shared_ptr< const Feedback > getFeedback()
Get the most recent feedback.
typename ActionT::Result Result
ClientGoalHandle::SharedPtr getActiveGoalHandle()
Get the goal handle of the currently active goal.
static ActionGoalStatus getGoalStatus(const ResultFuture &future)
Determine the status of a specific goal by evaluating the corresponding ActionClientWrapper::ResultFu...
rclcpp_action::ClientGoalHandle< ActionT > ClientGoalHandle
typename ActionT::Feedback Feedback
ActionClientWrapper(rclcpp::Node &node, const std::string &action_name)
Constructor.
ActionGoalStatus
Enum for indicating a ROS 2 action goal's current status.