ROS 2 导航栈 Nav2

Nav2 把定位、全局规划、局部控制、恢复行为与任务编排组织成一组可插拔的服务器进程。本文讲清行为树 XML 的节点类型与自定义、代价地图各层的语义与膨胀调参、规划器与控制器插件选型、恢复行为与进度检查、生命周期启动顺序、过滤器与 keepout 区域,以及与 SLAM 和 AMCL 的接口和仿真回归验证方法。

引言

Nav2 是 ROS 2 的标准导航框架,它把「给一个目标点,把机器人安全开过去」拆成定位、全局规划、局部控制、恢复行为四件事,每件事由一个独立的生命周期服务器承担,再用行为树把它们编排起来。理解 Nav2 的关键不是记住某个参数,而是理解这组服务器之间的数据流与生命周期依赖。

工程上的第一个难点是「参数太多且互相耦合」。一个完整的 Nav2 配置有 200 个以上的参数,膨胀半径、代价衰减、控制器容差、进度检查阈值彼此影响。现场调参时改一个参数导致另一个场景退化是常态,因此必须有系统的调参顺序与回归方法,而不是逐个试。

第二个难点是行为树。Nav2 的默认行为树只是起点,真实产品几乎都要改:加急停分支、加多段恢复、加充电桩对接、加等待电梯。读懂默认的 navigate_to_pose_w_replanning_and_recovery.xml 并能安全地裁剪它,是从「跑通 demo」到「能上线」的分水岭。

第三个难点是与定位的接口。Nav2 不负责定位,它消费 /map 与 map → odom 变换。定位漂移、TF 跳变、地图过期都会表现为「导航行为诡异」,但根因不在 Nav2。本文按「架构 → 行为树 → 代价地图 → 规划器 → 控制器 → 恢复 → 生命周期 → 过滤器 → 定位接口 → 验证」的顺序展开,通用规划算法本身见运动规划与轨迹优化 ,本文只讲 Nav2 的工程落地。

目录

  1. Nav2 架构与服务器拓扑
  2. 行为树:节点类型与编排
  3. 自定义行为树节点与插件
  4. 代价地图:层、语义与更新
  5. 膨胀层与代价衰减调参
  6. 全局规划器与插件选型
  7. 局部控制器与速度平滑
  8. 恢复行为、进度检查与目标检查
  9. 生命周期、启动顺序与参数组织
  10. 过滤器:keepout、速度限制与安全监视
  11. 与 SLAM 和 AMCL 的接口
  12. 仿真回归验证与现场调参

1. Nav2 架构与服务器拓扑

Nav2 的每个能力都是一个生命周期节点,由 nav2_bringup 的 launch 一次性拉起,彼此通过话题与动作通信。

核心服务器与职责:
  map_server        :发布静态地图与 /map 元数据
  amcl              :在已知地图上定位,发布 map->odom
  planner_server    :全局规划(ComputePathToPose 动作)
  controller_server :局部控制(FollowPath 动作),输出 /cmd_vel
  behavior_server   :恢复行为(Spin、BackUp、Wait、DriveOnHeading)
  bt_navigator      :行为树执行器(NavigateToPose 动作入口)
  waypoint_follower :多点巡航(FollowWaypoints 动作)
  smoother_server   :路径平滑
  velocity_smoother :速度指令限幅与平滑
  collision_monitor :基于传感器与多边形区域的安全减速/急停

一次 NavigateToPose 的数据流是:目标 → bt_navigator → 行为树 → planner_server 产出全局路径 → controller_server 逐段跟踪并输出 /cmd_vel → velocity_smoother → 底盘驱动。bt_navigator 是唯一的对外入口,它不直接规划,只负责按行为树的逻辑调用其他服务器。这样设计的价值是「策略与算法分离」:换规划器改插件参数,换任务逻辑改行为树 XML,两者互不干扰。服务器之间用动作而非话题协作,因为规划与跟踪都是长耗时、可取消、有反馈的操作。

2. 行为树:节点类型与编排

行为树是一种从根向下执行、返回 SUCCESS/FAILURE/RUNNING 三态的树形控制结构,Nav2 用 BehaviorTree.CPP 实现。

