马库斯·布赫霍尔茨
一、说明
由于 BT 是提供创建 BT 框架(C++ 年)的C++库,因此本文重点介绍 BT 的概述。 您对此领域的兴趣可以通过在线课程和实践课程来扩展,我强烈推荐。如果您对学习ROS,机器人,AI,ML,软件等更感兴趣,请查看The ConstructSim网站,并从您可以学到的内容中获得灵感。他们还有一个完美的机器人开发人员大师班,让您进入梦想中的公司。
此外,对于在线课程,请下载免费书籍并从此YouTube频道中获得灵感。
在这里,您还可以找到BT的精彩介绍链接到在线环境,您可以在其中运行移动机器人。
二、行为树、软件架构
BT 框架是一个C++库,使您能够在机器人或游戏应用程序中构建 BT。BT 提供了抽象方法(内置于 C++ 年)来保护应用程序中的逻辑关系。例如,如果您开发游戏,则行为树可以与游戏的性能相关,或者定义游戏角色在某些游戏情况下的行为方式等。在这方面,我们声明BT框架提供了在C++类之间启用逻辑/抽象关系的工具,这些类的方法是根据程序的逻辑树(BT)结构调用的。
BT 可以与状态机 (SM) 相关联,但是 BT 优于 SM 存在某些差异。SM 的主要问题是状态之间的转换随着状态数量的增加而增加。
SM 中的状态是紧密连接的,当需要重用时,通常会出现问题。诚然,设计师(软件工程师)可以轻松处理SM,但一个人无疑会发现很难理解和预测系统的整体行为。这些问题中的大多数都由行为树解决,它增强了模块化设计并且可以重用。行为树本质上是分层的。
下图为您提供了BT的一般概述。 如您所见,BT框架可以独立运行,无需ROS(正如我在设计游戏时提到的)。稍后我将给你一个例子,说明如何构建一个简单的BT并运行你的第一个程序。请注意,官方网站上的教程非常出色,应相应地进行研究以解决所有剩余问题。
我们可以想象您的机器人执行任务。为了保证机器人(例如机器人清洁器)的任务性能,“主要”任务分为几个子任务,如路径规划、感知、空间定向和电源管理等(在 ROS 中,所有这些子任务都可以分布在节点上)。所有这些子任务都可以被视为低级任务,这些任务不是由BT构建的。
一个任务或动作可以依赖于一个或多个先前的动作,同时,不同的条件必须为真/假才能影响机器人/应用程序动作。
关于机器人技术,BT是一种从低级任务控制(路径规划器,电源管理等)转移到更高层次行为的抽象。
当我们设计机器人系统时,我们仍然必须考虑基本组件(低级动作/任务)如何工作并寻找优化(例如,通过添加启发式来提高路径算法的性能)。
然而,为了构建整个机器人应用程序的架构,我们需要转向更高层次的编程抽象,这意味着将路径规划器或电源管理等任务作为一个简单的组件进行评估。
我们使用高度抽象的概念,因此详细说明这种抽象的位置似乎是合理的。下图概述了抽象堆栈。
所描绘的堆栈可以考虑如下。工程师/设计师负责机器人应用程序的需求,并构建应用程序的逻辑流程(创建行为树)。
BT(逻辑流)的结构模仿机器人的行为(游戏中的人物)。我们可以说,设计人员在逻辑上连接了机器人基元以反映机器人应用的规范。
BT 被建模为 XML 格式化文件。C++框架(BehaviorTree.CPP)允许在您的应用程序中构建BT。机器人任务的定义(C++中的程序功能)完成以下堆栈。
在讨论BT的概念时,我使用了逻辑流公式。在软件设计的上下文中,我们可以将所讨论的概念评估为某些软件模块(节点)之间的逻辑连接。逻辑连接可以描述为使用某个逻辑运算符,如 AND、OR 或 NOT 包装到 BT 框架中。
BT框架的美妙之处在于,设计人员可以完全实现所有机制和其他组件,以构建复杂的逻辑结构。此外,逻辑流可以设计为多线程应用程序,而无需不必要的工作。支持线程处理的所有C++机制都合并到框架中。
三、逻辑基元
由于本文的想法是对BT的唯一介绍,我将重点介绍基本的逻辑组件,我将在后面的实际C++示例中使用这些组件。ROS2(银河)的例子,你可以在这里或课程中找到。
在简单的原理中(仅出于本文的目的),我们可以区分两个逻辑节点(BT 机制)。回退实现逻辑OR和序列节点,实现AND逻辑。
两者都可以描述如下,
回退节点(OR 运算符)。(作者)
BT 的体系结构在应用程序读取的 XML 文件中指定。BehaviorTree.CPP框架根据XML规范(我们说调用回调)管理逻辑流(勾选BT节点)。基于状态的节点(此处为任务)将返回信号发送到“根”(应用程序的主节点)。信号可以是成功、失败或正在运行。
下面的示例结合了上述机制,并显示了上述信号如何在BT上分布。
我们可以这样描述上面的BT,
推导出这种BT哲学,我们可以通过考虑以下示例来描述以下BT的工作原理。
1. 呈现的BT由4个任务组成,落回和序列节点。
2. 将刻度发送到该回退的根。我们沿着左侧走。回退向任务 1 发送一个时钟周期,这是一个失败(当然这只是模拟)。通常,成功或失败的决定是由正在运行的程序做出的。在我们的例子中,回调返回失败。
请注意,现在我们运行回退节点(OR),它至少需要一个成功才能返回成功,因此可以检查所有节点。对于序列节点 (AND),第一个 FAILURE 终止“节点调查”。
3. 与任务 1 类似,任务 2 回调返回失败。由于我们仍然运行回退机制(OR),因此我们继续。
4. 现在,即时报价被发送到序列节点 (AND)。序列将时钟周期发送到任务 3 并接收成功,但序列是逻辑 AND 操作,因此我们继续检查是否全部成功。
任务 4 是成功,因此根也收到成功。
描述上述 BT 描述的逻辑的 XML 可以表述为:
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Fallback name="root">
<Task1 name="task_1"/>
<Task2 name="task_2"/>
<Sequence>
<Task3 name="task_3"/>
<Task4 name="task_4"/>
</Sequence>
</Fallback>
</BehaviorTree>
</root>
)";
下面您将找到可以在 ROSject 中运行的代码。(ROS2银河)
#include "behaviortree_cpp_v3/bt_factory.h"
using namespace BT;
class Task1 : public BT::SyncActionNode
{
public:
Task1(const std::string& name) : BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
NodeStatus tick() override
{
std::cout << "Task1: " << this->name() << std::endl;
return BT::NodeStatus::FAILURE;
}
};
class Task2 : public BT::SyncActionNode
{
public:
Task2(const std::string& name) : BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
NodeStatus tick() override
{
std::cout << "Task2: " << this->name() << std::endl;
return BT::NodeStatus::FAILURE;
}
};
class Task3 : public BT::SyncActionNode
{
public:
Task3(const std::string& name) : BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
NodeStatus tick() override
{
std::cout << "Task3: " << this->name() << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
class Task4 : public BT::SyncActionNode
{
public:
Task4(const std::string& name) : BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
NodeStatus tick() override
{
std::cout << "Task4: " << this->name() << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
static const char* xml_text_medium = R"(
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Fallback name="root">
<Task1 name="task_1"/>
<Task2 name="task_2"/>
<Sequence>
<Task3 name="task_3"/>
<Task4 name="task_4"/>
</Sequence>
</Fallback>
</BehaviorTree>
</root>
)";
int main()
{
BehaviorTreeFactory factory;
factory.registerNodeType<Task1>("Task1");
factory.registerNodeType<Task2>("Task2");
factory.registerNodeType<Task3>("Task3");
factory.registerNodeType<Task4>("Task4");
std::cout << "\n------------ BUILDING A NEW TREE ------------" << std::endl;
auto tree = factory.createTreeFromText(xml_text_medium);
tree.tickRoot();
std::cout << std::endl;
// }
return 0;
}
In the ROSject (on a Linux terminal) perform the following actions,
cd /ros2_ws/src/bt_course_files/BehaviorTree.CPP/course_bt
touch bt_medium.cpp
// go to VSC to the same folder where you created bt_medium.cpp
// paste above code
// in the same folder modify the CMakeList.txt by adding:
// CompileExample("bt_medium")
cd ros2_ws/src/bt_course_files/BehaviorTree.CPP/build
cmake ..
make
// after building is finish
cd course_bt
./bt_medium
预期产出
感谢您的阅读。
四、结论
关于BT的概念,需要更多的文档介绍;BT 可以与状态机 (SM) 相关联,但是 BT 优于 SM 存在某些差异。SM 的主要问题是状态之间的转换随着状态数量的增加而增加,而BT没有类似难点。