系列文章目录

Behavoir Tree(BT树)–基本概念
Behavoir Tree(BT树)–c++实现
Behavoir Tree(BT树)–c++实现第二节


本片文章接着上一篇接着讲述BT树的C++实现以及有关的注意事项。

目录

  • 系列文章目录
  • 使用Subtree组合BT树
    • CrossDoor behavior
    • Loggers
  • 在树和子树中重映射接口
    • 例子
  • 如何使用多个 XML 文件来存储子树
    • 使用"include"加载文件
    • 手动加载多文件
  • 在初始化和/或构造期间传递额外的参数
    • 方法一 注册自定义builder
    • 方法2:使用init方法
  • 使用协程(Coroutines)的异步操作
    • C++例子

使用Subtree组合BT树

我们可以将更小且可重用的BT树组合成大规模BT树。换句话说,我们想要创建分层行为树。
这可以很容易地在 XML 中定义多个树,包括一个到另一个。

CrossDoor behavior

这个例子使用了装饰器和选择器。

<root main_tree_to_execute = "MainTree"><BehaviorTree ID="DoorClosed"><Sequence name="door_closed_sequence"><Inverter><IsDoorOpen/></Inverter><RetryUntilSuccessful num_attempts="4"><OpenDoor/></RetryUntilSuccessful><PassThroughDoor/></Sequence></BehaviorTree><BehaviorTree ID="MainTree"><Fallback name="root_Fallback"><Sequence name="door_open_sequence"><IsDoorOpen/><PassThroughDoor/></Sequence><SubTree ID="DoorClosed"/><PassThroughWindow/></Fallback></BehaviorTree></root>

可以注意到,我们将树的一个非常复杂的分支(在门关闭时执行的分支)封装到一个名为 DoorClosed 的单独树中。
这个BT树实现了:

  • 如果门是开的,”PassThroughDoor“
  • 如果门是关的,尝试4次"OpenDoor".然后"PassThroughDoor"
  • 如果不能打开门,“PassThroughWindow”

Loggers

在 C++ 方面,我们不需要做任何事情来构建可重用的子树。
因此,我们借此机会介绍 BehaviorTree.CPP 的另一个简洁功能:记录器Loggers。
Logger是一种显示、记录和/或发布树中任何状态变化的机制。

int main()
{using namespace BT;BehaviorTreeFactory factory;// register all the actions into the factory// We don't show how these actions are implemented, since most of the // times they just print a message on screen and return SUCCESS.// See the code on Github for more details.factory.registerSimpleCondition("IsDoorOpen", std::bind(IsDoorOpen));factory.registerSimpleAction("PassThroughDoor", std::bind(PassThroughDoor));factory.registerSimpleAction("PassThroughWindow", std::bind(PassThroughWindow));factory.registerSimpleAction("OpenDoor", std::bind(OpenDoor));factory.registerSimpleAction("CloseDoor", std::bind(CloseDoor));factory.registerSimpleCondition("IsDoorLocked", std::bind(IsDoorLocked));factory.registerSimpleAction("UnlockDoor", std::bind(UnlockDoor));// Load from text or file...auto tree = factory.createTreeFromText(xml_text);// This logger prints state changes on consoleStdCoutLogger logger_cout(tree);// This logger saves state changes on fileFileLogger logger_file(tree, "bt_trace.fbl");// This logger stores the execution time of each nodeMinitraceLogger logger_minitrace(tree, "bt_trace.json");#ifdef ZMQ_FOUND// This logger publish status changes using ZeroMQ. Used by GrootPublisherZMQ publisher_zmq(tree);
#endifprintTreeRecursively(tree.rootNode());//while (1){NodeStatus status = NodeStatus::RUNNING;// Keep on ticking until you get either a SUCCESS or FAILURE statewhile( status == NodeStatus::RUNNING){status = tree.tickRoot();CrossDoor::SleepMS(1);   // optional sleep to avoid "busy loops"}CrossDoor::SleepMS(2000);}return 0;
}

在树和子树中重映射接口

