ROS2中使用Behavior Tree (1)

接下来会记录如何在ROS2中使用BT(Behavior Tree)

1. 介绍 BT 框架

BT类似于 FSM 有着广泛的应用,BT框架可以集成到任何其他软件中,首先记录一下没有ROS的纯BT框架,

在理解核心原则之后,将BT程序的相同结构合并到ROS2环境中。

行为树(Behavior Tree,BT)是一种用于设计和管理机器人或自主智能的行为的框架。它可以将复杂的任务和行为分解成逻辑上连续的节点,使得整个系统的控制逻辑更加清晰、灵活和可扩展。

在BT中,整个行为树的结构类似一个树状图,从根节点开始,逐级向下展开,直到叶子节点为止。每个节点都代表着一个特定的行为或决策,并且可以根据节点之间的组合规则进行执行。

常见的BT节点类型包括:

序列节点(Sequence): 顺序执行它们包含的子节点,一个接一个地执行,只有当所有子节点都成功执行时,整个序列才被认为成功。

选择节点(Fallback): 依次执行它们的子节点,直到找到一个成功执行的节点,然后停止。它类似于“或”的关系。

行为节点(Action): 这些节点代表具体的行为,例如“捡起物体”、“放置物体”等,它们是执行最终任务的节点。

条件节点(Condition): 用于判断特定条件是否满足,例如“检测物体是否在视野范围内”、“是否已经抓住物体”等。

1.1 用C++ 实现简单 BT

在BT中,节点从子节点从左往右执行,使用符号 ⟶ 表示逻辑与操作(Sequence),而符号 “?” 表示逻辑或操作(Fallback),也称回退操作。通过这些逻辑运算符,我们可以推导机器人的行为。比如,机器人首先要找到、抓取并放置球。抓取操作需要执行特定的动作。

在这里插入图片描述

如图所示的行为树的逻辑是这样的:

1.寻找球的任务 (FindBall)。
2.依次执行两个回退节点,如果球没有靠近,则执行接近球的任务 (ApproachBall);如果球没有被抓住,则执行抓住球的任务 (GraspBall)。
3.执行放置球的任务 (PlaceBall)。

代码如下:

  1. 首先定义四个行为节点类:ApproachBall、FindBall、PlaceBall和GripperInterface:

    class ApproachBall: public BT::SyncActionNode
    {
    public:
    ApproachBall(const std::string& name):BT::SyncActionNode(name,{}){

       }
       NodeStatus tick() override
       {
           std::count <<"ApproachBall: " << this->name() << std::endl;
           return BT::NodeStatus::SUCCESS;
    
       }
       };
    
      //----------------Class FindBall------------------------------------------
      class FindBall : public BT::SyncActionNode
      {
        public:
          FindBall(const std::string& name) : BT::SyncActionNode(name, {})
          {
          }
          NodeStatus tick() override
          {
              std::cout << "FindBall: " << this->name() << std::endl;
              return BT::NodeStatus::SUCCESS;
          }
      };
    
      //----------------Class PlaceBall------------------------------------------
      class PlaceBall : public BT::SyncActionNode
      {
        public:
          PlaceBall(const std::string& name) : BT::SyncActionNode(name, {})
          {
          }
          NodeStatus tick() override
          {
              std::cout << "PlaceBall: " << this->name() << std::endl;
              return BT::NodeStatus::SUCCESS;
          }
      };
    

    //----------------Class GripperInterface------------------------------------------
    class GripperInterface
    {
    private:
    bool _opened;

     public:
       GripperInterface() : _opened(true)
       {
       }
    
       NodeStatus open()
       {
           _opened = true;
           std::cout << "GripperInterface::open" << std::endl;
           return BT::NodeStatus::SUCCESS;
       }
    
       NodeStatus close()
       {
           std::cout << "GripperInterface::close" << std::endl;
           _opened = false;
           return BT::NodeStatus::SUCCESS;
       }
    

    };

    BT::NodeStatus BallClose()
    {
    std::cout << “[ Close to ball: NO ]” << std::endl;
    return BT::NodeStatus::FAILURE;
    }

    BT::NodeStatus BallGrasped()
    {
    std::cout << “[ Grasped: NO ]” << std::endl;
    return BT::NodeStatus::FAILURE;
    }

