BehaviorTree + Groot 在ros中的运用

 一、BehaviorTree + Groot 在ros中的运用的参考资料

1、BehaviorTree.cpp开源库

地址:github.com/BehiaviorTree/BehaviorTree.CPP

提供BehaviorTree框架,提供examples学习,但是相对比较零碎,需要对BehaviorTree有一个初步了解

2、BehaviorTree主要概念、API与教程

地址:BehaviorTree.CPP

对开源库有一些讲解,相对清晰

3、古月居小明工坊的 《ROS实验 | 行为树实现机器人智能》
网页链接:ROS实验 | 行为树实现机器人智能 - 古月居

二、BehaviorTree + Groot 在ros中的运用基础

1、运行过程

  1. 一个名为“tick”的信号被发送到树的根部,并在树中传播,直到到达叶节点。

  2. 接收tick信号的树节点执行其callback。此回调必须返回。

    SUCCESS,FAILURE or RUNNING

  3. 如果一个TreeNode有一个或多个子节点,它将根据其状态、外部参数或前一个同级节点的结果来顺序tick他们。

  4. LeafNode,即那些没有任何子节点的TreeNode,是实际的命令,即行为树与系统其余部分交互的地方。Actions节点是最常见的叶节点类型。

2、tick的运行原理

Sequence 处在运行中, tick 传到 DetectObject, 返回SUCCESS,tick传到GraspObject,返回SUCCESS,子节点全部完成,父节点Sequence状态变成SUCCESS。

其余详细资料请参考提供的参考资料

三、调试工具

1、groot

Groot是与BehaviorTree.CPP搭配使用的工具,分为Editor、Monitor、Log Replay 3种模式,具有行为树编辑、状态监控、历史log回放等功能。

指南:Groot - Interacting with Behavior Trees — Navigation 2 1.0.0 documentation

2、StdCoutLogger

作用:在终端打印行为树中的节点执行状态变化。

代码仅需在加载tree后添加StdCoutLogger类的1个实例(且只能有1个实例),运行效果如下:

StdCoutLogger logger_cout(tree);

四、实践

        理论的东西看起来容易,但离实践还有一定距离,接下来用一个实践小项目来对BehaviorTree + Groot + Ros进行演示,项目题目来源于古月居,实现一个巡逻的小乌龟的游戏

Groot下的行为树如下图

运行过程

1、attack子节点haveEnemy判断周围是否有敌人。

2、若没有敌人,Ation节点moveToEnemy将不被触发,守卫将执行patrol节点,对区域进行搜索。

3、若找到敌人,moveToEnemy节点将被触发,守卫向敌人前进。

1、小乌龟生成与tf发布

此部分不再详细描述,代码如下

void poseCallback(const turtlesim::PoseConstPtr& msg)
{
    // tf广播器
    static tf::TransformBroadcaster br;
    // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
    tf::Transform transform;
    transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
    tf::Quaternion q;
    q.setRPY(0, 0, msg->theta);
    transform.setRotation(q);
    // 发布坐标变换
    br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));
}

int main(int argc, char** argv)
{
    // 初始化节点
    ros::init(argc, argv, "my_tf_broadcaster");
    if (argc != 2)
    {
        ROS_ERROR("need turtle name as argument"); 
        return -1;
    };
    turtle_name = argv[1];

    // 订阅乌龟的pose信息
    ros::NodeHandle node;
    ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
    ros::spin();
    return 0;
};
void poseCallback(const turtlesim::PoseConstPtr& msg)
{
    // tf广播器
    static tf::TransformBroadcaster br;
    // 根据乌龟当前的位姿,设置相对于世界坐标系的坐标变换
    tf::Transform transform;
    transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );
    tf::Quaternion q;
    q.setRPY(0, 0, msg->theta);
    transform.setRotation(q);

    // 发布坐标变换
    br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));

    // 发布固定点坐标
    tf::Transform transform_a;
    transform_a.setOrigin(tf::Vector3(1.0, 6.0, 0.0));
    tf::Quaternion q_a;
    q_a.setRPY(0.0, 0.0, 0.0);
    transform_a.setRotation(q_a);
    br.sendTransform(tf::StampedTransform(transform_a, ros::Time::now(), "world", "point_a"));
    
    tf::Transform transform_b;
    transform_b.setOrigin(tf::Vector3(10.0, 6.0, 0.0));
    tf::Quaternion q_b;
    q_b.setRPY(0.0, 0.0, 0.0);
    transform_b.setRotation(q_b);
    br.sendTransform(tf::StampedTransform(transform_b, ros::Time::now(), "world", "point_b"));
}

