2.ROS2 核心机制与命令行实操

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 析构 / Python destroy_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 踩坑点

  1. 节点名重复 。ROS2 同域内节点名必须唯一,重复启动失败。解决:__node:= 重命名或 __ns:= 改命名空间。
  2. 话题类型不匹配 。消息类型不一致则静默失败。解决:ros2 topic info /topic 查看类型,确保一致。
  3. 服务超时未设。默认无限等待,服务端崩溃则客户端卡死。解决:客户端设超时。
  4. 动作状态未监听 。只监听结果不监听状态,取消/中止无感知。解决:监听 <action>/_status 话题。
  5. 参数未声明就获取 。抛 ParameterNotDeclaredException。解决:先 declare_parameter 再 get_parameter。
  6. 参数类型不匹配 。声明 INT 实际传 DOUBLE,抛异常。解决:类型严格匹配。
  7. QoS 不匹配静默失败 。无报错、无数据。解决:ros2 topic info -v /topic 查看双方 QoS,对比匹配规则。
  8. 跨语言 QoS 默认值不同 。C++ BEST_EFFORT,Python RELIABLE。解决:显式声明 QoS。
  9. 节点资源未释放 。C++ Node 未 rclcpp::shutdown(),Python node 未 destroy_node(),资源泄漏。解决:规范退出流程。
  10. Lifecycle 节点状态未迁移 。configure 未调用直接 activate,抛异常。解决:严格按状态机迁移。

2.1.10 工程最佳实践

  1. 节点命名规范 :<模块>_<子模块>_<实例>,如 nav2_local_planner_01,全局唯一且可读。
  2. 话题命名规范 :/vehicle/<子系统>/<数据>,如 /vehicle/chassis/cmd_vel,分层清晰。
  3. 服务命名规范 :/robot/<操作>,如 /robot/joint/set_position,动词 + 宾语。
  4. 动作命名规范 :/robot/<任务>,如 /robot/move_to,任务语义明确。
  5. 参数命名规范 :小写 + 下划线,层级用 .,如 nav2.max_speed。
  6. 显式 QoS:所有 Publisher/Subscriber 显式声明,用 QoS 模板(2.1.6)。
  7. 服务超时:客户端必设超时(建议 5-10s),避免卡死。
  8. 动作状态监听 :监听 <action>/_status,取消/中止无感知。
  9. 参数声明必须 :先 declare_parameter 再 get_parameter,类型严格匹配。
  10. 动态参数回调:关键参数注册回调,运行时改参自动响应。
  11. 节点退出规范 :C++ rclcpp::shutdown(),Python destroy_node() + rclpy.shutdown()。
  12. 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 踩坑点

  1. 长任务用服务。服务端阻塞,客户端卡死。解决:改用动作。
  2. 高频数据用服务。请求/响应开销大,频率受限。解决:改用话题。
  3. 坐标变换用话题。无缓存、无时间戳查询。解决:改用 TF。
  4. 配置用话题。无动态回调、无 YAML 加载。解决:改用参数。
  5. 动作状态未监听 。只监听结果,取消/中止无感知。解决:监听 <action>/_status。
  6. 服务超时未设。服务端崩溃客户端卡死。解决:客户端设超时。
  7. QoS 不匹配静默失败 。无报错、无数据。解决:ros2 topic info -v 查看双方 QoS。
  8. TF 坐标丢失无感知 。TF 树断裂,lookup 抛异常。解决:监听 TF 树完整性。
  9. 参数未持久化。节点重启参数丢失。解决:YAML 文件 + launch 加载。
  10. 动作取消未响应 。客户端 cancel_goal() 服务端无响应。解决:服务端检查 goal_handle 状态。