<BehaviorTree ID="MainTree">
  <RecoveryNode number_of_retries="6" name="NavigateRecovery">
    <PipelineSequence name="NavigateWithReplanning">
      <RateController hz="1.0">
        <RecoveryNode number_of_retries="1" name="ComputePathToPose">
          <ComputePathToPose goal="{goal}" path="{path}"/>
          <ClearEntireCostmap service_name="global_costmap/clear_entirely_global_costmap"/>
        </RecoveryNode>
      </RateController>
      <RecoveryNode number_of_retries="1" name="FollowPath">
        <FollowPath path="{path}" controller_id="FollowPath"/>
        <ClearEntireCostmap service_name="local_costmap/clear_entirely_local_costmap"/>
      </RecoveryNode>
    </PipelineSequence>
    <SequenceStar name="RecoveryActions">
      <Spin spin_dist="1.57"/>
      <Wait wait_duration="5"/>
    </SequenceStar>
  </RecoveryNode>
</BehaviorTree>

关键节点类型分三类:

控制节点(决定子节点如何执行):
  Sequence / SequenceStar :依次执行,任一失败即失败;Star 版重试时不重跑已完成节点
  Fallback                :依次尝试,任一成功即成功(OR 语义)
  PipelineSequence        :前者 RUNNING 时后者也并行跑,用于边规划边跟踪
  RecoveryNode            :先跑第一个子节点,失败则跑第二个(恢复),可重试 n 次
  RateController          :限制子节点执行频率

动作节点(叶节点,调用服务器):
  ComputePathToPose、FollowPath、Spin、BackUp、Wait、ClearEntireCostmap

条件节点(只读黑板,无副作用):
  GoalUpdated、IsPathValid、TimeExpired

PipelineSequence 是 Nav2 的巧妙设计:它让「规划」与「跟踪」并行,当规划节点返回 RUNNING(还没算完)时,跟踪节点继续用旧路径跑。这样重规划不会打断运动,避免了「每次重规划都停一下」的抖动。

3. 自定义行为树节点与插件

默认行为树覆盖不了真实产品逻辑,必须能加自己的节点。

// 自定义动作节点:在门口等待,直到条件满足或超时
#include "behaviortree_cpp/action_node.h"

class WaitForDoorOpen : public BT::StatefulActionNode {
 public:
  WaitForDoorOpen(const std::string & name, const BT::NodeConfiguration & cfg)
      : BT::StatefulActionNode(name, cfg) {
    node_ = config().blackboard->get<rclcpp::Node::SharedPtr>("node");
  }
  static BT::PortsList providedPorts() {
    return { BT::InputPort<double>("timeout", 30.0, "最长等待秒数") };
  }
  BT::NodeStatus onStart() override {
    getInput("timeout", timeout_);
    start_ = node_->now();
    return BT::NodeStatus::RUNNING;
  }
  BT::NodeStatus onRunning() override {
    if (door_open_) return BT::NodeStatus::SUCCESS;
    if ((node_->now() - start_).seconds() > timeout_) return BT::NodeStatus::FAILURE;
    return BT::NodeStatus::RUNNING;
  }
  void onHalted() override {}
 private:
  rclcpp::Node::SharedPtr node_;
  rclcpp::Time start_;
  double timeout_ = 30.0;
  bool door_open_ = false;
};

BT_REGISTER_NODES(factory) {
  factory.registerNodeType<WaitForDoorOpen>("WaitForDoorOpen");
}

两条必须遵守的纪律。第一,黑板 key 要唯一且有文档,{path}、{goal} 这类默认 key 被多个子树共享,自己新增的 key 加前缀(如 door_retry_count)避免冲突。第二,自定义节点不能阻塞,onRunning 必须快速返回,长等待用计时器与状态机表达,不要在里面 sleep,否则会卡死整个执行器。bt_navigator 的 XML 路径由参数 default_nav_to_pose_bt_xml 指定,产品里通常维护多套 XML(正常导航、充电、电梯、窄通道),运行时通过 NavigateToPose 的 behavior_tree 字段切换。

4. 代价地图:层、语义与更新

Nav2 有全局与局部两张代价地图,都由若干可插拔的层叠加而成。

global_costmap:
  global_costmap:
    ros__parameters:
      update_frequency: 1.0            # 全局图更新慢
      resolution: 0.05                 # 5 cm/格
      track_unknown_space: true
      plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
      obstacle_layer:
        observation_sources: scan
        scan: { topic: /scan, clearing: true, marking: true,
                raytrace_max_range: 3.5, obstacle_max_range: 3.0 }
      inflation_layer:
        inflation_radius: 0.55
        cost_scaling_factor: 3.0