其中BallClose()和BallGrasped()为简单的简单的条件节点函数

  1. 定义了一个 XML 文本,用于构建整个行为树的结构

  2. static const char* xml_text = R"(

        <BehaviorTree ID="MainTree">
            <Sequence name="root_sequence">
                <FindBall   name="ball_ok"/>
                    <Sequence>
                        <Fallback>
                            <BallClose   name="no_ball"/>
                            <ApproachBall    name="approach_ball"/>
                        </Fallback>
                        <Fallback>
                            <BallGrasped   name="no_grasp"/>
                            <GraspBall  name="grasp_ball"/>
                        </Fallback>
                    </Sequence>
                <PlaceBall   name="ball_placed"/>
            </Sequence>
        </BehaviorTree>
    </root>
    

    )";
    根节点是root_sequence,是一个序列节点,它包含了一系列子节点。

首先是FindBall节点,表示机器人寻找球的任务。然后是另一个序列节点,其中包含两个回退节点:BallClose和ApproachBall,用于决定机器人是否接近球。

如果BallClose返回FAILURE,表示机器人没有靠近球,则会执行ApproachBall节点,表示机器人接近球的任务。同理,对于抓取动作,如果BallGrasped返回FAILURE,表示机器人没有成功抓住球,则会执行GraspBall节点,表示机器人抓住球的任务。

  1. 最后,在主函数创建和执行行为树:

    首先,我们创建了一个BehaviorTreeFactory对象 factory,用于注册自定义的行为节点。

    接着,我们通过 factory.registerNodeType 注册了自定义的行为节点类 ApproachBall、FindBall和PlaceBall。

    然后,我们通过 factory.registerSimpleCondition 注册了两个简单的条件节点函数 BallClose 和 BallGrasped。

    通过 factory.createTreeFromText(xml_text) 从 XML 文本中创建了整个行为树结构

    最后,通过调用 tree.tickRoot() 来执行行为树的根节点(root_sequence)。从根节点开始,树的执行逻辑会根据节点之间的连接和条件判断,依次调用每个节点的 tick() 函数来决定下一步执行的动作。整个行为树的执行过程会根据具体的逻辑规则依次展开,直到任务完成或者出现其他条件导致中止。

    int main()
    {
    BehaviorTreeFactory factory;

    factory.registerNodeType<ApproachBall>("ApproachBall");
    factory.registerNodeType<FindBall>("FindBall");
    factory.registerNodeType<PlaceBall>("PlaceBall");
    
    factory.registerSimpleCondition("BallClose", std::bind(BallClose));
    factory.registerSimpleCondition("BallGrasped", std::bind(BallGrasped));
    
    GripperInterface gripper;
    factory.registerSimpleAction("GraspBall", std::bind(&GripperInterface::close, &gripper));
    
    auto tree = factory.createTreeFromText(xml_text);
    
    tree.tickRoot();
    
    return 0;
    

    }

记得添加头文件:

#include “behaviortree_cpp_v3/bt_factory.h”

using namespace BT;

最后执行的结果就是这样:

FindBall: ball_ok

[ Close to ball: NO ]

ApproachBall: approach_ball

[ Grasped: NO ]

GripperInterface::close

PlaceBall: ball_placed

1.2 BT节点类型

BT是一个有向的根树,如何组织节点间的联系,有四种类型:。

tick操作是行为树执行的核心操作,它驱动着整个行为树的逻辑和任务的执行,下面是他的执行规则:

从根节点开始,根据节点的类型和连接关系选择要执行的子节点。

