AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
action_client_wrapper.hpp
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
27
33{
34
42enum class ActionGoalStatus : uint8_t
43{
44 REJECTED = 0,
45 RUNNING,
46 COMPLETED
47};
48
53template <typename ActionT>
55{
56public:
57 using Goal = typename ActionT::Goal;
58 using SendGoalOptions = typename rclcpp_action::Client<ActionT>::SendGoalOptions;
59 using Feedback = typename ActionT::Feedback;
60 using Result = typename ActionT::Result;
61 using ClientGoalHandle = rclcpp_action::ClientGoalHandle<ActionT>;
62 using WrappedResultSharedPtr = std::shared_ptr<typename ClientGoalHandle::WrappedResult>;
63 using ResultFuture = std::shared_future<WrappedResultSharedPtr>;
64
70 ActionClientWrapper(rclcpp::Node::SharedPtr node_ptr, const std::string & action_name);
71
83 ResultFuture syncSendGoal(
84 const Goal & goal = Goal{}, const SendGoalOptions & options = SendGoalOptions{},
85 const std::chrono::seconds server_timeout = std::chrono::seconds{3},
86 const std::chrono::seconds response_timeout = std::chrono::seconds{3});
87
97 bool syncCancelLastGoal(const std::chrono::seconds response_timeout = std::chrono::seconds{3});
98
106 static ActionGoalStatus getGoalStatus(const ResultFuture & future);
107
113 typename ClientGoalHandle::SharedPtr getActiveGoalHandle();
114
119 std::shared_ptr<const Feedback> getFeedback();
120
121private:
122 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_ptr_;
123 const rclcpp::Logger logger_;
124 const std::string action_name_;
125 typename rclcpp_action::Client<ActionT>::SharedPtr client_ptr_;
126 typename ClientGoalHandle::SharedPtr goal_handle_ptr_;
127 std::shared_ptr<const Feedback> feedback_ptr_;
128};
129
130template <typename ActionT>
131ActionClientWrapper<ActionT>::ActionClientWrapper(rclcpp::Node::SharedPtr node_ptr, const std::string & action_name)
132: node_base_interface_ptr_{node_ptr->get_node_base_interface()},
133 logger_{node_ptr->get_logger()},
134 action_name_{action_name}
135{
136 client_ptr_ = rclcpp_action::create_client<ActionT>(node_ptr, action_name_);
137}
138
139template <typename ActionT>
140typename ActionClientWrapper<ActionT>::ResultFuture ActionClientWrapper<ActionT>::syncSendGoal(
141 const Goal & goal, const SendGoalOptions & options, const std::chrono::seconds server_timeout,
142 const std::chrono::seconds response_timeout)
143{
144 auto promise_ptr = std::make_shared<std::promise<WrappedResultSharedPtr>>();
145 if (!client_ptr_->wait_for_action_server(server_timeout)) {
146 throw std::runtime_error("Action server '" + action_name_ + "' is not available");
147 }
148
149 SendGoalOptions _options;
150 _options.goal_response_callback = options.goal_response_callback;
151 _options.feedback_callback = [this, feedback_callback = options.feedback_callback](
152 typename ClientGoalHandle::SharedPtr client_goal_handle,
153 const std::shared_ptr<const Feedback> feedback) {
154 feedback_ptr_ = feedback;
155 if (feedback_callback) {
156 feedback_callback(client_goal_handle, feedback);
157 }
158 };
159 _options.result_callback = [this, promise_ptr, result_callback = options.result_callback](
160 const typename ClientGoalHandle::WrappedResult & wr) {
161 if (result_callback) {
162 result_callback(wr);
163 }
164 promise_ptr->set_value(std::make_shared<typename ClientGoalHandle::WrappedResult>(wr));
165 goal_handle_ptr_ = nullptr; // Reset active goal handle
166 };
167
168 auto goal_response_future = client_ptr_->async_send_goal(goal, _options);
169
170 switch (rclcpp::spin_until_future_complete(node_base_interface_ptr_, goal_response_future, response_timeout)) {
171 case rclcpp::FutureReturnCode::SUCCESS:
172 break;
173 case rclcpp::FutureReturnCode::TIMEOUT:
174 throw std::runtime_error("No goal response due to timeout");
175 case rclcpp::FutureReturnCode::INTERRUPTED:
176 throw std::runtime_error("No goal response due to interruption");
177 }
178
179 auto client_goal_handle_ptr = goal_response_future.get();
180 if (!client_goal_handle_ptr) {
181 promise_ptr->set_value(nullptr);
182 return promise_ptr->get_future();
183 }
184
185 goal_handle_ptr_ = client_goal_handle_ptr;
186 return promise_ptr->get_future();
187}
188
189template <typename ActionT>
190bool ActionClientWrapper<ActionT>::syncCancelLastGoal(const std::chrono::seconds response_timeout)
191{
192 if (!goal_handle_ptr_) {
193 throw std::runtime_error("Cannot cancel goal because goal_handle_ptr_ is nullptr");
194 }
195
196 // Send request and await response
197 auto cancel_response_future = client_ptr_->async_cancel_goal(goal_handle_ptr_);
198 switch (rclcpp::spin_until_future_complete(node_base_interface_ptr_, cancel_response_future, response_timeout)) {
199 case rclcpp::FutureReturnCode::SUCCESS:
200 break;
201 case rclcpp::FutureReturnCode::TIMEOUT:
202 throw std::runtime_error("No cancel response due to timeout");
203 case rclcpp::FutureReturnCode::INTERRUPTED:
204 throw std::runtime_error("No cancel response due to interruption");
205 }
206
207 // Evaluate the response message
208 switch (cancel_response_future.get()->return_code) {
209 case action_msgs::srv::CancelGoal::Response::ERROR_NONE:
210 return true;
211 case action_msgs::srv::CancelGoal::Response::ERROR_REJECTED:
212 break;
213 case action_msgs::srv::CancelGoal::Response::ERROR_UNKNOWN_GOAL_ID:
214 RCLCPP_WARN(logger_, "Cancel response ERROR_UNKNOWN_GOAL_ID");
215 break;
216 case action_msgs::srv::CancelGoal::Response::ERROR_GOAL_TERMINATED:
217 RCLCPP_WARN(logger_, "Cancel response ERROR_GOAL_TERMINATED");
218 break;
219 }
220 return false;
221}
222
223template <typename ActionT>
225{
226 if (!future.valid()) {
227 throw std::runtime_error("ResultFuture is not valid");
228 }
229 if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
230 if (!future.get()) {
231 return ActionGoalStatus::REJECTED;
232 }
233 return ActionGoalStatus::COMPLETED;
234 }
235 return ActionGoalStatus::RUNNING;
236}
237
238template <typename ActionT>
243
244template <typename ActionT>
245std::shared_ptr<const typename ActionClientWrapper<ActionT>::Feedback> ActionClientWrapper<ActionT>::getFeedback()
246{
247 return feedback_ptr_;
248}
249
250} // namespace auto_apms_util
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.
bool syncCancelLastGoal(const std::chrono::seconds response_timeout=std::chrono::seconds{3})
Request to cancel the most recent goal.
std::shared_ptr< const Feedback > getFeedback()
Get the most recent feedback.
ClientGoalHandle::SharedPtr getActiveGoalHandle()
Get the goal handle of the currently active goal.
ActionClientWrapper(rclcpp::Node::SharedPtr node_ptr, const std::string &action_name)
ActionClientWrapper constructor.
static ActionGoalStatus getGoalStatus(const ResultFuture &future)
Determine the status of a specific goal by evaluating the corresponding ActionClientWrapper::ResultFu...
ActionGoalStatus
Enum for indicating a ROS 2 action goal's current status.
Fundamental helper classes and utility functions.