local_costmap:
  local_costmap:
    ros__parameters:
      update_frequency: 10.0           # 局部图高频更新
      rolling_window: true             # 跟随机器人滚动
      width: 6                         # 6m × 6m
      plugins: ["voxel_layer", "inflation_layer"]

代价的语义是固定分级的,理解它才能读懂规划器行为:

代价分级(0~255):
  0             :空闲,可自由通过
  1~252         :膨胀产生的代价,越高越靠近障碍
  253 (INSCRIBED):内切圆会碰到障碍,不可通行
  254 (LETHAL)  :致命障碍,绝对不可通行
  255 (NO_INFORMATION):未知区域,取决于 track_unknown_space

全局图与局部图分工明确:全局图追求稳定与全环境覆盖,用静态层加低更新频率;局部图追求实时,用滚动窗口加体素层加高更新频率。局部图不做 raytrace 清除会让幽灵障碍残留,做了又可能把真实障碍误清,clearing 与 raytrace_max_range 是这组矛盾的调节旋钮。

5. 膨胀层与代价衰减调参

膨胀层是 Nav2 最难调的层,因为它同时决定「安全性」与「可通过性」。

三个几何量的关系(必须满足):
  inscribed_radius   :机器人内切圆半径(由 footprint 算出)
  inflation_radius   :代价扩散半径,通常 = 2~3 × inscribed_radius
  cost_scaling_factor:衰减指数,值越大代价下降越快

经验判据:
  狭窄通道可通行条件:inflation_radius < 通道宽度/2 - 机器人半径
  若环境有大量窄通道,只能减小 inflation_radius,
  改由局部控制器的避障与安全裕度兜底

cost_scaling_factor 的效果(inflation_radius = 0.55 m):
  1.0  :衰减极慢,距障碍 0.5m 处仍高代价 → 远离墙走
  3.0  :中等衰减,0.3m 处代价约 100 → 推荐起点
  10.0 :衰减极快,只有贴障处有代价 → 敢贴墙但易刮蹭

footprint 的配置有两种形式,选择会显著影响性能:

robot_radius: 0.35                     # 圆形:碰撞检测最快
# 或精确多边形:长条形机器人必须用
footprint: "[[0.35,0.25],[0.35,-0.25],[-0.35,-0.25],[-0.35,0.25]]"

长条形机器人(如叉车、清洁车)必须用多边形,用圆形近似会导致「明明能过的地方规划失败」。但多边形足迹让碰撞检测成本上升数倍,一个折中是「全局规划用圆形近似、局部控制用精确多边形」。

6. 全局规划器与插件选型

全局规划器在全局代价地图上找一条从起点到目标的路径,输出路径点序列。

planner_server:
  ros__parameters:
    expected_planner_frequency: 1.0
    planner_plugins: ["GridBased"]
    GridBased:
      plugin: "nav2_navfn_planner/NavfnPlanner"
      tolerance: 0.5                   # 目标容差
      use_astar: true                  # true 用 A*,false 用 Dijkstra
      allow_unknown: true              # 允许穿越未知区域
Nav2 内置全局规划器对比:
  NavFn           :Dijkstra/A* 在代价栅格上搜索,快、成熟,不考虑运动学
  Smac 2D         :状态格搜索,把朝向纳入状态,路径更可执行
  Smac Hybrid-A*  :支持阿克曼与差速的运动学约束(最低转弯半径)
  Smac State Lattice:用预计算运动原语,路径最平滑,适合阿克曼
  Theta*          :任意角度路径,减少栅格锯齿

选型规则:
  差速轮 + 通道宽 :NavFn 够用,最快
  差速轮 + 通道窄 :Smac 2D,路径更贴运动学
  阿克曼/车式     :Smac Hybrid-A* 或 State Lattice(必需)

allow_unknown 是最容易踩坑的参数。设为 true 时规划器愿意穿越未知区域,在建图不完整的环境里能出路径,但可能一头扎进实际存在的障碍;设为 false 时路径只走已知安全区,更保守但可能无解。建图阶段设 true,运行阶段设 false 是常见策略。tolerance 决定「多接近目标算到达」,太小(0.1 m)时若目标点在障碍膨胀区内会永远规划失败,太大(1.0 m)时机器人停在离目标很远的地方,经验值是机器人半径的量级。

