使用行为树控制机器人(二) —— 黑板
文章目录
- 一、概念
- 二、黑板功能实现
- 1. 功能实现
- 1.1 头文件定义
- 1.2 源文件实现
- 1.3 main文件实现
- 1.4 my_tree.xml 实现
- 2. 执行结果
使用行为树控制机器人(一) —— 节点
使用行为树控制机器人(二) —— 黑板
使用行为树控制机器人(三) —— 通用端口
在 使用行为树控制机器人(一) —— 节点 实现了节点执行,但节点间是封闭的无法进行数据传递的,此文的目的即是解决节点间的数据传输问题 —— 黑板。
学习时参考链接:ROS机器人行为树教程
一、概念
“黑板” 是一个简单的键/值存储,由树的所有节点共享。黑板上的一个 "条目 "是一个键/值对。输入端口可以读取黑板上的一个条目,而输出端口可以写入一个条目。
下面创建一个行为树逻辑:创建一个ThinWhatToSay
节点 使用一个输出端口通过键 topic
写入黑板条目,然后创建另一个节点SaySomething
使用一个输入端口通过键 topic
读取黑板条目该键指向的对应值并将其内容进行打印 (下图中键 topic
其值为"The answer is 42"
)。
其中,
Script Input
用于调试,可使用内置的名为"Script"的动作将一个静态值写入条目中。<Script code=" topic1:='message by script' " />
二、黑板功能实现
1. 功能实现
1.1 头文件定义
#ifndef BEHAVIOR_TREE_NODES_H
#define BEHAVIOR_TREE_NODES_H#include "behaviortree_cpp/bt_factory.h"
#include <iostream>namespace BT
{// 带输入端口的同步动作节点
class SaySomething : public SyncActionNode
{
public:SaySomething(const std::string& name, const NodeConfig& config);// 必须实现静态端口声明方法static PortsList providedPorts();// 节点执行函数NodeStatus tick() override;
};// 带输出端口的同步动作节点
class ThinkWhatToSay : public SyncActionNode
{
public:ThinkWhatToSay(const std::string& name, const NodeConfig& config);// 端口声明static PortsList providedPorts();// 节点执行函数NodeStatus tick() override;std::string _info = "The answer is 42";
};} // namespace BT#endif // BEHAVIOR_TREE_NODES_H
注意:自定义的TreeNode有输入和/或输出端口时,这些端口必须在静态方法中声明,可以使用模板方法TreeNode::getInput(key)来读取端口消息的输入。
static PortsList providedPorts();
1.2 源文件实现
#include "behavior_tree_nodes.h"using namespace BT;// SaySomething 实现
SaySomething::SaySomething(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config)
{}PortsList SaySomething::providedPorts()
{return { InputPort<std::string>("message") };
}NodeStatus SaySomething::tick()
{// 直接使用 BT::Expected 类型BT::Expected<std::string> msg = getInput<std::string>("message");// 检查是否成功获取输入if (!msg){throw BT::RuntimeError("missing required input [message]: " + msg.error());}// 输出消息std::cout << "Robot says: " << msg.value() << std::endl;return NodeStatus::SUCCESS;
}// ThinkWhatToSay 实现
ThinkWhatToSay::ThinkWhatToSay(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config)
{}PortsList ThinkWhatToSay::providedPorts()
{return {InputPort<std::string>("info"), // 添加输入端口OutputPort<std::string>("text")};
}NodeStatus ThinkWhatToSay::tick() {auto msg = getInput<std::string>("info");// 检查是否成功获取输入if (!msg){throw BT::RuntimeError("missing required input [info]: " + msg.error());}_info = msg.value();setOutput("text", _info);return NodeStatus::SUCCESS;
}
1.3 main文件实现
#include "behavior_tree_nodes.h"
#include "behaviortree_cpp/bt_factory.h"int main(int argc, char *argv[])
{std::string think_info="The answer is 43";BT::BehaviorTreeFactory factory;factory.registerNodeType<BT::SaySomething>("SaySomething");factory.registerNodeType<BT::ThinkWhatToSay>("ThinkWhatToSay");if (argc > 1) {think_info = argv[1];std::cout << "Using command line argument: " << think_info << std::endl;} else {std::cout << "Using default value: " << think_info << std::endl;}try {auto tree = factory.createTreeFromFile("../trees/my_tree.xml");tree.rootBlackboard()->set("topic1", think_info); // 设置黑板参数std::cout << "------ Behavior Tree Structure ------" << std::endl;BT::printTreeRecursively(tree.rootNode());std::cout << "------------------------------------" << std::endl;tree.tickWhileRunning();} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}
1.4 my_tree.xml 实现
行为树实现逻辑如上,xml文件定义如下,使用相同的键(topic) 将输出端口与输入端口 “连接” 起来:
<root BTCPP_format="4" main_tree_to_execute="MainTree"><BehaviorTree ID="MainTree"><Sequence name="root_sequence"><!-- Script code 注释,可命令动态传参 --><Script code=" topic1:='message by script' " /><!-- 直接传递字符串 --><SaySomething name="say_hello" message="hello"/><!-- 通过黑板传递 --><ThinkWhatToSay name="think" text="{topic}" info="{topic1}"/><SaySomething name="say_answer" message="{topic}"/></Sequence></BehaviorTree>
</root>
2. 执行结果
上述行为树执行结果如下:
如不想通过 <Script code=" topic1:='message by script' " />
,而是通过命令动态传参,且只需将其注释,重新运行即可。