15 #ifndef NAV2_BEHAVIOR_TREE__BT_ACTION_NODE_HPP_
16 #define NAV2_BEHAVIOR_TREE__BT_ACTION_NODE_HPP_
23 #include "behaviortree_cpp/action_node.h"
24 #include "behaviortree_cpp/json_export.h"
25 #include "nav2_ros_common/node_utils.hpp"
26 #include "rclcpp_action/rclcpp_action.hpp"
27 #include "nav2_behavior_tree/bt_utils.hpp"
28 #include "nav2_behavior_tree/json_utils.hpp"
30 namespace nav2_behavior_tree
33 using namespace std::chrono_literals;
40 template<
class ActionT>
44 using ActionResult =
typename ActionT::Result;
53 const std::string & xml_tag_name,
54 const std::string & action_name,
55 const BT::NodeConfiguration & conf)
56 : BT::ActionNodeBase(xml_tag_name, conf), action_name_(action_name), should_send_goal_(true)
58 node_ = config().blackboard->template get<nav2::LifecycleNode::SharedPtr>(
"node");
59 callback_group_ = node_->create_callback_group(
60 rclcpp::CallbackGroupType::MutuallyExclusive,
62 callback_group_executor_.add_callback_group(callback_group_, node_->get_node_base_interface());
65 auto bt_loop_duration =
66 config().blackboard->template get<std::chrono::milliseconds>(
"bt_loop_duration");
67 getInputOrBlackboard(
"server_timeout", server_timeout_);
68 getInputOrBlackboard(
"cancel_timeout", cancel_timeout_);
69 wait_for_service_timeout_ =
70 config().blackboard->template get<std::chrono::milliseconds>(
"wait_for_service_timeout");
73 max_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(bt_loop_duration * 0.5);
76 goal_ =
typename ActionT::Goal();
77 result_ =
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult();
80 !requires {ActionT::Result::TIMEOUT;} ||
81 !requires {ActionT::Result::GOAL_REJECTED;} ||
82 !requires {ActionT::Result::SEND_GOAL_FAILURE;})
84 if constexpr (requires {ActionT::Result::UNKNOWN;}) {
87 "Action type for \"%s\" does not define one or more of the TIMEOUT, "
88 "GOAL_REJECTED, and SEND_GOAL_FAILURE error codes. UNKNOWN will be "
89 "used for unavailable errors.",
90 xml_tag_name.c_str());
94 "Action type for \"%s\" does not define one or more of the TIMEOUT, "
95 "GOAL_REJECTED, and SEND_GOAL_FAILURE error codes. The error_code_id "
96 "output will not be set for unavailable errors.",
97 xml_tag_name.c_str());
101 std::string remapped_action_name;
102 if (getInput(
"server_name", remapped_action_name)) {
103 action_name_ = remapped_action_name;
105 createActionClient(action_name_);
108 RCLCPP_DEBUG(node_->get_logger(),
"\"%s\" BtActionNode initialized", xml_tag_name.c_str());
124 action_client_ = node_->create_action_client<ActionT>(action_name, callback_group_);
127 RCLCPP_DEBUG(node_->get_logger(),
"Waiting for \"%s\" action server", action_name.c_str());
128 if (!action_client_->wait_for_action_server(wait_for_service_timeout_)) {
130 node_->get_logger(),
"\"%s\" action server not available after waiting for %.2fs",
132 wait_for_service_timeout_.count() / 1000.0);
133 throw std::runtime_error(
134 std::string(
"Action server ") + action_name +
135 std::string(
" not available"));
147 BT::PortsList basic = {
148 BT::InputPort<std::string>(
"server_name",
"Action server name"),
149 BT::InputPort<std::chrono::milliseconds>(
"server_timeout"),
150 BT::OutputPort<uint16_t>(
"error_code_id",
"The action error code"),
151 BT::OutputPort<std::string>(
"error_msg",
"The action error message")
153 basic.insert(addition.begin(), addition.end());
164 return providedBasicPorts({});
196 return BT::NodeStatus::SUCCESS;
205 return BT::NodeStatus::FAILURE;
214 return BT::NodeStatus::SUCCESS;
223 if constexpr (requires {ActionT::Result::TIMEOUT;}) {
224 setOutput(
"error_code_id", ActionResult::TIMEOUT);
225 }
else if constexpr (requires {ActionT::Result::UNKNOWN;}) {
226 setOutput(
"error_code_id", ActionResult::UNKNOWN);
228 setOutput(
"error_msg",
"Behavior Tree action client timed out waiting.");
237 if constexpr (requires {ActionT::Result::GOAL_REJECTED;}) {
238 setOutput(
"error_code_id", ActionResult::GOAL_REJECTED);
239 }
else if constexpr (requires {ActionT::Result::UNKNOWN;}) {
240 setOutput(
"error_code_id", ActionResult::UNKNOWN);
242 setOutput(
"error_msg",
"Goal was rejected by the action server.");
251 if constexpr (requires {ActionT::Result::SEND_GOAL_FAILURE;}) {
252 setOutput(
"error_code_id", ActionResult::SEND_GOAL_FAILURE);
253 }
else if constexpr (requires {ActionT::Result::UNKNOWN;}) {
254 setOutput(
"error_code_id", ActionResult::UNKNOWN);
256 setOutput(
"error_msg",
"Failed to send goal to the action server.");
266 if (!BT::isStatusActive(status())) {
268 should_send_goal_ =
true;
271 goal_ =
typename ActionT::Goal();
272 result_ =
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult();
278 setStatus(BT::NodeStatus::RUNNING);
280 if (!should_send_goal_) {
281 return BT::NodeStatus::FAILURE;
289 if (future_goal_handle_) {
291 (node_->now() - time_goal_sent_).
template to_chrono<std::chrono::milliseconds>();
292 if (!is_future_goal_handle_complete(elapsed)) {
294 if (elapsed < server_timeout_) {
295 return BT::NodeStatus::RUNNING;
297 handle_goal_response_timeout();
298 return BT::NodeStatus::FAILURE;
303 if (rclcpp::ok() && !goal_result_available_) {
305 on_wait_for_result(feedback_);
310 auto goal_status = goal_handle_->get_status();
312 (goal_status == action_msgs::msg::GoalStatus::STATUS_EXECUTING ||
313 goal_status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED))
315 goal_updated_ =
false;
318 (node_->now() - time_goal_sent_).
template to_chrono<std::chrono::milliseconds>();
319 if (!is_future_goal_handle_complete(elapsed)) {
320 if (elapsed < server_timeout_) {
321 return BT::NodeStatus::RUNNING;
323 handle_goal_response_timeout();
324 return BT::NodeStatus::FAILURE;
328 callback_group_executor_.spin_some();
331 if (!goal_result_available_) {
333 return BT::NodeStatus::RUNNING;
336 }
catch (
const std::runtime_error & e) {
337 if (e.what() == std::string(
"Goal was rejected by the action server")) {
339 return BT::NodeStatus::FAILURE;
340 }
else if (e.what() == std::string(
"send_goal failed")) {
341 on_send_goal_failure();
343 return BT::NodeStatus::FAILURE;
350 BT::NodeStatus status;
351 switch (result_.code) {
352 case rclcpp_action::ResultCode::SUCCEEDED:
353 status = on_success();
356 case rclcpp_action::ResultCode::ABORTED:
357 status = on_aborted();
360 case rclcpp_action::ResultCode::CANCELED:
361 status = on_cancelled();
365 throw std::logic_error(
"BtActionNode::Tick: invalid status value");
368 goal_handle_.reset();
381 if (future_goal_handle_) {
383 (node_->now() - time_goal_sent_).
template to_chrono<std::chrono::milliseconds>();
384 auto remaining = server_timeout_ - elapsed;
385 if (remaining > std::chrono::milliseconds(0)) {
387 callback_group_executor_.spin_until_future_complete(
388 *future_goal_handle_, remaining) ==
389 rclcpp::FutureReturnCode::SUCCESS)
391 goal_handle_ = future_goal_handle_->get();
395 future_goal_handle_.reset();
398 if (should_cancel_goal()) {
399 auto future_result = action_client_->async_get_result(goal_handle_);
400 auto future_cancel = action_client_->async_cancel_goal(goal_handle_);
401 if (callback_group_executor_.spin_until_future_complete(future_cancel, server_timeout_) !=
402 rclcpp::FutureReturnCode::SUCCESS)
406 "Failed to cancel action server for %s", action_name_.c_str());
409 if (callback_group_executor_.spin_until_future_complete(future_result, cancel_timeout_) !=
410 rclcpp::FutureReturnCode::SUCCESS)
414 "Failed to get result for %s in node halt!", action_name_.c_str());
433 "Timed out waiting for action server to acknowledge goal request for %s, "
434 "canceling all goals",
435 action_name_.c_str());
436 auto future_cancel = action_client_->async_cancel_all_goals();
437 if (callback_group_executor_.spin_until_future_complete(
438 future_cancel, cancel_timeout_) != rclcpp::FutureReturnCode::SUCCESS)
442 "Timed out while waiting for action server to cancel all goals for %s",
443 action_name_.c_str());
445 future_goal_handle_.reset();
456 if (status() != BT::NodeStatus::RUNNING) {
465 callback_group_executor_.spin_some();
466 auto status = goal_handle_->get_status();
469 return status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
470 status == action_msgs::msg::GoalStatus::STATUS_EXECUTING;
478 goal_result_available_ =
false;
479 auto send_goal_options =
typename nav2::ActionClient<ActionT>::SendGoalOptions();
480 send_goal_options.result_callback =
481 [
this](
const typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult & result) {
482 if (future_goal_handle_) {
485 "Goal result for %s available, but it hasn't received the goal response yet. "
486 "It's probably a goal result for the last goal request", action_name_.c_str());
493 if (this->goal_handle_->get_goal_id() == result.goal_id) {
494 goal_result_available_ =
true;
499 send_goal_options.feedback_callback =
500 [
this](
typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr,
501 const std::shared_ptr<const typename ActionT::Feedback> feedback) {
502 feedback_ = feedback;
506 future_goal_handle_ = std::make_shared<
507 std::shared_future<typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>>(
508 action_client_->async_send_goal(goal_, send_goal_options));
509 time_goal_sent_ = node_->now();
520 auto remaining = server_timeout_ - elapsed;
523 if (remaining <= std::chrono::milliseconds(0)) {
527 auto timeout = remaining > max_timeout_ ? max_timeout_ : remaining;
529 callback_group_executor_.spin_until_future_complete(*future_goal_handle_, timeout);
532 if (result == rclcpp::FutureReturnCode::INTERRUPTED) {
533 future_goal_handle_.reset();
534 throw std::runtime_error(
"send_goal failed");
537 if (result == rclcpp::FutureReturnCode::SUCCESS) {
538 goal_handle_ = future_goal_handle_->get();
539 future_goal_handle_.reset();
541 throw std::runtime_error(
"Goal was rejected by the action server");
554 int recovery_count = 0;
555 [[maybe_unused]]
auto res = config().blackboard->get(
"number_recoveries", recovery_count);
557 config().blackboard->set(
"number_recoveries", recovery_count);
560 std::string action_name_;
561 typename nav2::ActionClient<ActionT>::SharedPtr action_client_;
564 typename ActionT::Goal goal_;
565 bool goal_updated_{
false};
566 bool goal_result_available_{
false};
567 typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr goal_handle_;
568 typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult result_;
571 std::shared_ptr<const typename ActionT::Feedback> feedback_;
574 nav2::LifecycleNode::SharedPtr node_;
575 rclcpp::CallbackGroup::SharedPtr callback_group_;
576 rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
580 std::chrono::milliseconds server_timeout_;
583 std::chrono::milliseconds cancel_timeout_;
586 std::chrono::milliseconds max_timeout_;
589 std::chrono::milliseconds wait_for_service_timeout_;
592 std::shared_ptr<std::shared_future<typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>>
594 rclcpp::Time time_goal_sent_;
597 bool should_send_goal_;
Abstract class representing an action based BT node.
BT::NodeStatus tick() override
The main override required by a BT action.
bool is_future_goal_handle_complete(std::chrono::milliseconds &elapsed)
Function to check if the action server acknowledged a new goal.
virtual BT::NodeStatus on_cancelled()
Function to perform some user-defined operation when the action is cancelled.
virtual void on_send_goal_failure()
Function to perform work when sending a goal to the action server fails Such as setting the error cod...
virtual BT::NodeStatus on_success()
Function to perform some user-defined operation upon successful completion of the action....
void halt() override
The other (optional) override required by a BT action. In this case, we make sure to cancel the ROS2 ...
virtual void on_timeout()
Function to perform work in a BT Node when the action server times out Such as setting the error code...
void handle_goal_response_timeout()
Handle a timeout while waiting for a goal response.
virtual BT::NodeStatus on_aborted()
Function to perform some user-defined operation when the action is aborted.
static BT::PortsList providedBasicPorts(BT::PortsList addition)
Any subclass of BtActionNode that accepts parameters must provide a providedPorts method and call pro...
virtual void on_tick()
Function to perform some user-defined operation on tick Could do dynamic checks, such as getting upda...
virtual void on_goal_rejected()
Function to perform work in a BT Node when the action server rejects a goal Such as setting the error...
void createActionClient(const std::string &action_name)
Create instance of an action client.
virtual void on_wait_for_result(std::shared_ptr< const typename ActionT::Feedback >)
Function to perform some user-defined operation after a timeout waiting for a result that hasn't been...
void send_new_goal()
Function to send new goal to action server.
BtActionNode(const std::string &xml_tag_name, const std::string &action_name, const BT::NodeConfiguration &conf)
A nav2_behavior_tree::BtActionNode constructor.
static BT::PortsList providedPorts()
Creates list of BT ports.
bool should_cancel_goal()
Function to check if current goal should be cancelled.
void increment_recovery_count()
Function to increment recovery count on blackboard if this node wraps a recovery.