7. 局部控制器与速度平滑

局部控制器以高频(10~20 Hz)跟踪全局路径并输出速度指令,是避障的最后一道防线。

controller_server:
  ros__parameters:
    controller_frequency: 20.0
    failure_tolerance: 0.3             # 超过 0.3s 无有效控制则失败
    progress_checker_plugin: "progress_checker"
    goal_checker_plugins: ["general_goal_checker"]
    controller_plugins: ["FollowPath"]
    FollowPath:
      plugin: "nav2_mppi_controller::MPPIController"
      time_steps: 56
      model_dt: 0.05                   # 预测步长 50ms
      batch_size: 2000                 # 每周期采样轨迹数
      vx_max: 0.8
      wz_max: 1.9
      motion_model: "DiffDrive"
局部控制器对比:
  DWB     :DWA 的 ROS2 版,采样速度空间+评分,参数直观,调参量大
  TEB     :时间弹性带,把路径当橡皮筋优化,适合阿克曼与窄通道
  RPP     :纯追踪,简单、平滑、无避障
  MPPI    :模型预测路径积分,采样+加权,避障与平滑俱佳,算力高
  Graceful:优雅控制,适合全向移动

选型:差速+算力足用 MPPI(当前社区默认推荐);阿克曼用 TEB 或 MPPI CarLike;
      极简场景用 RPP(避障交给代价地图)

velocity_smoother 是独立于控制器的一层,对 cmd_vel 做限幅与低通滤波。它的价值是「把控制器的理想指令变成底盘能执行的指令」:限制最大加速度、限制速度变化率、统一不同频率的指令源。核心参数是 max_velocity(如 [0.8, 0.0, 1.9],对应 vx/vy/wz)、max_accel、max_decel 与 feedback(OPEN_LOOP 或 CLOSED_LOOP)。不要用控制器内部的限幅替代 velocity_smoother:控制器限幅针对自身算法,smoother 是面向底盘的统一保护层。

8. 恢复行为、进度检查与目标检查

恢复行为是「正常路径走不通时的兜底」,是 Nav2 可靠性的关键。

behavior_server 内置恢复行为:
  Spin            :原地旋转,用于重新观测、脱离局部极小
  BackUp          :后退固定距离,用于脱离贴障状态
  DriveOnHeading  :沿指定朝向直行一段
  Wait            :原地等待,用于让行人先过
  AssistedTeleop  :人工遥操作辅助脱困

进度检查器:若在 movement_time_allowance 秒内移动距离小于
  required_movement_radius,判定为卡住并触发恢复。典型:0.5m / 10s
目标检查器:用 xy_goal_tolerance 与 yaw_goal_tolerance 判断到达,
  到达后需在容差内稳定 stopped_velocity_tolerance 秒才确认
progress_checker:
  plugin: "nav2_controller::SimpleProgressChecker"
  required_movement_radius: 0.5
  movement_time_allowance: 10.0
general_goal_checker:
  plugin: "nav2_controller::SimpleGoalChecker"
  xy_goal_tolerance: 0.15
  yaw_goal_tolerance: 0.15
  stateful: true                       # 到达后保持,避免抖动

恢复行为的编排逻辑在行为树里而不在服务器里,这意味着「什么情况用什么恢复」是策略问题。常见编排是「清除代价地图 → 旋转 → 后退 → 等待 → 重新规划」,重试若干次后放弃并上报。恢复行为不能无限重试,RecoveryNode 的重试次数必须有限,否则机器人会永远转圈。另一个常见现场问题是恢复行为本身触发安全:BackUp 后退时若后方有人会撞上去,因此应配合后向传感器与碰撞监视器使用。

9. 生命周期、启动顺序与参数组织

Nav2 的服务器都是生命周期节点,由 lifecycle_manager 统一驱动,启动顺序有严格依赖。

推荐启动顺序与依赖:
  1. map_server      激活(先有地图)
  2. amcl            激活(发布 map->odom)
  3. planner_server  激活(需全局代价地图)
  4. controller_server 激活(需局部代价地图)
  5. behavior_server 激活
  6. bt_navigator    激活(最后,依赖以上全部)
  7. waypoint_follower、smoother 等按需激活