在 CrossDoor 示例中,我们看到从父节点(示例中为 MainTree)的角度来看,子树看起来像单个叶节点。
此外,为避免在非常大的树中出现名称冲突,任何树和子树都使用不同的 Blackboard 实例。
出于这个原因,我们需要将树的端口显式连接到其子树的端口。不需要修改 C++ 实现,因为重映射完全在 XML 定义中完成。

例子

<root main_tree_to_execute = "MainTree"><BehaviorTree ID="MainTree"><Sequence name="main_sequence"><SetBlackboard output_key="move_goal" value="1;2;3" /><SubTree ID="MoveRobot" target="move_goal" output="move_result" /><SaySomething message="{move_result}"/></Sequence></BehaviorTree><BehaviorTree ID="MoveRobot"><Fallback name="move_robot_main"><SequenceStar><MoveBase       goal="{target}"/><SetBlackboard output_key="output" value="mission accomplished" /></SequenceStar><ForceFailure><SetBlackboard output_key="output" value="mission failed" /></ForceFailure></Fallback></BehaviorTree></root>

注意:

  • 我们有一个"MainTreee",它包含一个子树叫"MoveRobot"
  • 我们想要链接(重映射)MoveRobot和MainTree的接口
  • 我们用关键词"internal/external"表示子树和主树
  • 下图表示了两个树的映射关系。

在 C++ 方面,我们不需要做太多事情。出于调试目的,我们可以使用 debugMessage() 方法显示有关balckboard当前状态的一些信息。

int main()
{BT::BehaviorTreeFactory factory;factory.registerNodeType<SaySomething>("SaySomething");factory.registerNodeType<MoveBaseAction>("MoveBase");auto tree = factory.createTreeFromText(xml_text);NodeStatus status = NodeStatus::RUNNING;// Keep on ticking until you get either a SUCCESS or FAILURE statewhile( status == NodeStatus::RUNNING){status = tree.tickRoot();SleepMS(1);   // optional sleep to avoid "busy loops"}// let's visualize some information about the current state of the blackboards.std::cout << "--------------" << std::endl;tree.blackboard_stack[0]->debugMessage();std::cout << "--------------" << std::endl;tree.blackboard_stack[1]->debugMessage();std::cout << "--------------" << std::endl;return 0;
}/* Expected output:[ MoveBase: STARTED ]. goal: x=1 y=2.0 theta=3.00[ MoveBase: FINISHED ]Robot says: mission accomplished--------------move_result (std::string) -> fullmove_goal (Pose2D) -> full--------------output (std::string) -> remapped to parent [move_result]target (Pose2D) -> remapped to parent [move_goal]--------------
*/

如何使用多个 XML 文件来存储子树

在前面的例子中,我们总是从单个 XML 文件创建一整棵树。如果使用了多个子树,它们都将包含在同一个 XML 中。在最新版本的 BT.CPP (3.7+) 中,用户可以更轻松地从多个文件加载树。

使用"include"加载文件

考虑调用 2 个不同子树的主树。
main_tree.xml

<root main_tree_to_execute = "MainTree"><include path="./subtree_A.xml" /><include path="./subtree_B.xml" /><BehaviorTree ID="MainTree"><Sequence><SaySomething message="starting MainTree" /><SubTree ID="SubTreeA" /><SubTree ID="SubTreeB" /></Sequence></BehaviorTree>
<root>

subtree_A.xml

<root><BehaviorTree ID="SubTreeA"><SaySomething message="Executing Sub_A" /></BehaviorTree>
</root>

subtree_B.xml:

<root><BehaviorTree ID="SubTreeB"><SaySomething message="Executing Sub_B" /></BehaviorTree>
</root>

注意到,我们在 main_tree.xml 中包含了两个相对路径,它们告诉 BehaviorTreeFactory 在哪里可以找到所需的依赖项。

接着我们需要像往常一样创建树就行了:

factory.createTreeFromFile("main_tree.xml")

手动加载多文件

如果我们不想将相对路径和硬编码路径添加到我们的 XML 中,或者如果我们想实例化一个子树而不是主树,那么自 BT.CPP 3.7+ 起就有一种新方法。

<root><BehaviorTree ID="MainTree"><Sequence><SaySomething message="starting MainTree" /><SubTree ID="SubTreeA" /><SubTree ID="SubTreeB" /></Sequence></BehaviorTree>
<root>

