25 实践课-Na 导航入门与导航控制节点开发
Nav2 导航入门与导航控制节点开发
关联:索引
- 推荐系统:Ubuntu 22.04 + ROS2 Humble。
- 依赖(示例口径):
nav2_msgs、geometry_msgs、action_msgs、std_srvs、rclpy(本讲用 Fake 动作服务器,不依赖底盘/仿真)。
0)快速自检:先确认接口依赖是否存在(证据优先)
source /opt/ros/humble/setup.bash
ros2 pkg list | grep -E "nav2_msgs|geometry_msgs|action_msgs" | head
逐行解释:
ros2 pkg list:列出当前环境可见的 ROS2 包。grep -E ...:筛出本讲用到的关键接口包名。head:保留关键证据即可。
如果查不到(常见于未安装),按需安装(示例,择一即可,以你机器实际可安装包为准):
sudo apt update
sudo apt install -y ros-humble-navigation2
逐行解释:
-
apt update:更新软件源索引。 -
apt install ...:安装 Nav2 相关元包(以你机器实际可安装包为准)。 -
路径 A(真实 Nav2,完整体验):你有底盘(真机或仿真),能提供
odom/TF,并消费cmd_vel。此时可以启动 Nav2 bringup,用地图/定位/代价地图/规划器/控制器跑通“真正导航”。
本当前采用:路径 B(Fake NavigateToPose)。
迁移原则(从 B 到 A):
- 控制节点
agv_nav_controller不需要推翻,只要把action_name等接口名对齐真实系统,并用真实 Nav2 替换 Fake server。
导航任务 = Action(Goal → Feedback(多次) → Result) + Cancel
三条“零基础必背”:
- 导航是长任务,所以必须能看到过程反馈(feedback),并且必须允许取消(cancel)。
- 你的“导航控制节点”本质是一个动作客户端:订阅 goal → 发 action goal → 读反馈 → 等结果 → 提供取消入口 → 输出证据状态。
1)启动 Fake 动作服务器(必须完成)
本节目标:在完全没有 AGV/仿真的情况下,仍然让学生具备“动作客户端开发”的前置证据——动作存在、可反馈、可取消、可返回结果。
创建 Fake 动作服务器包(Python):
source /opt/ros/humble/setup.bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create sorting_nav2_fake --build-type ament_python --dependencies rclpy nav2_msgs geometry_msgs action_msgs
建议文件路径:
~/ros2_ws/src/sorting_nav2_fake/sorting_nav2_fake/fake_navigate_to_pose_server.py
入口点配置(setup.py):
entry_points={
'console_scripts': [
'fake_navigate_to_pose_server = sorting_nav2_fake.fake_navigate_to_pose_server:main',
],
},
Fake 动作服务器代码(可直接复制):
import time # 用于模拟“导航需要时间”,并控制 feedback 的发布频率
import rclpy # ROS 2 Python 客户端库(rclpy)
from rclpy.action import ActionServer # 动作服务端(接收 goal / 发送 feedback / 返回 result)
from rclpy.action import CancelResponse # 取消请求的应答类型(接受/拒绝)
from rclpy.action import GoalResponse # goal 请求的应答类型(接受/拒绝)
from rclpy.node import Node # ROS 2 节点基类
from nav2_msgs.action import NavigateToPose # Nav2 的 NavigateToPose 动作接口定义
class FakeNavigateToPoseServer(Node):
"""
Fake NavigateToPose 动作服务器(课堂用,无底盘/无仿真也可运行)
作用:
- 提供与 Nav2 同名同类型的动作接口:/navigate_to_pose (nav2_msgs/action/NavigateToPose)
- 让学生在“没有机器人运动”的条件下,仍能练习 Action 的完整交互:
goal → feedback(多次) → result,以及 cancel
注意:
- 这不是导航算法,也不会让机器人动起来。
- 这里只模拟“距离逐步减少”,便于产生可观察的 feedback。
"""
def __init__(self):
super().__init__('fake_navigate_to_pose_server')
# ActionServer 的第三个参数是 action 名称。
# 真实 Nav2 中通常为 'navigate_to_pose'(对应 /navigate_to_pose)。
self._server = ActionServer(
self,
NavigateToPose,
'navigate_to_pose',
execute_callback=self.execute_callback,
goal_callback=self.goal_callback,
cancel_callback=self.cancel_callback,
)
def goal_callback(self, goal_request: NavigateToPose.Goal) -> GoalResponse:
# goal_callback:在执行前做一次“是否接受目标”的快速判断。
# 真实系统里可做更多校验:frame_id 合法性、坐标范围、速度限制等。
frame_id = getattr(getattr(goal_request, "pose", None), "header", None).frame_id
if not frame_id:
# 没有 frame_id 直接拒绝,帮助学生理解“接口契约”与“证据化失败”。
return GoalResponse.REJECT
return GoalResponse.ACCEPT
def cancel_callback(self, goal_handle) -> CancelResponse:
# cancel_callback:收到取消请求时,决定是否接受取消。
# 课堂直接接受,确保 cancel 流程一定可演示。
return CancelResponse.ACCEPT
def execute_callback(self, goal_handle):
# execute_callback:真正执行“导航任务”的逻辑。
# 这里用 while 循环模拟“还在走”,每 0.1 秒发布一次 feedback。
feedback = NavigateToPose.Feedback()
distance = 2.0
while distance > 0.0:
if goal_handle.is_cancel_requested:
# 一旦检测到取消请求:
# 1) 标记为 canceled;2) 返回一个 Result(这里填入可读的 error_msg)
goal_handle.canceled()
result = NavigateToPose.Result()
result.error_code = 0
result.error_msg = "canceled (fake)"
return result
# “距离逐步减少”只是为了让 feedback 有变化,方便验收截图/日志对比。
distance = max(0.0, distance - 0.1)
feedback.distance_remaining = float(distance)
feedback.number_of_recoveries = 0
goal_handle.publish_feedback(feedback)
# 课堂写法:time.sleep 会阻塞当前执行线程,用于最小可复现。
# 生产环境通常用更可控的异步/定时器模型,避免长时间阻塞执行器线程。
time.sleep(0.1)
# 任务正常结束:标记 succeed 并返回 Result
goal_handle.succeed()
result = NavigateToPose.Result()
result.error_code = 0
result.error_msg = "succeeded (fake)"
return result
def main(args=None):
# rclpy.init:初始化 ROS 2 通信(必须先 init 才能创建 Node)
rclpy.init(args=args)
node = FakeNavigateToPoseServer()
try:
# spin:进入事件循环,处理 action goal、cancel 等回调
rclpy.spin(node)
finally:
# 退出时释放资源
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
编译并启动 Fake 动作服务器:
cd ~/ros2_ws
colcon build --symlink-install
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run sorting_nav2_fake fake_navigate_to_pose_server
动作存在证据(带类型):
ros2 action list -t | grep -E "navigate_to_pose|NavigateToPose"
用命令行直接发一次目标(得到 feedback 与 result 证据):
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose "{
pose: {header: {frame_id: 'map'}, pose: {position: {x: 1.0, y: 0.5, z: 0.0}, orientation: {w: 1.0}}}
}" --feedback
取消证据(取消当前动作目标):
ros2 action cancel /navigate_to_pose
- 看不到 navigate_to_pose:先确认 Fake server 已运行,再执行
source install/setup.bash,最后ros2 action list -t查证据。 - send_goal 没有反馈:先确认
ros2 action list -t能看到类型为nav2_msgs/action/NavigateToPose,再检查 action 名称是否写成了/navigate_to_pose。 - cancel 没效果:先确认你取消的是同一个 action 名称(
ros2 action cancel /navigate_to_pose),并观察服务端是否输出 canceled 日志。
ros2 interface show nav2_msgs/action/NavigateToPose
要求:你只能按 ros2 interface show 看到的字段写代码与排错,不能“脑补字段”。
1)创建导航控制包(Python)
source /opt/ros/humble/setup.bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create sorting_agv_nav --build-type ament_python --dependencies rclpy nav2_msgs geometry_msgs action_msgs std_msgs std_srvs
建议文件路径:
~/ros2_ws/src/sorting_agv_nav/sorting_agv_nav/agv_nav_controller.py
入口点配置(~/ros2_ws/src/sorting_agv_nav/setup.py):
entry_points={
'console_scripts': [
'agv_nav_controller = sorting_agv_nav.agv_nav_controller:main',
],
},
import rclpy # ROS 2 Python 客户端库
from rclpy.action import ActionClient # 动作客户端:发送 goal、接收 feedback、获取 result、发送 cancel
from rclpy.node import Node # ROS 2 节点基类
from action_msgs.msg import GoalStatus # Action 通用的状态枚举(SUCCEEDED / ABORTED / CANCELED ...)
from geometry_msgs.msg import PoseStamped # 目标点类型:必须带 frame_id 的位姿
from nav2_msgs.action import NavigateToPose # Nav2 NavigateToPose 动作定义(课堂对接 Fake server/真实 Nav2 均可)
from std_msgs.msg import String # 课堂用 String 承载状态证据(工程建议后续换自定义 msg)
from std_srvs.srv import Trigger # 最小取消入口:ros2 service call 即可触发
class AgvNavController(Node):
"""
导航控制节点(动作客户端)
设计目标(课堂最小闭环版):
- 上层输入:订阅 PoseStamped(goal_topic)
- 内部执行:调用 NavigateToPose Action(action_name)
- 证据输出:发布 /sorting/agv/state(state_topic),包含 request_id/phase/step/msg
- 最小取消:提供 Trigger 服务(cancel_service)
迁移到真实 Nav2:
- 保持本节点逻辑不变
- 只需确保 action_name/topic/service 名称与真实系统一致
"""
def __init__(self):
super().__init__('agv_nav_controller')
# 参数化:不要把 action/topic/service 名称写死在代码里,便于换环境复用。
# 课堂默认:对接 Fake server 的 navigate_to_pose。
self.declare_parameter("action_name", "navigate_to_pose")
self.declare_parameter("goal_topic", "/sorting/agv/goal")
self.declare_parameter("state_topic", "/sorting/agv/state")
self.declare_parameter("cancel_service", "/sorting/agv/cancel")
# wait_for_server_timeout_sec:等待动作服务端就绪的超时(避免无限等待)
self.declare_parameter("wait_server_timeout_sec", 2.0)
# qos_depth:简单起见统一用深度队列;真实系统应按数据类型选择 QoS
self.declare_parameter("qos_depth", 10)
# 读取参数值并缓存成成员变量,后续使用更清晰。
self._action_name = str(self.get_parameter("action_name").value)
self._goal_topic = str(self.get_parameter("goal_topic").value)
self._state_topic = str(self.get_parameter("state_topic").value)
self._cancel_service = str(self.get_parameter("cancel_service").value)
self._wait_server_timeout_sec = float(self.get_parameter("wait_server_timeout_sec").value)
self._qos_depth = int(self.get_parameter("qos_depth").value)
# ActionClient:连接到动作服务端(这里是 Fake server 或真实 Nav2)
self._client = ActionClient(self, NavigateToPose, self._action_name)
# 订阅目标点:收到目标点才启动一次动作任务(避免节点启动就立刻发 goal)
self._goal_sub = self.create_subscription(PoseStamped, self._goal_topic, self.on_goal, self._qos_depth)
# 取消服务:通过 ros2 service call 触发取消,方便课堂给证据
self._cancel_srv = self.create_service(Trigger, self._cancel_service, self.on_cancel)
# 状态发布:统一的证据通道(终端 echo 即可验收)
self._state_pub = self.create_publisher(String, self._state_topic, self._qos_depth)
# 证据字段:
# - seq:本节点内部递增序号,便于截图时定位“第几条状态”
# - request_id:每次收到新目标就 +1,贯穿一次任务全过程
self._seq = 0
self._request_id = 0
self._active_request_id = None
# 记录最近一次 goal_handle,用于取消
self._last_goal_handle = None
# busy:课堂做一个简单串行执行(一次只跑一个目标),避免并发状态混乱
self._busy = False
def now_sec(self) -> float:
# 使用 ROS Clock(兼容 use_sim_time 场景;课堂可当作“统一时间戳”)
return self.get_clock().now().nanoseconds / 1e9
def status_text(self, status: int) -> str:
# 把 GoalStatus 的数字状态码映射成可读字符串,便于学生截图/复盘。
if status == GoalStatus.STATUS_UNKNOWN:
return "UNKNOWN"
if status == GoalStatus.STATUS_ACCEPTED:
return "ACCEPTED"
if status == GoalStatus.STATUS_EXECUTING:
return "EXECUTING"
if status == GoalStatus.STATUS_CANCELING:
return "CANCELING"
if status == GoalStatus.STATUS_SUCCEEDED:
return "SUCCEEDED"
if status == GoalStatus.STATUS_CANCELED:
return "CANCELED"
if status == GoalStatus.STATUS_ABORTED:
return "ABORTED"
return str(status)
def publish_state(self, phase: str, step: str, message: str, request_id: int | None = None):
# phase:跨模块统一口径(idle/executing/success/failed/canceled)
# step:模块内细分阶段(received_goal/wait_server/send_goal/feedback/result/cancel ...)
# message:给“证据可复盘”的信息(比如错误原因、目标点、状态码)
self._seq += 1
rid = request_id if request_id is not None else self._active_request_id
msg = String()
msg.data = f"seq={self._seq} request_id={rid} ros_ts={self.now_sec():.3f} phase={phase} step={step} msg={message}"
self._state_pub.publish(msg)
# 同时打印日志:一份证据在 topic 和日志中都能看到
self.get_logger().info(msg.data)
def build_goal(self, pose: PoseStamped):
# 把输入 PoseStamped 转成 NavigateToPose.Goal
# 这里重新写 stamp,避免上游给的时间戳过旧导致服务端拒绝/异常(真实系统常见坑)。
goal = NavigateToPose.Goal()
p = PoseStamped()
p.header.frame_id = pose.header.frame_id
p.header.stamp = self.get_clock().now().to_msg()
p.pose = pose.pose
goal.pose = p
return goal
def on_goal(self, pose_msg: PoseStamped):
# 订阅回调:收到新目标点就启动一次动作调用。
if self._busy:
# 课堂简化:忙时直接拒绝新目标(避免并发)
self.publish_state("executing", "busy", "ignored new goal because controller is busy")
return
# 新目标:生成新的 request_id,并作为“贯穿证据”的主键
self._request_id += 1
self._active_request_id = self._request_id
self.publish_state(
"executing",
"received_goal",
f"frame={pose_msg.header.frame_id} x={pose_msg.pose.position.x:.3f} y={pose_msg.pose.position.y:.3f}",
request_id=self._active_request_id,
)
# 等待动作服务端就绪(Fake server 或真实 Nav2);设置超时,避免课堂“卡死”
self.publish_state("executing", "wait_server", f"waiting for {self._action_name} action server")
if not self._client.wait_for_server(timeout_sec=self._wait_server_timeout_sec):
self.publish_state("failed", "wait_server", "action server not available")
self._active_request_id = None
return
# 构造 goal 并异步发送
goal = self.build_goal(pose_msg)
self._busy = True
self.publish_state(
"executing",
"send_goal",
f"frame={pose_msg.header.frame_id} x={pose_msg.pose.position.x:.3f} y={pose_msg.pose.position.y:.3f}",
request_id=self._active_request_id,
)
# send_goal_async:异步发送 goal,避免阻塞执行器线程
# feedback_callback:服务端每发一次 feedback,就触发 on_feedback
future = self._client.send_goal_async(goal, feedback_callback=self.on_feedback)
# future 完成后会得到 goal_handle(accepted/rejected)
future.add_done_callback(self.on_goal_response)
def on_goal_response(self, future):
# 处理 send_goal_async 的返回(goal_handle)
try:
goal_handle = future.result()
except Exception as e:
self._busy = False
self.publish_state("failed", "send_goal", f"send_goal failed: {type(e).__name__}: {e}")
self._active_request_id = None
return
if not goal_handle.accepted:
# 目标被拒绝:常见原因包括 frame_id 为空、参数不合法等
self._busy = False
self.publish_state("failed", "goal_response", "goal rejected")
self._active_request_id = None
return
# 目标被接受:保存 goal_handle,用于后续 cancel
self._last_goal_handle = goal_handle
self.publish_state("executing", "goal_response", "goal accepted")
# get_result_async:异步等待最终结果(SUCCEEDED/ABORTED/CANCELED)
result_future = goal_handle.get_result_async()
result_future.add_done_callback(self.on_result)
def on_result(self, future):
# 处理最终结果(Result + status)
self._busy = False
try:
result = future.result()
except Exception as e:
self.publish_state("failed", "result", f"get_result failed: {type(e).__name__}: {e}")
self._active_request_id = None
self._last_goal_handle = None
return
# result.status:GoalStatus 状态码(不是 NavigateToPose.Result 的字段)
status = int(getattr(result, "status", 0))
status_text = self.status_text(status)
# result.result:NavigateToPose.Result(含 error_code / error_msg)
nav_result = getattr(result, "result", None)
error_code = getattr(nav_result, "error_code", None) if nav_result is not None else None
error_msg = getattr(nav_result, "error_msg", None) if nav_result is not None else None
# 把动作层状态码映射为课堂统一 phase
if status == GoalStatus.STATUS_SUCCEEDED:
phase = "success"
elif status == GoalStatus.STATUS_CANCELED:
phase = "canceled"
else:
phase = "failed"
# 拼装可读结果文本,作为证据输出
parts = [f"status={status_text}"]
if error_code is not None:
parts.append(f"error_code={error_code}")
if error_msg:
parts.append(f"error_msg={error_msg}")
self.publish_state(phase, "result", " ".join(parts))
# 清理一次任务的上下文
self._active_request_id = None
self._last_goal_handle = None
def on_feedback(self, msg):
# feedback 回调:尽量输出关键字段,避免日志过长难截图
fb = msg.feedback
parts = []
distance_remaining = getattr(fb, "distance_remaining", None)
if distance_remaining is not None:
parts.append(f"distance_remaining={float(distance_remaining):.3f}")
recoveries = getattr(fb, "number_of_recoveries", None)
if recoveries is not None:
parts.append(f"recoveries={int(recoveries)}")
if not parts:
parts.append(str(fb))
self.publish_state("executing", "feedback", " ".join(parts))
def on_cancel(self, request, response):
# Trigger 服务回调:把“取消”做成可 CLI 触发、可给证据的入口
if self._last_goal_handle is None:
response.success = False
response.message = "no active goal"
self.publish_state("idle", "cancel", "no active goal to cancel")
return response
self.publish_state("executing", "cancel", "cancel requested")
# cancel_goal_async:向服务端发送取消请求(异步)
cancel_future = self._last_goal_handle.cancel_goal_async()
cancel_future.add_done_callback(self.on_cancel_done)
response.success = True
response.message = "cancel sent"
return response
def on_cancel_done(self, future):
# 取消请求的结果回调:返回 cancel_response(含 return_code)
try:
cancel_response = future.result()
except Exception as e:
self.publish_state("failed", "cancel", f"cancel failed: {type(e).__name__}: {e}")
return
self.publish_state("executing", "cancel", f"return_code={cancel_response.return_code}")
def main(args=None):
# 入口函数:初始化 → 创建节点 → spin → 退出清理
rclpy.init(args=args)
node = AgvNavController()
try:
rclpy.spin(node)
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
3)参数文件示例(推荐)
agv_nav_controller:
ros__parameters:
action_name: navigate_to_pose
goal_topic: /sorting/agv/goal
state_topic: /sorting/agv/state
cancel_service: /sorting/agv/cancel
wait_server_timeout_sec: 2.0
qos_depth: 10
终端 A(先启动 Fake 动作服务器,提供 navigate_to_pose):
cd ~/ros2_ws
colcon build --symlink-install
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run sorting_nav2_fake fake_navigate_to_pose_server
编译与运行:
cd ~/ros2_ws
colcon build --symlink-install
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run sorting_agv_nav agv_nav_controller --ros-args --params-file /path/to/agv_nav_params.yaml
证据 1:状态通道可见
ros2 topic echo /sorting/agv/state
证据 2:下发目标(与参数文件保持一致)
ros2 topic pub --once /sorting/agv/goal geometry_msgs/msg/PoseStamped "{
header: {frame_id: 'map'},
pose: {
position: {x: 1.0, y: 0.5, z: 0.0},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
}"
证据 3:取消请求
ros2 service call /sorting/agv/cancel std_srvs/srv/Trigger {}
作业(布置)
1)提交动作就绪证据(截图或终端输出均可):ros2 action list -t(能看到 navigate_to_pose,类型为 nav2_msgs/action/NavigateToPose)。
2)提交 AGV 导航控制节点完整代码(订阅目标、调用 NavigateToPose、输出 state、支持 cancel),并附 150 字说明:你的 request_id/phase/step 设计如何保证可追溯。
3)提交控制节点测试证据:至少包含 1 次目标下发与 1 次取消(服务调用)终端输出,且能在 /sorting/agv/state 中看到过程反馈与最终结果。
4)提交一次失败排查记录(200–300 字):按“现象→证据→定位→修复→复测”结构,问题可选“动作 server 不可用/目标被拒绝/反馈无输出/取消无效/依赖缺失”等任意一种,必须有证据输出。
说明:
- 本讲“路径 B(Fake NavigateToPose)”不依赖静态地图。
- 这份静态地图留作后续有底盘/仿真时接入真实 Nav2 使用。
迁移指南:从路径 B 接入真实 Nav2 + 地图(路径 A)
你可以按“先接入真实动作接口 → 再接入地图/定位/底盘”的顺序迁移,风险最低:
- 保留
sorting_agv_nav的agv_nav_controller不变,先停止 Fake server。 - 启动真实 Nav2(以及底盘/仿真),用
ros2 action list -t确认真实系统存在navigate_to_pose(类型为nav2_msgs/action/NavigateToPose)。 - 修改
agv_nav_params.yaml(或运行时参数)把action_name对齐真实系统动作名(多数情况下仍为navigate_to_pose,但以ros2 action list -t证据为准)。 - 把
goal_topic/state_topic/cancel_service保持不变,确保上层接口不变,联调成本最低。 - 引入地图:把静态地图(
classroom_map.yaml+classroom_map.pgm或你的真实地图)放到可访问路径,并在 Nav2 bringup 时传入map:=/path/to/map.yaml。 - 确认定位与 TF:至少要有
map→odom→base_link链路;若用 AMCL,需要在 RViz2 里设置初始位姿;若用 SLAM,启动方式与参数不同。 - 用 RViz2 先点一次
2D Nav Goal验证真实导航可跑,再用agv_nav_controller发/sorting/agv/goal验证“控制节点→真实 Nav2”闭环。
在你的工作空间创建地图文件(复制粘贴即可):
mkdir -p ~/ros2_ws/maps
cd ~/ros2_ws/maps
1)地图 YAML:~/ros2_ws/maps/classroom_map.yaml
image: classroom_map.pgm
mode: trinary
resolution: 0.05
origin: [0.0, 0.0, 0.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196
2)地图 PGM:~/ros2_ws/maps/classroom_map.pgm
P2
# classroom_map.pgm (20x20) 0=occupied, 254=free
20 20
255
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 0
0 254 254 0 0 0 254 254 254 254 254 254 254 0 0 0 254 254 254 0
0 254 254 0 254 0 254 254 254 254 254 254 254 0 254 0 254 254 254 0
0 254 254 0 0 0 254 254 254 254 254 254 254 0 0 0 254 254 254 0
0 254 254 254 254 254 254 0 0 0 0 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 0 254 254 254 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 0 254 254 254 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 0 0 0 0 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 0
0 254 254 254 0 0 0 254 254 254 254 254 254 0 0 0 254 254 254 0
0 254 254 254 0 254 0 254 254 254 254 254 254 0 254 0 254 254 254 0
0 254 254 254 0 0 0 254 254 254 254 254 254 0 0 0 254 254 254 0
0 254 254 254 254 254 254 254 254 0 0 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 0 254 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 0 0 0 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 0
0 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 254 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
提示:
mode: trinary表示把地图当作黑白占用栅格使用;本示例中0为障碍、254为可通行区域。- 将来启动真实 Nav2 时,launch 的
map:=参数填写 YAML 文件路径即可,例如:map:=/home/<user>/ros2_ws/maps/classroom_map.yaml。