behavior tree常用语法
我们继续通过Navigate To Pose and Pause Near Goal-Obstacle(导航到目标位姿并具有接近障碍物暂停功能)来讲解BT常用指令。
4.ReactiveSquence反应序列
该节点对于连续检查条件特别有用;但是用户在使用异步子节点时也应该小心,以确保它们不会比预期的更频繁(可以通过装饰节点控制第一个子节点的频率)。
主要功能是同过调用第一个子节点,根据反馈情况,调用第二个节点。例如,调用第一个节点返回running,则不会调用第二个节点。调用第一个节点返回success,则调用第二个节点。调用第一个节点返回failtrue,则终止所有节点,返回failtrue。当第二个节点running时,父节点返回running,下个tick继续调用第一个节点。
看伪代码更容易理解,暂时未找到源代码,后续更新
让我们看另一个例子:
status = RUNNING;
for (int index=0; index < number_of_children; index++)
{
child_status = child[index]->tick();
if( child_status == RUNNING ) {
return RUNNING;
}
else if( child_status == FAILURE ) {
HaltAllChildren();
return FAILURE;
}
}
// all the children returned success. Return SUCCESS too.
HaltAllChildren();
return SUCCESS;
5.Fallback 后退控制节点
如果第一个节点failtrue,则不返回直接tick第二节点,一直到有节点success,父节点Fallback返回success。如果子节点返回running,则父节点返回running。
c++伪代码:
// index is initialized to 0 in the constructor
status = RUNNING;
while( _index < number_of_children )
{
child_status = child[index]->tick();
if( child_status == RUNNING ) {
// Suspend execution and return RUNNING.
// At the next tick, _index will be the same.
return RUNNING;
}
else if( child_status == FAILURE ) {
// continue the while loop
_index++;
}
else if( child_status == SUCCESS ) {
// Suspend execution and return SUCCESS.
HaltAllChildren();
_index = 0;
return SUCCESS;
}
}
// all the children returned FAILURE. Return FAILURE too.
index = 0;
HaltAllChildren();
return FAILURE;
6.ReactiveFallback 反应式后退控制节点
如果前面的条件之一将其状态从 FAILURE 更改为 SUCCESS,则当您想要中断异步子节点时使用此 控制节点。
在下面的示例中,角色最多可以睡8 个小时。如果他/她已经充分休息,则节点将返回SUCCESS ,areYouRested?异步节点将被中断。Timeout (8 hrs)Sleep
status = RUNNING;
for (int index=0; index < number_of_children; index++)
{
child_status = child[index]->tick();
if( child_status == RUNNING ) {
// Suspend all subsequent siblings and return RUNNING.
HaltSubsequentSiblings();
return RUNNING;
}
// if child_status == FAILURE, continue to tick next sibling
if( child_status == SUCCESS ) {
// Suspend execution and return SUCCESS.
HaltAllChildren();
return SUCCESS;
}
}
// all the children returned FAILURE. Return FAILURE too.
HaltAllChildren();
return FAILURE;
7.Squence序列节点
就是顺序执行,tick子节点,子节点返回成功继续调用下一个,知道所有都成功,返回success。如果子节点running,父节点返回running,但当前子节点索引记忆,下一次从当前节点调用。如果子节点返回failtrue,父节点返回failtrue,记忆索引清零。
c++伪代码:特点是如果失败,要清楚_index,从开始重新调用
status = RUNNING;
// _index is a private member
while(_index < number_of_children)
{
child_status = child[_index]->tick();
if( child_status == SUCCESS ) {
_index++;
}
else if( child_status == RUNNING ) {
// keep same index
return RUNNING;
}
else if( child_status == FAILURE ) {
HaltAllChildren();
_index = 0;
return FAILURE;
}
}
// all the children returned success. Return SUCCESS too.
HaltAllChildren();
_index = 0;
return SUCCESS;
8.SequenceStar序列星
当您不想再次勾选已返回 SUCCESS 的子项时,请使用此 ControlNode。
示例:
这是一个必须访问位置 A、B 和 C一次的巡逻代理/机器人。如果动作GoTo(B)失败,则不会再次勾选GoTo(A) 。
另一方面,isBatteryOK必须在每次滴答时检查,因此其父级必须是ReactiveSequence.
c++代码:特点是子节点failtrue,不清除_index,下次调用时继续上次的_index位置开始调用。
status = RUNNING;
// _index is a private member
while( index < number_of_children)
{
child_status = child[index]->tick();
if( child_status == SUCCESS ) {
_index++;
}
else if( child_status == RUNNING ||
child_status == FAILURE )
{
// keep same index
return child_status;
}
}
// all the children returned success. Return SUCCESS too.
HaltAllChildren();
_index = 0;
return SUCCESS;
Decorators装饰器节点
装饰器节点只能拥有一个子节点。
装饰器决定了子节点什么时候调用,以及调用多少次。
InverterNode
调用子节点一次,如果子节点失败,则返回 SUCCESS,如果子节点成功,则返回 FAILURE。
如果子节点返回 RUNNING,则该节点也返回 RUNNING
ForceSuccessNode
如果子节点返回 RUNNING,则该节点也返回 RUNNING。
否则,它总是返回 SUCCESS。
ForceFailureNode
如果子节点返回 RUNNING,则该节点也返回 RUNNING。
否则,它总是返回 FAILURE。
RepeatNode
将孩子最多勾选 N 次,其中 N 作为Input Port传递,只要孩子返回 SUCCESS。
如果孩子返回 FAILURE,则中断循环,在这种情况下,也返回 FAILURE。
如果子节点返回 RUNNING,则该节点也返回 RUNNING。
RetryNode
将孩子最多勾选 N 次,其中 N 作为Input Port传递,只要孩子返回 FAILURE。
如果孩子返回 SUCCESS,则中断循环,在这种情况下,也返回 SUCCESS。
如果子节点返回 RUNNING,则该节点也返回 RUNNING。
RateController
将子节点限制为给定速率
#include <chrono>
#include <string>
#include "nav2_behavior_tree/plugins/decorator/rate_controller.hpp"
namespace nav2_behavior_tree
{
RateController::RateController(
const std::string & name,
const BT::NodeConfiguration & conf)
: BT::DecoratorNode(name, conf),
first_time_(false)
{
double hz = 1.0;
getInput("hz", hz);
period_ = 1.0 / hz;
}
BT::NodeStatus RateController::tick()
{
if (status() == BT::NodeStatus::IDLE) {
// Reset the starting point since we're starting a new iteration of
// the rate controller (moving from IDLE to RUNNING)
start_ = std::chrono::high_resolution_clock::now();
first_time_ = true;
}
setStatus(BT::NodeStatus::RUNNING);
// Determine how long its been since we've started this iteration
auto now = std::chrono::high_resolution_clock::now();
auto elapsed = now - start_;
// Now, get that in seconds
typedef std::chrono::duration<float> float_seconds;
auto seconds = std::chrono::duration_cast<float_seconds>(elapsed);
// The child gets ticked the first time through and any time the period has
// expired. In addition, once the child begins to run, it is ticked each time
// 'til completion
if (first_time_ || (child_node_->status() == BT::NodeStatus::RUNNING) ||
seconds.count() >= period_)
{
first_time_ = false;
const BT::NodeStatus child_state = child_node_->executeTick();
switch (child_state) {
case BT::NodeStatus::RUNNING:
return BT::NodeStatus::RUNNING;
case BT::NodeStatus::SUCCESS:
start_ = std::chrono::high_resolution_clock::now(); // Reset the timer
return BT::NodeStatus::SUCCESS;
case BT::NodeStatus::FAILURE:
default:
return BT::NodeStatus::FAILURE;
}
}
return status();
}
} // namespace nav2_behavior_tree
#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<nav2_behavior_tree::RateController>("RateController");
}
Distance Controller
根据机器人行进的距离标记子节点
#include <chrono>
#include <string>
#include <memory>
#include <cmath>
#include "nav2_util/robot_utils.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "tf2_ros/buffer.h"
#include "behaviortree_cpp_v3/decorator_node.h"
#include "nav2_behavior_tree/plugins/decorator/distance_controller.hpp"
namespace nav2_behavior_tree
{
DistanceController::DistanceController(
const std::string & name,
const BT::NodeConfiguration & conf)
: BT::DecoratorNode(name, conf),
distance_(1.0),
global_frame_("map"),
robot_base_frame_("base_link"),
first_time_(false)
{
getInput("distance", distance_);
getInput("global_frame", global_frame_);
getInput("robot_base_frame", robot_base_frame_);
node_ = config().blackboard->get<rclcpp::Node::SharedPtr>("node");
tf_ = config().blackboard->get<std::shared_ptr<tf2_ros::Buffer>>("tf_buffer");
node_->get_parameter("transform_tolerance", transform_tolerance_);
}
inline BT::NodeStatus DistanceController::tick()
{
if (status() == BT::NodeStatus::IDLE) {
// Reset the starting position since we're starting a new iteration of
// the distance controller (moving from IDLE to RUNNING)
if (!nav2_util::getCurrentPose(
start_pose_, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_DEBUG(node_->get_logger(), "Current robot pose is not available.");
return BT::NodeStatus::FAILURE;
}
first_time_ = true;
}
setStatus(BT::NodeStatus::RUNNING);
// Determine distance travelled since we've started this iteration
geometry_msgs::msg::PoseStamped current_pose;
if (!nav2_util::getCurrentPose(
current_pose, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_DEBUG(node_->get_logger(), "Current robot pose is not available.");
return BT::NodeStatus::FAILURE;
}
// Get euclidean distance
auto travelled = nav2_util::geometry_utils::euclidean_distance(
start_pose_.pose, current_pose.pose);
// The child gets ticked the first time through and every time the threshold
// distance is crossed. In addition, once the child begins to run, it is
// ticked each time 'til completion
if (first_time_ || (child_node_->status() == BT::NodeStatus::RUNNING) ||
travelled >= distance_)
{
first_time_ = false;
const BT::NodeStatus child_state = child_node_->executeTick();
switch (child_state) {
case BT::NodeStatus::RUNNING:
return BT::NodeStatus::RUNNING;
case BT::NodeStatus::SUCCESS:
if (!nav2_util::getCurrentPose(
start_pose_, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_DEBUG(node_->get_logger(), "Current robot pose is not available.");
return BT::NodeStatus::FAILURE;
}
return BT::NodeStatus::SUCCESS;
case BT::NodeStatus::FAILURE:
default:
return BT::NodeStatus::FAILURE;
}
}
return status();
}
} // namespace nav2_behavior_tree
#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<nav2_behavior_tree::DistanceController>("DistanceController");
}
© 2022 GitHub, Inc.
Terms
Privacy
Security
Speed Controller
将子节点限制为基于当前机器人速度的速率。
Goal Updater
更新通过主题订阅收到的目标。
#include <string>
#include <memory>
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "behaviortree_cpp_v3/decorator_node.h"
#include "nav2_behavior_tree/plugins/decorator/goal_updater_node.hpp"
#include "rclcpp/rclcpp.hpp"
namespace nav2_behavior_tree
{
using std::placeholders::_1;
GoalUpdater::GoalUpdater(
const std::string & name,
const BT::NodeConfiguration & conf)
: BT::DecoratorNode(name, conf)
{
node_ = config().blackboard->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());
std::string goal_updater_topic;
node_->get_parameter_or<std::string>("goal_updater_topic", goal_updater_topic, "goal_update");
rclcpp::SubscriptionOptions sub_option;
sub_option.callback_group = callback_group_;
goal_sub_ = node_->create_subscription<geometry_msgs::msg::PoseStamped>(
goal_updater_topic,
10,
std::bind(&GoalUpdater::callback_updated_goal, this, _1),
sub_option);
}
inline BT::NodeStatus GoalUpdater::tick()
{
geometry_msgs::msg::PoseStamped goal;
getInput("input_goal", goal);
callback_group_executor_.spin_some();
if (rclcpp::Time(last_goal_received_.header.stamp) > rclcpp::Time(goal.header.stamp)) {
goal = last_goal_received_;
}
setOutput("output_goal", goal);
return child_node_->executeTick();
}
void
GoalUpdater::callback_updated_goal(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
{
last_goal_received_ = *msg;
}
} // namespace nav2_behavior_tree
#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<nav2_behavior_tree::GoalUpdater>("GoalUpdater");
}
Single Trigger
每次 BT 运行仅触发一次以下节点/子树。
PathLongerOnApproach
如果新的全局路径明显大于接近目标的旧全局路径,则触发子节点
#include <string>
#include <memory>
#include <vector>
#include "nav2_util/geometry_utils.hpp"
#include "nav2_behavior_tree/plugins/decorator/path_longer_on_approach.hpp"
namespace nav2_behavior_tree
{
PathLongerOnApproach::PathLongerOnApproach(
const std::string & name,
const BT::NodeConfiguration & conf)
: BT::DecoratorNode(name, conf)
{
node_ = config().blackboard->get<rclcpp::Node::SharedPtr>("node");
}
bool PathLongerOnApproach::isPathUpdated(
nav_msgs::msg::Path & new_path,
nav_msgs::msg::Path & old_path)
{
return new_path != old_path && old_path.poses.size() != 0 &&
new_path.poses.size() != 0 &&
old_path.poses.back() == new_path.poses.back();
}
bool PathLongerOnApproach::isRobotInGoalProximity(
nav_msgs::msg::Path & old_path,
double & prox_leng)
{
return nav2_util::geometry_utils::calculate_path_length(old_path, 0) < prox_leng;
}
bool PathLongerOnApproach::isNewPathLonger(
nav_msgs::msg::Path & new_path,
nav_msgs::msg::Path & old_path,
double & length_factor)
{
return nav2_util::geometry_utils::calculate_path_length(new_path, 0) >
length_factor * nav2_util::geometry_utils::calculate_path_length(
old_path, 0);
}
inline BT::NodeStatus PathLongerOnApproach::tick()
{
getInput("path", new_path_);
getInput("prox_len", prox_len_);
getInput("length_factor", length_factor_);
if (status() == BT::NodeStatus::IDLE) {
// Reset the starting point since we're starting a new iteration of
// PathLongerOnApproach (moving from IDLE to RUNNING)
first_time_ = true;
}
setStatus(BT::NodeStatus::RUNNING);
// Check if the path is updated and valid, compare the old and the new path length,
// given the goal proximity and check if the new path is longer
if (isPathUpdated(new_path_, old_path_) && isRobotInGoalProximity(old_path_, prox_len_) &&
isNewPathLonger(new_path_, old_path_, length_factor_) && !first_time_)
{
const BT::NodeStatus child_state = child_node_->executeTick();
switch (child_state) {
case BT::NodeStatus::RUNNING:
return BT::NodeStatus::RUNNING;
case BT::NodeStatus::SUCCESS:
old_path_ = new_path_;
return BT::NodeStatus::SUCCESS;
case BT::NodeStatus::FAILURE:
old_path_ = new_path_;
return BT::NodeStatus::FAILURE;
default:
old_path_ = new_path_;
return BT::NodeStatus::FAILURE;
}
}
old_path_ = new_path_;
first_time_ = false;
return BT::NodeStatus::SUCCESS;
}
} // namespace nav2_behavior_tree
#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<nav2_behavior_tree::PathLongerOnApproach>("PathLongerOnApproach");
}
Compute Path to Pose Action
#include <memory>
#include <string>
#include "nav2_behavior_tree/plugins/action/compute_path_to_pose_action.hpp"
namespace nav2_behavior_tree
{
ComputePathToPoseAction::ComputePathToPoseAction(
const std::string & xml_tag_name,
const std::string & action_name,
const BT::NodeConfiguration & conf)
: BtActionNode<nav2_msgs::action::ComputePathToPose>(xml_tag_name, action_name, conf)
{
}
void ComputePathToPoseAction::on_tick()
{
getInput("goal", goal_.goal);
getInput("planner_id", goal_.planner_id);
if (getInput("start", goal_.start)) {
goal_.use_start = true;
}
}
BT::NodeStatus ComputePathToPoseAction::on_success()
{
setOutput("path", result_.result->path);
return BT::NodeStatus::SUCCESS;
}
} // namespace nav2_behavior_tree
#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
BT::NodeBuilder builder =
[](const std::string & name, const BT::NodeConfiguration & config)
{
return std::make_unique<nav2_behavior_tree::ComputePathToPoseAction>(
name, "compute_path_to_pose", config);
};
factory.registerBuilder<nav2_behavior_tree::ComputePathToPoseAction>(
"ComputePathToPose", builder);
}```