BT11:行为树内外的数据传输

大家好,欢迎大家关注我的知乎专栏慢慢悠悠小马车


下面的文章有关于blackboard、entry、port、subtree remapping的简介,这是理解行为树的数据解析和传递的重点和难点。行为树不是封闭的,对内有不同subtree、不同port间的数据传递,对外有调用方的状态或结果获取,毕竟很多情况调用方不会满足于只知道执行结果的成功与失败,还想知道为何失败等详细信息。BT4:库中基本类型——Factory和Blackboard_silver bullet-CSDN博客基本的类定义https://blog.csdn.net/linxigjs/article/details/123033365?spm=1001.2014.3001.5501

树内即ports之间

参考BehaviorTree.CPP/examples/t02_basic_ports.cpp 的示例。

 <root main_tree_to_execute = "MainTree" >
     <BehaviorTree ID="MainTree">
        <Sequence name="root">
            <SaySomething     message="start thinking..." />
            <ThinkWhatToSay   text="{the_answer}"/>
            <SaySomething     message="{the_answer}" />
            <SaySomething2    message="SaySomething2 works too..." />
            <SaySomething2    message="{the_answer}" />
        </Sequence>
     </BehaviorTree>
 </root>

类ThinkWhatToSay有1个string类型的OutputPort,并会在tick()时向该port写入值。

class ThinkWhatToSay : public BT::SyncActionNode {
 public:
  BT::NodeStatus tick() override {
    setOutput("text", "The answer is 42");
    return BT::NodeStatus::SUCCESS;
  }
  static BT::PortsList providedPorts() {
    return {BT::OutputPort<std::string>("text")};
  }
};

类SaySomething有1个string类型的InputPort,并会在tick()时从该port读取值。

class SaySomething : public BT::SyncActionNode {
 public:
  BT::NodeStatus tick() override {
    auto msg = getInput<std::string>("message");
    ...
    std::cout << "Robot says: " << msg.value() << std::endl;
    return BT::NodeStatus::SUCCESS;
  }
  static BT::PortsList providedPorts() {
    return {BT::InputPort<std::string>("message")};
  }
};

ThinkWhatToSay和SaySomething,通过所在树的blackboard的1个名称为"the_answer"的entry进行数据读写传输。entry可以传输的数据的类型,由port限定。我们把blackboard的数量和包含的entry的名称打印出来,size=1因为没有子树,且只有1个名为"the_answer"的entry。

std::cout << tree.blackboard_stack.size() << std::endl;
for(const auto str : tree.blackboard_stack[0]->getKeys()) {
  std::cout << str << std::endl;
}
1
the_answer

subtree之间

参考BehaviorTree.CPP/examples/t06_basic_ports.cpp 的示例。

<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>

MainTree中包含1个MoveRobot subtree,所以有2个blackboard,blackboard[0]是MainTree的(一定是最外层树的),具有move_result和move_goal 2个entry,而MoveRobot具有output和target 2个entry。在树运行之后,使用debugMessage()可以打印树之间的ports映射如下。full表明对应port已经被设置值,可以被外部读取。

move_result (std::string) -> full
move_goal (Pose2D) -> full
--------------
output (std::string) -> remapped to parent [move_result]
target (Pose2D) -> remapped to parent [move_goal]

如果我们调整下debugMessage()在树创建之后、运行之前,映射会变成什么样呢?

int main() {
  ...
  auto tree = factory.createTreeFromText(xml_text);
  // 在树创建之后、运行之前打印映射信息
  std::cout << "--------------" << std::endl;
  tree.blackboard_stack[0]->debugMessage();
  std::cout << "--------------" << std::endl;
  tree.blackboard_stack[1]->debugMessage();
  std::cout << "--------------" << std::endl;
  //  auto p = tree.blackboard_stack[0]->get<Pose2D>("move_goal");
  //  std::cout << "get pose (" << p.x << "," << p.y << "," << p.theta << ")" << std::endl;

  NodeStatus status = NodeStatus::RUNNING;
  while (status == NodeStatus::RUNNING) {
    status = tree.tickRoot();
    SleepMS(1);  // optional sleep to avoid "busy loops"
  }

  return 0;
}

结果如下,可见MainTree的2个entry都没有设置值(empty),且MoveRobot subtree也少了1个entry信息,因为此时树未运行SetBlackboard节点,自然也就不会创建出output entry。若读取empty的entry,就会发生异常。

move_result (std::string) -> empty
move_goal (Pose2D) -> empty
--------------
target (Pose2D) -> remapped to parent [move_goal]

树与调用方之间

本小节需要和上一小节结合起来看。

当debugMessage()打印key->full时,才可以在外部读取树的blackboard的entry的值。

while (status == NodeStatus::RUNNING) {
  status = tree.tickRoot();
  SleepMS(1);  // optional sleep to avoid "busy loops"
}
// 添加在树运行后和return之间,此时entry的值是保持的。
// 如果添加在树运行之前,可能会因为port还未被设置而读取触发异常。
auto p = tree.blackboard_stack[0]->get<Pose2D>("move_goal");
std::cout << "get pose (" << p.x << "," << p.y << "," << p.theta << ")" << std::endl;

return 0;

运行上面的代码,我们会得到:

get pose (1,2,3)    // 是XML中SetBlackboard的结果

当debugMessage()打印key->empty时, 读取值抛出异常,参考上一小节被注释的代码。

terminate called after throwing an instance of 'std::runtime_error'
what():  Any::cast failed because it is empty

当然,从外部设置entry的值并不受其是empty/full的影响。

Pose2D p, q;
p.x = 10, p.y = 11, p.theta = 3.14;
tree.blackboard_stack[0]->set<Pose2D>("move_goal", p);
tree.blackboard_stack[0]->get<Pose2D>("move_goal", q);
std::cout << "get pose (" << q.x << "," << q.y << "," << q.theta << ")" << std::endl;

运行上面的代码,我们会得到: 

get pose (10,11,3.14)
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值