资讯动态

ROS2自定义消息实战:从SystemStatus定义到系统状态监控节点

发布时间:2026/9/7 3:25:10 来源:尧图企业网站定制
如果你已经跟 ROS2 打过一阵子交道应该会有这种感觉刚上手时觉得话题、节点、服务这些概念够折腾了等真正开始写自己的机器人应用很快会撞到下一个坎——系统自带的消息类型不够用。比如我想把状态监控数据打包发出去标准类型里根本找不到一个“既包含 CPU 占用率、又带内存和磁盘信息”的消息结构。这时候就必须接触 ROS2 的自定义消息。今天这篇“ROS2 学习日记八”我就把自己从零定义消息、再到基于它做系统状态监控节点的完整过程整理出来包括踩过的坑和最后的排查思路希望对正在学 ROS2 自定义消息的同学有帮助。1. 自定义消息这件事到底解决什么问题1.1 为什么标准消息类型经常不够用ROS2 内置的 std_msgs、geometry_msgs、sensor_msgs 确实覆盖了很大一部分场景坐标变换、激光扫描、图像、速度指令都有现成类型。但实际项目里数据往往是组合型的。以系统状态监控为例我想周期采集 CPU 占用、内存剩余、磁盘空间、运行时长还要带上时间戳。如果全部用内置类型拼要么发布多个话题订阅端得手动对齐时间要么用 std_msgs/String 把 JSON 塞进去发送端省事了接收端解析起来却又累又容易出错。自定义消息的核心价值就是把“一组结构化字段”定义成规范的消息类型让发布端和订阅端共享同一份接口协议编译期和运行期都能做检查而不是靠人肉约定字符串格式。1.2 什么时候推荐自定义消息不是所有场景都需要自定义消息。我自己的判断标准有三个第一数据字段超过两个且强相关放在一起才有意义第二同一套数据结构会被多个节点复用比如监控数据既要给界面显示节点用又要给日志记录节点用第三希望类型检查提前介入而不是等到运行时才发现字段名写错。反过来如果只是临时在单个节点内部传递数据直接用 Python 的 dict 或自定义类就行没必要上升到消息层面。在 ROS2 里自定义消息本质上是一种接口定义跨节点、跨语言C / Python传递时才能真正发挥价值。1.3 自定义消息的三个组成部分ROS2 的自定义消息并不只是写一个 .msg 文件那么简单它由三部分组成消息定义文件.msg、接口包声明package.xml、构建规则CMakeLists.txt 或 setup.py。其中消息定义文件描述数据结构的字段和类型接口包声明让系统知道这是一个包含 rosidl 接口的包构建规则负责调用 rosidl 代码生成器把 .msg 文件转换成对应语言的源码。理解这三者的关系很重要因为初学者最容易犯的错误就是只写了 .msg 文件忘了在 CMakeLists.txt 里调用 rosidl_generate_interfaces结果编译时提示找不到生成的消息头文件。2. 从零开始定义 SystemStatus 消息2.1 创建接口功能包先说结论自定义消息不要放在某个可执行节点包里建议单独建一个 interface 包。这样做的原因很简单可执行节点包往往会被拆成多个仓库或多个可执行文件如果接口包和可执行程序混在一起后续别的包想依赖这套消息就得被迫引入一堆不需要的运行代码。我按社区惯例建一个名为 robot_interfaces 的接口包专门存放这套状态监控消息。初始化工作空间这一步就不展开讲了如果你还没建过工作空间简单说就是建一个 src 目录然后在工作空间根目录执行一次 colcon build。创建接口包的命令如下cd ~/ros2_ws/src ros2 pkg create robot_interfaces --build-type ament_cmake这里用 ament_cmake 类型而不是 ament_python是因为 ROS2 的接口生成工具链对 CMake 支持更加成熟Python 接口包虽然也能做但需要额外配置没必要一开始就增加难度。创建完成之后在 robot_interfaces 目录下新建一个 msg 文件夹用来放消息定义文件。2.2 编写 SystemStatus.msg在设计状态监控消息字段之前我先想了一下监控节点到底要采集哪些数据。结合机器人实际使用场景我最关心五类信息时间戳、CPU 占用率、内存使用率、磁盘使用率、以及系统运行时长。其中时间戳用标准消息里的 Header这样后续可以用 tf 或 rqt 工具做时间同步比较规范。磁盘信息我考虑过只报根分区使用率但实际机器人如果挂了 SD 卡或者数据盘只报根分区不够所以干脆用一个字符串字段保存挂载点路径再配一个浮点字段保存使用率既灵活又直观。最终的 SystemStatus.msg 内容如下std_msgs/Header header float32 cpu_usage float32 memory_usage float32 disk_usage string disk_mount_point float32 system_uptime_hours uint16 process_count字段类型的选择有一个细节CPU 占用率、内存使用率用 float32 足够因为机器人领域很少需要毫米级精度的百分比运行时长用 float32 存小时数如果系统跑了很久精度会略降但作为监控完全够用进程数量用 uint16因为进程数几乎不可能超过 65535。这里就想提醒一句ROS2 的 .msg 字段类型和 C 或 Python 原生类型并不完全一致比如没有 double 对应的 Python float 这种说法实际生成代码时会自动映射但你在 .msg 里还是得严格用 rosidl 支持的类型名。2.3 修改 CMakeLists.txt 和 package.xml准备工作做完到了最容易踩坑的环节修改构建配置。先看 CMakeLists.txt。在 ament_cmake 类型包里需要确保以下内容存在find_package(rosidl_default_generators REQUIRED) find_package(std_msgs REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} msg/SystemStatus.msg DEPENDENCIES std_msgs )注意 rosidl_generate_interfaces 必须放在 find_package 之后并且 DEPENDENCIES 要列出消息里用到的所有依赖包。因为我用了 std_msgs/Header所以这里必须声明 std_msgs。再看 package.xml。除了常规的 buildtool 依赖必须添加以下内容buildtool_dependrosidl_default_generators/buildtool_depend exec_dependrosidl_default_runtime/exec_depend dependstd_msgs/depend member_of_grouprosidl_interface_packages/member_of_groupmember_of_group 这一行是关键很多教程不会特别强调。如果不加这行构建核心有时不会把这个包识别为接口包会导致其他包找不到这套消息。我身边已经有好几个同事栽在这个细节上编译时报 “package robot_interfaces not found” 或者找不到头文件最后检查出来都是这行缺失。配置完成后回工作空间根目录执行构建cd ~/ros2_ws colcon build --packages-select robot_interfaces source install/setup.bash构建成功后可以用 ros2 interface 系列命令验证消息是否注册成功ros2 interface show robot_interfaces/msg/SystemStatus如果能看到刚才定义的字段列表说明接口包本身已经 OK。这是一个非常重要的自查步骤我每次定义完自定义消息都会先做这一步避免后面连到其他节点时才发现问题。2.4 Python 和 C 各自怎么引用自定义消息消息包构建好了接下来在不同语言里引用它。我做监控节点用的是 Pythonrclpy导入方式如下from robot_interfaces.msg import SystemStatus这里有一个高频报错明明已经 colcon build 成功但 Python 里 import 仍然失败提示找不到模块。排查思路很简单先确认当前终端是否 source 过 install/setup.bash再确认 install 目录下是否生成了 robot_interfaces 的 Python 模块正常路径是 install/robot_interfaces/lib/python3.x/site-packages/robot_interfaces。如果找不到大概率是构建时没有生成 Python 绑定这时检查一下 CMakeLists 里是否少了 rosidl_generate_interfaces 调用或者构建时没有把 install 目录刷新干净。如果是 C 节点需要在 CMakeLists 里加find_package(robot_interfaces REQUIRED) ament_target_dependencies(your_node robot_interfaces)然后代码里直接#include robot_interfaces/msg/system_status.hppC 的头文件命名规则是包名 消息名全小写并且用下划线连接这个和 .msg 文件名的驼峰式不一样。我第一次写的时候按文件名写 SystemStatus.h结果编译直接失败后来才反应过来是 system_status.hpp。建议新手在 C 里引用自定义消息之前先去 install 目录下看一眼实际生成的头文件路径就不会在这上面浪费时间。3. 系统状态监控节点的完整设计3.1 监控节点整体架构我准备用一个 Python 节点完成所有数据采集、计算和发布节点起名 system_monitor。为了后续扩展我把采集逻辑和发布逻辑分开用三个内部函数分别获取 CPU 使用率、内存使用率、磁盘使用率然后用一个定时器回调把这些数据填充进自定义消息并发布到 /system_status 话题。订阅端只需要关心这一个话题就能拿到系统运行的全部关键指标。这种设计的好处很明显如果后续想增加 GPU 温度、电池电量、WiFi 信号强度之类的监控项只需要在消息定义里加字段再在节点里加对应的采集函数完全不需要改动话题发布逻辑和订阅端代码。所以自定义消息的字段定义一开始要规划好宁可多预留几个字段也不要后补因为改消息定义就意味着所有相关方都要重新编译部署。3.2 CPU 占用率的正确采集姿势CPU 占用率如果直接读 /proc/loadavg 里的 load average反映的是系统整体负载趋势不是实时占用率。做状态监控时我更喜欢读 /proc/stat然后通过两次采样计算得到实际的 CPU 占用百分比。原理很简单/proc/stat 的第一行以 cpu 开头后面跟着一组累加值分别表示用户态时间、内核态时间、空闲时间等等。单位是 USER_HZ通常就是 jiffies。要计算占用率需要间隔一段时间采样两次然后按以下公式算total idle iowait user nice system irq softirq steal cpu_usage (1 - (idle2 - idle1) / (total2 - total1)) * 100实际编码时我并没有把全部字段都解析出来只取最关键的 idle第4列和 total所有列之和。代码大致这样写def read_cpu_stat(): with open(/proc/stat, r) as f: line f.readline().strip() parts line.split()[1:] idle int(parts[3]) # idle 在第4个位置下标为3 total sum(int(v) for v in parts) return total, idle def get_cpu_usage(): t1, i1 read_cpu_stat() time.sleep(0.1) t2, i2 read_cpu_stat() delta_total t2 - t1 delta_idle i2 - i1 return round((1.0 - delta_idle / delta_total) * 100.0, 2)这里我故意只 sleep 0.1 秒而不是直接用 1 秒的定时器间隔。原因是 ROS2 定时器的触发精度受节点主线程阻塞影响如果你在回调里 sleep 1 秒定时器的节奏会被打乱。单独用 0.1 秒采样窗口既能保证计算稳定又不会拖累发布频率。如果你希望发布频率本身是 1Hz就在回调开头取第一次采样结束时再取第二次而不是把 sleep 放进回调里。3.3 内存和磁盘的采集技巧内存信息从 /proc/meminfo 读取只需要两个关键字段MemTotal 和 MemAvailable。MemAvailable 是 Linux 内核专门提供给用户态计算的字段已经从缓存和缓冲中扣掉了可回收部分比直接用 MemFree 更准确。所以内存使用率可以这样算def get_memory_usage(): with open(/proc/meminfo, r) as f: lines f.readlines() mem_total None mem_available None for line in lines: if line.startswith(MemTotal:): mem_total int(line.split()[1]) elif line.startswith(MemAvailable:): mem_available int(line.split()[1]) if mem_total is not None and mem_available is not None: break usage (1.0 - mem_available / mem_total) * 100.0 return round(usage, 2)磁盘使用率就简单多了Python 的 shutil 模块直接能给结果import shutil def get_disk_usage(mount_point/): usage shutil.disk_usage(mount_point) return round(usage.used / usage.total * 100.0, 2)shutil.disk_usage 返回一个 namedtuple包含 total、used、free 三个字段单位是字节。这里有个很容易忽略的坑如果机器人挂载了网络存储或某些特殊文件系统比如 docker overlayshutil.disk_usage 可能会抛 OSError。所以我实际写的时候会在外层加 try-except监控失败返回 None然后让发布逻辑跳过该字段或者填一个默认值避免一个采集点异常导致整个节点挂掉。监控节点本身必须足够健壮它是用来监视别人的不能自己先倒下。3.4 运行时长和进程数量运行时长直接从 /proc/uptime 读取第一列是秒数def get_uptime_hours(): with open(/proc/uptime, r) as f: uptime_sec float(f.readline().split()[0]) return round(uptime_sec / 3600.0, 2)进程数量可以用 os.listdir(/proc) 然后把纯数字的目录统计出来这是一种轻量级的办法不需要依赖 psutil。但注意 /proc 下面除了数字目录还有大量系统目录比如 cpuinfo、meminfo、net 等所以一定要做纯数字判断而且要捕获 int() 转换异常import os def get_process_count(): count 0 for name in os.listdir(/proc): if name.isdigit(): count 1 return count当然如果开发机器上已经装了 psutil直接用 psutil.cpu_percent()、psutil.virtual_memory() 更省事。但放在机器人主控里我倾向尽量少装第三方依赖多利用系统自带的 /proc 接口。这样部署到新环境时能少踩一堆依赖坑。3.5 发布节点完整代码所有采集函数准备好之后把它们组装进一个 rclpy 节点。节点里我做了一个 2 秒周期的定时器回调里依次采集各项数据填充 SystemStatus 消息然后发布出去。import rclpy from rclpy.node import Node from robot_interfaces.msg import SystemStatus import shutil import os import time class SystemMonitor(Node): def __init__(self): super().__init__(system_monitor) self.publisher self.create_publisher(SystemStatus, /system_status, 10) self.timer self.create_timer(2.0, self.timer_callback) self.get_logger().info(System Monitor node started) def timer_callback(self): msg SystemStatus() msg.header.stamp self.get_clock().now().to_msg() msg.cpu_usage self.get_cpu_usage() msg.memory_usage self.get_memory_usage() msg.disk_usage self.get_disk_usage(/) msg.disk_mount_point / msg.system_uptime_hours self.get_uptime_hours() msg.process_count self.get_process_count() self.publisher.publish(msg) def read_cpu_stat(self): with open(/proc/stat, r) as f: line f.readline().strip() parts line.split()[1:] idle int(parts[3]) total sum(int(v) for v in parts) return total, idle def get_cpu_usage(self): t1, i1 self.read_cpu_stat() time.sleep(0.1) t2, i2 self.read_cpu_stat() delta_total t2 - t1 delta_idle i2 - i1 return round((1.0 - delta_idle / delta_total) * 100.0, 2) def get_memory_usage(self): with open(/proc/meminfo, r) as f: lines f.readlines() mem_total None mem_available None for line in lines: if line.startswith(MemTotal:): mem_total int(line.split()[1]) elif line.startswith(MemAvailable:): mem_available int(line.split()[1]) if mem_total is not None and mem_available is not None: break return round((1.0 - mem_available / mem_total) * 100.0, 2) def get_disk_usage(self, mount_point): usage shutil.disk_usage(mount_point) return round(usage.used / usage.total * 100.0, 2) def get_uptime_hours(self): with open(/proc/uptime, r) as f: uptime_sec float(f.readline().split()[0]) return round(uptime_sec / 3600.0, 2) def get_process_count(self): count 0 for name in os.listdir(/proc): if name.isdigit(): count 1 return count def main(argsNone): rclpy.init(argsargs) node SystemMonitor() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这段代码我在 Humble 和 Jazzy 上都跑过没有ROS2版本适配问题。有几个细节值得提一下header.stamp 必须用 self.get_clock().now().to_msg()而不是 time.time()因为 ROS2 的时间系统支持模拟时间和自动同步使用系统时间会导致后续在仿真场景里时间戳对不上发布 QoS 我用的默认深度 10对于 2 秒一帧的监控数据完全足够如果改成高频发布再考虑设置更深的队列另外节点里取名为 system_monitor话题名为 /system_status一个叫节点一个叫话题不要搞混。4. 订阅端验证与可视化展示4.1 用 CLI 快速验证话题输出监控节点跑起来之后最简单的验证方法就是开第二个终端用 ros2 topic echo 看一下数据ros2 topic echo /system_status正常情况下2 秒后能看到输出header: stamp: sec: 1710000000 nanosec: 123456789 frame_id: cpu_usage: 12.5 memory_usage: 34.2 disk_usage: 28.7 disk_mount_point: / system_uptime_hours: 26.14 process_count: 189这里有个细节因为监控数据和 echo 显示的时间差几乎可以忽略所以你可以肉眼判断 CPU 数据是否合理。比如我同时跑着一个 Gazebo 仿真时CPU 占用率会跳到 40% 以上空闲时回到 10% 以下。如果你的输出一直恒定不变可能不是节点采集问题而是计算逻辑里采样间隔太短导致数据被平均掉了可以适当加大 /proc/stat 的两次采样间隔。4.2 用 rqt 插件实现图形化监控命令行验证只能看瞬时值做长时间的机器人运行测试时不直观。这里推荐 rqt_topic 或 rqt_plot 配合 rqt_graph 组合使用。在终端里运行rqt_graph可以看到 /system_monitor 节点发布 /system_status 话题订阅端节点如果有的话订阅这个话题。这个图能帮你快速确认话题连通性。再看数据曲线我建议用 rqt_plot 或 rqt_multiplot。如果你更习惯用现代的工具PlotJuggler 也可以它支持直接订阅 ROS2 话题把多个字段拖进同一个坐标轴里对比。rqt_plot 的基本用法是在启动后输入话题名rqt_plot /system_status/cpu_usage /system_status/memory_usage /system_status/disk_usage这样就可以把 CPU、内存、磁盘使用率画在同一条时间轴上对比。实际观察机器人跑导航算法时我经常就是开着 PlotJuggler 盯 CPU 和内存曲线哪个指标突然飙升多半就对应某个节点开始异常了。这种监控对排查周期性卡顿、内存泄漏特别有用。4.3 监听端的简单写法如果不想用现成工具自己写一个订阅端也很简单。这里放一个最小的 Python 订阅节点import rclpy from rclpy.node import Node from robot_interfaces.msg import SystemStatus class StatusListener(Node): def __init__(self): super().__init__(status_listener) self.subscription self.create_subscription( SystemStatus, /system_status, self.listener_callback, 10) def listener_callback(self, msg): self.get_logger().info( fCPU: {msg.cpu_usage}% Mem: {msg.memory_usage}% fDisk: {msg.disk_usage}% Proc: {msg.process_count} ) def main(argsNone): rclpy.init(argsargs) node StatusListener() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这里就体现出自定义消息的好处了。订阅端代码完全不需要知道消息内部是怎样采集的只需要按照 robot_interfaces/msg/SystemStatus 这个接口来读字段。将来监控节点内部换成 psutil 实现订阅端一行都不用改。这就是接口和实现分离的价值。5. 构建运行中的常见问题与排查思路5.1 自定义消息包无法被其他包找到这是所有自定义消息初学者遇到最多的问题。症状是单独构建接口包没问题但另一个包 find_package(robot_interfaces) 时报错或者 Python 里 import 失败。排查步骤按顺序来第一步确认接口包是否成功构建在 install 目录下有没有对应文件。如果没有重新 colcon build --packages-select robot_interfaces。第二步确认当前终端是否 source 过 install/setup.bash。新开终端忘记 source 是最常见的。第三步确认 package.xml 里有没有 member_of_grouprosidl_interface_packages/member_of_group没有这个标记部分版本的构建系统不会把接口信息暴露给其他包。第四步如果前面都没问题试试清理重新构建rm -rf build install log然后重新 colcon build。这个问题多数情况下是增量构建产生的缓存问题清理后立竿见影。有个技巧值得推荐在 colcon build 之后马上执行 ros2 pkg list | grep robot_interfaces如果能看到你的接口包说明接口包本身已经被系统发现。接下来再在另一个包里做依赖测试就能把问题范围缩小。5.2 QoS 不匹配导致订阅不到数据写自定义消息的发布订阅时如果节点 A 能正常收到 echo节点 B 却收不到首先检查的话题名而不是消息类型。如果话题名没错那大概率是 QoS 不匹配。ROS2 的 QoS 策略比 ROS1 严格得多发布端和订阅端如果兼容性协商失败话题状态会显示不匹配。我的建议是自定义消息的发布订阅双方如果都在同一个机器人系统里、网络稳定、不做多机通信直接用默认 QoSreliable volatile最省心。如果要做局域网内的多机通信或者和嵌入式设备交互再考虑调整 depth 和 reliability。千万不要一上来就照搬网络教程把 QoS 设成 best_effort除非你能明确说出为什么需要。5.3 topic echo 能看到数据但 rqt_plot 没有曲线这个现象也遇到过排查方法很简单rqt 插件启动时选择的 ROS2 版本必须和当前终端 source 的安装版本一致。如果你系统里装了多个 ROS2 版本比如 Humble 和 Jazzy 共存经常会出现 rqt_plot 和节点不在同一个 ROS_DOMAIN_ID 里话题自然对不上。检查方式echo $ROS_DOMAIN_ID发布方和订阅方必须都在同一个 domain默认都是 0但如果某个终端手动改过 domain 就会出问题。另一种可能是 rqt_plot 的话题路径不对。自定义消息字段的完整话题路径是 /system_status/cpu_usage不是 /system_status/CPU。如果你在 rqt 里看不到字段列表可以先在终端用 ros2 topic info /system_status -v 看它到底有哪些字段再照着填进去。5.4 CPU 利用率计算数据看起来偏高或偏低如果 CPU 占用率长时间为 0或者数值看起来不对先检查 /proc/stat 解析是否正确。一个很容易犯的错是取错了 idle 字段的位置。/proc/stat 第一行 cpu 后面的字段是这样排列的user、nice、system、idle、iowait、irq、softirq、steal。下标从 0 数起idle 在下标 3iowait 在下标 4。如果你把 iowait 当成 idleCPU 占用率会偏高尤其在磁盘 IO 繁忙时会高得离谱。更稳妥的做法是读取时加上字段名映射不要用裸下标。另外如果你发现第一次发布的数据特别大比如接近 100%但后面恢复正常这是正常的。因为进程刚启动时采到的第一次 CPU 数据没有历史基准计算出来的占用率没有意义。可以在节点初始化时先做一次采样当作基准第二次回调再开始发布或者在回调里判断记录是否已有第一次值没有就直接返回一个默认值。6. 这个监控系统的扩展思路6.1 把监控数据接入导航和建图的行为分析做完基础监控后下一步可以把它和实际机器人行为绑定。比如我在跑 Nav2 导航时会在导航启动前记录一份初始 CPU/内存基线然后一边跑导航一边盯着 /system_status。如果发现 CPU 突然飙升到 80% 以上我就会猜是局部代价地图更新频率太高或者是 costmap 里的障碍物点云太多。这种“数据驱动”的排查方式比靠感觉调参要高效得多。进一步可以做一个简单规则判断节点订阅 /system_status如果内存使用率连续 5 次超过 90%就输出一条 WARN 日志甚至发布一个 /system_warning 标准消息方便上层调度逻辑处理。这在长时间无人值守的机器人任务里非常有用能提前预警内存泄漏导致的进程 OOM。6.2 从单机监控扩展到多机集群监控如果有多个机器人需要同时监控可以把监控节点部署在每个机器人上话题名保持 /system_status 相同然后用域名空间namespace或者话题前缀区分。比如机器人 A 发布 /robot_a/system_status机器人 B 发布 /robot_b/system_status地面站订阅这两个话题再汇总展示。这个场景里自定义消息的好处尤其明显所有机器人都用同一套 SystemStatus 消息定义地面站代码只需要写一个订阅回调就能同时处理多台机器人的状态数据。6.3 从 ROS2 监控到系统服务化的思考最后多说一句自定义消息的思路不只是用在系统状态监控上它完全可以作为机器人软件架构里模块间通信的规范。你把每个通信接口都想清楚谁发布、谁订阅、字段有哪些、类型是什么、QoS 怎么配再开始写代码后期的维护成本会低很多。ROS2 的自定义消息接口其实约等于模块之间的一份“契约”把这份契约定义清楚团队成员并行开发时就不会互相踩脚。7. 实际操作中的心得和小技巧7.1 命名习惯决定后期维护成本自定义消息的包名、消息名、字段名最好在一开始就形成统一风格。我推荐这种组合接口包名全部小写比如 robot_interfaces消息文件名用驼峰式比如 SystemStatus.msg字段名全部小写加下划线比如 cpu_usage。如果你在团队里最好把这些约定写进项目文档否则后续会看到 message 文件里混着 cpuUsage、cpu_usage、CPU_Usage 各种风格统一改起来非常痛苦。7.2 利用 ros2 interface 命令做接口检查每次改完 .msg 文件不要急着写业务代码先用三件套验证ros2 interface list | grep robot_interfaces ros2 interface show robot_interfaces/msg/SystemStatus ros2 interface proto robot_interfaces/msg/SystemStatus第一条看接口有没有被系统识别第二条看字段结构第三条看生成后的底层数据结构是否正常。这三条命令的执行时间不到十秒但能把几乎所有接口定义问题挡在编译之前。7.3 修改消息后一定要重新构建相关的所有包自定义消息最大的一个隐性坑在于你改了接口包必须让所有依赖它的包都重新编译否则旧的编译产物还在用上次生成的消息类型。如果你只单独 build 接口包而不重新 build 使用它的节点包运行时会报类型不匹配或者段错误。稳妥的做法是在工作空间根目录直接colcon build --packages-select robot_interfaces system_monitor如果你只想偷懒直接在根目录 colcon build 全部重新构建也行。总之不要 break 这个规则接口变了消费方必须重新构建。7.4 用 Docker 做跨机器部署时的注意事项如果你的机器人系统在 Docker 容器里运行需要注意容器里的 /proc 是宿主机的还是容器自己的。默认 Docker 容器和宿主机共享内核/proc/stat 是宿主机的视角所以容器里读到的 CPU 占用率其实是宿主机整体 CPU 占用率不是容器的资源占用。要获取容器视角的 CPU 统计需要用 cgroup 相关接口比如 /sys/fs/cgroup/cpu.stat。做系统监控时要把这个前提搞清楚不然监控数据和实际感知对不上排查问题时会更加困惑。8. 最后想分享的一段踩坑经历这套监控节点我最初是在一台装了 Ubuntu 22.04 和 ROS2 Humble 的机器人主控上调试的。第一次跑通自定义消息那天我以为最难的接口定义部分已经解决了结果真正让我折腾最久的反而是 rqt_plot 画不出曲线。当时我开了终端 A 启动监控节点终端 B 运行 rqt_plot两边都 source 了 install/setup.bash话题也能 echo 到数据但曲线面板就是一片空白。后来我无意中发现另一个终端里之前 source 过另一个 ROS2 版本的 setup.bash导致整个 ROS_DOMAIN_ID 不一致。这种环境类问题比代码本身更隐蔽也更消耗耐心。所以我后来养成了一个习惯只要 ROS2 表现古怪第一件事就用 env | grep ROS 检查所有环境变量而不是先怀疑自己的代码。自定义消息和系统状态监控这个组合说难不难说简单也不简单。它把 ROS2 的接口定义、话题通信、节点设计、Linux 系统信息读取全都串起来了非常适合作为学习 ROS2 的进阶练手项目。你现在写好的这套 SystemStatus 接口以后做机器人健康管理、远程运维、数据记录全都能复用上。希望这篇学习日记能帮你少走一些弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价