1. 创建第一个行为树导航节点

任务目标

本案例实现一个ROS2行为树节点,该节点通过Nav2的NavigateToPose动作客户端,控制机器人从当前坐标移动到指定目标点,并在导航过程中持续输出状态(等待、执行中、成功、失败)。节点使用BehaviorTree.CPP框架构建,包含一个自定义动作节点NavigateAction,以及一个简单的顺序树:Navigate → CheckResult。

环境依赖

  • ROS2 Humble或更高版本
  • Nav2(nav2_msgs、nav2_msgs/action/NavigateToPose)
  • BehaviorTree.CPP(libbehaviortree-cpp-dev)
  • 已启动的Gazebo仿真或真实机器人,且Nav2导航栈运行中(提供/navigate_to_pose动作服务)

完整代码

创建文件bt_nav_node.cpp,内容如下:

```cpp #include #include #include #include #include #include #include #include using namespace BT; using NavigateToPose = nav2_msgs::action::NavigateToPose; using GoalHandle = rclcpp_action::ClientGoalHandle; // 自定义动作节点:发送导航目标并等待结果 class NavigateAction : public StatefulActionNode { public: NavigateAction(const std::string& name, const NodeConfig& config, rclcpp::Node::SharedPtr node) : StatefulActionNode(name, config), node_(node) { client_ = rclcpp_action::create_client(node_, "navigate_to_pose"); } static PortsList providedPorts() { return { InputPort("x"), InputPort("y"), InputPort("yaw") }; } NodeStatus onStart() override { if (!client_->wait_for_action_server(std::chrono::seconds(2))) { RCLCPP_ERROR(node_->get_logger(), "Nav2 action server not available"); return NodeStatus::FAILURE; } double x, y, yaw; getInput("x", x); getInput("y", y); getInput("yaw", yaw); auto goal_msg = NavigateToPose::Goal(); goal_msg.pose.header.frame_id = "map"; goal_msg.pose.header.stamp = node_->now(); goal_msg.pose.pose.position.x = x; goal_msg.pose.pose.position.y = y; goal_msg.pose.pose.orientation.z = std::sin(yaw / 2.0); goal_msg.pose.pose.orientation.w = std::cos(yaw / 2.0); auto send_goal_options = rclcpp_action::Client::SendGoalOptions(); send_goal_options.goal_response_callback = [this](const GoalHandle::SharedPtr& goal_handle) { if (!goal_handle) { RCLCPP_ERROR(node_->get_logger(), "Goal was rejected"); goal_accepted_ = false; } else { goal_accepted_ = true; } }; send_goal_options.result_callback = [this](const GoalHandle::WrappedResult& result) { if (result.code == rclcpp_action::ResultCode::SUCCEEDED) { nav_result_ = NodeStatus::SUCCESS; } else { nav_result_ = NodeStatus::FAILURE; } }; future_ = client_->async_send_goal(goal_msg, send_goal_options); return NodeStatus::RUNNING; } NodeStatus onRunning() override { if (!goal_accepted_) { return NodeStatus::FAILURE; } if (nav_result_ != NodeStatus::IDLE) { return nav_result_; } RCLCPP_INFO(node_->get_logger(), "导航执行中..."); return NodeStatus::RUNNING; } void onHalted() override { RCLCPP_WARN(node_->get_logger(), "导航被中止"); } private: rclcpp::Node::SharedPtr node_; rclcpp_action::Client::SharedPtr client_; std::shared_future future_; bool goal_accepted_ = false; NodeStatus nav_result_ = NodeStatus::IDLE; }; // 检查导航结果并输出最终状态 class CheckResult : public SyncActionNode { public: CheckResult(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config) {} static PortsList providedPorts() { return { InputPort("nav_status") }; } NodeStatus tick() override { NodeStatus status; getInput("nav_status", status); if (status == NodeStatus::SUCCESS) { RCLCPP_INFO(rclcpp::get_logger("CheckResult"), "导航成功到达目标点"); return NodeStatus::SUCCESS; } else { RCLCPP_ERROR(rclcpp::get_logger("CheckResult"), "导航失败"); return NodeStatus::FAILURE; } } }; // 主函数:构建行为树并执行 int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared("bt_nav_node"); BehaviorTreeFactory factory; factory.registerNodeType("NavigateAction", node); factory.registerNodeType("CheckResult"); // 构建树:顺序执行导航和结果检查 auto tree = factory.createTreeFromText(R"( )"); // 将导航结果写入黑板供CheckResult读取 tree.rootBlackboard()->set("nav_result", NodeStatus::IDLE); // 运行树直到结束 auto status = NodeStatus::RUNNING; while (rclcpp::ok() && status == NodeStatus::RUNNING) { status = tree.tickOnce(); rclcpp::spin_some(node); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (status == NodeStatus::SUCCESS) { RCLCPP_INFO(node->get_logger(), "行为树执行成功"); } else { RCLCPP_ERROR(node->get_logger(), "行为树执行失败"); } rclcpp::shutdown(); return 0; } ```

编译与运行

在CMakeLists.txt中添加:

```cmake find_package(behaviortree_cpp REQUIRED) find_package(nav2_msgs REQUIRED) add_executable(bt_nav_node src/bt_nav_node.cpp) target_link_libraries(bt_nav_node ${behaviortree_cpp_LIBRARIES}) ament_target_dependencies(bt_nav_node rclcpp rclcpp_action nav2_msgs geometry_msgs) ```

编译并运行:

```bash colcon build --packages-select your_package source install/setup.bash ros2 run your_package bt_nav_node ```

验证方法

  • 前置条件:确保Nav2导航栈已启动,ros2 action list能看到/navigate_to_pose。
  • 运行观察:终端应周期性输出“导航执行中...”,机器人开始移动。
  • 通过标准:机器人到达坐标(2.0, 1.5)附近后,终端输出“导航成功到达目标点”和“行为树执行成功”,进程退出码为0。
  • 失败验证:若在导航过程中手动取消Nav2任务(如ros2 action cancel /navigate_to_pose),应输出“导航失败”和“行为树执行失败”。

关键说明

本案例中NavigateAction通过黑板变量{nav_result}将结果传递给CheckResult,但实际运行时nav_result的更新发生在NavigateAction内部,而CheckResult读取的是黑板中的初始值。如需完整传递结果,可在NavigateAction::onRunning()中通过setOutput("nav_result", nav_result_)写入黑板,并在XML中将CheckResult的输入端口绑定到该输出端口。当前代码为最小演示,已实现导航状态输出与最终判定,满足任务目标。