接下来手动加载多个文件

int main()
{BT::BehaviorTreeFactory factory;factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");// Register the behavior tree definitions, but don't instantiate them, yet.// Order is not important.factory.registerBehaviorTreeFromText("main_tree.xml");factory.registerBehaviorTreeFromText("subtree_A.xml");factory.registerBehaviorTreeFromText("subtree_B.xml");//Check that the BTs have been registered correctlystd::cout << "Registered BehaviorTrees:" << std::endl;for(const std::string& bt_name: factory.registeredBehaviorTrees()){std::cout << " - " << bt_name << std::endl;}// You can create the MainTree and the subtrees will be added automatically.std::cout << "----- MainTree tick ----" << std::endl;auto main_tree = factory.createTree("MainTree");main_tree.tickRoot();// ... or you can create only one of the subtreestd::cout << "----- SubA tick ----" << std::endl;auto subA_tree = factory.createTree("SubTreeA");subA_tree.tickRoot();return 0;
}
/* Expected output:Registered BehaviorTrees:- MainTree- SubTreeA- SubTreeB
----- MainTree tick ----
Robot says: starting MainTree
Robot says: Executing Sub_A
Robot says: Executing Sub_B
----- SubA tick ----
Robot says: Executing Sub_A

在初始化和/或构造期间传递额外的参数

在前面的简单例子中,我们都“被迫”提供具有以下签名的构造函数

    MyCustomNode(const std::string& name, const NodeConfiguration& config);

有时需要向我们的类的构造函数传递额外的参数、指针、引用等。许多人使用blackboard来做到这一点:但是这是不推荐的。
接下来,我们将只使用“参数”这个词。即使理论上可以使用输入端口传递这些参数,但如果出现以下情况,那将是错误的方法:

  • 这些参数在部署时是已知的。
  • 参数在运行时不会改变。
  • 不需要从 XML 设置参数。

如果所有这些条件都满足,那么使用blackboard会很麻烦并且非常不鼓励使用。

方法一 注册自定义builder

考虑下面这个自定义节点Action_A。我们想传递三个额外的参数;它们可以是任意复杂的对象,您不限于内置类型。

// Action_A has a different constructor than the default one.
class Action_A: public SyncActionNode
{public:// additional arguments passed to the constructorAction_A(const std::string& name, const NodeConfiguration& config,int arg1, double arg2, std::string arg3 ):SyncActionNode(name, config),_arg1(arg1),_arg2(arg2),_arg3(arg3) {}// this example doesn't require any portstatic PortsList providedPorts() { return {}; }// tick() can access the private membersNodeStatus tick() override;private:int _arg1;double _arg2;std::string _arg3;
};

这个节点接下来应该这样被注册:

BehaviorTreeFactory factory;// A node builder is a functor that creates a std::unique_ptr<TreeNode>.
// Using lambdas or std::bind, we can easily "inject" additional arguments.
NodeBuilder builder_A =[](const std::string& name, const NodeConfiguration& config)
{return std::make_unique<Action_A>( name, config, 42, 3.14, "hello world" );
};// BehaviorTreeFactory::registerBuilder is a more general way to
// register a custom node.
factory.registerBuilder<Action_A>( "Action_A", builder_A);// Register more custom nodes, if needed.
// ....// The rest of your code, where you create and tick the tree, goes here.
// ....

方法2:使用init方法

我们还可以在触发树之前调用init方法:

class Action_B: public SyncActionNode
{public:// The constructor looks as usual.Action_B(const std::string& name, const NodeConfiguration& config):SyncActionNode(name, config) {}// We want this method to be called ONCE and BEFORE the first tick()void init( int arg1, double arg2, const std::string& arg3 ){_arg1 = (arg1);_arg2 = (arg2);_arg3 = (arg3);}// this example doesn't require any portstatic PortsList providedPorts() { return {}; }// tick() can access the private membersNodeStatus tick() override;private:int _arg1;double _arg2;std::string _arg3;
};

我们注册和初始化 Action_B 的方式略有不同:

BehaviorTreeFactory factory;// The regitration of  Action_B is done as usual, but remember
// that we still need to call Action_B::init()
factory.registerNodeType<Action_B>( "Action_B" );// Register more custom nodes, if needed.
// ....// Create the whole tree
auto tree = factory.createTreeFromText(xml_text);// Iterate through all the nodes and call init() if it is an Action_B
for( auto& node: tree.nodes )
{// Not a typo: it is "=", not "=="if( auto action_B = dynamic_cast<Action_B*>( node.get() )){action_B->init( 42, 3.14, "hello world");}
}// The rest of your code, where you tick the tree, goes here.
// ....

使用协程(Coroutines)的异步操作

BehaviorTree.CPP 提供了两个易于使用的abstraction来创建异步操作.
异步操作比如说:

  • 需要很长时间才能得出结果。
  • 可能返回“正在运行”。
  • 可以暂停。

第一个类是 AsyncActionNode,它在单独的线程中执行 tick() 方法。

接着我们介绍第二种:CoroActionNode,这是一种使用协程实现类似结果的不同操作。协程不会产生新线程并且效率更高。此外,您无需担心代码中的线程安全…

在 Coroutines 中,当用户希望暂停执行 Action 时,应显式调用 yield 方法。CoroActionNode 将此 yield 函数包装到一个方便的方法 setStatusRunningAndYield() 中。

C++例子

typedef std::chrono::milliseconds Milliseconds;class MyAsyncAction: public CoroActionNode
{public:MyAsyncAction(const std::string& name):CoroActionNode(name, {}){}private:// This is the ideal skeleton/template of an async action://  - A request to a remote service provider.//  - A loop where we check if the reply has been received.//  - You may call setStatusRunningAndYield() to "pause".//  - Code to execute after the reply.//  - A simple way to handle halt().NodeStatus tick() override{std::cout << name() <<": Started. Send Request to server." << std::endl;TimePoint initial_time = Now();TimePoint time_before_reply = initial_time + Milliseconds(100);int count = 0;bool reply_received = false;while( !reply_received ){if( count++ == 0){// call this only oncestd::cout << name() <<": Waiting Reply..." << std::endl;}// pretend that we received a replyif( Now() >= time_before_reply ){reply_received = true;}if( !reply_received ){// set status to RUNNING and "pause/sleep"// If halt() is called, we will NOT resume executionsetStatusRunningAndYield();}}// This part of the code is never reached if halt() is invoked,// only if reply_received == true;std::cout << name() <<": Done. 'Waiting Reply' loop repeated "<< count << " times" << std::endl;cleanup(false);return NodeStatus::SUCCESS;}// you might want to cleanup differently if it was halted or successfulvoid cleanup(bool halted){if( halted ){std::cout << name() <<": cleaning up after an halt()\n" << std::endl;}else{std::cout << name() <<": cleaning up after SUCCESS\n" << std::endl;}}void halt() override{std::cout << name() <<": Halted." << std::endl;cleanup(true);// Do not forget to call this at the end.CoroActionNode::halt();}TimePoint Now(){ return std::chrono::high_resolution_clock::now(); };
};

您可能已经注意到,该操作“假装”等待请求消息;后者将在 100 毫秒后到达。
为了增加趣味,我们创建了一个包含两个动作的序列,但整个序列将在 150 毫秒后因超时而暂停。

 <root ><BehaviorTree><Timeout msec="150"><SequenceStar name="sequence"><MyAsyncAction name="action_A"/><MyAsyncAction name="action_B"/></SequenceStar></Timeout></BehaviorTree></root>
int main()
{// Simple tree: a sequence of two asycnhronous actions,// but the second will be halted because of the timeout.BehaviorTreeFactory factory;factory.registerNodeType<MyAsyncAction>("MyAsyncAction");auto tree = factory.createTreeFromText(xml_text);//---------------------------------------// keep executin tick until it returns etiher SUCCESS or FAILUREwhile( tree.tickRoot() == NodeStatus::RUNNING){std::this_thread::sleep_for( Milliseconds(10) );}return 0;
}/* Expected output:action_A: Started. Send Request to server.
action_A: Waiting Reply...
action_A: Done. 'Waiting Reply' loop repeated 11 times
action_A: cleaning up after SUCCESSaction_B: Started. Send Request to server.
action_B: Waiting Reply...
action_B: Halted.
action_B: cleaning up after an halt()*/

