XML的基础知识
一棵简单的行为树
1 2 3 4 5 6 7 8 9 10
| <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>
|
- 树的第一个标签,这个标签内可以包含一个或多个子标签
- 标签应该包含属性[main_tree_to_execute]
- 如果文件中包含多个子标签,那么属性[main_tree_to_execute]就是必需的,否则可选
- 每个标签都应该有属性[ID]
- 每个树节点都由一个单独的标签来描述:
- 标签的名字就是树节点在工厂中注册的ID
- 属性[name]是指实例的名称,并且是可选的
- 端口Ports是使用属性来配置的,如在动作SaySomething中,需要输入端口message
- 子节点的数量
- 控制节点包含1个到多个子节点
- 装饰节点和子树只有一个子节点
- 动作节点和条件节点都是叶子节点
端口重映射与指向Blackboards入口的指针
此部分内容需要先学习行为树的教程第二节的知识。
输入/输出端口可以用Blackboard入口的名称来进行重映射,换句话说,就是用BB键值对中的键来重映射。
一个BB的key用语法{key_name}来描述
1 2 3 4 5 6 7 8
| <root main_tree_to_execute = "MainTree" > <BehaviorTree ID="MainTree"> <Sequence name="root_sequence"> <SaySomething message="Hello"/> <SaySomething message="{my_message}"/> </Sequence> </BehaviorTree> </root>
|
在这个例子中
- 序列节点的第一个子节点是“Hello”
- 第二个子节点读取和写入名为“my_message”的BB的key中包含的值
简洁的描述 vs 明确的描述
以下两种子节点的写法都是正确的
1 2
| <SaySomething name="action_hello" message="Hello World"/> <Action ID="SaySomething" name="action_hello" message="Hello World"/>
|
我们称前一种语法为简洁的,后一种则是明确的
第一个例子如若用明确的语法来描述,则如下所示
1 2 3 4 5 6 7 8 9 10
| <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>
|
及时简洁的语法更加方便、更容易编写,但是它提供的关于树节点模型的信息太少。类似Groot这样的工具需要明确的语法或者附加的信息。为了兼容Groot,可以使用标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <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_schema.xsd。
子树
在后续的教程中,我们会尝试在一棵树中包含另一树作为子树,以避免大量的复制粘贴操作,减少复杂度。
在下面的例子中,我们将包含一些动作的行为树“GraspObject”封装到了主行为树中,为简单起见,属性[name]被省略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree"> <Sequence> <Action ID="SaySomething" message="Hello World"/> <SubTree ID="GraspObject"/> </Sequence> </BehaviorTree>
<BehaviorTree ID="GraspObject"> <Sequence> <Action ID="OpenGripper"/> <Action ID="ApproachObject"/> <Action ID="CloseGripper"/> </Sequence> </BehaviorTree> </root>
|
子树“GraspObject”作为主行为树的节点,将在“SaySomething”之后被执行。
包含外部文件
我们也可以使用类似#include的C++方法来导入行为树
1
| <include path="relative_or_absolute_path_to_file">
|
将前面的示例差分为两个文件,每个文件中都有一棵行为树
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- 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>
|
1 2 3 4 5 6 7 8 9 10 11
| <!-- 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>
|
如果要在ROS 包中查找文件,可以使用以下语法:
1
| <include ros_pkg="name_package" path="path_relative_to_pkg/grasp.xml"/>
|