int main(int argc, char** argv)
{
    // 初始化节点
    ros::init(argc, argv, "my_tf_broadcaster");
    if (argc != 2)
    {
        ROS_ERROR("need turtle name as argument"); 
        return -1;
    };
    turtle_name = argv[1];

    // 订阅乌龟的pose信息
    ros::NodeHandle node;
    ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
    
    // 通过服务调用,产生第二只乌龟turtle2
    ros::service::waitForService("spawn");
    ros::ServiceClient add_turtle =
    node.serviceClient<turtlesim::Spawn>("spawn");
    turtlesim::Spawn srv;
    add_turtle.call(srv);
    
    ros::spin();

    return 0;
};

2、Action节点构建

        节点构建以静态库的方式进行构建,构建的类需要对behaviorTree的类进行继承,巡逻节点类构建如下

class BTActionPatrol : public BT::AsyncActionNode
{
protected:
    ros::NodeHandle nh_;
    // 定义turtle2的速度控制发布器
    ros::Publisher turtle_vel =
    nh_.advertise<geometry_msgs::Twist>("turtle2/cmd_vel", 10);


public:
    BTActionPatrol(const std::string& name, const BT::NodeConfiguration& config)
      :BT:: AsyncActionNode(name, config)
    {
    }

    ~BTActionPatrol(void)
    {}

    BT::NodeStatus tick() override;

    static BT::PortsList providedPorts()
    {
        return{ BT::InputPort<std::string>("message") };
    }

    virtual void halt() override;
};

publisher与providedPorts定义本不应该放头文件里面。

在构建节点时需要注意重写 tick 与 halt函数,需要提供static BT::PortsList providedPorts接口函数

重写的tick函数如下,主要操作在这里实现。

BT::NodeStatus BTActionPatrol::tick()
{
    tf::TransformListener listener;
    tf::StampedTransform transform_a, transform_b, transform;
    
    // 查找坐标变换
    listener.waitForTransform("/turtle2", "/point_a", ros::Time(0), ros::Duration(3.0));
    listener.lookupTransform("/turtle2", "/point_a", ros::Time(0), transform_a);
    listener.waitForTransform("/turtle2", "/point_b", ros::Time(0), ros::Duration(3.0));
    listener.lookupTransform("/turtle2", "/point_b", ros::Time(0), transform_b);
    
    double distance_a, distance_b;
    distance_a = sqrt(pow(transform_a.getOrigin().x(), 2) + pow(transform_a.getOrigin().y(), 2));
    distance_b = sqrt(pow(transform_b.getOrigin().x(), 2) + pow(transform_b.getOrigin().y(), 2));
    
    if (nh_.hasParam("/goal_point")){
        if(distance_a < 0.5) {
            nh_.setParam("/goal_point", "b");
            ROS_INFO("Change direction to b");
        }
        else if(distance_b < 0.5){
            nh_.setParam("/goal_point", "a");
            ROS_INFO("Change direction to b");
        }
    }
    else{
        nh_.setParam("/goal_point", "a");
    }
    
    std::string direction;
    nh_.getParam("/goal_point", direction);
    
    if(direction == "a"){
        ROS_INFO("Nav to a");
        transform = transform_a;
    }
    else if(direction == "b"){
        ROS_INFO("Nav to b");
        transform = transform_b;
    }
        
    // 根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
    // 并发布速度控制指令,使turtle2向turtle1移动
    geometry_msgs::Twist vel_msg;
    vel_msg.angular.z = 1.0 * atan2(transform.getOrigin().y(),
                                    transform.getOrigin().x());
//      vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +
//                                          pow(transform.getOrigin().y(), 2));
    vel_msg.linear.x = 0.8;
                                                                        
    turtle_vel.publish(vel_msg);
    ROS_INFO("Action nav_b is successful!");
    return BT::NodeStatus::SUCCESS;
    
}

