BehaviorTree.CPP.行为树XML格式(六)
The XML format
XML模式的基础
在第一个教程中,介绍了这个简单的树。
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<SaySomething name="action_hello" message="Hello"/>
<OpenGripper name="open_gripper"/>
<ApproachObject name="approach_object"/>
<CloseGripper name="close_gripper"/>
</Sequence>
</BehaviorTree>
</root>
您可能会注意到:
- 树的第一个标签是
<root>
。它应包含1个或多个标签<BehaviorTree>
。 - 标签
<BehaviorTree>
应具有属性[ID]
。 - 标签
<root>
应包含属性[main_tree_to_execute]
。 [main_tree_to_execute]
如果文件包含多个<BehaviorTree>
,则该属性为必填,否则为可选。- 每个
TreeNode
由单个标签表示。特别是:- 标签的名称是用于在工厂中注册
TreeNode
的ID
。 - 该属性
[name]
引用实例的名称,并且是可选的。 - 使用属性配置端口。在前面的示例中,该操作
SaySomething
需要输入端口message
。
- 标签的名称是用于在工厂中注册
- 在子节点的数量方面:
ControlNodes
容纳1到N个子节点。DecoratorNodes
和子树仅包含1
个子项。ActionNodes
和ConditionNodes
有没有子项。
端口重新映射和指向Blackboards条目的指针
如第二篇教程中所述,可以使用Blackboard中条目的名称(即BB的键/值对的键)来重新映射输入/输出端口。
BB密钥使用以下语法表示:{key_name}
。
在以下示例中:
- 序列的第一个子节点打印
"Hello"
, - 第二个子节点读取和写入包含在黑板条目“ my_message”中的值;
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<SaySomething message="Hello"/>
<SaySomething message="{my_message}"/>
</Sequence>
</BehaviorTree>
</root>
紧凑与显式表示
以下两种语法均有效:
<SaySomething name="action_hello" message="Hello World"/>
<Action ID="SaySomething" name="action_hello" message="Hello World"/>
我们将前一种语法称为“紧凑”,而后一种称为“显式”。用显式语法表示的第一个示例将变为:
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<Action ID="SaySomething" name="action_hello" message="Hello"/>
<Action ID="OpenGripper" name="open_gripper"/>
<Action ID="ApproachObject" name="approach_object"/>
<Action ID="CloseGripper" name="close_gripper"/>
</Sequence>
</BehaviorTree>
</root>
即使紧凑语法更方便和易于编写,但它提供的有关TreeNode
模型的信息也太少了。像Groot这样的工具需要显式语法或其他信息。可以使用标签添加此信息<TreeNodeModel>
。
为了使我们的树的精简版与Groot兼容,必须对XML进行如下修改:
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<SaySomething name="action_hello" message="Hello"/>
<OpenGripper name="open_gripper"/>
<ApproachObject name="approach_object"/>
<CloseGripper name="close_gripper"/>
</Sequence>
</BehaviorTree>
<!-- the BT executor don't require this, but Groot does -->
<TreeNodeModel>
<Action ID="SaySomething">
<input_port name="message" type="std::string" />
</Action>
<Action ID="OpenGripper"/>
<Action ID="ApproachObject"/>
<Action ID="CloseGripper"/>
</TreeNodeModel>
</root>
您可以在此处下载XML模式。
子树
正如我们在本教程中所看到的,可以在另一棵树中包括一个子树,以避免在多个位置“复制和粘贴”同一棵树并降低复杂性。
假设我们想将很少的动作封装到behaviorTree"GraspObject"
中(为方便起见,省略了属性[name]
)。
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence>
<Action ID="SaySomething" message="Hello World"/>
<Subtree ID="GraspObject"/> <!-- "GraspObject" -->
</Sequence>
</BehaviorTree>
<BehaviorTree ID="GraspObject">
<Sequence>
<Action ID="OpenGripper"/>
<Action ID="ApproachObject"/>
<Action ID="CloseGripper"/>
</Sequence>
</BehaviorTree>
</root>
我们可能会注意到,整个树"GraspObject"在"SaySomething"之后执行。
包括外部文件
从2.4版本开始。
您可以采用类似于#include的方式包含外部文件在C++
中。我们可以使用标签轻松地做到这一点:
<include path="relative_or_absolute_path_to_file">
使用前面的示例,我们可以将两个行为树分成两个文件:
<!-- file maintree.xml -->
<root main_tree_to_execute = "MainTree" >
<include path="grasp.xml"/>
<BehaviorTree ID="MainTree">
<Sequence>
<Action ID="SaySomething" message="Hello World"/>
<Subtree ID="GraspObject"/>
</Sequence>
</BehaviorTree>
</root>
<!-- file grasp.xml -->
<root main_tree_to_execute = "GraspObject" >
<BehaviorTree ID="GraspObject">
<Sequence>
<Action ID="OpenGripper"/>
<Action ID="ApproachObject"/>
<Action ID="CloseGripper"/>
</Sequence>
</BehaviorTree>
</root>
^ x ^
ROS用户注意事项
如果要在ROS包中查找文件,可以使用以下语法:
<include ros_pkg="name_package" path="path_relative_to_pkg/grasp.xml"/>