2.2.8 工程最佳实践

  1. 选型决策流程:长任务 → 动作;请求/响应 → 服务;数据流 → 话题;坐标 → TF;配置 → 参数。
  2. QoS 模板化:控制/传感器/状态/配置/TF 五类模板,团队统一。
  3. 服务超时:客户端必设超时(5-10s),避免卡死。
  4. 动作状态监听 :监听 <action>/_status,取消/中止无感知。
  5. TF 树监控:量产系统部署 TF 树完整性监控,断裂告警。
  6. 参数持久化:YAML 文件 + launch 加载,节点重启参数不丢。
  7. 接口独立成包 :my_interfaces 独立,多包共用。
  8. 通信端点文档化 :ros2 topic list / ros2 service list / ros2 action list 输出纳入运维手册。
  9. QoS 显式声明:所有 Publisher/Subscriber 显式声明,不依赖默认值。
  10. 动作取消响应 :服务端检查 goal_handle 状态,及时响应取消。

2.3 全套 ros2cli 命令行实操:查询、运行、监控、诊断、管理

是什么 :ros2cli(ros2 命令组)是 ROS2 官方命令行工具集,涵盖节点管理、话题调试、服务调用、动作操作、参数管理、接口查询、包管理、launch 运行、bag 录制等全流程。

核心原理 :ros2 命令是 Python 编写的 CLI 工具,底层调用 rclpy API,通过 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 踩坑点

  1. ​ros2 topic echo​ ** 无数据**。QoS 不匹配或类型不匹配。解决:ros2 topic info -v 查看 QoS 与类型。
  2. ​ros2 service call​ ** 超时**。服务未启动或无响应。解决:ros2 service list 确认服务存在,ros2 node info 确认节点运行。
  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 / --end-time 指定时间,或 use_sim_time:=true。
  7. ​ros2 doctor check​ ** 报 WARN**。DDS 发现/网络问题。解决:ros2 multicast check 检查组播,ifconfig 检查网络。
  8. ​ros2 topic hz​ ** 频率异常**。话题频率低于预期。解决:ros2 topic bw 查看带宽,ros2 node info 查看节点状态。
  9. ​ros2 run​ ** 找不到节点**。包未编译或节点名错误。解决:ros2 pkg list 确认包存在,ros2 pkg executables 查看节点名。
  10. ​ros2 launch​ ** 参数未生效**。launch 文件参数覆盖顺序错误。解决:检查 launch 文件参数加载顺序,命令行参数优先级最高。

2.3.18 工程最佳实践

  1. 调试脚本化 :将常用 ros2cli 命令写成 shell 脚本,bash debug.sh 一键诊断。
  2. 日志收集 :ros2 topic echo / ros2 node info 输出收集到日志文件,便于回溯。
  3. bag 命名规范 :<日期>_<场景>_<版本号>,如 20240101_debug_v1.0。
  4. bag 存储:大 bag 存到 NFS / 对象存储,本地仅存小 bag。
  5. 诊断定时 :生产系统部署 ros2 doctor check 定时任务,异常告警。
  6. 频率监控 :ros2 topic hz 部署为监控脚本,频率异常告警。
  7. 参数版本化 :ros2 param dump 输出纳入版本控制,参数变更可追溯。
  8. launch 参数外置:launch 文件参数用 YAML,不硬编码。
  9. ros2cli 版本锁定 :生产环境锁定 ros2cli 版本,防止 apt upgrade 意外升级。
  10. 性能追踪常态化 :生产系统定期 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 踩坑点

  1. 接口文件路径错误 。CMakeLists.txt 中路径与文件实际路径不一致。解决:检查 rosidl_generate_interfaces 路径。
  2. ​rosidl_default_generators​ ** 未声明**。编译报 rosidl_default_generators 未找到。解决:package.xml 加 <build_depend>rosidl_default_generators</build_depend>。
  3. 接口类型不匹配 。字段类型与 C++/Python 代码不一致。解决:ros2 interface show 查看生成类型。
  4. 嵌套消息未声明依赖 。引用 std_msgs/Header 但未 find_package(std_msgs)。解决:CMakeLists.txt 加 find_package(std_msgs REQUIRED)。
  5. 服务/动作分隔符错误 。--- 分隔符缺失或位置错误。解决:检查 .srv / .action 文件结构。
  6. 接口编译失败 。colcon build 报语法错误。解决:cat my_interfaces/msg/xxx.msg 检查语法。
  7. 接口运行时找不到 。节点 #include / import 路径错误。解决:检查 install/<pkg>/include/ / install/<pkg>/lib/python3.10/site-packages/。
  8. 接口版本不兼容 。字段删除/类型变更导致旧代码崩溃。解决:语义化版本,废弃接口用 @deprecated。
  9. 大消息性能差。大数组(图像、点云)序列化开销大。解决:压缩 / 共享内存 / 降频。
  10. 接口命名冲突。不同包定义同名消息。解决:接口独立成包,命名加前缀。