moveToEnemy节点与上一节点一样,继承Action类,判断是否有enemy的节点为条件节点,继承Condition类详细代码将不在此处详写

3、构建树

使用xml文件构建行为树,代码如下

<root main_tree_to_execute = "DoorClosed">
    <BehaviorTree ID="DoorClosed">
        <Fallback name="playGame">
            <Sequence name="attack">
                <haveEnemy/>
                <moveToEnemy/>
            </Sequence>
            <patrol/>  
        </Fallback>
    </BehaviorTree>
</root>

载入节点

BehaviorTreeFactory factory;

factory.registerNodeType<BTActionNav>("moveToEnemy");
factory.registerNodeType<BTActionPatrol>("patrol");
factory.registerNodeType<BTActionHaveEnemy>("haveEnemy");

auto tree = factory.createTreeFromText(xml_text);

添加Groot调试

PublisherZMQ publisher_zmq(tree);

4、修改CmakeLists

构建静态库

add_library(sample_nodes STATIC
  src/treeNode/action_nav_enemy.cpp
  src/treeNode/action_patrol.cpp
  src/treeNode/condition_have_enemy.cpp
    )

target_link_libraries(sample_nodes 
${catkin_LIBRARIES}
BT::behaviortree_cpp_v3)

 添加可执行文件

add_executable(guard_robot_tree src/tree/guard_robot_tree.cpp)
target_link_libraries(
  guard_robot_tree
  ${catkin_LIBRARIES}
  BT::behaviortree_cpp_v3
  sample_nodes
  # /opt/ros/melodic/lib/librosconsole.so
  # /opt/ros/melodic/lib/libroscpp_serialization.so
)

5、运行效果

五、总结

对BehaviorTree进行了学习,还有很多功能有待开发,如节点间的通讯,由可以使用ros话题通讯代替,暂时不需要,后续有新内容还会进行更新,对于文章中的错误希望大家一起讨论。

六、参考

BT9:各种调试工具介绍_linxigjs的博客-CSDN博客

ROS实验 | 行为树实现机器人智能 - 古月居

Behavior-tree 在ROS中的应用(入门)_bug有点多的博客-CSDN博客

