2.ROS2核心机制与命令行实操
ROS2 完整系统化使用手册
基准版本 :ROS2 Humble Hawksbill(LTS) / Ubuntu 22.04 LTS
兼容说明 :涉及 Iron、Rolling 等版本差异处单独标注
读者对象 :具备 Linux、C++/Python 基础,零 ROS/ROS2 基础的研发 / 算法 / 机器人工程师
手册定位 :从零基础入门 → 进阶开发 → 高阶工程化量产落地,所有命令、代码、配置 100% 可复制运行
行文范式:是什么 → 核心原理 → 适用场景 → 实操步骤 → 代码示例 → 踩坑点 + 工程最佳实践
全书目录
- 模块一:ROS2 基础认知与环境工程搭建(已完成)
- 模块二:ROS2 核心机制与命令行实操 (本文档)
- 2.1 六大核心机制深度讲解:节点、话题、服务、Action、参数、QoS
- 2.2 各通信机制原理、优缺点、适用业务场景对比
- 2.3 全套 ros2cli 命令行实操:查询、运行、监控、诊断、管理
- 2.4 自定义接口全流程:msg / srv / action 语法规范、编写、编译、调用
- 2.5 节点高级特性:命名空间、重映射、多节点管理、节点隔离机制
模块二:ROS2 核心机制与命令行实操
本模块目标:建立 ROS2 六大核心机制的完整心智模型,掌握全套
ros2cli命令,理解自定义接口全流程,掌握节点命名空间 / 重映射 / 多节点管理等高级特性。完成本模块后,读者应能:独立设计通信架构、熟练用
ros2cli调试、自定义 msg/srv/action、管理多节点系统。
2.1 六大核心机制深度讲解:节点、话题、服务、Action、参数、QoS
是什么 :ROS2 通信与计算的核心抽象为六大机制:节点(Node) 、话题(Topic) 、服务(Service) 、动作(Action) 、参数(Parameter) 、QoS(Quality of Service) 。节点是计算单元,话题/服务/Action 是通信机制,参数是配置机制,QoS 是通信质量保障机制。
核心原理:节点是 ROS2 程序的基本单位,承载所有通信端点(Publisher/Subscriber/Service/Client/Action)。话题/服务/Action 全部跑在 DDS 之上,参数跑在节点本地 + parameter 服务,QoS 是 DDS 的端到端策略。
2.1.1 节点(Node)
| 维度 | 说明 |
|---|---|
| 是什么 | ROS2 程序的基本计算单元,承载所有通信端点 |
| 核心原理 | 节点 = rclcpp::Node / rclpy.node.Node 实例,初始化 DDS Domain Participant,提供创建 Publisher/Subscriber/Service/Client/Action 的 API |
| 命名 | 节点名在 DDS 域内必须唯一,默认取可执行文件名 |
| 生命周期 | 普通节点常驻,Lifecycle 节点有 5 状态(unconfigured → inactive → active → finalized → unconfigured) |
| 多节点 | 同一进程可创建多个节点(rclcpp::Node 实例),但通常一进程一节点 |
| 通信端点 | 节点可创建任意数量的 Publisher/Subscriber/Service/Client/Action |
| 资源管理 | 节点析构时自动关闭所有端点,释放 DDS 资源 |
节点核心 API(C++ / Python) :
| API | C++ | Python | 说明 |
|---|---|---|---|
| 创建节点 | rclcpp::Node("name") |
Node("name") |
命名节点 |
| 创建 Publisher | create_publisher<T>(topic, qos) |
create_subscription(T, topic, cb, qos) |
发布端 |
| 创建 Subscriber | create_subscription<T>(topic, qos, cb) |
create_subscription(T, topic, cb, qos) |
订阅端 |
| 创建 Service | create_service<T>(srv, cb) |
create_service(T, srv, cb) |
服务端 |
| 创建 Client | create_client<T>(srv) |
create_client(T, srv) |
客户端 |
| 创建 Action Server | create_action_server<T>(action, cb) |
create_action_server(T, action, cb) |
动作服务端 |
| 创建 Action Client | create_action_client<T>(action) |
create_action_client(T, action) |
动作客户端 |
| 声明参数 | declare_parameter<T>(name, default) |
declare_parameter(name, default) |
参数声明 |
| 获取参数 | get_parameter(name) |
get_parameter(name) |
参数读取 |
| 定时器 | create_wall_timer(period, cb) |
create_timer(period, cb) |
周期回调 |
| 日志 | RCLCPP_INFO(logger, ...) |
self.get_logger().info(...) |
分级日志 |
高亮重点:
- 节点名唯一性:ROS2 同域内节点名必须唯一,重复启动失败。
- 节点是 DDS Participant 的封装:一个节点对应一个 DDS Domain Participant,资源开销约 5-10MB。
- 节点析构自动清理 :C++
Node析构 / Pythondestroy_node()自动关闭所有端点。- Lifecycle 节点 :量产系统用
rclcpp_lifecycle::LifecycleNode,支持热启停、状态查询、故障恢复。
2.1.2 话题(Topic)
| 维度 | 说明 |
|---|---|
| 是什么 | 命名数据通道,类型化,发布/订阅范式 |
| 核心原理 | 话题 = DDS Topic,DataWriter(Publisher)发布数据,DataReader(Subscriber)订阅数据,基于 DDS RTPS 协议 |
| 类型 | 消息类型(std_msgs/String / sensor_msgs/Image 等),类型不匹配则通信失败 |
| 命名 | 全小写 + 下划线,命名空间分层,如 /vehicle/chassis/cmd_vel |
| 方向 | 单向(发布者 → 订阅者),无请求/响应 |
| 适用 | 高频、连续、一对多、数据流(传感器、控制指令、状态) |
| QoS | 可靠性、持久性、历史、死线、生命周期等(见 2.1.6) |
话题核心特性:
| 特性 | 说明 |
|---|---|
| 一对多 | 一个发布者,多个订阅者,互不影响 |
| 异步 | 发布者不关心订阅者是否收到,fire-and-forget |
| 类型安全 | 消息类型必须完全一致,否则静默失败 |
| QoS 匹配 | 发送端与接收端 QoS 必须兼容,否则静默失败 |
| 命名空间 | 话题名含命名空间,/a/b/c 是完整名字 |
话题适用场景:
| 场景 | 消息类型 | QoS 建议 |
|---|---|---|
| 传感器数据(高频) | sensor_msgs/Image / sensor_msgs/LaserScan |
BEST_EFFORT + KEEP_LAST(5) |
| 控制指令(关键) | geometry_msgs/Twist |
RELIABLE + KEEP_LAST(1) |
| 状态(低频) | std_msgs/String / 自定义 |
RELIABLE + KEEP_LAST(1) + TRANSIENT_LOCAL |
| 事件(一次性) | std_msgs/Empty |
RELIABLE + KEEP_LAST(1) + TRANSIENT_LOCAL |
| TF 坐标 | tf2_msgs/TFMessage |
BEST_EFFORT + KEEP_LAST(10) |
2.1.3 服务(Service)
| 维度 | 说明 |
|---|---|
| 是什么 | 请求/响应通信,同步或异步 |
| 核心原理 | 服务 = DDS Request-Reply(基于 RTPS),客户端发请求,服务端处理并返回响应 |
| 类型 | 服务类型(std_srvs/Trigger / std_srvs/SetBool 等),类型不匹配则调用失败 |
| 命名 | 全小写 + 下划线,命名空间分层,如 /robot/joint/set_position |
| 方向 | 双向(请求 → 响应),一对一 |
| 适用 | 低频、请求/响应、命令查询(设置参数、查询状态、触发操作) |
| 超时 | 客户端可设超时(默认无限),超时抛异常 |
| 取消 | ROS2 Humble 支持请求取消(cancel_request) |
服务核心特性:
| 特性 | 说明 |
|---|---|
| 同步/异步 | C++ 客户端可同步(service->call())或异步(async_send_request()),Python 客户端用 async_send_request |
| 一对一 | 一个服务端,多个客户端(客户端轮询或广播) |
| 超时 | 客户端可设超时,超时抛 ServiceException |
| 取消 | ROS2 Humble 支持取消未完成的请求 |
| QoS | 请求与响应分别可配 QoS(Humble 起) |
服务适用场景:
| 场景 | 服务类型 | 说明 |
|---|---|---|
| 设置参数 | std_srvs/SetBool / 自定义 |
运行时改参数 |
| 触发操作 | std_srvs/Trigger |
一次性触发(拍照、校准) |
| 查询状态 | 自定义 | 查询节点状态、版本 |
| 控制命令 | geometry_msgs/SetTwist |
设置速度、模式 |
| 资源管理 | std_srvs/Empty |
资源申请/释放 |
2.1.4 动作(Action)
| 维度 | 说明 |
|---|---|
| 是什么 | 长时间运行的任务通信,带反馈、取消、结果 |
| 核心原理 | 动作 = 3 个话题 + 2 个服务的组合(ROS2 原生实现):goal(目标)、feedback(反馈)、result(结果)+ cancel(取消) |
| 类型 | 动作类型(nav2_msgs/Action/BTAction / 自定义),类型不匹配则失败 |
| 命名 | 全小写 + 下划线,命名空间分层,如 /robot/move_to |
| 方向 | 双向 + 反馈流(客户端 → 服务端 → 客户端) |
| 适用 | 长时间任务(移动、规划、抓取、建图) |
| 反馈 | 服务端可周期性发送反馈(进度、状态) |
| 取消 | 客户端可随时取消,服务端响应取消 |
动作核心特性(3 话题 + 2 服务) :
| 端点 | 类型 | 方向 | 说明 |
|---|---|---|---|
<action>/_action |
话题 | 客户端 → 服务端 | 目标(Goal) |
<action>/_feedback |
话题 | 服务端 → 客户端 | 反馈(Feedback) |
<action>/_result |
话题 | 服务端 → 客户端 | 结果(Result) |
<action>/_cancel |
服务 | 客户端 → 服务端 | 取消请求 |
<action>/_status |
话题 | 服务端 → 客户端 | 状态(Accepted/Executing/Succeeded/Aborted/Cancelled) |
动作适用场景:
| 场景 | 动作类型 | 说明 |
|---|---|---|
| 移动(Nav2) | nav2_msgs/Action/NavigateTo |
全局/局部导航 |
| 机械臂抓取 | 自定义 | 抓取/放置 |
| 建图 | nav2_msgs/Action/ComputeMap |
建图任务 |
| 充电 | 自定义 | 充电任务 |
| 长时间校准 | 自定义 | 手眼标定、IMU 校准 |
高亮重点:
- 动作是"长任务"的专用通信机制,比"服务 + 话题"组合更优雅、更强大。
- 动作有 5 种状态 :
ACCEPTED/EXECUTING/SUCCEEDED/ABORTED/CANCELLED。- 动作可取消 :客户端
cancel_goal(),服务端响应取消。- 动作有反馈:服务端周期性发送进度,客户端实时掌握。
- 动作底层是 3 话题 + 2 服务 :可用
ros2 topic list看到<action>/_action等。
2.1.5 参数(Parameter)
| 维度 | 说明 |
|---|---|
| 是什么 | 节点运行时配置,类型化 |
| 核心原理 | 参数 = 节点本地键值对 + parameter 服务,支持 7 种类型 |
| 类型 | PARAMETER_INTEGER / PARAMETER_DOUBLE / PARAMETER_STRING / PARAMETER_BOOL / PARAMETER_BYTE_ARRAY / PARAMETER_INTEGER_ARRAY / PARAMETER_DOUBLE_ARRAY / PARAMETER_STRING_ARRAY / PARAMETER_BOOL_ARRAY / PARAMETER_NOT_SET |
| 声明 | declare_parameter 必须先声明,未声明的 get_parameter 抛异常 |
| 作用域 | 节点本地,不依赖 Master(与 ROS1 不同) |
| 动态 | 支持运行时修改(set_parameter),可触发回调 |
| 来源 | 命令行 --ros-args -p / launch 文件 / YAML 文件 / 运行时 API |
参数核心特性:
| 特性 | 说明 |
|---|---|
| 类型严格 | 声明时指定类型,运行时类型不匹配抛异常 |
| 声明必须 | 未声明的 get_parameter 抛 ParameterNotDeclaredException |
| 默认值 | 声明时可设默认值,未传参则用默认值 |
| 动态回调 | 可注册参数变更回调,运行时改参自动触发 |
| 层级 | 参数名可含 .,如 nav2.max_speed,表示层级 |
| YAML 加载 | 支持从 YAML 文件批量加载 |
| 命令行覆盖 | --ros-args -p param:=value 覆盖默认值 |
参数适用场景:
| 场景 | 参数类型 | 说明 |
|---|---|---|
| 控制参数 | PARAMETER_DOUBLE / PARAMETER_INTEGER |
速度、加速度、阈值 |
| 路径 | PARAMETER_STRING |
配置文件路径、URDF 路径 |
| 开关 | PARAMETER_BOOL |
功能使能/禁用 |
| 列表 | PARAMETER_DOUBLE_ARRAY |
关节限位、PID 参数 |
| 版本 | PARAMETER_STRING |
软件版本、固件版本 |
2.1.6 QoS(Quality of Service)
| 维度 | 说明 |
|---|---|
| 是什么 | 通信质量保障策略,端到端 |
| 核心原理 | QoS = DDS QoS Policy,发送端与接收端策略必须兼容匹配,否则静默失败 |
| 参数 | reliability / durability / history / depth / deadline / lifespan / liveliness / liveliness_lease_duration |
| 匹配规则 | 接收端要求 ≥ 发送端保证/提供 |
| 默认值 | Python RELIABLE,C++ BEST_EFFORT(跨语言首查) |
| 静默失败 | QoS 不匹配无报错、无数据、无日志 |
QoS 参数详解:
| 参数 | 取值 | 说明 |
|---|---|---|
reliability |
BEST_EFFORT / RELIABLE |
可靠性:尽力 / 可靠 |
durability |
VOLATILE / TRANSIENT_LOCAL / TRANSIENT |
持久性:易失 / 本地持久 / 持久 |
history |
KEEP_LAST / KEEP_ALL |
历史策略:保留最近 N 条 / 保留所有 |
depth |
正整数 | KEEP_LAST 时保留条数 |
deadline |
rclcpp::Duration |
截止时间,超时触发 |
lifespan |
rclcpp::Duration |
数据存活时间,超时丢弃 |
liveliness |
AUTOMATIC / MANUAL_BY_TOPIC |
存活检测:自动 / 手动 |
liveliness_lease_duration |
rclcpp::Duration |
存活租约时间 |
QoS 模板(量产推荐) :
| 场景 | reliability | durability | history | depth | 说明 |
|---|---|---|---|---|---|
| 控制指令 | RELIABLE |
VOLATILE |
KEEP_LAST |
1 | 关键指令不丢 |
| 传感器高频 | BEST_EFFORT |
VOLATILE |
KEEP_LAST |
5 | 高频数据允许丢帧 |
| 状态/事件 | RELIABLE |
TRANSIENT_LOCAL |
KEEP_LAST |
1 | 新订阅者获取最新状态 |
| 配置/参数 | RELIABLE |
TRANSIENT_LOCAL |
KEEP_LAST |
1 | 新订阅者获取配置 |
| TF 坐标 | BEST_EFFORT |
VOLATILE |
KEEP_LAST |
10 | 高频坐标 |
高亮重点:
- QoS 是 ROS2 最高频故障源:不匹配则静默失败(无报错、无数据、无日志)。
- 跨语言首查 QoS :C++ 发布(默认
BEST_EFFORT)+ Python 订阅(默认RELIABLE)= 失败。- 显式 QoS:量产项目所有 Publisher/Subscriber 显式声明,不依赖默认值。
- QoS 模板化:控制/传感器/状态/配置/TF 五类模板,团队统一。
2.1.7 六大机制对比总表
| 机制 | 方向 | 同步/异步 | 适用频率 | 适用场景 | 底层 |
|---|---|---|---|---|---|
| 话题 | 单向(发布→订阅) | 异步 | 高频(10Hz-1kHz) | 数据流、传感器、控制指令 | DDS Topic |
| 服务 | 双向(请求→响应) | 同步/异步 | 低频(< 10Hz) | 命令查询、触发操作 | DDS Request-Reply |
| 动作 | 双向 + 反馈 | 异步 + 反馈 | 长任务(秒-分钟) | 移动、规划、抓取 | 3 话题 + 2 服务 |
| 参数 | 节点本地 + 服务 | 同步 | 极低频(启动/运行时) | 配置、阈值 | 节点本地 + parameter 服务 |
| QoS | 端到端策略 | N/A | N/A | 通信质量保障 | DDS QoS Policy |
| 节点 | 计算单元 | N/A | N/A | 程序基本单位 | DDS Domain Participant |
2.1.8 实操步骤(六大机制验证)
步骤 1:验证节点唯一性。
bash
# 启动两个同名节点
ros2 run demo_nodes_cpp talker __node:=talker
ros2 run demo_nodes_cpp talker __node:=talker
# 第二个节点报错:
# [ERROR] [talker]: A node with the same name already exists
步骤 2:验证话题发布/订阅。
bash
# 终端 1:发布
ros2 run demo_nodes_cpp talker
# 终端 2:订阅
ros2 topic echo /chatter
# 输出:data: 'Hello World: 0'
# ---
# 终端 3:查看频率
ros2 topic hz /chatter
# 输出:average rate: 1.000
步骤 3:验证服务请求/响应。
bash
# 终端 1:启动服务(demo 包无默认服务,用参数服务)
ros2 param list
# 输出:所有节点参数
# 终端 2:查询参数
ros2 param get /talker use_sim_time
# 输出:False
# 终端 3:设置参数(运行时动态)
ros2 param set /talker use_sim_time true
# 输出:Set for [use_sim_time]: True
步骤 4:验证动作(Nav2 动作)。
bash
# 需 Nav2 运行环境(见模块五)
ros2 action list
# 输出:/navigate_to_pose [nav2_msgs/action/NavigateToPose]
# /follow_waypoints [nav2_msgs/action/FollowWaypoints]
# 查看动作状态
ros2 action list -t
步骤 5:验证参数动态回调。
bash
# 自写节点(见模块三),启动后:
ros2 param set /my_node max_speed 1.5
# 节点日志:[INFO] [my_node]: max_speed changed to 1.5
步骤 6:验证 QoS 不匹配(静默失败)。
bash
# C++ 发布(默认 BEST_EFFORT)
ros2 run demo_nodes_cpp talker
# Python 订阅(默认 RELIABLE,与 C++ 不匹配)
# 注意:demo_nodes_py listener 内部 QoS 已兼容,故此例可通信
# 要复现不匹配,需自写节点(见模块三)
2.1.9 踩坑点
- 节点名重复 。ROS2 同域内节点名必须唯一,重复启动失败。解决:
__node:=重命名或__ns:=改命名空间。 - 话题类型不匹配 。消息类型不一致则静默失败。解决:
ros2 topic info /topic查看类型,确保一致。 - 服务超时未设。默认无限等待,服务端崩溃则客户端卡死。解决:客户端设超时。
- 动作状态未监听 。只监听结果不监听状态,取消/中止无感知。解决:监听
<action>/_status话题。 - 参数未声明就获取 。抛
ParameterNotDeclaredException。解决:先declare_parameter再get_parameter。 - 参数类型不匹配 。声明
INT实际传DOUBLE,抛异常。解决:类型严格匹配。 - QoS 不匹配静默失败 。无报错、无数据。解决:
ros2 topic info -v /topic查看双方 QoS,对比匹配规则。 - 跨语言 QoS 默认值不同 。C++
BEST_EFFORT,PythonRELIABLE。解决:显式声明 QoS。 - 节点资源未释放 。C++
Node未rclcpp::shutdown(),Pythonnode未destroy_node(),资源泄漏。解决:规范退出流程。 - Lifecycle 节点状态未迁移 。
configure未调用直接activate,抛异常。解决:严格按状态机迁移。
2.1.10 工程最佳实践
- 节点命名规范 :
<模块>_<子模块>_<实例>,如nav2_local_planner_01,全局唯一且可读。 - 话题命名规范 :
/vehicle/<子系统>/<数据>,如/vehicle/chassis/cmd_vel,分层清晰。 - 服务命名规范 :
/robot/<操作>,如/robot/joint/set_position,动词 + 宾语。 - 动作命名规范 :
/robot/<任务>,如/robot/move_to,任务语义明确。 - 参数命名规范 :小写 + 下划线,层级用
.,如nav2.max_speed。 - 显式 QoS:所有 Publisher/Subscriber 显式声明,用 QoS 模板(2.1.6)。
- 服务超时:客户端必设超时(建议 5-10s),避免卡死。
- 动作状态监听 :监听
<action>/_status,取消/中止无感知。 - 参数声明必须 :先
declare_parameter再get_parameter,类型严格匹配。 - 动态参数回调:关键参数注册回调,运行时改参自动响应。
- 节点退出规范 :C++
rclcpp::shutdown(),Pythondestroy_node()+rclpy.shutdown()。 - Lifecycle 节点 :量产系统用
LifecycleNode,支持热启停、故障恢复。
2.2 各通信机制原理、优缺点、适用业务场景对比
是什么:ROS2 提供 5 种通信机制:话题、服务、动作、参数、TF。本章对比各机制的通信模型、优缺点、适用场景,给出选型决策。
核心原理:不同机制对应不同通信语义:话题是"数据流"(异步、一对多、高频),服务是"请求/响应"(同步/异步、一对一、低频),动作是"长任务"(异步 + 反馈 + 取消),参数是"配置"(节点本地 + 动态),TF 是"坐标变换"(树状、缓存、监听)。
2.2.1 通信模型对比
#mermaid-svg-E1ZiMV2SocrGznQg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-E1ZiMV2SocrGznQg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-E1ZiMV2SocrGznQg .error-icon{fill:#552222;}#mermaid-svg-E1ZiMV2SocrGznQg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E1ZiMV2SocrGznQg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E1ZiMV2SocrGznQg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E1ZiMV2SocrGznQg .marker.cross{stroke:#333333;}#mermaid-svg-E1ZiMV2SocrGznQg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E1ZiMV2SocrGznQg p{margin:0;}#mermaid-svg-E1ZiMV2SocrGznQg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-E1ZiMV2SocrGznQg .cluster-label text{fill:#333;}#mermaid-svg-E1ZiMV2SocrGznQg .cluster-label span{color:#333;}#mermaid-svg-E1ZiMV2SocrGznQg .cluster-label span p{background-color:transparent;}#mermaid-svg-E1ZiMV2SocrGznQg .label text,#mermaid-svg-E1ZiMV2SocrGznQg span{fill:#333;color:#333;}#mermaid-svg-E1ZiMV2SocrGznQg .node rect,#mermaid-svg-E1ZiMV2SocrGznQg .node circle,#mermaid-svg-E1ZiMV2SocrGznQg .node ellipse,#mermaid-svg-E1ZiMV2SocrGznQg .node polygon,#mermaid-svg-E1ZiMV2SocrGznQg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-E1ZiMV2SocrGznQg .rough-node .label text,#mermaid-svg-E1ZiMV2SocrGznQg .node .label text,#mermaid-svg-E1ZiMV2SocrGznQg .image-shape .label,#mermaid-svg-E1ZiMV2SocrGznQg .icon-shape .label{text-anchor:middle;}#mermaid-svg-E1ZiMV2SocrGznQg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-E1ZiMV2SocrGznQg .rough-node .label,#mermaid-svg-E1ZiMV2SocrGznQg .node .label,#mermaid-svg-E1ZiMV2SocrGznQg .image-shape .label,#mermaid-svg-E1ZiMV2SocrGznQg .icon-shape .label{text-align:center;}#mermaid-svg-E1ZiMV2SocrGznQg .node.clickable{cursor:pointer;}#mermaid-svg-E1ZiMV2SocrGznQg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-E1ZiMV2SocrGznQg .arrowheadPath{fill:#333333;}#mermaid-svg-E1ZiMV2SocrGznQg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-E1ZiMV2SocrGznQg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-E1ZiMV2SocrGznQg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E1ZiMV2SocrGznQg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-E1ZiMV2SocrGznQg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E1ZiMV2SocrGznQg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-E1ZiMV2SocrGznQg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-E1ZiMV2SocrGznQg .cluster text{fill:#333;}#mermaid-svg-E1ZiMV2SocrGznQg .cluster span{color:#333;}#mermaid-svg-E1ZiMV2SocrGznQg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-E1ZiMV2SocrGznQg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-E1ZiMV2SocrGznQg rect.text{fill:none;stroke-width:0;}#mermaid-svg-E1ZiMV2SocrGznQg .icon-shape,#mermaid-svg-E1ZiMV2SocrGznQg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E1ZiMV2SocrGznQg .icon-shape p,#mermaid-svg-E1ZiMV2SocrGznQg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-E1ZiMV2SocrGznQg .icon-shape .label rect,#mermaid-svg-E1ZiMV2SocrGznQg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E1ZiMV2SocrGznQg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-E1ZiMV2SocrGznQg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-E1ZiMV2SocrGznQg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} TF 坐标 (TF2)
TFMessage
lookup
TF Publisher
TF Buffer
TF Listener
参数 (Parameter)
set_parameter
callback
Node Local
Parameter Service
动作 (Action)
Goal
Feedback
Result
Cancel
Action Client
Action Server
服务 (Service)
同步/异步 一对一 请求
响应
Client
Server
话题 (Topic)
异步 一对多 高频
Publisher
Subscriber1
Subscriber2
SubscriberN
2.2.2 五大机制对比总表
| 维度 | 话题 | 服务 | 动作 | 参数 | TF |
|---|---|---|---|---|---|
| 通信模型 | 发布/订阅 | 请求/响应 | 长任务(3 话题 + 2 服务) | 键值对 + 回调 | 坐标树 + 缓存 |
| 方向 | 单向 | 双向 | 双向 + 反馈 | 双向(节点 ↔ 服务) | 单向(发布 → 监听) |
| 同步/异步 | 异步 | 同步/异步 | 异步 + 反馈 | 同步 | 异步 |
| 适用频率 | 高频(10Hz-1kHz) | 低频(< 10Hz) | 长任务(秒-分钟) | 极低频(启动/运行时) | 高频(100Hz-1kHz) |
| 数据量 | 小-大(消息) | 小(请求/响应) | 小(目标/反馈/结果) | 小(键值对) | 小(变换) |
| 可靠性 | QoS 可配 | QoS 可配 | QoS 可配 | N/A | QoS 可配 |
| 一对多 | ✅ | ✅(客户端轮询) | ✅(多客户端) | ❌(节点本地) | ✅ |
| 请求/响应 | ❌ | ✅ | ✅(目标/结果) | ❌ | ❌ |
| 反馈 | ❌ | ❌ | ✅ | ❌ | ❌ |
| 取消 | ❌ | ✅(Humble) | ✅ | ❌ | ❌ |
| 持久性 | QoS 可配 | QoS 可配 | QoS 可配 | 节点本地 | QoS 可配 |
| 类型安全 | ✅ | ✅ | ✅ | ✅(7 类型) | ✅ |
| 底层 | DDS Topic | DDS Request-Reply | 3 话题 + 2 服务 | 节点本地 + 服务 | TF2 库 |
2.2.3 优缺点对比
话题(Topic)
| 优点 | 缺点 |
|---|---|
| 高频、低延迟 | 无请求/响应语义 |
| 一对多,解耦 | 无反馈(订阅者不确认收到) |
| 异步,发布者不阻塞 | QoS 不匹配静默失败 |
| 类型安全 | 无取消机制 |
| QoS 灵活 | 无持久性(除非 TRANSIENT_LOCAL) |
服务(Service)
| 优点 | 缺点 |
|---|---|
| 请求/响应语义清晰 | 低频(< 10Hz) |
| 同步/异步可选 | 长任务阻塞(需异步) |
| 一对一,命令查询 | 无反馈(仅请求/响应) |
| 超时可控 | 无反馈(进度未知) |
| 类型安全 | 服务端崩溃客户端卡死(需超时) |
动作(Action)
| 优点 | 缺点 |
|---|---|
| 长任务专用 | 复杂(3 话题 + 2 服务) |
| 反馈(进度实时) | 低频(长任务) |
| 取消(随时中止) | 状态机复杂(5 状态) |
| 结果(成功/失败/中止) | 调试复杂 |
| 类型安全 | 资源开销大 |
参数(Parameter)
| 优点 | 缺点 |
|---|---|
| 配置集中 | 节点本地,无全局 |
| 动态回调 | 类型严格 |
| YAML 批量加载 | 无持久性(节点重启丢失) |
| 命令行覆盖 | 无版本控制 |
| 类型安全(7 类型) | 无并发控制 |
TF(TF2)
| 优点 | 缺点 |
|---|---|
| 坐标树,层级清晰 | 树状结构(无环) |
| 缓存,时间戳查询 | 缓存有限(默认 10s) |
| 静态/动态发布 | 多坐标系融合需手动 |
| 监听回调 | 坐标丢失无感知 |
| 类型安全 | 跨机同步需时间戳对齐 |
2.2.4 适用业务场景对比
| 业务场景 | 推荐机制 | 理由 |
|---|---|---|
| 传感器数据(激光/相机/IMU) | 话题 | 高频、一对多、数据流 |
| 控制指令(速度/方向) | 话题 | 高频、实时、可靠 |
| 状态广播(电池/网络/模式) | 话题 | 一对多、低频 |
| 设置参数(速度/阈值) | 服务 或 参数 | 低频、命令查询 |
| 触发操作(拍照/校准) | 服务 | 一次性、请求/响应 |
| 查询状态(版本/健康) | 服务 | 请求/响应 |
| 移动(Nav2) | 动作 | 长任务、反馈、取消 |
| 机械臂抓取 | 动作 | 长任务、反馈、取消 |
| 建图 | 动作 | 长任务、反馈 |
| 充电 | 动作 | 长任务、反馈 |
| 运行时配置(阈值/开关) | 参数 | 动态回调 |
| 启动配置(路径/模型) | 参数 | YAML 加载 |
| 坐标变换(base → 工具) | TF | 坐标树 |
| 多传感器融合 | TF | 时间戳对齐 |
| 手眼标定 | TF | 静态坐标 |
2.2.5 选型决策流程
#mermaid-svg-2vRKCzTrpHa8weOz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2vRKCzTrpHa8weOz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2vRKCzTrpHa8weOz .error-icon{fill:#552222;}#mermaid-svg-2vRKCzTrpHa8weOz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2vRKCzTrpHa8weOz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2vRKCzTrpHa8weOz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2vRKCzTrpHa8weOz .marker.cross{stroke:#333333;}#mermaid-svg-2vRKCzTrpHa8weOz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2vRKCzTrpHa8weOz p{margin:0;}#mermaid-svg-2vRKCzTrpHa8weOz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2vRKCzTrpHa8weOz .cluster-label text{fill:#333;}#mermaid-svg-2vRKCzTrpHa8weOz .cluster-label span{color:#333;}#mermaid-svg-2vRKCzTrpHa8weOz .cluster-label span p{background-color:transparent;}#mermaid-svg-2vRKCzTrpHa8weOz .label text,#mermaid-svg-2vRKCzTrpHa8weOz span{fill:#333;color:#333;}#mermaid-svg-2vRKCzTrpHa8weOz .node rect,#mermaid-svg-2vRKCzTrpHa8weOz .node circle,#mermaid-svg-2vRKCzTrpHa8weOz .node ellipse,#mermaid-svg-2vRKCzTrpHa8weOz .node polygon,#mermaid-svg-2vRKCzTrpHa8weOz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2vRKCzTrpHa8weOz .rough-node .label text,#mermaid-svg-2vRKCzTrpHa8weOz .node .label text,#mermaid-svg-2vRKCzTrpHa8weOz .image-shape .label,#mermaid-svg-2vRKCzTrpHa8weOz .icon-shape .label{text-anchor:middle;}#mermaid-svg-2vRKCzTrpHa8weOz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2vRKCzTrpHa8weOz .rough-node .label,#mermaid-svg-2vRKCzTrpHa8weOz .node .label,#mermaid-svg-2vRKCzTrpHa8weOz .image-shape .label,#mermaid-svg-2vRKCzTrpHa8weOz .icon-shape .label{text-align:center;}#mermaid-svg-2vRKCzTrpHa8weOz .node.clickable{cursor:pointer;}#mermaid-svg-2vRKCzTrpHa8weOz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2vRKCzTrpHa8weOz .arrowheadPath{fill:#333333;}#mermaid-svg-2vRKCzTrpHa8weOz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2vRKCzTrpHa8weOz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2vRKCzTrpHa8weOz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2vRKCzTrpHa8weOz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2vRKCzTrpHa8weOz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2vRKCzTrpHa8weOz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2vRKCzTrpHa8weOz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2vRKCzTrpHa8weOz .cluster text{fill:#333;}#mermaid-svg-2vRKCzTrpHa8weOz .cluster span{color:#333;}#mermaid-svg-2vRKCzTrpHa8weOz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2vRKCzTrpHa8weOz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2vRKCzTrpHa8weOz rect.text{fill:none;stroke-width:0;}#mermaid-svg-2vRKCzTrpHa8weOz .icon-shape,#mermaid-svg-2vRKCzTrpHa8weOz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2vRKCzTrpHa8weOz .icon-shape p,#mermaid-svg-2vRKCzTrpHa8weOz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2vRKCzTrpHa8weOz .icon-shape .label rect,#mermaid-svg-2vRKCzTrpHa8weOz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2vRKCzTrpHa8weOz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2vRKCzTrpHa8weOz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2vRKCzTrpHa8weOz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
是
否
是
否
通信需求
是否长任务?
动作 Action
是否请求/响应?
服务 Service
是否数据流?
话题 Topic
是否坐标变换?
TF TF2
是否配置?
参数 Parameter
重新评估需求
高亮重点:
- 长任务用动作,不用"服务 + 话题"组合。
- 高频数据用话题,不用服务(服务低频)。
- 命令查询用服务,不用话题(无请求/响应语义)。
- 坐标变换用 TF,不用话题(TF2 有缓存、时间戳查询)。
- 配置用参数,不用话题/服务(参数有动态回调、YAML 加载)。
2.2.6 实操步骤(选型验证)
步骤 1 :用 ros2 topic / ros2 service / ros2 action 查看当前系统通信端点。
bash
# 查看话题
ros2 topic list
# 输出:/chatter /parameter_events /rosout /scan /cmd_vel ...
# 查看服务
ros2 service list
# 输出:/talker/describe_parameters /talker/get_parameter_descriptions ...
# 查看动作
ros2 action list
# 输出:(无 Nav2 时为空)
步骤 2 :用 ros2 interface 查看接口定义。
bash
# 查看消息类型
ros2 interface show std_msgs/String
# 输出:string data
# 查看服务类型
ros2 interface show std_srvs/Trigger
# 输出:
# bool success
# string message
# ---
# 查看动作类型
ros2 interface show nav2_msgs/action/NavigateToPose
步骤 3 :用 ros2 topic info -v 查看 QoS 详情。
bash
ros2 topic info -v /chatter
# 输出:
# Type: std_msgs/msg/String
# Publisher count: 1
# node: /talker
# QoS: reliability=reliable, durability=volatile, history=keep_last, depth=10
# Subscriber count: 1
# node: /listener
# QoS: reliability=reliable, durability=volatile, history=keep_last, depth=10
步骤 4 :用 ros2 param 查看/设置参数。
bash
# 查看所有节点参数
ros2 param list
# 查看某节点某参数
ros2 param get /talker use_sim_time
# 设置参数(运行时动态)
ros2 param set /talker use_sim_time true
# 查看参数描述
ros2 param describe /talker use_sim_time
2.2.7 踩坑点
- 长任务用服务。服务端阻塞,客户端卡死。解决:改用动作。
- 高频数据用服务。请求/响应开销大,频率受限。解决:改用话题。
- 坐标变换用话题。无缓存、无时间戳查询。解决:改用 TF。
- 配置用话题。无动态回调、无 YAML 加载。解决:改用参数。
- 动作状态未监听 。只监听结果,取消/中止无感知。解决:监听
<action>/_status。 - 服务超时未设。服务端崩溃客户端卡死。解决:客户端设超时。
- QoS 不匹配静默失败 。无报错、无数据。解决:
ros2 topic info -v查看双方 QoS。 - TF 坐标丢失无感知 。TF 树断裂,
lookup抛异常。解决:监听 TF 树完整性。 - 参数未持久化。节点重启参数丢失。解决:YAML 文件 + launch 加载。
- 动作取消未响应 。客户端
cancel_goal()服务端无响应。解决:服务端检查goal_handle状态。
2.2.8 工程最佳实践
- 选型决策流程:长任务 → 动作;请求/响应 → 服务;数据流 → 话题;坐标 → TF;配置 → 参数。
- QoS 模板化:控制/传感器/状态/配置/TF 五类模板,团队统一。
- 服务超时:客户端必设超时(5-10s),避免卡死。
- 动作状态监听 :监听
<action>/_status,取消/中止无感知。 - TF 树监控:量产系统部署 TF 树完整性监控,断裂告警。
- 参数持久化:YAML 文件 + launch 加载,节点重启参数不丢。
- 接口独立成包 :
my_interfaces独立,多包共用。 - 通信端点文档化 :
ros2 topic list/ros2 service list/ros2 action list输出纳入运维手册。 - QoS 显式声明:所有 Publisher/Subscriber 显式声明,不依赖默认值。
- 动作取消响应 :服务端检查
goal_handle状态,及时响应取消。
2.3 全套 ros2cli 命令行实操:查询、运行、监控、诊断、管理
是什么 :
ros2cli(ros2命令组)是 ROS2 官方命令行工具集,涵盖节点管理、话题调试、服务调用、动作操作、参数管理、接口查询、包管理、launch 运行、bag 录制等全流程。核心原理 :
ros2命令是 Python 编写的 CLI 工具,底层调用rclpyAPI,通过 DDS 与 ROS2 节点通信。命令分 14 个子命令组:node/topic/service/action/param/interface/pkg/run/launch/bag/component/doctor/trace/multicast。
2.3.1 ros2cli 子命令总览
| 子命令 | 用途 | 常用命令 |
|---|---|---|
ros2 node |
节点管理 | list / info / kill |
ros2 topic |
话题调试 | list / echo / pub / hz / info / bw |
ros2 service |
服务调用 | list / call / info / type |
ros2 action |
动作操作 | list / send_goal / info / cancel |
ros2 param |
参数管理 | list / get / set / describe / dump / load |
ros2 interface |
接口查询 | list / show / pkg |
ros2 pkg |
包管理 | list / prefix / exec / scripts |
ros2 run |
运行节点 | run <pkg> <node> |
ros2 launch |
运行 launch | launch <file> |
ros2 bag |
数据录制/回放 | record / play / info / reindex |
ros2 component |
组件管理 | list / load / unload |
ros2 doctor |
系统诊断 | check |
ros2 trace |
性能追踪 | record / play |
ros2 multicast |
组播调试 | check |
2.3.2 节点管理(ros2 node)
2.3.2.1 查看节点列表
bash
# 查看所有节点
ros2 node list
# 输出:
# /listener
# /talker
# /rviz2
# 查看节点详细信息(含发布/订阅/服务/参数)
ros2 node info /talker
# 输出:
# /talker
# Publishers:
# /chatter [std_msgs/msg/String]
# /parameter_events [rcl_interfaces/msg/ParameterEvent]
# /rosout [rcl_interfaces/msg/Log]
# Subscribers:
# /rosout [rcl_interfaces/msg/Log]
# Service Servers:
# /talker/describe_parameters [rcl_interfaces/srv/DescribeParameters]
# /talker/get_parameter_descriptions [rcl_interfaces/srv/GetParameterDescriptions]
# /talker/get_parameters [rcl_interfaces/srv/GetParameters]
# /talker/list_parameters [rcl_interfaces/srv/ListParameters]
# /talker/set_parameters [rcl_interfaces/srv/SetParameters]
# /talker/set_parameters_atomically [rcl_interfaces/srv/SetParametersAtomically]
# /talker/update_parameter [rcl_interfaces/srv/SetParametersFromFile]
# Service Clients:
# (empty)
# Action Servers:
# (empty)
# Action Clients:
# (empty)
2.3.2.2 节点计数
bash
# 查看节点数量
ros2 node list | wc -l
# 输出:3
2.3.2.3 节点状态监控(脚本)
bash
# 持续监控节点列表(每 2 秒刷新)
watch -n 2 ros2 node list
2.3.3 话题调试(ros2 topic)
2.3.3.1 查看话题列表
bash
# 查看所有话题
ros2 topic list
# 输出:
# /chatter
# /parameter_events
# /rosout
# /scan
# /cmd_vel
# 按类型过滤
ros2 topic list -t
# 输出:
# /chatter [std_msgs/msg/String]
# /scan [sensor_msgs/msg/LaserScan]
# 按正则过滤
ros2 topic list -r "scan|cmd"
# 输出:
# /scan
# /cmd_vel
2.3.3.2 查看话题信息
bash
# 查看话题类型与 QoS
ros2 topic info /chatter
# 输出:
# Type: std_msgs/msg/String
# Publisher count: 1
# Subscriber count: 1
# 详细查看(含 QoS)
ros2 topic info -v /chatter
# 输出:
# Type: std_msgs/msg/String
# Publisher count: 1
# node: /talker
# QoS: reliability=reliable, durability=volatile, history=keep_last, depth=10
# Subscriber count: 1
# node: /listener
# QoS: reliability=reliable, durability=volatile, history=keep_last, depth=10
2.3.3.3 监听话题数据
bash
# 监听话题(默认显示所有字段)
ros2 topic echo /chatter
# 输出:
# data: 'Hello World: 0'
# ---
# data: 'Hello World: 1'
# ---
# 仅显示某字段
ros2 topic echo /chatter --field data
# 输出:
# 'Hello World: 0'
# 'Hello World: 1'
# 监听 N 条后退出
ros2 topic echo /chatter --once
# 输出:
# data: 'Hello World: 0'
# ---
# 监听并统计频率
ros2 topic hz /chatter
# 输出:
# average rate: 1.000
# min: 0.900s max: 1.100s std dev: 0.050s window: 10
# average rate: 1.000
# 监听并统计带宽
ros2 topic bw /chatter
# 输出:
# average rate: 1.000
# min: 100B max: 100B std dev: 0B window: 10
# average size: 100
# min: 100B max: 100B std dev: 0B window: 10
# 监听 N 秒后退出
timeout 5 ros2 topic echo /chatter
2.3.3.4 发布话题数据
bash
# 发布字符串
ros2 topic pub /chatter std_msgs/msg/String "data: 'Hello from CLI'"
# 发布 1 次后退出
# 持续发布(10Hz)
ros2 topic pub -r 10 /chatter std_msgs/msg/String "data: 'tick'"
# 发布几何消息(TF 坐标)
ros2 topic pub /tf2_msgs/TFMessage tf2_msgs/msg/TFMessage "
transforms:
- header:
frame_id: 'base_link'
stamp: now
child_frame_id: 'laser'
transform:
translation: {x: 0.1, y: 0.0, z: 0.05}
rotation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
"
# 发布速度指令
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "
linear: {x: 0.5, y: 0.0, z: 0.0}
angular: {x: 0.0, y: 0.0, z: 0.0}
"
2.3.3.5 话题 QoS 调试
bash
# 查看话题 QoS 详情
ros2 topic info -v /chatter
# 用特定 QoS 监听(BEST_EFFORT)
ros2 topic echo /scan --qos-reliability best_effort --qos-history keep_last --qos-depth 5
# 用特定 QoS 发布
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "linear: {x: 0.5}" \
--qos-reliability reliable --qos-history keep_last --qos-depth 1
2.3.4 服务调用(ros2 service)
2.3.4.1 查看服务列表
bash
# 查看所有服务
ros2 service list
# 输出:
# /talker/describe_parameters
# /talker/get_parameter_descriptions
# /talker/get_parameters
# /talker/list_parameters
# /talker/set_parameters
# /talker/set_parameters_atomically
# /talker/update_parameter
# /rviz2/describe_parameters
# ...
# 按类型过滤
ros2 service list -t
# 输出:
# /talker/describe_parameters [rcl_interfaces/srv/DescribeParameters]
2.3.4.2 查看服务类型
bash
# 查看服务类型
ros2 service type /talker/get_parameters
# 输出:rcl_interfaces/srv/GetParameters
# 查看服务定义
ros2 interface show rcl_interfaces/srv/GetParameters
# 输出:
# string[] names
# ---
# rcl_interfaces/msg/Parameter[] parameters
2.3.4.3 调用服务
bash
# 调用服务(简单)
ros2 service call /talker/get_parameters rcl_interfaces/srv/GetParameters "names: ['use_sim_time']"
# 输出:
# status: 0
# parameters:
# - name: use_sim_time
# value:
# bool_value: false
# 调用服务(Trigger)
ros2 service call /standard_srvs/trigger std_srvs/srv/Trigger "{}"
# 输出:
# success: true
# message: 'Triggered'
# 调用服务(SetBool)
ros2 service call /robot/enable std_srvs/srv/SetBool "data: true"
# 输出:
# success: true
# message: 'Enabled'
2.3.4.4 服务调用超时
bash
# 设置超时(5 秒)
timeout 5 ros2 service call /robot/trigger std_srvs/srv/Trigger "{}"
# 若服务无响应,5 秒后超时退出
2.3.5 动作操作(ros2 action)
2.3.5.1 查看动作列表
bash
# 查看所有动作
ros2 action list
# 输出:(无 Nav2 时为空)
# 按类型过滤
ros2 action list -t
# 输出:
# /navigate_to_pose [nav2_msgs/action/NavigateToPose]
# /follow_waypoints [nav2_msgs/action/FollowWaypoints]
2.3.5.2 查看动作类型
bash
# 查看动作类型
ros2 action type /navigate_to_pose
# 输出:nav2_msgs/action/NavigateToPose
# 查看动作定义
ros2 interface show nav2_msgs/action/NavigateToPose
2.3.5.3 发送动作目标
bash
# 发送导航目标(需 Nav2 运行)
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose "
goal:
pose:
header:
frame_id: 'map'
position:
x: 1.0
y: 1.0
z: 0.0
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
behavior_tree: 'NavigateToPose'
"
# 输出:
# Goal accepted with ID: <uuid>
# Feedback:
# status: EXECUTING
# Result:
# status: SUCCEEDED
2.3.5.4 取消动作
bash
# 取消动作(需先获取 goal ID)
ros2 action send_goal /navigate_to_pose ... &
GOAL_PID=$!
# 取消
kill $GOAL_PID
# 或用 ros2 action cancel
2.3.6 参数管理(ros2 param)
2.3.6.1 查看参数列表
bash
# 查看所有节点参数
ros2 param list
# 输出:
# /talker
# use_sim_time
# /listener
# use_sim_time
# /rviz2
# use_sim_time
# ...
# 查看某节点参数
ros2 param list /talker
# 输出:
# use_sim_time
2.3.6.2 查看/设置参数
bash
# 查看参数值
ros2 param get /talker use_sim_time
# 输出:False
# 查看参数描述
ros2 param describe /talker use_sim_time
# 输出:
# name: use_sim_time
# type: BOOL
# value: false
# description: Use simulation time
# 设置参数(运行时动态)
ros2 param set /talker use_sim_time true
# 输出:
# Set for [use_sim_time]: True
# 设置参数(强制类型)
ros2 param set /talker use_sim_time true --force
2.3.6.3 参数导出/导入
bash
# 导出所有节点参数到文件
ros2 param dump > params.yaml
# 输出:
# /talker:
# ros__parameters:
# use_sim_time: false
# /listener:
# ros__parameters:
# use_sim_time: false
# 导入参数到节点
ros2 param load /talker params.yaml
2.3.7 接口查询(ros2 interface)
2.3.7.1 查看接口列表
bash
# 查看所有接口
ros2 interface list
# 输出:
# std_msgs/msg/String
# std_msgs/msg/Int32
# std_msgs/msg/Bool
# ...
# geometry_msgs/msg/Twist
# sensor_msgs/msg/LaserScan
# ...
# 按类型过滤
ros2 interface list -t msg
# 输出:所有 msg 接口
2.3.7.2 查看接口定义
bash
# 查看消息定义
ros2 interface show std_msgs/msg/String
# 输出:
# string data
# 查看服务定义
ros2 interface show std_srvs/srv/Trigger
# 输出:
# bool success
# string message
# ---
# 查看动作定义
ros2 interface show nav2_msgs/action/NavigateToPose
2.3.7.3 接口包查询
bash
# 查看某包的所有接口
ros2 interface pkg std_msgs
# 输出:
# std_msgs/msg/String
# std_msgs/msg/Int32
# ...
# 查看接口所在包
ros2 interface pkg std_msgs/msg/String
# 输出:std_msgs
2.3.8 包管理(ros2 pkg)
2.3.8.1 查看包列表
bash
# 查看所有包
ros2 pkg list
# 输出:
# actionlib_msgs
# ament_index
# ament_package
# ...
# rclcpp
# rclpy
# ...
# 按名称过滤
ros2 pkg list -r "rcl|nav"
# 输出:
# rclcpp
# rclpy
# rcl
# nav2
# ...
2.3.8.2 查看包路径
bash
# 查看包路径
ros2 pkg prefix rclcpp
# 输出:/opt/ros/humble
# 查看包可执行文件
ros2 pkg executables rclcpp
# 输出:(无)
# 查看包脚本
ros2 pkg scripts rclcpp
# 输出:(无)
2.3.8.3 运行包命令
bash
# 运行包命令
ros2 pkg exec <pkg> <cmd>
# 例如:
ros2 pkg exec tf2_ros tf2_echo map base_link
2.3.9 运行节点(ros2 run / ros2 launch)
2.3.9.1 运行单节点
bash
# 运行节点
ros2 run <pkg> <node>
# 例如:
ros2 run demo_nodes_cpp talker
ros2 run rviz2 rviz2
# 运行节点并传参
ros2 run <pkg> <node> --ros-args -p param:=value
# 例如:
ros2 run demo_nodes_cpp talker --ros-args -p use_sim_time:=false
# 运行节点并重命名
ros2 run <pkg> <node> __node:=new_name
# 例如:
ros2 run demo_nodes_cpp talker __node:=talker2
# 运行节点并改命名空间
ros2 run <pkg> <node> __ns:=/new_ns
# 例如:
ros2 run demo_nodes_cpp talker __ns:=/vehicle
2.3.9.2 运行 launch 文件
bash
# 运行 launch 文件
ros2 launch <pkg> <launch_file>
# 例如:
ros2 launch demo_nodes_cpp talker_listener.launch.py
# 运行 launch 并传参
ros2 launch <pkg> <launch_file> param:=value
# 例如:
ros2 launch my_pkg my_system.launch.py use_sim_time:=false
# 运行 launch 并指定参数文件
ros2 launch <pkg> <launch_file> --params-file <yaml>
2.3.10 数据录制/回放(ros2 bag)
2.3.10.1 录制话题
bash
# 录制所有话题
ros2 bag record
# 录制特定话题
ros2 bag record /scan /cmd_vel /odom
# 录制并指定输出目录
ros2 bag record -o ~/bags/session1
# 录制并指定存储后端(默认 sqlite3)
ros2 bag record --storage mcap /scan /cmd_vel
# 录制并指定 QoS
ros2 bag record --qos-profile /scan:=sensor_data_qos /scan
# 录制 N 秒后停止
timeout 30 ros2 bag record -o ~/bags/test
2.3.10.2 查看 bag 信息
bash
# 查看 bag 信息
ros2 bag info ~/bags/session1
# 输出:
# Files: session1_0.mdb
# Bag size: 100 MB
# Storage format: mcap
# Start time: 2024-01-01 10:00:00
# Duration: 30.0s
# Messages: 1000
# Topic information: /scan [sensor_msgs/msg/LaserScan] 500 messages
# /cmd_vel [geometry_msgs/msg/Twist] 500 messages
2.3.10.3 回放 bag
bash
# 回放 bag
ros2 bag play ~/bags/session1
# 回放并指定速率(2 倍速)
ros2 bag play ~/bags/session1 -r 2.0
# 回放并指定起始时间
ros2 bag play ~/bags/session1 --start-time 2024-01-01 10:00:10
# 回放并指定结束时间
ros2 bag play ~/bags/session1 --end-time 2024-01-01 10:00:20
# 回放特定话题
ros2 bag play ~/bags/session1 /scan /cmd_vel
2.3.10.4 bag 重索引
bash
# 重索引 bag(修复损坏)
ros2 bag reindex ~/bags/session1
2.3.11 组件管理(ros2 component)
2.3.11.1 查看组件
bash
# 查看节点内的组件
ros2 component list /talker
# 输出:(无组件)
# 查看组件信息
ros2 component info /talker <component_id>
2.3.11.2 加载/卸载组件
bash
# 加载组件
ros2 component load /talker <component_lib> <component_class>
# 卸载组件
ros2 component unload /talker <component_id>
2.3.12 系统诊断(ros2 doctor)
2.3.12.1 诊断检查
bash
# 系统诊断
ros2 doctor check
# 输出:
# [PASS] ROS_DISTRO is set
# [PASS] ROS_DOMAIN_ID is set
# [PASS] RMW_IMPLEMENTATION is set
# [WARN] DDS discovery: multicast not available
# [PASS] Network interface: eth0 is up
# ...
# 详细诊断
ros2 doctor check --report
# 输出完整诊断报告
2.3.12.2 网络诊断
bash
# 网络诊断
ros2 doctor check network
# 输出:
# [PASS] Network interface: eth0 is up
# [WARN] Multicast not available on eth0
# [PASS] DDS domain ID: 0
2.3.13 性能追踪(ros2 trace)
2.3.13.1 记录 trace
bash
# 记录 trace(需安装 ros2_tracing)
sudo apt install ros-humble-ros2-tracing
ros2 trace record -o ~/traces/test
# 记录 N 秒
timeout 10 ros2 trace record -o ~/traces/test
2.3.13.2 查看 trace
bash
# 查看 trace(需安装 perfetto 或 lttng)
ros2 trace info ~/traces/test
2.3.14 组播调试(ros2 multicast)
2.3.14.1 组播检查
bash
# 检查组播
ros2 multicast check
# 输出:
# [PASS] Multicast available on eth0
# [INFO] Multicast group: 239.255.0.1
2.3.15 常用命令速查表
| 场景 | 命令 |
|---|---|
| 查看节点 | ros2 node list |
| 查看节点详情 | ros2 node info /node |
| 查看话题 | ros2 topic list |
| 查看话题详情 | ros2 topic info -v /topic |
| 监听话题 | ros2 topic echo /topic |
| 监听频率 | ros2 topic hz /topic |
| 监听带宽 | ros2 topic bw /topic |
| 发布话题 | ros2 topic pub /topic <type> "<data>" |
| 查看服务 | ros2 service list |
| 调用服务 | ros2 service call /srv <type> "<data>" |
| 查看动作 | ros2 action list |
| 发送动作 | ros2 action send_goal /action <type> "<goal>" |
| 查看参数 | ros2 param list |
| 获取参数 | ros2 param get /node param |
| 设置参数 | ros2 param set /node param value |
| 导出参数 | ros2 param dump > params.yaml |
| 导入参数 | ros2 param load /node params.yaml |
| 查看接口 | ros2 interface list |
| 查看接口定义 | ros2 interface show <type> |
| 查看包 | ros2 pkg list |
| 查看包路径 | ros2 pkg prefix <pkg> |
| 运行节点 | ros2 run <pkg> <node> |
| 运行 launch | ros2 launch <pkg> <file> |
| 录制 bag | ros2 bag record -o <dir> <topics> |
| 查看 bag | ros2 bag info <dir> |
| 回放 bag | ros2 bag play <dir> |
| 系统诊断 | ros2 doctor check |
| 性能追踪 | ros2 trace record -o <dir> |
| 组播检查 | ros2 multicast check |
2.3.16 实操步骤(完整调试流程)
场景:调试一个机器人系统(节点 + 话题 + 服务 + 参数 + 录制)。
步骤 1:启动系统。
bash
# 启动系统(launch 文件)
ros2 launch my_pkg my_system.launch.py
步骤 2:查看节点。
bash
ros2 node list
# 输出:
# /my_robot/chassis
# /my_robot/laser
# /my_robot/planner
# /rviz2
步骤 3:查看节点详情。
bash
ros2 node info /my_robot/chassis
# 输出:
# /my_robot/chassis
# Publishers:
# /my_robot/cmd_vel [geometry_msgs/msg/Twist]
# /my_robot/odom [nav_msgs/msg/Odometry]
# Subscribers:
# /my_robot/cmd_vel [geometry_msgs/msg/Twist]
# Service Servers:
# /my_robot/enable [std_srvs/srv/SetBool]
# /my_robot/status [std_srvs/srv/Trigger]
# ...
步骤 4:查看话题。
bash
ros2 topic list
# 输出:
# /my_robot/cmd_vel
# /my_robot/odom
# /my_robot/laser/scan
# /my_robot/planner/path
# /parameter_events
# /rosout
步骤 5:监听话题。
bash
# 监听速度指令
ros2 topic echo /my_robot/cmd_vel
# 监听激光数据
ros2 topic echo /my_robot/laser/scan --once
步骤 6:监听频率。
bash
ros2 topic hz /my_robot/odom
# 输出:
# average rate: 10.000
步骤 7:调用服务。
bash
# 启用机器人
ros2 service call /my_robot/enable std_srvs/srv/SetBool "data: true"
# 输出:
# success: true
# message: 'Enabled'
# 查询状态
ros2 service call /my_robot/status std_srvs/srv/Trigger "{}"
# 输出:
# success: true
# message: 'Robot is running'
步骤 8:设置参数。
bash
# 查看参数
ros2 param get /my_robot/chassis max_speed
# 输出:1.5
# 设置参数
ros2 param set /my_robot/chassis max_speed 2.0
# 输出:
# Set for [max_speed]: 2.0
步骤 9:导出参数。
bash
ros2 param dump > my_system_params.yaml
# 查看导出
cat my_system_params.yaml
步骤 10:录制 bag。
bash
# 录制 30 秒
timeout 30 ros2 bag record -o ~/bags/debug_session \
/my_robot/cmd_vel \
/my_robot/odom \
/my_robot/laser/scan
步骤 11:查看 bag。
bash
ros2 bag info ~/bags/debug_session
# 输出:
# Files: debug_session_0.mdb
# Bag size: 50 MB
# Storage format: mcap
# Start time: 2024-01-01 10:00:00
# Duration: 30.0s
# Messages: 1500
# Topic information: /my_robot/cmd_vel [geometry_msgs/msg/Twist] 500 messages
# /my_robot/odom [nav_msgs/msg/Odometry] 500 messages
# /my_robot/laser/scan [sensor_msgs/msg/LaserScan] 500 messages
步骤 12:回放 bag。
bash
# 回放
ros2 bag play ~/bags/debug_session
# 2 倍速回放
ros2 bag play ~/bags/debug_session -r 2.0
步骤 13:系统诊断。
bash
ros2 doctor check
# 输出:
# [PASS] ROS_DISTRO is set
# [PASS] ROS_DOMAIN_ID is set
# [PASS] RMW_IMPLEMENTATION is set
# [PASS] DDS discovery: multicast available
# [PASS] Network interface: eth0 is up
步骤 14:性能追踪。
bash
# 记录 trace 10 秒
timeout 10 ros2 trace record -o ~/traces/perf
# 查看 trace
ros2 trace info ~/traces/perf
2.3.17 踩坑点
-
ros2 topic echo ** 无数据**。QoS 不匹配或类型不匹配。解决:ros2 topic info -v查看 QoS 与类型。 -
ros2 service call ** 超时**。服务未启动或无响应。解决:ros2 service list确认服务存在,ros2 node info确认节点运行。 -
ros2 action send_goal ** 失败**。动作未启动或类型不匹配。解决:ros2 action list确认动作存在。 -
ros2 param set ** 失败**。参数未声明或类型不匹配。解决:ros2 param describe查看参数类型。 -
ros2 bag record ** 无数据**。QoS 不匹配或话题未发布。解决:ros2 topic hz确认话题有数据。 -
ros2 bag play ** 时间戳错乱**。回放与系统时间不一致。解决:--start-time/--end-time指定时间,或use_sim_time:=true。 -
ros2 doctor check ** 报 WARN**。DDS 发现/网络问题。解决:ros2 multicast check检查组播,ifconfig检查网络。 -
ros2 topic hz ** 频率异常**。话题频率低于预期。解决:ros2 topic bw查看带宽,ros2 node info查看节点状态。 -
ros2 run ** 找不到节点**。包未编译或节点名错误。解决:ros2 pkg list确认包存在,ros2 pkg executables查看节点名。 -
ros2 launch ** 参数未生效**。launch 文件参数覆盖顺序错误。解决:检查 launch 文件参数加载顺序,命令行参数优先级最高。
2.3.18 工程最佳实践
- 调试脚本化 :将常用
ros2cli命令写成 shell 脚本,bash debug.sh一键诊断。 - 日志收集 :
ros2 topic echo/ros2 node info输出收集到日志文件,便于回溯。 - bag 命名规范 :
<日期>_<场景>_<版本号>,如20240101_debug_v1.0。 - bag 存储:大 bag 存到 NFS / 对象存储,本地仅存小 bag。
- 诊断定时 :生产系统部署
ros2 doctor check定时任务,异常告警。 - 频率监控 :
ros2 topic hz部署为监控脚本,频率异常告警。 - 参数版本化 :
ros2 param dump输出纳入版本控制,参数变更可追溯。 - launch 参数外置:launch 文件参数用 YAML,不硬编码。
- ros2cli 版本锁定 :生产环境锁定
ros2cli版本,防止apt upgrade意外升级。 - 性能追踪常态化 :生产系统定期
ros2 trace record,分析延迟与卡顿。
2.4 自定义接口全流程:msg / srv / action 语法规范、编写、编译、调用
是什么 :ROS2 允许用户自定义消息(msg)、服务(srv)、动作(action)接口,用于类型化通信。自定义接口定义在功能包的
msg//srv//action/目录下,编译后生成 C++/Python 类,供节点引用。核心原理 :接口文件(
.msg/.srv/.action)是"类型定义",编译时rosidl生成 C++/Python 代码 + 类型描述(.type文件)。编译产物安装到install/<pkg>/share/<pkg>/,运行时节点通过#include/import引用。
2.4.1 接口类型总览
| 类型 | 文件 | 用途 | 编译工具 | 适用 |
|---|---|---|---|---|
| msg | .msg |
话题数据 | rosidl_default_generators |
传感器、控制、状态 |
| srv | .srv |
服务请求/响应 | rosidl_default_generators |
命令查询、触发 |
| action | .action |
长任务(目标/反馈/结果) | rosidl_default_generators |
移动、规划、抓取 |
2.4.2 消息(msg)语法规范
2.4.2.1 基本语法
# 注释(以 # 开头)
<类型> <字段名>
2.4.2.2 支持的数据类型
| 类型 | 说明 | C++ 类型 | Python 类型 |
|---|---|---|---|
bool |
布尔 | bool |
bool |
int8 / int16 / int32 / int64 |
有符号整数 | int8_t / int16_t / int32_t / int64_t |
int |
uint8 / uint16 / uint32 / uint64 |
无符号整数 | uint8_t / uint16_t / uint32_t / uint64_t |
int |
float32 / float64 |
浮点 | float / double |
float |
string |
字符串 | std::string |
str |
byte |
单字节 | uint8_t |
bytes |
char |
单字符 | char |
str |
time |
时间戳 | builtin_interfaces::msg::Time |
Time |
duration |
时长 | builtin_interfaces::msg::Duration |
Duration |
Header |
标准头 | std_msgs::msg::Header |
Header |
<pkg>/<Msg> |
嵌套消息 | pkg::msg::Msg |
pkg.msg.Msg |
<type>[N] |
定长数组 | std::array<type, N> |
list(长度 N) |
<type>[] |
变长数组 | std::vector<type> |
list |
<type>[N][M] |
二维数组 | std::array<std::array<type, M>, N> |
list[list] |
2.4.2.3 字段属性
| 属性 | 语法 | 说明 |
|---|---|---|
| 默认值 | <type> <name> := <value> |
字段默认值(仅字面量) |
| 常数 | <type> <NAME> = <value> |
大写常数(全大写) |
| 数组 | <type> <name>[N] / <type> <name>[] |
定长/变长数组 |
2.4.2.4 消息示例
msg
# msg/RobotState.msg
# 机器人状态消息
std_msgs/Header header # 时间戳 + 坐标系
string frame_id # 坐标系 ID
float64 x # X 位置 (m)
float64 y # Y 位置 (m)
float64 theta # 姿态角 (rad)
float64 vx # X 速度 (m/s)
float64 vy # Y 速度 (m/s)
float64 vtheta # 角速度 (rad/s)
float64[] joint_positions # 关节位置 (rad)
bool is_moving # 是否运动中
msg
# msg/SensorData.msg
# 传感器数据消息
std_msgs/Header header
float32[] values # 传感器值
float32 noise_stddev # 噪声标准差
bool is_valid # 数据有效
msg
# msg/Config.msg
# 配置消息
string name # 配置名
string[] keys # 键
string[] values # 值
uint32 version = 1 # 版本常数
2.4.3 服务(srv)语法规范
2.4.3.1 基本语法
# 注释
<请求字段 1>
<请求字段 2>
---
<响应字段 1>
<响应字段 2>
2.4.3.2 服务示例
srv
# srv/SetPosition.srv
# 设置位置服务
# 请求
float64 x # X 目标位置 (m)
float64 y # Y 目标位置 (m)
float64 theta # 目标姿态角 (rad)
float64 tolerance # 容差 (m)
---
# 响应
bool success # 是否成功
string message # 消息
float64 actual_x # 实际 X 位置
float64 actual_y # 实际 Y 位置
float64 actual_theta # 实际姿态角
srv
# srv/Calibrate.srv
# 校准服务
# 请求
string sensor_name # 传感器名
float64[] calibration_data # 校准数据
bool save # 是否保存
---
# 响应
bool success # 是否成功
string message # 消息
float64[] result # 校准结果
srv
# srv/GetVersion.srv
# 获取版本服务
# 请求
---
# 响应
string version # 版本号
string commit # Git commit
string build_time # 构建时间
2.4.4 动作(action)语法规范
2.4.4.1 基本语法
# 注释
<目标字段 1>
<目标字段 2>
---
# 反馈
<反馈字段 1>
<反馈字段 2>
---
# 结果
<结果字段 1>
<结果字段 2>
2.4.4.2 动作示例
action
# action/MoveTo.action
# 移动到目标位置动作
# 目标
float64 x # X 目标位置 (m)
float64 y # Y 目标位置 (m)
float64 theta # 目标姿态角 (rad)
float64 max_speed # 最大速度 (m/s)
float64 tolerance # 容差 (m)
---
# 反馈
float64 current_x # 当前 X 位置
float64 current_y # 当前 Y 位置
float64 remaining_distance # 剩余距离 (m)
float64 progress # 进度 (0-1)
---
# 结果
bool success # 是否成功
string message # 消息
float64 actual_x # 实际 X 位置
float64 actual_y # 实际 Y 位置
float64 actual_theta # 实际姿态角
float64 duration # 耗时 (s)
action
# action/Grasp.action
# 抓取动作
# 目标
string object_name # 物体名
float64 x # 抓取点 X
float64 y # 抓取点 Y
float64 z # 抓取点 Z
float64 force # 抓取力 (N)
---
# 反馈
float64 current_z # 当前 Z 位置
float64 force_feedback # 力反馈 (N)
float64 progress # 进度 (0-1)
---
# 结果
bool success # 是否成功
string message # 消息
float64 actual_x # 实际 X
float64 actual_y # 实际 Y
float64 actual_z # 实际 Z
2.4.5 编写接口包
步骤 1:创建接口包。
bash
# 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 创建接口包
ros2 pkg create --build-type ament_cmake my_interfaces --dependencies std_msgs
步骤 2:创建消息/服务/动作文件。
bash
# 创建目录
mkdir -p my_interfaces/msg
mkdir -p my_interfaces/srv
mkdir -p my_interfaces/action
# 消息文件
cat > my_interfaces/msg/RobotState.msg << 'EOF'
std_msgs/Header header
float64 x
float64 y
float64 theta
float64 vx
float64 vy
float64 vtheta
float64[] joint_positions
bool is_moving
EOF
# 服务文件
cat > my_interfaces/srv/SetPosition.srv << 'EOF'
float64 x
float64 y
float64 theta
float64 tolerance
---
bool success
string message
float64 actual_x
float64 actual_y
float64 actual_theta
EOF
# 动作文件
cat > my_interfaces/action/MoveTo.action << 'EOF'
float64 x
float64 y
float64 theta
float64 max_speed
float64 tolerance
---
float64 current_x
float64 current_y
float64 remaining_distance
float64 progress
---
bool success
string message
float64 actual_x
float64 actual_y
float64 actual_theta
float64 duration
EOF
步骤 3 :配置 package.xml。
xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>my_interfaces</name>
<version>0.1.0</version>
<description>自定义接口包</description>
<maintainer email="dev@example.com">dev</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>std_msgs</depend>
<depend>builtin_interfaces</depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
步骤 4 :配置 CMakeLists.txt。
cmake
cmake_minimum_required(VERSION 3.8)
project(my_interfaces)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(std_msgs REQUIRED)
find_package(builtin_interfaces REQUIRED)
# 生成接口
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/RobotState.msg"
"srv/SetPosition.srv"
"action/MoveTo.action"
DEPENDENCIES std_msgs builtin_interfaces
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
步骤 5:编译。
bash
cd ~/ros2_ws
colcon build --symlink-install
# 编译产物
# build/my_interfaces/
# install/my_interfaces/
# 激活
source install/setup.bash
步骤 6:验证接口。
bash
# 查看消息
ros2 interface show my_interfaces/RobotState
# 输出:
# std_msgs/Header header
# float64 x
# float64 y
# float64 theta
# ...
# 查看服务
ros2 interface show my_interfaces/SetPosition
# 输出:
# float64 x
# float64 y
# float64 theta
# float64 tolerance
# ---
# bool success
# string message
# ...
# 查看动作
ros2 interface show my_interfaces/MoveTo
# 输出:
# float64 x
# float64 y
# ...
# ---
# float64 current_x
# ...
# ---
# bool success
# ...
2.4.6 在节点中使用接口
2.4.6.1 C++ 节点引用接口
cpp
#include <rclcpp/rclcpp.hpp>
#include <my_interfaces/msg/robot_state.hpp>
#include <my_interfaces/srv/set_position.hpp>
#include <my_interfaces/action/move_to.hpp>
using RobotState = my_interfaces::msg::RobotState;
using SetPosition = my_interfaces::srv::SetPosition;
using MoveTo = my_interfaces::action::MoveTo;
class MyNode : public rclcpp::Node
{
public:
MyNode() : Node("my_node")
{
// 话题
pub_ = this->create_publisher<RobotState>("/robot/state", 10);
// 服务
srv_ = this->create_service<SetPosition>(
"/robot/set_position",
std::bind(&MyNode::handle_set_position, this, std::placeholders::_1, std::placeholders::_2));
// 动作
action_server_ = rclcpp_action::create_server<MoveTo>(
this, "/robot/move_to",
std::bind(&MyNode::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&MyNode::handle_cancel, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&MyNode::handle_feedback, this, std::placeholders::_1, std::placeholders::_2));
}
private:
void handle_set_position(const std::shared_ptr<SetPosition::Request> req,
std::shared_ptr<SetPosition::Response> res)
{
RCLCPP_INFO(this->get_logger(), "SetPosition: x=%.2f, y=%.2f", req->x, req->y);
res->success = true;
res->message = "Position set";
res->actual_x = req->x;
res->actual_y = req->y;
}
void handle_goal(const rclcpp_action::GoalUUID& uuid,
const std::shared_ptr<MoveTo::Goal> goal)
{
RCLCPP_INFO(this->get_logger(), "MoveTo goal: x=%.2f, y=%.2f", goal->x, goal->y);
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
void handle_cancel(const std::shared_ptr<rclcpp_action::ServerGoalHandle<MoveTo>> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "MoveTo cancel requested");
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_feedback(const std::shared_ptr<rclcpp_action::ServerGoalHandle<MoveTo>> goal_handle)
{
auto feedback = std::make_shared<MoveTo::Feedback>();
feedback->current_x = 0.0;
feedback->current_y = 0.0;
feedback->remaining_distance = 1.0;
feedback->progress = 0.5;
goal_handle->publish_feedback(feedback);
}
rclcpp::Publisher<RobotState>::SharedPtr pub_;
rclcpp::Service<SetPosition>::SharedPtr srv_;
rclcpp_action::Server<MoveTo>::SharedPtr action_server_;
};
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
2.4.6.2 Python 节点引用接口
python
#!/usr/bin/env python3
"""引用自定义接口的节点。"""
import rclpy
from rclpy.node import Node
from my_interfaces.msg import RobotState
from my_interfaces.srv import SetPosition
from my_interfaces.action import MoveTo
class MyNode(Node):
def __init__(self):
super().__init__("my_node")
# 话题
self.pub = self.create_publisher(RobotState, "/robot/state", 10)
# 服务
self.srv = self.create_service(
SetPosition, "/robot/set_position", self.handle_set_position
)
# 动作
self.action_server = self.create_action_server(
MoveTo, "/robot/move_to",
goal_callback=self.handle_goal,
cancel_callback=self.handle_cancel,
execute_callback=self.handle_execute,
)
def handle_set_position(self, request, response):
self.get_logger().info(f"SetPosition: x={request.x:.2f}, y={request.y:.2f}")
response.success = True
response.message = "Position set"
response.actual_x = request.x
response.actual_y = request.y
return response
def handle_goal(self, goal_request):
self.get_logger().info(f"MoveTo goal: x={goal_request.goal.x:.2f}")
from rclpy.action import GoalResponse
return GoalResponse.ACCEPT
def handle_cancel(self, goal_handle):
self.get_logger().info("MoveTo cancel requested")
from rclpy.action import CancelResponse
return CancelResponse.ACCEPT
def handle_execute(self, goal_handle):
from rclpy.action import GoalStatus
feedback = MoveTo.Feedback()
feedback.current_x = 0.0
feedback.current_y = 0.0
feedback.remaining_distance = 1.0
feedback.progress = 0.5
goal_handle.publish_feedback(feedback)
# 执行任务(模拟)
result = MoveTo.Result()
result.success = True
result.message = "MoveTo completed"
result.actual_x = goal_handle.goal.x
result.actual_y = goal_handle.goal.y
goal_handle.succeed()
return result
def main(args=None):
rclpy.init(args=args)
node = MyNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
2.4.7 调用自定义接口
2.4.7.1 命令行调用
bash
# 发布自定义消息
ros2 topic pub /robot/state my_interfaces/msg/RobotState "
header:
frame_id: 'base_link'
x: 1.0
y: 2.0
theta: 0.5
vx: 0.1
vy: 0.0
vtheta: 0.0
joint_positions: [0.0, 0.5, 1.0]
is_moving: true
"
# 调用自定义服务
ros2 service call /robot/set_position my_interfaces/srv/SetPosition "
x: 1.0
y: 2.0
theta: 0.5
tolerance: 0.1
"
# 输出:
# success: True
# message: 'Position set'
# actual_x: 1.0
# actual_y: 2.0
# actual_theta: 0.5
# 发送自定义动作
ros2 action send_goal /robot/move_to my_interfaces/action/MoveTo "
x: 1.0
y: 2.0
theta: 0.5
max_speed: 0.5
tolerance: 0.1
"
# 输出:
# Goal accepted with ID: <uuid>
# Feedback:
# current_x: 0.0
# current_y: 0.0
# remaining_distance: 1.0
# progress: 0.5
# Result:
# success: True
# message: 'MoveTo completed'
2.4.7.2 代码调用(C++)
cpp
// 客户端调用服务
#include <rclcpp/rclcpp.hpp>
#include <my_interfaces/srv/set_position.hpp>
class MyClient : public rclcpp::Node
{
public:
MyClient() : Node("my_client")
{
client_ = this->create_client<my_interfaces::srv::SetPosition>(
"/robot/set_position");
// 等待服务
while (!client_->wait_for_service(1s)) {
if (!rclcpp::ok()) return;
RCLCPP_WARN(this->get_logger(), "Waiting for service...");
}
// 发送请求
auto request = std::make_shared<my_interfaces::srv::SetPosition::Request>();
request->x = 1.0;
request->y = 2.0;
request->theta = 0.5;
request->tolerance = 0.1;
auto future = client_->async_send_request(request);
rclcpp::spin_until_future_complete(this, future);
if (future.get()->success) {
RCLCPP_INFO(this->get_logger(), "Service called successfully");
}
}
private:
rclcpp::Client<my_interfaces::srv::SetPosition>::SharedPtr client_;
};
2.4.7.3 代码调用(Python)
python
#!/usr/bin/env python3
"""调用自定义服务的客户端。"""
import rclpy
from rclpy.node import Node
from my_interfaces.srv import SetPosition
class MyClient(Node):
def __init__(self):
super().__init__("my_client")
self.client = self.create_client(SetPosition, "/robot/set_position")
# 等待服务
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().warn("Waiting for service...")
# 发送请求
request = SetPosition.Request()
request.x = 1.0
request.y = 2.0
request.theta = 0.5
request.tolerance = 0.1
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
if future.done():
response = future.result()
if response.success:
self.get_logger().info(f"Service called: {response.message}")
def main(args=None):
rclpy.init(args=args)
node = MyClient()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
2.4.8 接口版本管理
2.4.8.1 版本号规范
- 接口包版本号用语义化版本(
MAJOR.MINOR.PATCH)。 MAJOR:不兼容变更(字段删除、类型变更)。MINOR:兼容新增(字段新增)。PATCH:兼容修复(注释修正)。
2.4.8.2 兼容性检查
bash
# 查看接口版本
ros2 interface show my_interfaces/RobotState
# 对比接口变更
git diff my_interfaces/msg/RobotState.msg
2.4.8.3 废弃接口
msg
# msg/RobotState.msg
# @deprecated: 1.0.0, 使用 RobotStateV2 替代
std_msgs/Header header
float64 x
float64 y
# ...
2.4.9 踩坑点
- 接口文件路径错误 。
CMakeLists.txt中路径与文件实际路径不一致。解决:检查rosidl_generate_interfaces路径。 -
rosidl_default_generators ** 未声明**。编译报rosidl_default_generators未找到。解决:package.xml加<build_depend>rosidl_default_generators</build_depend>。 - 接口类型不匹配 。字段类型与 C++/Python 代码不一致。解决:
ros2 interface show查看生成类型。 - 嵌套消息未声明依赖 。引用
std_msgs/Header但未find_package(std_msgs)。解决:CMakeLists.txt加find_package(std_msgs REQUIRED)。 - 服务/动作分隔符错误 。
---分隔符缺失或位置错误。解决:检查.srv/.action文件结构。 - 接口编译失败 。
colcon build报语法错误。解决:cat my_interfaces/msg/xxx.msg检查语法。 - 接口运行时找不到 。节点
#include/import路径错误。解决:检查install/<pkg>/include//install/<pkg>/lib/python3.10/site-packages/。 - 接口版本不兼容 。字段删除/类型变更导致旧代码崩溃。解决:语义化版本,废弃接口用
@deprecated。 - 大消息性能差。大数组(图像、点云)序列化开销大。解决:压缩 / 共享内存 / 降频。
- 接口命名冲突。不同包定义同名消息。解决:接口独立成包,命名加前缀。
2.4.10 工程最佳实践
- 接口独立成包 :
my_interfaces独立,多包共用,避免重复定义。 - 接口命名规范 :消息大驼峰(
RobotState),服务/动作大驼峰(SetPosition/MoveTo)。 - 字段注释 :每个字段注释含义、单位、范围(如
float64 x # X 位置 (m))。 - 默认值 :字段设合理默认值(
:=),避免零值歧义。 - 常数 :版本、ID 等用大写常数(
uint32 VERSION = 1)。 - 版本管理 :接口包语义化版本,废弃接口用
@deprecated。 - 兼容性检查 :CI 流水线检查接口变更,不兼容变更需
MAJOR版本。 - 大消息优化:图像/点云用压缩格式(JPEG / PCD),或共享内存。
- 接口文档化 :
README.md列出所有接口,字段含义、单位、范围。 - 接口测试 :
colcon test跑接口单元测试,验证类型与字段。
2.5 节点高级特性:命名空间、重映射、多节点管理、节点隔离机制
是什么 :ROS2 节点支持命名空间(Namespace) 、话题/服务/参数重映射(Remap) 、多节点管理(Multi-Node) 、节点隔离(Node Isolation) 等高级特性,用于大型系统的模块化、复用与隔离。
核心原理 :命名空间是"逻辑分组",节点/话题/服务/参数可含命名空间前缀。重映射是"运行时改名",命令行
__remap:=或 launch 文件可改节点名/话题名/服务名。多节点是"一进程多节点",同一进程可创建多个Node实例。节点隔离是"域隔离 + 命名空间隔离",ROS_DOMAIN_ID隔离 DDS 域,命名空间隔离逻辑分组。
2.5.1 命名空间(Namespace)
2.5.1.1 基本概念
| 概念 | 说明 |
|---|---|
| 命名空间 | 节点/话题/服务/参数的逻辑分组,全小写 + 下划线 |
| 作用域 | 命名空间内名字唯一,跨命名空间可同名 |
| 层级 | 命名空间可分层,/a/b/c 是三级命名空间 |
| 默认 | 节点默认命名空间为 /(根),话题/服务继承节点命名空间 |
2.5.1.2 命名空间规则
| 规则 | 说明 |
|---|---|
| 绝对命名 | 以 / 开头,如 /vehicle/chassis/cmd_vel |
| 相对命名 | 不以 / 开头,继承节点命名空间,如 cmd_vel → /vehicle/cmd_vel |
| 私有命名 | 以 ~ 开头,私有(仅本节点可见),如 ~internal |
| 层级 | /a/b/c 是三级,/a 是一级 |
| 唯一性 | 同命名空间内名字唯一 |
2.5.1.3 命名空间示例
/vehicle
├── /vehicle/chassis
│ ├── /vehicle/chassis/cmd_vel # 控制指令
│ ├── /vehicle/chassis/odom # 里程计
│ └── /vehicle/chassis/joint_states # 关节状态
├── /vehicle/perception
│ ├── /vehicle/perception/laser/scan # 激光数据
│ ├── /vehicle/perception/camera/image # 相机数据
│ └── /vehicle/perception/depth/image # 深度图
└── /vehicle/planning
├── /vehicle/planning/path # 规划路径
└── /vehicle/planning/costmap # 代价地图
2.5.1.4 节点命名空间
bash
# 启动节点并指定命名空间
ros2 run demo_nodes_cpp talker __ns:=/vehicle
# 节点名:/vehicle/talker
# 话题名:/vehicle/chatter(继承命名空间)
# 启动节点并指定命名空间 + 节点名
ros2 run demo_nodes_cpp talker __ns:=/vehicle __node:=chassis
# 节点名:/vehicle/chassis
# 话题名:/vehicle/chatter
2.5.1.5 话题/服务命名空间
bash
# 话题绝对命名
/vehicle/chassis/cmd_vel
# 话题相对命名(节点命名空间 /vehicle)
cmd_vel → /vehicle/cmd_vel
# 话题私有命名
~internal → /vehicle/talker/internal
2.5.2 重映射(Remap)
2.5.2.1 基本概念
| 概念 | 说明 |
|---|---|
| 重映射 | 运行时改节点名/话题名/服务名/参数名 |
| 命令行 | __node:= / __ns:= / __remap:= |
| launch | node_name / namespace / remappings |
| 参数 | --ros-args -p / launch parameters |
| 作用 | 复用节点、多实例、测试环境 |
2.5.2.2 命令行重映射
bash
# 重命名节点
ros2 run demo_nodes_cpp talker __node:=chassis
# 节点名:/chassis
# 改命名空间
ros2 run demo_nodes_cpp talker __ns:=/vehicle
# 节点名:/vehicle/talker
# 话题重映射
ros2 run demo_nodes_cpp talker __remap:=chatter:=my_chatter
# 话题名:/my_chatter(原 /chatter)
# 服务重映射
ros2 run demo_nodes_cpp talker __remap:=service_name:=new_service
# 服务名:/new_service
# 参数重映射
ros2 run demo_nodes_cpp talker __remap:=param_name:=new_param
# 参数名:new_param
2.5.2.3 launch 文件重映射
python
# launch/my_system.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_nodes_cpp',
executable='talker',
name='chassis', # 节点名
namespace='vehicle', # 命名空间
output='screen',
remappings=[
('chatter', 'my_chatter'), # 话题重映射
('service_name', 'new_service'), # 服务重映射
],
parameters=[
{'param_name': 'new_param'}, # 参数重映射
],
),
Node(
package='demo_nodes_py',
executable='listener',
name='chassis_listener',
namespace='vehicle',
output='screen',
remappings=[
('chatter', 'my_chatter'),
],
),
])
bash
# 运行 launch
ros2 launch my_pkg my_system.launch.py
# 节点 1:/vehicle/chassis(发布 /vehicle/my_chatter)
# 节点 2:/vehicle/chassis_listener(订阅 /vehicle/my_chatter)
2.5.2.4 参数重映射
bash
# 命令行参数
ros2 run my_pkg my_node --ros-args -p max_speed:=2.0 -p use_sim_time:=false
# launch 参数
Node(
package='my_pkg',
executable='my_node',
parameters=[
{'max_speed': 2.0},
{'use_sim_time': False},
],
)
# YAML 参数文件
# config/params.yaml
/chassis:
ros__parameters:
max_speed: 2.0
use_sim_time: false
# launch 加载 YAML
Node(
package='my_pkg',
executable='my_node',
name='chassis',
namespace='vehicle',
parameters=[
get_package_share_directory('my_pkg') + '/config/params.yaml',
],
)
2.5.3 多节点管理(Multi-Node)
2.5.3.1 一进程多节点(C++)
cpp
#include <rclcpp/rclcpp.hpp>
#include <memory>
#include <vector>
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
// 创建多个节点
auto node1 = std::make_shared<rclcpp::Node>("node1");
auto node2 = std::make_shared<rclcpp::Node>("node2");
auto node3 = std::make_shared<rclcpp::Node>("node3");
// 各节点创建通信端点
auto pub1 = node1->create_publisher<std_msgs::msg::String>("/topic1", 10);
auto sub2 = node2->create_subscription<std_msgs::msg::String>(
"/topic1", 10, [](std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(rclcpp::get_logger("node2"), "Received: %s", msg->data.c_str());
});
// 多节点共享执行器(单线程)
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node1);
executor.add_node(node2);
executor.add_node(node3);
executor.spin();
rclcpp::shutdown();
return 0;
}
2.5.3.2 一进程多节点(Python)
python
#!/usr/bin/env python3
"""一进程多节点示例。"""
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Node1(Node):
def __init__(self):
super().__init__("node1")
self.pub = self.create_publisher(String, "/topic1", 10)
self.timer = self.create_timer(1.0, self.callback)
self.count = 0
def callback(self):
msg = String()
msg.data = f"From node1: {self.count}"
self.pub.publish(msg)
self.count += 1
class Node2(Node):
def __init__(self):
super().__init__("node2")
self.sub = self.create_subscription(String, "/topic1", self.callback, 10)
def callback(self, msg):
self.get_logger().info(f"node2 received: {msg.data}")
class Node3(Node):
def __init__(self):
super().__init__("node3")
self.get_logger().info("Node3 started")
def main(args=None):
rclpy.init(args=args)
node1 = Node1()
node2 = Node2()
node3 = Node3()
# 多节点共享执行器
from rclpy.executors import SingleThreadedExecutor
executor = SingleThreadedExecutor()
executor.add_node(node1)
executor.add_node(node2)
executor.add_node(node3)
executor.spin()
node1.destroy_node()
node2.destroy_node()
node3.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
2.5.3.3 多进程多节点(推荐)
bash
# 终端 1
ros2 run my_pkg node1
# 终端 2
ros2 run my_pkg node2
# 终端 3
ros2 run my_pkg node3
2.5.3.4 执行器类型
| 执行器 | 线程 | 适用 |
|---|---|---|
SingleThreadedExecutor |
单线程 | 简单场景,节点少 |
MultiThreadedExecutor |
多线程 | 高性能,节点多,回调独立 |
StaticSingleThreadedExecutor |
单线程(静态) | 实时场景 |
StaticMultiThreadedExecutor |
多线程(静态) | 实时场景 |
2.5.3.5 多节点管理最佳实践
| 实践 | 说明 |
|---|---|
| 一进程一节点 | 生产环境推荐,隔离故障、独立重启 |
| 一进程多节点 | 仅教学 / 轻量场景,共享执行器 |
| launch 编排 | 多节点用 launch 文件编排,统一启动 |
| systemd 托管 | 生产环境 systemd 托管节点,故障自动重启 |
| 执行器选择 | 节点少用单线程,节点多用多线程 |
2.5.4 节点隔离机制
2.5.4.1 DDS 域隔离(ROS_DOMAIN_ID)
bash
# 机器 A(域 0)
export ROS_DOMAIN_ID=0
ros2 run demo_nodes_cpp talker
# 机器 B(域 0,同域)
export ROS_DOMAIN_ID=0
ros2 topic list
# 输出:/chatter(可见 A 的话题)
# 机器 B(域 1,跨域)
export ROS_DOMAIN_ID=1
ros2 topic list
# 输出:(空,不可见 A 的话题)
| 隔离级别 | 机制 | 说明 |
|---|---|---|
| 域隔离 | ROS_DOMAIN_ID |
DDS 域隔离,跨域不可发现 |
| 命名空间隔离 | __ns:= |
逻辑分组,同域内可发现 |
| 本机隔离 | ROS_LOCALHOST_ONLY=1 |
仅本地,跨机不可发现 |
2.5.4.2 命名空间隔离
bash
# 节点 A(命名空间 /vehicle)
ros2 run my_pkg node_a __ns:=/vehicle
# 节点 B(命名空间 /drone)
ros2 run my_pkg node_b __ns:=/drone
# 查看节点
ros2 node list
# 输出:
# /vehicle/node_a
# /drone/node_b
# 查看话题
ros2 topic list
# 输出:
# /vehicle/topic
# /drone/topic
2.5.4.3 本机隔离
bash
# 仅本地监听(跨机不可发现)
export ROS_LOCALHOST_ONLY=1
ros2 run demo_nodes_cpp talker
# 跨机不可见
# 机器 B
ros2 topic list
# 输出:(空)
2.5.4.4 隔离场景
| 场景 | 隔离机制 | 说明 |
|---|---|---|
| 测试 vs 生产 | ROS_DOMAIN_ID |
域隔离,零成本 |
| 多机器人 | ROS_DOMAIN_ID + 命名空间 |
域隔离 + 逻辑分组 |
| 开发 vs 生产 | ROS_DOMAIN_ID |
域隔离 |
| 跨机调试 | ROS_LOCALHOST_ONLY=0 |
允许跨机 |
| 单机器人多模块 | 命名空间 | 逻辑分组,同域 |
2.5.5 实操步骤(命名空间 + 重映射 + 多节点 + 隔离)
步骤 1:启动节点并指定命名空间。
bash
ros2 run demo_nodes_cpp talker __ns:=/vehicle __node:=chassis
# 节点名:/vehicle/chassis
# 话题名:/vehicle/chatter
步骤 2:话题重映射。
bash
ros2 run demo_nodes_cpp talker __ns:=/vehicle __node:=chassis __remap:=chatter:=cmd_vel
# 节点名:/vehicle/chassis
# 话题名:/vehicle/cmd_vel(原 /vehicle/chatter)
步骤 3:启动多个节点(一进程多节点)。
bash
# 编译一进程多节点包(见 2.5.3)
ros2 run my_pkg multi_node
# 输出:
# [INFO] [node1]: Node1 started
# [INFO] [node2]: Node2 started
# [INFO] [node3]: Node3 started
步骤 4:命名空间隔离。
bash
# 终端 1
ros2 run my_pkg node_a __ns:=/vehicle
# 终端 2
ros2 run my_pkg node_b __ns:=/drone
# 查看节点
ros2 node list
# 输出:
# /vehicle/node_a
# /drone/node_b
步骤 5:DDS 域隔离。
bash
# 机器 A
export ROS_DOMAIN_ID=0
ros2 run demo_nodes_cpp talker
# 机器 B(同域)
export ROS_DOMAIN_ID=0
ros2 topic list
# 输出:/chatter
# 机器 B(跨域)
export ROS_DOMAIN_ID=1
ros2 topic list
# 输出:(空)
2.5.6 踩坑点
- 命名空间未继承 。话题相对命名未继承节点命名空间。解决:检查节点
__ns:=设置。 - **私有命名空间
~ ** 不可见 。~internal话题仅本节点可见,外部不可订阅。解决:用绝对命名或相对命名。 - 重映射未生效 。
__remap:=语法错误或 launch 文件remappings未配置。解决:检查语法与 launch 文件。 - 多节点资源冲突 。一进程多节点共享执行器,回调阻塞。解决:用
MultiThreadedExecutor或一进程一节点。 - DDS 域隔离未生效 。
ROS_DOMAIN_ID未source或未导出。解决:export ROS_DOMAIN_ID+source ~/.bashrc。 - 跨机隔离误设 。
ROS_LOCALHOST_ONLY=1导致跨机不可发现。解决:跨机调试时设为 0。 - 命名空间冲突 。同命名空间内节点/话题名重复。解决:
__node:=重命名或__ns:=改命名空间。 - launch 参数覆盖顺序错误。launch 文件参数被命令行覆盖,行为异常。解决:命令行参数优先级最高,注意覆盖顺序。
- 多节点日志混乱。一进程多节点日志交错,难以排查。解决:一进程一节点,或日志加节点名前缀。
- 隔离后通信失败 。域隔离 / 本机隔离后跨域 / 跨机通信失败。解决:确认隔离机制,跨域需
ROS_DOMAIN_ID一致,跨机需ROS_LOCALHOST_ONLY=0。
2.5.7 工程最佳实践
- 命名空间分层 :
/<机器人>/<子系统>/<数据>,如/vehicle/chassis/cmd_vel,分层清晰。 - 节点命名规范 :
<模块>_<子模块>_<实例>,如nav2_local_planner_01,全局唯一。 - 一进程一节点:生产环境推荐,隔离故障、独立重启。
- launch 编排:多节点用 launch 文件编排,统一启动、参数、重映射。
- systemd 托管:生产环境 systemd 托管节点,故障自动重启。
- DDS 域隔离 :测试 / 预发 / 生产用不同
ROS_DOMAIN_ID,零成本隔离。 - 命名空间隔离:多机器人类型用命名空间隔离,同域内逻辑分组。
- 跨机调试 :
ROS_LOCALHOST_ONLY=0,允许跨机发现。 - 重映射测试:测试环境用重映射复用节点,避免重复部署。
- 日志前缀:多节点日志加节点名前缀,便于排查。
模块二 本章工程最佳实践 + 高频报错排查
A. 本章工程最佳实践汇总
- 通信选型:长任务 → 动作;请求/响应 → 服务;数据流 → 话题;坐标 → TF;配置 → 参数。
- 显式 QoS:所有 Publisher/Subscriber 显式声明,用 QoS 模板(控制/传感器/状态/配置/TF)。
- 服务超时:客户端必设超时(5-10s),避免卡死。
- 动作状态监听 :监听
<action>/_status,取消/中止无感知。 - 参数声明必须 :先
declare_parameter再get_parameter,类型严格匹配。 - 动态参数回调:关键参数注册回调,运行时改参自动响应。
- 节点退出规范 :C++
rclcpp::shutdown(),Pythondestroy_node()+rclpy.shutdown()。 - Lifecycle 节点 :量产系统用
LifecycleNode,支持热启停、故障恢复。 - 命名空间分层 :
/<机器人>/<子系统>/<数据>,分层清晰。 - 节点命名规范 :
<模块>_<子模块>_<实例>,全局唯一。 - 一进程一节点:生产环境推荐,隔离故障、独立重启。
- launch 编排:多节点用 launch 文件编排,统一启动、参数、重映射。
- DDS 域隔离 :测试 / 预发 / 生产用不同
ROS_DOMAIN_ID。 - 接口独立成包 :
my_interfaces独立,多包共用。 - 接口版本管理 :语义化版本,废弃接口用
@deprecated。 - ros2cli 调试脚本化:常用命令写成 shell 脚本,一键诊断。
- bag 命名规范 :
<日期>_<场景>_<版本号>,如20240101_debug_v1.0。 - 参数版本化 :
ros2 param dump输出纳入版本控制,参数变更可追溯。 - 诊断定时 :生产系统部署
ros2 doctor check定时任务,异常告警。 - 性能追踪常态化 :生产系统定期
ros2 trace record,分析延迟与卡顿。
B. 本章高频报错排查速查
| 编号 | 报错关键词 | 根因 | 解决方案 |
|---|---|---|---|
| 1 | ros2 topic echo 无数据 |
QoS 不匹配 / 类型不匹配 | ros2 topic info -v 查看 QoS 与类型 |
| 2 | ros2 service call 超时 |
服务未启动 / 无响应 | ros2 service list 确认服务存在 |
| 3 | ros2 action send_goal 失败 |
动作未启动 / 类型不匹配 | ros2 action list 确认动作存在 |
| 4 | ros2 param set 失败 |
参数未声明 / 类型不匹配 | ros2 param describe 查看参数类型 |
| 5 | ros2 bag record 无数据 |
QoS 不匹配 / 话题未发布 | ros2 topic hz 确认话题有数据 |
| 6 | ros2 bag play 时间戳错乱 |
回放与系统时间不一致 | --start-time / use_sim_time:=true |
| 7 | ros2 doctor check 报 WARN |
DDS 发现 / 网络问题 | ros2 multicast check 检查组播 |
| 8 | ros2 topic hz 频率异常 |
话题频率低于预期 | ros2 topic bw 查看带宽 |
| 9 | ros2 run 找不到节点 |
包未编译 / 节点名错误 | ros2 pkg list 确认包存在 |
| 10 | ros2 launch 参数未生效 |
launch 参数覆盖顺序错误 | 检查参数加载顺序 |
| 11 | 节点名重复 | 同域内节点名唯一 | __node:= 重命名 / __ns:= 改命名空间 |
| 12 | 话题类型不匹配 | 消息类型不一致 | ros2 topic info 查看类型 |
| 13 | 服务超时未设 | 默认无限等待 | 客户端设超时 |
| 14 | 动作状态未监听 | 只监听结果不监听状态 | 监听 <action>/_status |
| 15 | 参数未声明就获取 | 抛 ParameterNotDeclaredException |
先 declare_parameter |
| 16 | QoS 不匹配静默失败 | 无报错无数据 | ros2 topic info -v 查看双方 QoS |
| 17 | 跨语言 QoS 默认值不同 | C++ BEST_EFFORT / Python RELIABLE |
显式声明 QoS |
| 18 | 接口编译失败 | 语法错误 / 依赖缺失 | cat 检查语法 / package.xml 加依赖 |
| 19 | 接口运行时找不到 | #include / import 路径错误 |
检查 install/<pkg>/ 路径 |
| 20 | 命名空间未继承 | 话题相对命名未继承 | 检查节点 __ns:= 设置 |
| 21 | DDS 域隔离未生效 | ROS_DOMAIN_ID 未导出 |
export ROS_DOMAIN_ID + source ~/.bashrc |
| 22 | 跨机隔离误设 | ROS_LOCALHOST_ONLY=1 |
跨机调试时设为 0 |
| 23 | 多节点资源冲突 | 一进程多节点共享执行器 | MultiThreadedExecutor / 一进程一节点 |
| 24 | launch 参数覆盖顺序错误 | 命令行参数优先级最高 | 注意覆盖顺序 |
C. 模块二自检清单
完成本模块后,逐项验证:
- 能解释六大核心机制(节点/话题/服务/Action/参数/QoS)的原理与适用场景
- 能用
ros2 topic list/echo/pub/hz/info -v调试话题 - 能用
ros2 service list/call调用服务 - 能用
ros2 action list/send_goal发送动作 - 能用
ros2 param list/get/set/dump/load管理参数 - 能用
ros2 interface show查看接口定义 - 能独立创建 msg/srv/action 接口包并编译运行
- 能在 C++ / Python 节点中引用自定义接口
- 能理解命名空间(绝对/相对/私有)与继承规则
- 能用
__node:=/__ns:=/__remap:=重映射节点/话题/服务 - 能用 launch 文件编排多节点(参数、重映射、命名空间)
- 能用
ROS_DOMAIN_ID隔离 DDS 域 - 能用
ROS_LOCALHOST_ONLY本机隔离 - 能理解一进程多节点与多进程多节点的区别
- 能用
ros2 doctor check诊断系统 - 能用
ros2 bag record/play录制/回放数据 - QoS 显式声明,不依赖默认值
- 服务客户端设超时
- 动作状态监听
- 参数声明必须