2.4.10 工程最佳实践

  1. 接口独立成包 :my_interfaces 独立,多包共用,避免重复定义。
  2. 接口命名规范 :消息大驼峰(RobotState),服务/动作大驼峰(SetPosition / MoveTo)。
  3. 字段注释 :每个字段注释含义、单位、范围(如 float64 x # X 位置 (m))。
  4. 默认值 :字段设合理默认值(:=),避免零值歧义。
  5. 常数 :版本、ID 等用大写常数(uint32 VERSION = 1)。
  6. 版本管理 :接口包语义化版本,废弃接口用 @deprecated。
  7. 兼容性检查 :CI 流水线检查接口变更,不兼容变更需 MAJOR 版本。
  8. 大消息优化:图像/点云用压缩格式(JPEG / PCD),或共享内存。
  9. 接口文档化 :README.md 列出所有接口,字段含义、单位、范围。
  10. 接口测试 :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 踩坑点

  1. 命名空间未继承 。话题相对命名未继承节点命名空间。解决:检查节点 __ns:= 设置。
  2. **私有命名空间 ​ ​~​​ ** 不可见 。~internal 话题仅本节点可见,外部不可订阅。解决:用绝对命名或相对命名。
  3. 重映射未生效 。__remap:= 语法错误或 launch 文件 remappings 未配置。解决:检查语法与 launch 文件。
  4. 多节点资源冲突 。一进程多节点共享执行器,回调阻塞。解决:用 MultiThreadedExecutor 或一进程一节点。
  5. DDS 域隔离未生效 。ROS_DOMAIN_ID 未 source 或未导出。解决:export ROS_DOMAIN_ID + source ~/.bashrc。
  6. 跨机隔离误设 。ROS_LOCALHOST_ONLY=1 导致跨机不可发现。解决:跨机调试时设为 0。
  7. 命名空间冲突 。同命名空间内节点/话题名重复。解决:__node:= 重命名或 __ns:= 改命名空间。
  8. launch 参数覆盖顺序错误。launch 文件参数被命令行覆盖,行为异常。解决:命令行参数优先级最高,注意覆盖顺序。
  9. 多节点日志混乱。一进程多节点日志交错,难以排查。解决:一进程一节点,或日志加节点名前缀。
  10. 隔离后通信失败 。域隔离 / 本机隔离后跨域 / 跨机通信失败。解决:确认隔离机制,跨域需 ROS_DOMAIN_ID 一致,跨机需 ROS_LOCALHOST_ONLY=0。

2.5.7 工程最佳实践

  1. 命名空间分层 :/<机器人>/<子系统>/<数据>,如 /vehicle/chassis/cmd_vel,分层清晰。
  2. 节点命名规范 :<模块>_<子模块>_<实例>,如 nav2_local_planner_01,全局唯一。
  3. 一进程一节点:生产环境推荐,隔离故障、独立重启。
  4. launch 编排:多节点用 launch 文件编排,统一启动、参数、重映射。
  5. systemd 托管:生产环境 systemd 托管节点,故障自动重启。
  6. DDS 域隔离 :测试 / 预发 / 生产用不同 ROS_DOMAIN_ID,零成本隔离。
  7. 命名空间隔离:多机器人类型用命名空间隔离,同域内逻辑分组。
  8. 跨机调试 :ROS_LOCALHOST_ONLY=0,允许跨机发现。
  9. 重映射测试:测试环境用重映射复用节点,避免重复部署。
  10. 日志前缀:多节点日志加节点名前缀,便于排查。

模块二 本章工程最佳实践 + 高频报错排查

A. 本章工程最佳实践汇总

  1. 通信选型:长任务 → 动作;请求/响应 → 服务;数据流 → 话题;坐标 → TF;配置 → 参数。
  2. 显式 QoS:所有 Publisher/Subscriber 显式声明,用 QoS 模板(控制/传感器/状态/配置/TF)。
  3. 服务超时:客户端必设超时(5-10s),避免卡死。
  4. 动作状态监听 :监听 <action>/_status,取消/中止无感知。
  5. 参数声明必须 :先 declare_parameter 再 get_parameter,类型严格匹配。
  6. 动态参数回调:关键参数注册回调,运行时改参自动响应。
  7. 节点退出规范 :C++ rclcpp::shutdown(),Python destroy_node() + rclpy.shutdown()。
  8. Lifecycle 节点 :量产系统用 LifecycleNode,支持热启停、故障恢复。
  9. 命名空间分层 :/<机器人>/<子系统>/<数据>,分层清晰。
  10. 节点命名规范 :<模块>_<子模块>_<实例>,全局唯一。
  11. 一进程一节点:生产环境推荐,隔离故障、独立重启。
  12. launch 编排:多节点用 launch 文件编排,统一启动、参数、重映射。
  13. DDS 域隔离 :测试 / 预发 / 生产用不同 ROS_DOMAIN_ID。
  14. 接口独立成包 :my_interfaces 独立,多包共用。
  15. 接口版本管理 :语义化版本,废弃接口用 @deprecated。
  16. ros2cli 调试脚本化:常用命令写成 shell 脚本,一键诊断。
  17. bag 命名规范 :<日期>_<场景>_<版本号>,如 20240101_debug_v1.0。
  18. 参数版本化 :ros2 param dump 输出纳入版本控制,参数变更可追溯。
  19. 诊断定时 :生产系统部署 ros2 doctor check 定时任务,异常告警。
  20. 性能追踪常态化 :生产系统定期 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 显式声明,不依赖默认值
  • 服务客户端设超时
  • 动作状态监听
  • 参数声明必须

相关推荐
大雨歌5 天前
笛卡尔坐标 → 自然坐标(Frenet) 逐步推导全解 -2
ros2
米糕闯编程9 天前
鱼香ros2(十七)C++小海龟巡逻
开发语言·c++·机器人·ros2·服务·小海龟
风合星语10 天前
2026 机器人工程实例(七):Nav2 到站了,任务却没完成——AMR 载货导航中的托盘滑移与验收闭环
人工智能·机器人·ros2·机器人仿真·nav2·mujoco·amr
米糕闯编程16 天前
鱼香ros2(十二)C++话题订阅与发布
开发语言·c++·ros2·话题
朝朝辞暮i18 天前
C++ 第 36 课:Action——机械臂/VLA 非常重要
开发语言·c++·算法·ros2
朝朝辞暮i19 天前
C++ 第 39 章: 阶段性总复习——类、对象、构造、继承、this、指针与智能指针
开发语言·c++·算法·ros2
朝朝辞暮i19 天前
C++ 第 40 章:ROS2 Publisher + Timer + Subscriber + Callback + Executor 完整闭环
开发语言·c++·算法·ros2
朝朝辞暮i19 天前
C++ 第 38 课:线程、SingleThreadedExecutor、MultiThreadedExecutor、Callback Group
开发语言·c++·算法·ros2
小奇不哭20 天前
ROS2 单节点,RGB-D 深度相机局部路径提取 + B 样条平滑路径 + YOLOv8 障碍物检测 + 深度测距,用于移动机器人视觉局部循迹。
python·yolov8·ros2·cv2·路径探索循迹