ROS诊断系统全链路:从节点状态到可视化监控
导读:ROS诊断系统解决一个核心问题——机器人有几十个节点,哪个在报错?在哪里报错?本文用流程图拆解诊断数据的产生、聚合、可视化全链路,基于 ROSCon 2024 最新分享和 ros/diagnostics v4.0 源码。
一、系统架构全景
ROS诊断系统围绕 /diagnostics Topic 构建,数据从各节点出发,经由 aggregator 聚合,最终由 rqt_robot_monitor 可视化呈现。
flowchart TB
subgraph 节点层["🔵 各ROS节点"]
Lidar["lidar_driver<br/>激光雷达驱动"]
Camera["camera_driver<br/>相机驱动"]
Motor["motor_controller<br/>电机控制器"]
Nav["nav2_node<br/>导航节点"]
end
subgraph 采集层["🟡 diagnostic_updater"]
direction TB
UP["Updater<br/>定时采集+发布"]
STATUS["DiagnosticStatusWrapper<br/>OK=0 WARN=1 ERROR=2 STALE=3"]
PUB["DiagnosedPublisher<br/>监控Topic频率"]
end
subgraph 通道["🟡 /diagnostics Topic"]
RAW["DiagnosticArray<br/>原始诊断数据"]
end
subgraph 聚合层["🟢 diagnostic_aggregator"]
direction TB
AGG["Aggregator<br/>按规则分组"]
RULE["YAML配置规则<br/>startswith/path/type"]
ANALYZER["GenericAnalyzer<br/>插件化分析器"]
end
subgraph 可视化["🟢 rqt_robot_monitor"]
TREE["树状视图<br/>按设备/功能分组"]
PANEL["状态面板<br/>颜色区分OK/WARN/ERROR"]
end
Lidar -->|"diagnostic_updater"| UP
Camera -->|"diagnostic_updater"| UP
Motor -->|"diagnostic_updater"| UP
Nav -->|"diagnostic_updater"| UP
UP --> STATUS
STATUS --> PUB
PUB --> RAW
RAW --> AGG
RULE --> ANALYZER
ANALYZER --> AGG
AGG --> TREE
AGG --> PANEL
style 节点层 fill:#E3F2FD,stroke:#1976D2
style UP fill:#FFF8E1,stroke:#F57C00
style STATUS fill:#FFF8E1,stroke:#F57C00
style RAW fill:#FFF8E1,stroke:#F57C00
style AGG fill:#E8F5E9,stroke:#388E3C
style TREE fill:#E8F5E9,stroke:#388E3C
二、诊断消息结构
2.1 三层消息模型
诊断数据有三层嵌套结构,从外到内:DiagnosticArray → DiagnosticStatus → KeyValue。
flowchart TB
DA["DiagnosticArray<br/>外层容器<br/>Header + Status数组"]
DS["DiagnosticStatus<br/>单个设备状态<br/>level / name / message<br/>hardware_id / values"]
KV["KeyValue<br/>键值对<br/>key + value<br/>用于承载具体指标"]
DA -->|"status[]"| DS
DS -->|"values[]"| KV
style DA fill:#E3F2FD,stroke:#1976D2
style DS fill:#FFF8E1,stroke:#F57C00
style KV fill:#E8F5E9,stroke:#388E3C
2.2 四级状态定义
| 状态值 | 含义 | 颜色 | 说明 |
|---|---|---|---|
| OK=0 | 正常 | 绿色 | 设备运行正常,无需干预 |
| WARN=1 | 警告 | 黄色 | 状态异常但可继续运行 |
| ERROR=2 | 错误 | 红色 | 必须立即处理,否则功能丧失 |
| STALE=3 | 数据过期 | 灰色 | 数据超时未更新,可能已断开 |
三、diagnostic_updater:诊断数据如何产生
3.1 Updater 工作流程
Updater 类负责定期调用用户注册的诊断函数,将诊断结果打包成 DiagnosticStatusWrapper 并发布到 /diagnostics。
flowchart TB
START(["节点启动"]) --> INIT["创建 Updater 实例<br/>设置 period=1.0s"]
INIT --> REG["注册诊断函数<br/>add / update 方法<br/>每个设备一条"]
REG --> LOOP["进入主循环<br/>每 period 秒执行一次"]
LOOP --> CALL["调用各注册函数<br/>采集硬件/软件状态"]
CALL --> WRAP["DiagnosticStatusWrapper<br/>设置 level + name + message"]
WRAP --> ADD["add 附加键值<br/>add Motor RPM: 1500<br/>add Temperature: 65"]
ADD --> PUB["发布到 /diagnostics<br/>DiagnosticArray 消息"]
PUB --> LOOP
style INIT fill:#E3F2FD,stroke:#1976D2
style REG fill:#E3F2FD,stroke:#1976D2
style CALL fill:#FFF8E1,stroke:#F57C00
style WRAP fill:#FFF8E1,stroke:#F57C00
style ADD fill:#FFF8E1,stroke:#F57C00
style PUB fill:#E8F5E9,stroke:#388E3C
3.2 三种典型诊断场景
flowchart TB
subgraph 场景1["场景一:Topic频率监控"]
DP["DiagnosedPublisher<br/>包装任意Publisher<br/>自动监控发布频率"]
DP --> OK1["频率正常<br/>OK 状态"]
DP --> WARN1["频率偏低<br/>WARN 状态"]
end
subgraph 场景2["场景二:数值阈值监控"]
FN["自定义函数<br/>读取传感器数值"]
FN --> CHECK["阈值判断<br/>temp > 80?"]
CHECK --> OK2["温度正常<br/>OK 状态"]
CHECK --> ERROR2["温度超标<br/>ERROR 状态<br/>Motor too hot"]
end
subgraph 场景3["场景三:设备连接状态"]
DEV["设备驱动节点<br/>检测硬件连接"]
DEV --> CONN["连接正常?"]
CONN -->|"是"| OK3["Device open<br/>OK 状态"]
CONN -->|"否"| ERROR3["Device closed<br/>ERROR 状态"]
end
style DP fill:#E3F2FD,stroke:#1976D2
style FN fill:#E3F2FD,stroke:#1976D2
style DEV fill:#E3F2FD,stroke:#1976D2
style OK1 fill:#E8F5E9,stroke:#388E3C
style WARN1 fill:#FFF8E1,stroke:#F57C00
style OK2 fill:#E8F5E9,stroke:#388E3C
style ERROR2 fill:#FFEBEE,stroke:#D32F2F
style OK3 fill:#E8F5E9,stroke:#388E3C
style ERROR3 fill:#FFEBEE,stroke:#D32F2F
3.3 diagnostic_common_diagnostics:开箱即用的诊断节点
系统自带一批预制诊断节点,覆盖 Linux 系统层面的常见监控指标:
来自 linuxros.cn · linuxROS
flowchart TB
subgraph 预制诊断节点["🟡 diagnostic_common_diagnostics"]
CPU["cpu_monitor.py<br/>监控CPU使用率<br/>>90% WARN<br/>>95% ERROR"]
NTP["ntp_monitor.py<br/>检查NTP时间同步<br/>偏移 >500ms WARN<br/>>5s ERROR"]
HD["hd_monitor.py<br/>监控磁盘空间<br/><5% free WARN<br/><1% free ERROR"]
MEM["memory_monitor.py<br/>监控内存使用率"]
TEMP["temperature_monitor.py<br/>监控CPU/硬件温度"]
end
subgraph 输出["统一输出 /diagnostics"]
OUT["DiagnosticArray<br/>各节点独立发布"]
end
CPU --> OUT
NTP --> OUT
HD --> OUT
MEM --> OUT
TEMP --> OUT
style CPU fill:#FFF8E1,stroke:#F57C00
style NTP fill:#FFF8E1,stroke:#F57C00
style HD fill:#FFF8E1,stroke:#F57C00
style MEM fill:#FFF8E1,stroke:#F57C00
style TEMP fill:#FFF8E1,stroke:#F57C00
四、diagnostic_aggregator:诊断数据如何聚合
4.1 为什么需要聚合?
一个机器人可能有上百个诊断项,全部平铺无法定位问题。aggregator 将其按规则分组合并,形成层次化的诊断视图。
flowchart TB
RAW["/diagnostics<br/>原始数据<br/>上百条DiagnosticStatus"]
AGG["Aggregator<br/>接收原始数据"]
CFG["YAML配置规则<br/>定义分组逻辑"]
ANAL["Analyzer插件<br/>GenericAnalyzer<br/>startswith匹配<br/>path重命名<br/>type分类"]
OUT["/diagnostics_agg<br/>/diagnostics_toplevel_state<br/>聚合后输出"]
RAW --> AGG
CFG --> ANAL
ANAL --> AGG
AGG --> OUT
style RAW fill:#FFEBEE,stroke:#D32F2F
style AGG fill:#FFF8E1,stroke:#F57C00
style CFG fill:#E3F2FD,stroke:#1976D2
style ANAL fill:#E3F2FD,stroke:#1976D2
style OUT fill:#E8F5E9,stroke:#388E3C
4.2 YAML 配置示例
analyzers:
path: Aggregation # 顶层分组名称
arms: # 分组名:Arms
type: diagnostic_aggregator/GenericAnalyzer
path: Arms # 聚合后显示的路径
startswith: ['/arms'] # 原始路径以 /arms 开头
sensors:
type: diagnostic_aggregator/GenericAnalyzer
path: Sensors
startswith: ['/camera', '/lidar']
motors:
type: diagnostic_aggregator/GenericAnalyzer
path: Motors
startswith: ['/motor']
4.3 聚合后的树状结构
flowchart TB
TOP["/diagnostics_toplevel_state<br/>总体状态:OK/WARN/ERROR"]
subgraph 聚合结果["聚合后诊断树"]
AGG["/Aggregation"]
ARM["/Aggregation/Arms"]
SEN["/Aggregation/Sensors"]
MOT["/Aggregation/Motors"]
SYS["/Aggregation/System"]
end
subgraph 原始叶子["原始叶子节点"]
L1["/arms/left_motor<br/>WARN"]
L2["/arms/right_motor<br/>OK"]
L3["/sensors/left_cam<br/>ERROR"]
L4["/sensors/lidar<br/>OK"]
L5["/motors/joint1<br/>OK"]
L6["/motors/joint2<br/>WARN"]
end
TOP --> AGG
AGG --> ARM
AGG --> SEN
AGG --> MOT
AGG --> SYS
ARM --> L1
ARM --> L2
SEN --> L3
SEN --> L4
MOT --> L5
MOT --> L6
style TOP fill:#F3E5F5,stroke:#7B1FA2
style AGG fill:#FFF8E1,stroke:#F57C00
style L3 fill:#FFEBEE,stroke:#D32F2F
style L1 fill:#FFF8E1,stroke:#F57C00
style L6 fill:#FFF8E1,stroke:#F57C00
style L2 fill:#E8F5E9,stroke:#388E3C
style L4 fill:#E8F5E9,stroke:#388E3C
style L5 fill:#E8F5E9,stroke:#388E3C
聚合逻辑:父节点状态 = 子节点最严重状态。任意一个叶子为 ERROR,父节点即为 ERROR。
五、rqt_robot_monitor:如何可视化
5.1 可视化界面布局
flowchart TB
subgraph rqt_robot_monitor界面
DIRECT["Directory 面板<br/>树状展示诊断层次<br/>双击进入任意节点"]
STATUS["Status 面板<br/>当前选中项详情<br/>hardware_id / values"]
SUMMARY["Summary 面板<br/>ALL OK / WARN x2 / ERROR x1<br/>全局故障统计"]
TIMELINE["Timeline 面板<br/>状态变化时间线<br/>辅助追溯故障时刻"]
end
DIRECT --> STATUS
DIRECT --> SUMMARY
STATUS --> TIMELINE
style DIRECT fill:#E3F2FD,stroke:#1976D2
style STATUS fill:#FFF8E1,stroke:#F57C00
style SUMMARY fill:#F3E5F5,stroke:#7B1FA2
style TIMELINE fill:#E8F5E9,stroke:#388E3C
5.2 故障定位工作流
flowchart TB
A(["打开 rqt_robot_monitor"]) --> B["看 Summary:ERROR x1"]
B --> C["展开 Aggregation 树<br/>找到 /sensors/left_cam ERROR"]
C --> D["双击进入 Status 面板<br/>查看 hardware_id + values"]
D --> E{"错误类型?"}
E -->|"温度过高"| F["去 hardware 层检查散热"]
E -->|"连接断开"| G["检查 USB/CAN 线缆"]
E -->|"频率异常"| H["检查 Topic 发布率<br/>dmesg 看驱动日志"]
style A fill:#E3F2FD,stroke:#1976D2
style B fill:#FFF8E1,stroke:#F57C00
style C fill:#FFF8E1,stroke:#F57C00
style D fill:#FFF8E1,stroke:#F57C00
style E fill:#FFF8E1,stroke:#F57C00
style F fill:#FFEBEE,stroke:#D32F2F
style G fill:#FFEBEE,stroke:#D32F2F
style H fill:#FFEBEE,stroke:#D32F2F
六、代码示例
6.1 C++ 节点注册诊断
#include <diagnostic_updater/diagnostic_updater.hpp>
class MotorDriver {
diagnostic_updater::Updater updater_;
double rpm_, temp_;
public:
MotorDriver() {
updater_.setHardwareID("motor-controller-001");
updater_.add("Motor Status", this, &MotorDriver::checkMotor);
}
void checkMotor(diagnostic_updater::DiagnosticStatusWrapper& stat) {
stat.add("RPM", rpm_);
stat.add("Temperature", temp_);
if (temp_ > 80.0) {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR,
"Motor overheated");
} else if (temp_ > 60.0) {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN,
"Motor temperature elevated");
} else {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Motor OK");
}
}
};
6.2 Python 节点注册诊断
import diagnostic_updater
class LaserDriver:
def __init__(self):
self.updater = diagnostic_updater.Updater()
self.updater.setHardwareID("lidar-ls01b")
self.updater.add("Laser Status", self.checkLaser)
def checkLaser(self, stat):
stat.add("Topic", "/scan")
stat.add("Frame ID", "laser_link")
if self.laser_ok:
stat.summary(diagnostic_updater.DiagnosticStatus.OK, "Laser OK")
else:
stat.summary(diagnostic_updater.DiagnosticStatus.ERROR, "Laser error")
return stat
七、全链路数据流总结
flowchart TB
subgraph 节点层["节点层"]
N1["激光雷达驱动<br/>diagnostic_updater"]
N2["电机控制器<br/>diagnostic_updater"]
N3["相机驱动<br/>diagnostic_updater"]
N4["CPU/Memory<br/>diagnostic_common_diagnostics"]
end
subgraph 发布层["发布层 /diagnostics"]
D1["DiagnosticArray<br/>原始状态数组<br/>level+name+message+values"]
end
subgraph 聚合层["聚合层"]
A1["diagnostic_aggregator<br/>YAML规则配置<br/>startswith分组"]
A2["输出:<br/>/diagnostics_agg<br/>/diagnostics_toplevel_state"]
end
subgraph 可视化层["可视化层"]
V1["rqt_robot_monitor<br/>树状+状态面板"]
V2["命令行:<br/>ros2 diagnostic utils<br/>--verb list/echo/csv"]
end
N1 --> D1
N2 --> D1
N3 --> D1
N4 --> D1
D1 --> A1
A1 --> A2
A2 --> V1
A2 --> V2
style N1 fill:#E3F2FD,stroke:#1976D2
style N2 fill:#E3F2FD,stroke:#1976D2
style N3 fill:#E3F2FD,stroke:#1976D2
style N4 fill:#E3F2FD,stroke:#1976D2
style D1 fill:#FFF8E1,stroke:#F57C00
style A1 fill:#FFF8E1,stroke:#F57C00
style A2 fill:#E8F5E9,stroke:#388E3C
style V1 fill:#E8F5E9,stroke:#388E3C
八、开源库速查
| 仓库 | 包 | 语言 | 说明 |
|---|---|---|---|
| ros/diagnostics | diagnostic_updater | C++/Python | 诊断数据采集发布,v4.0.6 |
| ros/diagnostics | diagnostic_aggregator | C++/Python | YAML规则分组聚合 |
| ros/diagnostics | diagnostic_common_diagnostics | Python | CPU/NTP/磁盘/内存/温度监控 |
| ros/diagnostics | self_test | C++ | 节点自检框架 |
| turtlebot4-user-manual | rqt_robot_monitor | Qt | 可视化监控界面 |
九、总结
ROS诊断系统三句话:
- 谁在报:各节点通过
diagnostic_updater将状态(OK/WARN/ERROR)发布到/diagnosticsTopic,附带键值对描述具体指标 - 怎么聚:
diagnostic_aggregator按 YAML 配置的startswith规则将上百条原始诊断归纳为树状结构,父节点状态等于子节点最严重状态 - 怎么看:通过
rqt_robot_monitor图形界面或ros2 diagnostic命令行工具定位具体故障节点
诊断系统是机器人的"控制面板"——一个面板看全貌,快速定位哪个节点、哪类硬件出了问题。ROSCon 2024 上官方表示后续将加入 LTL(线性时序逻辑)监控能力,支持对系统状态属性做形式化验证。
看完不点赞?今晚代码必跑飞。看完不转发?Bug跟你到白头。推荐给朋友,一起被坑到永久。评论区见,谁是今天的最强显眼包?👻
参考来源
- ros/diagnostics v4.0 官方仓库 — github.com/ros/diagnostics(1183 Commits,ROS2 Humble/Jazzy)
- diagnostic_updater API 文档 — index.ros.org/p/diagnostic_updater
- diagnostic_common_diagnostics 系统监控 — index.ros.org/p/diagnostic_common_diagnostics
- diagnostic_msgs 消息定义 — github.com/ros2/common_interfaces
- ROSCon 2024 — How is my robot? On the state of ROS Diagnostics(Christian Henkel, Bosch)
- TurtleBot 4 诊断实践 — turtlebot.github.io