使用行为树控制机器人(三) ——通用端口

文章目录

  • 一、通用端口功能实现
    • [1. 功能实现](#1. 功能实现)
      • [1.1 头文件定义](#1.1 头文件定义)
      • [1.2 源文件实现](#1.2 源文件实现)
      • [1.3 main文件实现](#1.3 main文件实现)
      • [1.4 tree.xml 实现](#1.4 tree.xml 实现)
    • [2. 执行结果](#2. 执行结果)

使用行为树控制机器人(一) ------ 节点
使用行为树控制机器人(二) ------ 黑板
使用行为树控制机器人(三) ------ 通用端口

有了上述前两节我们已经可以实现节点间的通信,BehaviorTree.CPP支持将字符串自动转换为常见类型(如int、long、double、bool、NodeStatus等),但作为导航,我们希望节点间实现自定义数据的传递,如:导航点的数据传递:

cpp 复制代码
// 自定义二维位姿类型
struct Pose2D 
{ 
  double x;
  double y; 
  double yaw; 
};

接下来,我们将展示如何将通用的 C++ 类型作为端口。

一、通用端口功能实现

1. 功能实现

1.1 头文件定义

为了让XML加载器能从字符串中实例化Pose2D,需提供convertFromString(BT::StringView str) 的模板类实现。如何将Pose2D序列化为字符串参考如下:

cpp 复制代码
// 在BT命名空间中特化字符串转换
namespace BT
{
    template <> inline Pose2D convertFromString(BT::StringView str)
    {
        // 使用字符串流替代 splitString
        std::string input(str.data(), str.size());
        std::istringstream ss(input);
        Pose2D output;
        char delimiter;
        
        // 解析格式: "x;y;yaw"
        if (!(ss >> output.x >> delimiter >> output.y >> delimiter >> output.yaw) || delimiter != ';')
        {
            throw BT::RuntimeError("invalid input for Pose2D: " + input);
        }
        
        return output;
    }
} // end namespace BT

接着按照惯例,就是头文件定义操作了。

cpp 复制代码
#ifndef BEHAVIOR_TREE_NODES_H
#define BEHAVIOR_TREE_NODES_H

#include "behaviortree_cpp/bt_factory.h"
#include <iostream>
#include <sstream> // 添加 sstream 头文件

// 自定义二维位姿类型
struct Pose2D 
{ 
  double x;
  double y; 
  double yaw; 
};

// 在BT命名空间中特化字符串转换
namespace BT
{
    template <> inline Pose2D convertFromString(BT::StringView str)
    {
        // 使用字符串流替代 splitString
        std::string input(str.data(), str.size());
        std::istringstream ss(input);
        Pose2D output;
        char delimiter;
        
        // 解析格式: "x;y;yaw"
        if (!(ss >> output.x >> delimiter >> output.y >> delimiter >> output.yaw) || delimiter != ';')
        {
            throw BT::RuntimeError("invalid input for Pose2D: " + input);
        }
        
        return output;
    }
} // end namespace BT

namespace BTNodes
{

// 写入端口 "goal"
class CalculateGoal : public BT::SyncActionNode
{
public:
    CalculateGoal(const std::string& name, const BT::NodeConfig& config);

    static BT::PortsList providedPorts();

    BT::NodeStatus tick() override;
};

// 读取端口
class PrintTarget : public BT::SyncActionNode
{
public:
    PrintTarget(const std::string& name, const BT::NodeConfig& config);

    static BT::PortsList providedPorts();
      
    BT::NodeStatus tick() override;
};

} // namespace BTNodes

#endif // BEHAVIOR_TREE_NODES_H

1.2 源文件实现

cpp 复制代码
#include "behavior_tree_nodes.h"

using namespace BTNodes;

// CalculateGoal 实现
CalculateGoal::CalculateGoal(const std::string& name, const BT::NodeConfig& config) : BT::SyncActionNode(name, config)
{}

BT::PortsList CalculateGoal::providedPorts()
{
  return { BT::OutputPort<Pose2D>("goal") };
}

BT::NodeStatus CalculateGoal::tick()
{
  // 设置目标位置
  Pose2D mygoal = {1.1, 2.3, 0.0};
  setOutput<Pose2D>("goal", mygoal);
  return BT::NodeStatus::SUCCESS;
}

// PrintTarget 实现
PrintTarget::PrintTarget(const std::string& name, const BT::NodeConfig& config) : BT::SyncActionNode(name, config)
{}

BT::PortsList PrintTarget::providedPorts()
{
  // 可选:添加人类可读的描述
  const char* description = "打印目标位置到控制台";
  return { BT::InputPort<Pose2D>("target", description) };
}

BT::NodeStatus PrintTarget::tick()
{
  auto res = getInput<Pose2D>("target");
  if (!res)
  {
    throw BT::RuntimeError("error reading port [target]: " + res.error());
  }
  Pose2D target = res.value();
  printf("目标位置: [%.1f, %.1f, %.1f]\n", target.x, target.y, target.yaw);
  return BT::NodeStatus::SUCCESS;
}

1.3 main文件实现

cpp 复制代码
#include "behavior_tree_nodes.h"
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/loggers/bt_cout_logger.h"

int main()
{
  BT::BehaviorTreeFactory factory;
  
  // 注册自定义节点
  factory.registerNodeType<BTNodes::CalculateGoal>("CalculateGoal");
  factory.registerNodeType<BTNodes::PrintTarget>("PrintTarget");
  
  // 内联 XML 定义
  const std::string xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
  <BehaviorTree ID="MainTree">
    <Sequence name="root">
      <CalculateGoal goal="{GoalPosition}" />
      <PrintTarget   target="{GoalPosition}" />
      <Script        code=" OtherGoal:='-1;3' " />
      <PrintTarget   target="{OtherGoal}" />
    </Sequence>
  </BehaviorTree>
</root>
)";

  try {
#if 0
    // 从文本创建行为树
    const std::string tree_xml_path = xml_text;
    auto tree = factory.createTreeFromText(tree_xml_path);

#else
    // 从XML文件 创建行为树
    const std::string tree_xml_path = "../trees/tree.xml";
    auto tree = factory.createTreeFromFile(tree_xml_path);
#endif

    // 添加日志记录器(可选)
    BT::StdCoutLogger logger(tree);
    
    // 打印树结构
    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 tree.xml 实现

行为树实现逻辑如上,xml文件定义如下:

xml 复制代码
<root BTCPP_format="4" main_tree_to_execute="MainTree">
  <BehaviorTree ID="MainTree">
    <Sequence name="root">
      <CalculateGoal goal="{GoalPosition}" />
      <PrintTarget   target="{GoalPosition}" />
      <Script        code=" OtherGoal:='-1;3;3.14' " />
      <PrintTarget   target="{OtherGoal}" />
    </Sequence>
  </BehaviorTree>
</root>

2. 执行结果

相关推荐
学Java的bb1 小时前
JavaWeb-后端Web实战(IOC + DI)
前端
No0d1es1 小时前
202506 电子学会青少年等级考试机器人五级器人理论真题
单片机·嵌入式硬件·青少年编程·机器人·电子学会·五级·理论综合
pe7er1 小时前
React Native 多环境配置全攻略:环境变量、iOS Scheme 和 Android Build Variant
前端·react native·react.js
河北北重机械9661 小时前
河北邢台数控滑台与机器人行走轨道的内在联系
机器人·数控滑台·机器人行走轨道
柯北(jvxiao)2 小时前
Vue vs React 多维度剖析: 哪一个更适合大型项目?
前端·vue·react
JefferyXZF2 小时前
Next.js 中间件:掌握请求拦截与处理的核心机制(六)
前端·全栈·next.js
知识分享小能手2 小时前
Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)
前端·javascript·css·vue.js·学习·typescript·vue3
石小石Orz2 小时前
React生态蓝图梳理:前端、全栈与跨平台全景指南
前端
袁煦丞3 小时前
8.12实验室 指尖魔法变出艺术感 Excalidraw:cpolar内网穿透实验室第495个成功挑战
前端·程序员·远程工作
烛阴3 小时前
Dot
前端·webgl