lifecycle_manager:
  ros__parameters:
    autostart: true
    node_names: ["map_server", "amcl", "planner_server",
                 "controller_server", "behavior_server", "bt_navigator"]
    bond_timeout: 4.0

bond_timeout 是容易被忽视的可靠性机制:lifecycle_manager 与各服务器之间维持一个心跳(bond),若某服务器崩溃或卡死,bond 断开,manager 会尝试重启或触发降级。这个超时不能设太短,嵌入式平台启动慢时误判会导致反复重启。参数组织上强烈建议分层覆盖:nav2_bringup 的默认参数作为基线,产品参数用独立 YAML 在 launch 里覆盖,绝不直接改上游文件,这样升级 Nav2 版本时能清楚看到自己改了哪些值。

10. 过滤器:keepout、速度限制与安全监视

Nav2 的过滤器机制允许在地图上叠加「业务规则」,而不改算法代码。

代价地图过滤器(costmap_filter_info_server):
  Keepout Filter    :指定区域标记为不可通行(禁区、维修区)
  Speed Limit Filter:指定区域限制最大速度(人行区、门口)
  数据来源:掩膜图像(PGM/PNG)+ YAML 元数据

碰撞监视器(collision_monitor):
  独立于代价地图,用多边形区域 + 实时传感器数据判断碰撞风险,
  可发布停止/减速指令,响应比代价地图快,是安全功能的正确落点
collision_monitor:
  ros__parameters:
    base_frame_id: "base_footprint"
    cmd_vel_in_topic: "cmd_vel_smoothed"
    cmd_vel_out_topic: "cmd_vel"
    polygons: ["StopZone"]
    StopZone:
      type: "polygon"
      points: "[[0.6,0.6],[0.6,-0.6],[-0.6,-0.6],[-0.6,0.6]]"
      action_type: "stop"
      slowdown_ratio: 0.3

collision_monitor 与代价地图的分工是「快与慢」:代价地图更新周期是 100 ms 量级,用于规划;collision_monitor 直接读传感器,响应在毫秒量级,用于最后的安全兜底。安全相关的停止逻辑应放在 collision_monitor,而不是指望局部规划器及时反应。Keepout 区域的掩膜要用与地图相同的分辨率与原点,否则区域会错位,元数据里的 origin、resolution、negate 三个字段必须与地图一致。

11. 与 SLAM 和 AMCL 的接口

Nav2 消费定位结果,定位的质量直接决定导航的质量,接口只有两处:/map 话题与 map → odom 变换。

TF 树(Nav2 期望的结构):
  map ──(定位发布)──> odom ──(里程计发布)──> base_link
  map → odom :由 amcl 或 SLAM 发布,代表「定位对里程计的修正」
  odom → base_link:由底盘发布,连续但会漂移
  分离的意义:局部平滑(odom)与全局准确(map)解耦,
  定位跳变时只影响 map→odom,不会让局部控制突然抖动

两种定位模式:
  建图模式:slam_toolbox 发布 map→odom 与 /map
  导航模式:amcl 在已知地图上发布 map→odom,map_server 提供 /map

建图与导航不能同时开:两个节点都发布 map → odom 会导致 TF 冲突。切换的正确流程是「SLAM 保存地图 → 关闭 SLAM → 启动 map_server + AMCL」。

ros2 run nav2_map_server map_saver_cli -f /etc/robot/maps/factory \
  --ros-args -p save_map_timeout:=10000.0
ros2 launch nav2_bringup localization_launch.py \
  map:=/etc/robot/maps/factory.yaml use_sim_time:=false

地图更新是长期运行机器人的必修课。环境变化(货架移位、新增隔断)后旧地图会误导规划。做法有两条:定期重新建图并替换地图文件;或让 AMCL 在定位的同时用激光数据对静态地图做局部更新(这一路径见SLAM 与定位建图 )。地图的版本与生效时间必须纳入管理,否则「用错地图版本」会成为难以复现的现场问题。

12. 仿真回归验证与现场调参

Nav2 的参数改动必须经过回归验证,否则改一处坏一处。

回归验证的最小场景集:
  1. 空旷直行(验证基本跟踪与速度平滑)
  2. 窄通道通过(验证膨胀半径与通道判据)
  3. 动态障碍绕行(验证局部避障)
  4. 目标不可达(验证恢复行为与放弃逻辑)
  5. 定位跳变注入(验证鲁棒性)
  6. 长时间运行(验证内存与地图一致性)