ROS学习|Behavoir Tree(BT树)--c++实现第二节相关推荐

  1. ROS学习|Behavoir Tree(BT树)--c++实现

    系列文章目录 Behavoir Tree(BT树)–基本概念 Behavoir Tree(BT树)–c++实现 Behavoir Tree(BT树)–c++实现第二节 目录 系列文章目录 创建行为树 ...

  2. 学习——Regression Tree 回归树

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_40604987/arti ...

  3. 转载——Regression Tree 回归树

    本文转载于" 一个拉风的名字"的"Regression Tree 回归树" 1. 引言 AI时代,机器学习算法成为了研究.应用的热点.当前,最火的两类算法莫过于 ...

  4. ROS学习笔记三:创建ROS软件包

    ,# ROS学习笔记三:创建ROS软件包 catkin软件包的组成 一个软件包必须满足如下条件才能被称之为catkin软件包: 这个软件包必须包含一个catkin编译文件package.xml(man ...

  5. 【学习笔记】线段树详解(全)

    [学习笔记]线段树详解(全) 和三个同学一起搞了接近两个月的线段树,头都要炸了T_T,趁心态尚未凉之前赶快把东西记下来... [目录] [基础]作者:\((Silent\)_\(EAG)\) [懒标记 ...

  6. ROS学习笔记之——robot_localization包

    之前博客已经介绍过robot_pose_ekf功能包以及(extended)kalman滤波的原理< ROS学习笔记之--EKF (Extended Kalman Filter) node 扩展 ...

  7. Webots+ROS学习记录(4)——六轮全地形移动机器人

    Webots+ROS学习记录(4)--六轮全地形移动机器人 有了以上经验,可以创造出一个全地形的移动机器人如图1 第一步,创建robot节点,并给robot节点编写相应参数 注意,这里的机身不能再使用 ...

  8. ROS学习----依据ROS入门教程,整理的ROS命令

    文章目录 ROS命令学习 文件系统介绍 ROS文件系统工具命令:rospack,rosstack roscd,rosls 创建ROS程序包命令:roscreate,catkin程序包结构,catkin ...

  9. 【ros学习】14.urdf、xacro机器人建模与rviz、gazebo仿真详解

    一.起因 学校的这学期课程是ros机器人开发实战,我们学习小组也要搞一个自己的机器人模型,我们组又叫葫芦组,所以我就做了个葫芦形状的机器人,虽说有点丑,本来想用maya建模再导入的,奈何不太懂maya ...

最新文章

  1. 如何设计恒流源输出电路?
  2. if(window.event) e = window.event
  3. Metasploit发布了新版本5.0.83
  4. python中常见的流程结构-python常见对象的结构
  5. 详解log4j2(下) - Log4j2在WEB项目中配置
  6. 运算符的优先级表(从高到低)
  7. docker-machine create --driver virtualbox myvm1 创建失败
  8. Python 调度算法 死锁 静动态链接 分页分段(七)
  9. laravel框架学习(三)
  10. 【转】IDEA类和方法注释模板设置(非常详细)
  11. linux 锐捷客户端 脚本,常熟理工学院锐捷客户端 for Linux
  12. Eucalyptus简介
  13. Mybatis多参数查询与列表查询不同方式实现
  14. pdf在线免费去水印 以及图片去水印 方法
  15. 基于SSM框架的狼途汽车门店管理系统的设计与实现
  16. 常见的国家语言缩写以及语言的代码
  17. 文本框失去焦点事件、获得焦点事件
  18. Elasticsearch Nested类型深入详解
  19. shell综合练习(二)
  20. Jetson Nano 关闭开启图形界面减少内存占用

热门文章

  1. 网络协议 3 - 物理层 和 MAC 层
  2. 速卖通提高订单速成法
  3. 使用Java代码实现杨辉三角
  4. python写我爱你_Python初体验之我爱你
  5. HbuilderX连接夜神模拟器进行app调试
  6. esp8266模块软件定时
  7. 【Docker】利用docker在window环境下部署python开发环境
  8. C4: ETF 和 ETF联接
  9. 闪信智能化物业维修管理系统
  10. 备案的老域名有什么好处?购买老域名一定要备案吗?