<think>嗯,用户想了解ROS2中行为树的用法和实现方法。首先,我需要回忆一下ROS2行为树的相关知识。记得行为树在机器人控制中常用于任务管理和决策,比如导航这样的复杂任务可能需要行为树来组织不同的动作和条件。 首先,安装行为树库是关键步骤。ROS2通常使用behaviortree_cpp_v3这个库,用户需要确认是否已经安装。根据引用[1]中的信息,安装命令是sudo apt-get install ros-$ROS_DISTRO-behaviortree-cpp-v3。这里要注意替换成用户当前的ROS2版本,比如humble或foxy。 接下来,创建功能包。用户需要使用colcon来构建,这和ROS1的catkin不同,引用[2]提到了colcon build的用法。所以需要指导用户创建功能包,可能依赖包括behaviortree_cpp_v3和rclcpp等。 然后,编写行为树节点。这里可能需要定义一个行为树的结构,比如根节点下包含序列或选择节点。每个叶节点对应具体的动作或条件,比如检测障碍物或移动。这部分需要参考引用[3]中Nav2的行为树应用,说明如何组织节点结构。 自定义节点也是常见的需求。用户可能需要创建自己的行为节点,比如检测障碍物的条件节点。这时候需要用到pluginlib来注册插件,如引用[4]提到的自定义插件的方法,需要配置CMakeLists.txt和插件描述文件。 测试行为树需要编译并运行节点,用colcon build构建后,再运行节点。同时,可视化工具Groot2可以帮助调试,用户需要知道如何加载生成的XML文件并监控状态变化。 在应用场景方面,导航系统Nav2是主要例子,引用[3]提到Nav2使用行为树协调任务,所以需要解释如何集成到Nav2中,比如替换默认的树或调整策略。 可能用户的问题会涉及具体实现细节,比如如何确保节点间的通信,或者如何处理异步动作。此外,性能优化和调试技巧也是常见问题,需要准备相关的建议,比如合理设计树结构,使用可视化工具等。 最后,确保所有步骤都正确引用了提供的资料,比如安装步骤引用[1],构建工具引用[2],Nav2的应用引用[3],插件自定义引用[4]。同时,生成的示例代码要符合用户的要求,使用正确的格式,并注意LaTeX语法,但看起来用户的问题不涉及数学公式,所以可能不需要处理这点。但根据系统指令,如果有数学内容需要用$...$格式,但这里可能不涉及。 总结回答的结构应该是分步骤讲解,包括安装、创建功能包、编写节点、自定义节点、测试和应用场景,然后相关问题部分覆盖安装问题、自定义节点方法、可视化工具、与Nav2的集成以及常见错误。</think>### ROS2行为树使用教程及实现方法 #### 一、行为树库安装与配置 1. **安装`behaviortree_cpp_v3`** 在ROS2中,行为树的核心库为`behaviortree_cpp_v3`,安装命令如下: ```bash sudo apt-get install ros-$ROS_DISTRO-behaviortree-cpp-v3 ``` 需将`$ROS_DISTRO`替换为当前ROS2版本(如`humble`或`foxy`)[^1]。 2. **创建功能包** 使用以下命令创建功能包,并添加依赖项: ```bash ros2 pkg create bt_demo --dependencies rclcpp behaviortree_cpp_v3 ``` --- #### 二、行为树节点编写 1. **定义行为树结构** 行为树由根节点、控制节点(如`Sequence`、`Selector`)和叶节点(动作/条件)组成。以下为示例XML定义(`tree.xml`): ```xml <root main_tree_to_execute="MainTree"> <BehaviorTree ID="MainTree"> <Sequence name="root_sequence"> <CheckCondition name="check_battery"/> <MoveBaseAction name="move_to_goal"/> </Sequence> </BehaviorTree> </root> ``` 2. **C++节点实现** 在ROS2节点中加载行为树并执行: ```cpp #include "behaviortree_cpp_v3/bt_factory.h" int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<rclcpp::Node>("bt_demo"); BT::BehaviorTreeFactory factory; factory.registerNodeType<CheckCondition>("CheckCondition"); factory.registerNodeType<MoveBaseAction>("MoveBaseAction"); auto tree = factory.createTreeFromFile("path/to/tree.xml"); tree.tickRoot(); // 执行行为树 rclcpp::spin(node); return 0; } ``` --- #### 三、自定义行为树节点 1. **创建条件节点** 继承`BT::ConditionNode`并实现`tick()`方法: ```cpp class CheckCondition : public BT::ConditionNode { public: CheckCondition(const std::string& name) : BT::ConditionNode(name, {}) {} BT::NodeStatus tick() override { // 实现条件检查逻辑 return has_obstacle ? BT::NodeStatus::FAILURE : BT::NodeStatus::SUCCESS; } }; ``` 2. **注册插件** 在`CMakeLists.txt`中添加插件注册: ```cmake pluginlib_export_plugin_description_file(behaviortree_cpp_v3 plugins.xml) ``` 并创建`plugins.xml`描述文件[^4]。 --- #### 四、测试与可视化 1. **编译与运行** 使用`colcon build`构建功能包: ```bash cd ~/ros2_ws && colcon build --packages-select bt_demo source install/setup.bash ros2 run bt_demo bt_demo_node ```[^2] 2. **使用Groot2可视化** 下载[Groot2](https://github.com/BehaviorTree/Groot2)加载XML文件,实时监控行为树状态变化[^3]。 --- #### 五、应用场景 1. **导航任务协调** 在`Nav2`中,行为树用于协调路径规划、避障、恢复机制等任务。例如: ```xml <RecoveryNode name="NavigateRecovery"> <PipelineSequence name="NavigatePipeline"> <ComputePathToPose/> <FollowPath/> </PipelineSequence> <ReactiveSequence name="RecoveryActions"> <ClearEntireCostmap/> <BackUp/> </ReactiveSequence> </RecoveryNode> ``` ---
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值