记录四个指标:到达率、到达时间、重规划次数、恢复触发次数
# 简化的回归断言:一次导航任务的成功与效率
def test_narrow_corridor_navigation():
    nav = BasicNavigator()
    nav.setInitialPose(start_pose)
    nav.waitUntilNav2Active()
    nav.goToPose(goal_pose)
    assert nav.isTaskComplete(), "导航未完成"
    assert nav.getResult().error_code == 0
    assert stats.replans < 20          # 重规划次数在阈值内
    assert stats.recoveries <= 2       # 恢复次数在阈值内

现场调参的顺序建议固定为:footprint → 膨胀 → 规划器容差 → 控制器速度与加速度 → 恢复行为阈值。这个顺序的原因是前面的决定「哪里能走」,后面的决定「怎么走」,先调后面的会掩盖前面的问题。回归验证在仿真里用重放数据做,把最小场景集自动化成 pytest 断言,每次改参都跑一遍。

ros2 topic echo /plan --once                     # 看全局路径是否合理
ros2 topic echo /local_costmap/costmap --once    # 看局部代价地图
ros2 topic hz /cmd_vel                           # 控制频率是否达标
ros2 topic echo /behavior_server/transition_event # 恢复行为何时触发

权衡取舍

决策选 A选 B
全局规划器NavFn:快、差速轮够用Smac Hybrid-A*:阿克曼必需
局部控制器MPPI:平滑避障好、算力高DWB:参数直观、算力低
代价地图形状圆形:快、适合圆机器人多边形:精确、长条机器人必需
膨胀半径大:安全、窄道过不去小:能过窄道、靠局部避障兜底
未知区域允许穿越:建图期能出路径禁止穿越:运行期更安全
定位来源SLAM:建图期AMCL:导航期、需已有地图
安全兜底局部规划器:集成度高collision_monitor:响应快、独立

核心原则:策略放行为树,算法放插件,安全放独立层。把任务逻辑写进行为树 XML,把规划/控制算法通过插件参数配置,把与安全相关的停止放在 collision_monitor。三层职责清晰,改一层不会意外影响另外两层。

常见坑清单

  1. footprint 用圆形近似长条机器人,明明能过的窄道规划失败——长条机器人必须用多边形足迹。
  2. 膨胀半径大于通道宽度一半,狭窄区域永远无解——按通道宽度定半径,余量交给局部避障。
  3. allow_unknown 运行期仍为 true,机器人扎进未知区域的真实障碍——建图期 true、运行期 false。
  4. SLAM 与 AMCL 同时发布 map → odom,TF 树冲突——切换定位模式必须先关旧节点。
  5. 恢复行为无限重试,机器人原地转圈永不放弃——RecoveryNode 的重试次数必须有限。
  6. progress_checker 阈值过松,机器人卡住十几秒才触发恢复——按实际移动能力设定半径与时长。
  7. keepout 掩膜分辨率与原地图不一致,禁区错位——掩膜的 origin/resolution 必须与地图相同。
  8. 只靠局部规划器做安全停止,响应不及时——安全停止用 collision_monitor。
  9. 直接用 cmd_vel 不经 velocity_smoother,加速度超底盘能力——smoother 是统一保护层。
  10. 改参数不做回归,修好一个场景退化另一个——维护最小场景集并自动化断言。

小结

Nav2 的骨架是「一组生命周期服务器 + 行为树编排 + 两层代价地图」。理解数据流(谁依赖谁)与职责边界(策略、算法、安全三层)比记住参数更重要。行为树是产品化最需要改动的部分,代价地图与膨胀是现场调参最花时间的部分,定位接口是最容易被误判为 Nav2 问题的部分。

下一步建议沿两条线深入:定位侧深入 AMCL 与地图更新的细节,理解它们如何影响导航质量;仿真侧读机器人仿真 ,把回归验证搭起来。全局与局部规划算法本身见运动规划与轨迹优化 ,Nav2 只是这些算法的一个工程封装。

最后一句经验:任何「导航行为诡异」的问题,先看 TF 树与定位质量,再怀疑 Nav2 参数。九成的诡异行为根因在 map → odom 的跳变或地图过期,而不在规划器。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「机器人」更多文章

  1. 机器人实时控制与嵌入式
  2. ROS 2 通信与 QoS
  3. 足式与人形机器人运动控制