执行选择的子节点的tick操作,并等待其返回状态。

根据子节点的返回状态,决定下一步要执行的节点。

如果子节点返回的是"Running"(正在执行)状态,表示该节点需要继续执行,此时tick操作会暂停并等待下一次tick继续执行。

如果子节点返回的是"Success"(成功)状态,表示该节点执行成功,tick操作会继续执行下一个节点。

如果子节点返回的是"Failure"(失败)状态,表示该节点执行失败,tick操作会继续执行下一个节点或者回溯到更高级的节点。
重复以上步骤,直到整个行为树执行完成或者遇到特定的终止条件。

在这里插入图片描述

1.2.1 Sequence节点

Sequence节点功能类似于“与”,顺序执行它们包含的子节点,一个接一个地执行,只有当所有子节点都成功执行时,整个序列才被认为成功

<root main_tree_to_execute = "MainTree" >

    <BehaviorTree ID="MainTree">
       <Sequence name="root_sequence">
           <RobotTask1   name="task1"/>
           <RobotTask2   name="task2"/>
           <RobotTask3   name="task3"/>
       </Sequence>
    </BehaviorTree>

</root>

假设执行task2失败,则显示task1执行的内容,并且返回FAILURE

1.2.2 Fallback节点

依次执行它们的子节点,执行失败就Fallback,直到找到一个成功执行的节点,然后停止。它类似于“或”的关系。

<root main_tree_to_execute = "MainTree" >

    <BehaviorTree ID="MainTree">
       <Fallback name="root_sequence">
           <RobotTask1   name="task1"/>
           <RobotTask2   name="task2"/>
           <RobotTask3   name="task3"/>
       </Fallback>
    </BehaviorTree>

</root>

假设执行task1失败,task2成功,则显示task1和task2执行的内容,并且返回SUCCESS

1.2.3 Action节点

这些节点代表具体的行为,结合上文提到tick的规则。动作节点的执行方式取决于它的回调函数,同步动作会,如打印消息等,立即执行完毕并返回结果,

而异步动作,比如耗时操作,可能会在一段时间内持续执行,并在执行完成后返回结果。

<root main_tree_to_execute = "MainTree" >

    <BehaviorTree ID="MainTree">
       <ReactiveFallback name="root">
           <EatSandwich name="eat_sandwich"/>
           <EatApple name="eat_apple"/>
           <Sequence>
               <OpenBanana name="open_banana"/>
               <EatBanana       goal="1;2;3"/>
               <SaySomething   message="banan is gone!" />
           </Sequence>
       </ReactiveFallback>
    </BehaviorTree>

</root>

1.3 BT 与FSM

行为树(Behavior Tree)相比有限状态机(FSM)更灵活、可扩展,适用于复杂行为逻辑的建模和实现,它通过树状结构组织任务、处理并行和优先级,并让开发者专注于高层次的任务设计。

有限状态机(FSM)在现实世界中遇到许多问题,特别是当状态之间的转换和条件变得非常复杂时。FSM可能会有大量的状态转换,当状态数量增多时,设计变得非常困难,状态之间紧密连接,重用性低,且难以理解和预测系统的行为。

而行为树(Behavior Tree)解决了这些问题,提高了模块化和重用性。行为树是一种层次结构的树状模型,每个子树都可以被视为一个可重用的行为,开发者可以利用行为树提供的语言来应用常见的设计模式,使得文本和图形表示更易于理解。

行为树使用动作(Actions)而不是状态(States),更符合定义行为和软件接口的概念模型,使得设计更直观。行为树的层次结构让人们更容易阅读和理解,避免了状态机中状态数量增多所带来的复杂性和困扰。

通过行为树,设计师可以更轻松地处理复杂的行为逻辑,提高开发效率,让系统的行为更加可控和可预测。

这里给出实现机器人避障的FSM 和BT 的比较:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mubibaiwhale

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值