Program Listing for File bt_action_node.hpp
↰ Return to documentation for file (include/as2_behavior_tree/bt_action_node.hpp
)
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef AS2_BEHAVIOR_TREE__BT_ACTION_NODE_HPP_
#define AS2_BEHAVIOR_TREE__BT_ACTION_NODE_HPP_
#include <chrono>
#include <memory>
#include <string>
#include <set>
#include "behaviortree_cpp_v3/action_node.h"
#include "nav2_behavior_tree/bt_conversions.hpp"
#include "nav2_util/node_utils.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
namespace nav2_behavior_tree
{
using namespace std::chrono_literals; // NOLINT
template<class ActionT>
class BtActionNode : public BT::ActionNodeBase
{
public:
BtActionNode(
const std::string & xml_tag_name, const std::string & action_name,
const BT::NodeConfiguration & conf)
: BT::ActionNodeBase(xml_tag_name, conf), action_name_(action_name)
{
node_ = config().blackboard->template get<rclcpp::Node::SharedPtr>("node");
callback_group_ = node_->create_callback_group(
rclcpp::CallbackGroupType::MutuallyExclusive, false);
callback_group_executor_.add_callback_group(
callback_group_, node_->get_node_base_interface());
// Get the required items from the blackboard
bt_loop_duration_ =
config().blackboard->template get<std::chrono::milliseconds>(
"bt_loop_duration");
server_timeout_ =
config().blackboard->template get<std::chrono::milliseconds>(
"server_timeout");
getInput<std::chrono::milliseconds>("server_timeout", server_timeout_);
// Initialize the input and output messages
goal_ = typename ActionT::Goal();
result_ =
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult();
std::string remapped_action_name;
if (getInput("server_name", remapped_action_name)) {
action_name_ = remapped_action_name;
}
createActionClient(action_name_);
// Give the derive class a chance to do any initialization
RCLCPP_DEBUG(
node_->get_logger(), "\"%s\" BtActionNode initialized",
xml_tag_name.c_str());
}
BtActionNode() = delete;
virtual ~BtActionNode() {}
void createActionClient(const std::string & action_name)
{
// Now that we have the ROS node to use, create the action client for this
// BT action
action_client_ = rclcpp_action::create_client<ActionT>(
node_, action_name,
callback_group_);
// Make sure the server is actually there before continuing
RCLCPP_DEBUG(
node_->get_logger(), "Waiting for \"%s\" action server",
action_name.c_str());
if (!action_client_->wait_for_action_server(1s)) {
RCLCPP_ERROR(
node_->get_logger(),
"\"%s\" action server not available after waiting for 1 s",
action_name.c_str());
throw std::runtime_error(
std::string("Action server %s not available", action_name.c_str()));
}
}
static BT::PortsList providedBasicPorts(BT::PortsList addition)
{
BT::PortsList basic = {
BT::InputPort<std::string>("server_name", "Action server name"),
BT::InputPort<std::chrono::milliseconds>("server_timeout")};
basic.insert(addition.begin(), addition.end());
return basic;
}
static BT::PortsList providedPorts() {return providedBasicPorts({});}
// Derived classes can override any of the following methods to hook into the
// processing for the action: on_tick, on_wait_for_result, and on_success
virtual void on_tick() {}
virtual void on_wait_for_result(
std::shared_ptr<const typename ActionT::Feedback>/*feedback*/) {}
virtual BT::NodeStatus on_success() {return BT::NodeStatus::SUCCESS;}
virtual BT::NodeStatus on_aborted() {return BT::NodeStatus::FAILURE;}
virtual BT::NodeStatus on_cancelled() {return BT::NodeStatus::SUCCESS;}
BT::NodeStatus tick() override
{
// first step to be done only at the beginning of the Action
if (status() == BT::NodeStatus::IDLE) {
// setting the status to RUNNING to notify the BT Loggers (if any)
setStatus(BT::NodeStatus::RUNNING);
// user defined callback
on_tick();
send_new_goal();
}
try {
// if new goal was sent and action server has not yet responded
// check the future goal handle
if (future_goal_handle_) {
auto elapsed = (node_->now() - time_goal_sent_)
.to_chrono<std::chrono::milliseconds>();
if (!is_future_goal_handle_complete(elapsed)) {
// return RUNNING if there is still some time before timeout happens
if (elapsed < server_timeout_) {
return BT::NodeStatus::RUNNING;
}
// if server has taken more time than the specified timeout value
// return FAILURE
RCLCPP_WARN(
node_->get_logger(),
"Timed out while waiting for action server to "
"acknowledge goal request for %s",
action_name_.c_str());
future_goal_handle_.reset();
return BT::NodeStatus::FAILURE;
}
}
// The following code corresponds to the "RUNNING" loop
if (rclcpp::ok() && !goal_result_available_) {
// user defined callback. May modify the value of "goal_updated_"
on_wait_for_result(feedback_);
// reset feedback to avoid stale information
feedback_.reset();
auto goal_status = goal_handle_->get_status();
if (goal_updated_ &&
(goal_status == action_msgs::msg::GoalStatus::STATUS_EXECUTING ||
goal_status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED))
{
goal_updated_ = false;
send_new_goal();
auto elapsed = (node_->now() - time_goal_sent_)
.to_chrono<std::chrono::milliseconds>();
if (!is_future_goal_handle_complete(elapsed)) {
if (elapsed < server_timeout_) {
return BT::NodeStatus::RUNNING;
}
RCLCPP_WARN(
node_->get_logger(),
"Timed out while waiting for action server to "
"acknowledge goal request for %s",
action_name_.c_str());
future_goal_handle_.reset();
return BT::NodeStatus::FAILURE;
}
}
auto ros_start = node_->now();
auto start = std::chrono::system_clock::now();
callback_group_executor_.spin_some();
auto ros_end = node_->now();
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
// std::cout << "SPIN SOME: Time " << std::setw(9) << " : " <<
// diff.count() << " s\n";
rclcpp::Duration ros_diff = ros_end - ros_start;
// std::cout << "SPIN SOME: ROS Time " << std::setw(9) << " : " <<
// ros_diff.nanoseconds() << " ns\n";
// check if, after invoking spin_some(), we finally received the result
if (!goal_result_available_) {
// Yield this Action, returning RUNNING
return BT::NodeStatus::RUNNING;
}
}
} catch (const std::runtime_error & e) {
if (e.what() == std::string("send_goal failed") ||
e.what() == std::string("Goal was rejected by the action server"))
{
// Action related failure that should not fail the tree, but the node
return BT::NodeStatus::FAILURE;
} else {
// Internal exception to propogate to the tree
throw e;
}
}
BT::NodeStatus status;
switch (result_.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
status = on_success();
break;
case rclcpp_action::ResultCode::ABORTED:
status = on_aborted();
break;
case rclcpp_action::ResultCode::CANCELED:
status = on_cancelled();
break;
default:
throw std::logic_error("BtActionNode::Tick: invalid status value");
}
goal_handle_.reset();
return status;
}
void halt() override
{
if (should_cancel_goal()) {
auto future_cancel = action_client_->async_cancel_goal(goal_handle_);
if (callback_group_executor_.spin_until_future_complete(
future_cancel, server_timeout_) !=
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_ERROR(
node_->get_logger(),
"Failed to cancel action server for %s",
action_name_.c_str());
}
}
setStatus(BT::NodeStatus::IDLE);
}
protected:
bool should_cancel_goal()
{
// Shut the node down if it is currently running
if (status() != BT::NodeStatus::RUNNING) {
return false;
}
// No need to cancel the goal if goal handle is invalid
if (!goal_handle_) {
return false;
}
callback_group_executor_.spin_some();
auto status = goal_handle_->get_status();
// Check if the goal is still executing
return status == action_msgs::msg::GoalStatus::STATUS_ACCEPTED ||
status == action_msgs::msg::GoalStatus::STATUS_EXECUTING;
}
void send_new_goal()
{
goal_result_available_ = false;
auto send_goal_options =
typename rclcpp_action::Client<ActionT>::SendGoalOptions();
// send_goal_options.goal_response_callback =
// [this](std::shared_future<typename
// rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr> future)
// {
// auto goal_handle = future.get();
// if (!goal_handle) {
// RCLCPP_ERROR(node_->get_logger(), "Goal was rejected by server");
// } else {
// RCLCPP_INFO(node_->get_logger(), "Goal accepted by server,
// waiting for result");
// }
// };
send_goal_options.result_callback =
std::bind(&BtActionNode::result_callback, this, std::placeholders::_1);
// send_goal_options.result_callback =
// [this](const typename
// rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult &result)
// {
// RCLCPP_INFO(node_->get_logger(), "result");
// if (future_goal_handle_)
// {
// RCLCPP_DEBUG(
// node_->get_logger(),
// "Goal result for %s available, but it hasn't received the
// goal response yet. " "It's probably a goal result for the
// last goal request", action_name_.c_str());
// return;
// }
// // TODO(#1652): a work around until rcl_action interface is updated
// // if goal ids are not matched, the older goal call this callback so
// ignore the result
// // if matched, it must be processed (including aborted)
// if (this->goal_handle_->get_goal_id() == result.goal_id)
// {
// goal_result_available_ = true;
// result_ = result;
// }
// };
send_goal_options.feedback_callback =
[this](
typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr,
const std::shared_ptr<const typename ActionT::Feedback> feedback) {
feedback_ = feedback;
};
future_goal_handle_ = std::make_shared<std::shared_future<
typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>>(
action_client_->async_send_goal(goal_, send_goal_options));
time_goal_sent_ = node_->now();
}
void result_callback(
const typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult
& result)
{
RCLCPP_INFO(node_->get_logger(), "result");
if (future_goal_handle_) {
RCLCPP_DEBUG(
node_->get_logger(),
"Goal result for %s available, but it hasn't received the "
"goal response yet. "
"It's probably a goal result for the last goal request",
action_name_.c_str());
return;
}
// TODO(#1652): a work around until rcl_action interface is updated
// if goal ids are not matched, the older goal call this callback so ignore
// the result if matched, it must be processed (including aborted)
if (this->goal_handle_->get_goal_id() == result.goal_id) {
goal_result_available_ = true;
result_ = result;
}
}
bool is_future_goal_handle_complete(std::chrono::milliseconds & elapsed)
{
auto remaining = server_timeout_ - elapsed;
// server has already timed out, no need to sleep
if (remaining <= std::chrono::milliseconds(0)) {
future_goal_handle_.reset();
return false;
}
auto timeout =
remaining > bt_loop_duration_ ? bt_loop_duration_ : remaining;
auto ros_start = node_->now().nanoseconds();
auto start = std::chrono::system_clock::now();
auto result = callback_group_executor_.spin_until_future_complete(
*future_goal_handle_, timeout);
auto ros_end = node_->now().nanoseconds();
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
// std::cout << "Time " << std::setw(9) << " : " << diff.count() << " s\n";
auto ros_diff = ros_end - ros_start;
// std::cout << "ROS Time " << std::setw(9) << " : " << ros_diff << " ns\n";
elapsed += timeout;
if (result == rclcpp::FutureReturnCode::INTERRUPTED) {
future_goal_handle_.reset();
throw std::runtime_error("send_goal failed");
}
if (result == rclcpp::FutureReturnCode::SUCCESS) {
goal_handle_ = future_goal_handle_->get();
future_goal_handle_.reset();
if (!goal_handle_) {
throw std::runtime_error("Goal was rejected by the action server");
}
// TODO(pariaspe): be careful, result will be always successful
// goal_result_available_ = true;
// result_.code = rclcpp_action::ResultCode::SUCCEEDED;
return true;
}
return false;
}
void increment_recovery_count()
{
int recovery_count = 0;
config().blackboard->template get<int>(
"number_recoveries",
recovery_count); // NOLINT
recovery_count += 1;
config().blackboard->template set<int>(
"number_recoveries",
recovery_count); // NOLINT
}
std::string action_name_;
typename std::shared_ptr<rclcpp_action::Client<ActionT>> action_client_;
// All ROS2 actions have a goal and a result
typename ActionT::Goal goal_;
bool goal_updated_{false};
bool goal_result_available_{false};
typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr goal_handle_;
typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult result_;
// To handle feedback from action server
std::shared_ptr<const typename ActionT::Feedback> feedback_;
// The node that will be used for any ROS operations
rclcpp::Node::SharedPtr node_;
rclcpp::CallbackGroup::SharedPtr callback_group_;
rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
// The timeout value while waiting for response from a server when a
// new action goal is sent or canceled
std::chrono::milliseconds server_timeout_;
// The timeout value for BT loop execution
std::chrono::milliseconds bt_loop_duration_;
// To track the action server acknowledgement when a new goal is sent
std::shared_ptr<std::shared_future<
typename rclcpp_action::ClientGoalHandle<ActionT>::SharedPtr>>
future_goal_handle_;
rclcpp::Time time_goal_sent_;
};
} // namespace nav2_behavior_tree
#endif // AS2_BEHAVIOR_TREE__BT_ACTION_NODE_HPP_