ROS2自定义消息从零到实践:msg文件编写与节点通信详解

发布时间:2026/9/8 6:12:49

ROS2自定义消息从零到实践:msg文件编写与节点通信详解
简介ROS2消息自定义与使用教程资源面向机器人通信开发初学者及需要扩展节点数据类型的ROS2开发者可应用于机器人导航、传感器数据封装、多节点协作等实际场景。资源基于ament工具链完整覆盖消息包创建、.msg文件定义、colcon构建安装、C接口生成及发布订阅节点实现等核心环节帮助读者打通从自定义消息到实际通信的完整链路。包体共645个文件包含cmake/make构建脚本、cpp/hpp源文件、py脚本、msg消息定义及colcon构建中间文件等压缩包仅3.43MB结构紧凑便于对照查阅已有470人学习使用。通过整理示例代码与工程文件读者可快速掌握消息类型设计方法并复用到自己项目中资源也呈现了典型构建配置与目录组织方式适合作为ROS2通信机制的自学参考或课堂教学示例能为后续深入学习服务质量、并发通信等高级主题打下基础。1. 为什么要折腾自定义消息先说个场景前阵子我在调一台差速小车节点想把“障碍物坐标置信度时间戳”一起发出来。用内置的std_msgs/Float32MultiArray也能凑合但数组里哪个元素是x、哪个是y全靠注释和默契。当时我就在想如果消息能像结构体一样字段名清清楚楚下游节点拿到的就是一份自解释的数据该多省心。这正是ROS2里自定义消息的用武之地。所谓消息就是话题、服务、动作之间传递数据的“通信协议”。内置的std_msgs、geometry_msgs能覆盖很多常见场景但实际项目一深入就发现不够用。要么字段组合太怪要么类型对不上要么希望一段数据里既有名字又有数值数组。与其硬编码、到处拼接解析不如直接定义一个符合业务逻辑的消息类型。这篇内容适合刚接触ROS2不久、已经会创建基础功能包、但对“接口定义”这件事还有点懵的朋友。看完之后你会明白怎么自己写.msg文件、怎么在CMakeLists和package.xml里做配置然后把自定义消息用在Python或C节点里。我也会把编译报错、接口找不到、QoS不匹配这些经典坑一起说出来。先立个共识在ROS2里自定义消息并不是什么玄学它本质上就是“定义数据结构 生成代码 被节点引用”。把这三个理解透剩下的都是套路。2. 搭建接口包把自定义消息建出来2.1 先分清三种接口文件msg、srv、action在动手之前先把类型搞清楚。ROS2里接口文件分为三种.msg定义话题消息。比如传感器数据、控制指令。.srv定义服务通信包含请求和响应两部分用---分隔。.action定义动作通信包含目标、结果、反馈三部分用两行---分隔。大多数人学自定义接口建议从.msg开始因为话题在顶层框架里用得最多也最容易理解。等消息搞明白了srv和action就是同一套思路的延伸。需要记住一个核心概念你写的是接口描述文件真正在C或Python里import的是由这些文件生成的代码。在C里你include的是“包名/msg/文件名.hpp”在Python里你import的是“包名.msg.文件名”。生成过程由构建系统自动完成但前提是你配置正确。2.2 创建接口功能包自定义消息不是随便放在执行包里建议单独建一个interface“接口包”这样多个功能包都能依赖它。用ros2 pkg create就能建ros2 pkg create my_interfaces --build-type ament_cmake这里用的构建类型是ament_cmake因为接口包需要CMake才方便生成代码。虽然也可以用ament_python包放msg但实际中维护麻烦我建议统一用CMake。建出来之后目录结构大概是这样my_interfaces/ ├── CMakeLists.txt ├── package.xml ├── msg/ │ └── (等你添加.msg文件) └── srv/ └── (如果需要服务)msg、srv、action这些目录不一定默认存在需要自己mkdir创建。2.3 写.msg文件字段类型有哪些讲究在msg目录下新建一个文件比如TargetPose.msg。文件名首字母大写这算ROS社区的约定你最好也遵守不然后面某些工具容易出怪问题。一个最简单的自定义消息string object_name float32 x float32 y float32 confidence builtin_interfaces/Time timestamp每一行就是一个字段格式是“类型 字段名”。类型可以是ROS2内置的基础类型比如int32、float64、string也可以是别的包里的消息类型比如geometry_msgs/PoseStamped甚至嵌套当前包里定义的其他msg。这里我踩过一个坑字段名不要用type、command这类和关键字贴近的词虽然编译不一定报错但某些代码生成环节会让人摸不着头脑。字段名建议语义化一看就知道是什么。如果你要在一个消息里放另一个自定义消息可以在类型前加包名比如my_interfaces/TargetPose target std_msgs/Header header2.4 CMakeLists与package.xml的必改项写完.msg如果直接编译会提示找不到头文件之类的问题因为你还没告诉构建系统“这里有接口要生成”。CMakeLists.txt里必须包含以下几块内容find_package(rosidl_default_generators REQUIRED) find_package(std_msgs REQUIRED) find_package(builtin_interfaces REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} msg/TargetPose.msg DEPENDENCIES std_msgs builtin_interfaces )第一行find_package(rosidl_default_generators REQUIRED)是生成接口代码的基础rosidl_generate_interfaces里的第一个参数是包名后续跟着所有.msg、.srv、.action文件路径最后的DEPENDENCIES写上引用了哪些外部包。package.xml里同样要加依赖dependrosidl_default_generators/depend dependstd_msgs/depend dependbuiltin_interfaces/depend member_of_grouprosidl_interface_packages/member_of_groupmember_of_group这行很容易漏掉它标记当前包属于接口包生成的消息才会被正确注册。漏了之后最典型的现象是ros2 interface list里看不到你的接口。改完之后回到工作空间根目录重新构建colcon build --packages-select my_interfaces source install/setup.bash2.5 编译并检查接口是否注册成功编译完先别急着写节点先确认接口真的“能用”。终端里输入ros2 interface list | grep my_interfaces能看到输出说明接口已经注册。还可以查看具体结构ros2 interface show my_interfaces/msg/TargetPose这个命令会打印字段列表和.msg里的内容一致。如果信息不对多半是source的路径不对或者构建缓存有问题清掉build/install再试试。3. 在Python节点中发送和接收自定义消息接口定义好之后你的功能包就需要依赖它然后在节点代码里引用。3.1 修改功能包的依赖假设你新建了一个功能包叫msg_demo那么package.xml和CMakeLists如果是C都要声明依赖my_interfaces。Python包只需要在package.xml里加dependmy_interfaces/depend同时如果使用Python包setup.py里的install_requires或data_files不一定需要特别处理只要通过ros2 pkg create生成的结构规范依赖一般没问题。但务必记得ament_python包的package.xml里也要加。3.2 发布端代码从填充字段到发布下面是发布端Python代码使用自定义消息TargetPoseimport rclpy from rclpy.node import Node from std_msgs.msg import Header from builtin_interfaces.msg import Time from my_interfaces.msg import TargetPose class TargetPosePublisher(Node): def __init__(self): super().__init__(target_pose_publisher) self.publisher self.create_publisher(TargetPose, target_pose, 10) self.timer self.create_timer(1.0, self.publish_callback) def publish_callback(self): msg TargetPose() msg.object_name obstacle msg.x 1.5 msg.y 2.0 msg.confidence 0.98 now self.get_clock().now().to_msg() msg.timestamp now self.publisher.publish(msg) self.get_logger().info(fPublished: {msg.object_name}, ({msg.x}, {msg.y}), conf{msg.confidence}) def main(argsNone): rclpy.init(argsargs) node TargetPosePublisher() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这里有个关键点from my_interfaces.msg import TargetPose如果接口包编译或者source没做好import直接失败。此外内嵌的Time消息用self.get_clock().now().to_msg()得到准确可靠。发布端用create_publisher时第二个参数是话题名第三个参数是QoS队列深度。10表示缓冲区最多存10条这个数值不是越大越好后文会展开说。3.3 订阅端代码回调里怎么取数据订阅端几乎就是一个镜像操作import rclpy from rclpy.node import Node from my_interfaces.msg import TargetPose class TargetPoseSubscriber(Node): def __init__(self): super().__init__(target_pose_subscriber) self.subscription self.create_subscription( TargetPose, target_pose, self.listener_callback, 10 ) self.subscription # 防止被垃圾回收 def listener_callback(self, msg): self.get_logger().info( fReceived: {msg.object_name}, ({msg.x}, {msg.y}), conf{msg.confidence}, time{msg.timestamp} ) def main(argsNone): rclpy.init(argsargs) node TargetPoseSubscriber() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()回调函数里的msg参数字段访问方式和发布端完全一致。有人会问“能不能直接打印msg”可以但打印结果通常是一大串嵌套结构没有字段逐个访问来得直观。3.4 用命令行工具验证消息内容没有写任何可视化工具的时候最直接的验证方式是用命令行。先启动发布节点然后在另一个终端执行ros2 topic list ros2 topic echo /target_poseros2 topic echo会打印话题上的所有消息字段结构一目了然。如果你想手动发送一条自定义消息可以用ros2 topic pub /target_pose my_interfaces/msg/TargetPose {object_name: test, x: 1.0, y: 2.0, confidence: 0.5}前提是终端里已经source过install/setup.bash且发布端没和它冲突。命令行一次性地发消息很适合调试。4. 实际开发中的疑难杂症与排查清单自定义消息本身并不复杂但实际使用中容易栽在环境、依赖和QoS上。我把自己遇到的、以及帮别人排查过的典型问题列出来。4.1 “找不到接口”的N种原因最常见的报错说辞是ModuleNotFoundError: No module named my_interfaces或者Package my_interfaces not found这不是代码问题基本是这三类原因现象原因解决办法Python import不到msg没有source接口包重新source install/setup.bash或直接source整个工作空间colcon build时找不到接口包顺序问题构建时接口包没先构建先构建依赖包再用--packages-select或直接colcon build全量构建ros2 interface list看不到接口包内package.xml漏了member_of_group补上并重新构建终端source没问题但新终端又不行忘了把source写入.bashrc建议在.bashrc里source工作空间的install/setup.bash跨工作站拷贝代码后找不到接口包没同步构建换环境后重新编译并source很多新手在同一个终端窗口里又构建又运行容易遇到缓存混乱。我的习惯是构建时开一个终端运行程序时重新开一个终端并source。构建和运行分离能少很多玄学问题。4.2 QoS匹配问题两边都写10还是会丢数据ROS2话题通信的QoS策略不是简单“队列长度”。create_publisher(TargetPose, target_pose, 10)里的10只是history深度完整的QoS还包括可靠性、持久性等。默认情况下发布端和订阅端都使用“系统默认”设置一般两者能互通。但如果你给发布端设置了RELIABILITY (RELIABLE)订阅端保持默认通常也兼容。反之如果订阅端设成BEST_EFFORT发布端是RELIABLE在某些场景下会建立连接但会有丢包行为不好预测。如果出现“节点启动但话题收不到数据”先检查QoS是否兼容用ros2 topic info /target_pose --verbose查看两端最终协商的策略。这个命令很实用能看到发布端和订阅端各自的可靠性、耐久性设置。给一个经验值传感器高频场景适合BEST_EFFORT控制指令用RELIABLE。自定义消息本身不绑定QoS完全是节点端设置所以不要把这个锅甩给消息定义。4.3 代码生成失败与缓存坑有时候改完.msg重新构建代码还是旧的。清理命令rm -rf build/ install/ log/ colcon build我在一次项目里同时改了多个接口包结果一个包构建成功、另一个包还是旧版本最后全量清理才解决。特别是当你改了消息文件名或者删除了字段旧生成的代码不会自动消失必须清理。另外colcon build默认会跳过没有变化的包这本身是优化机制。但当依赖包的接口变了目标包由于“看起来没变”而不会被重新编译导致运行时代码找不到新字段。遇到这种情况可以用colcon build --packages-select my_interfaces msg_demo把相关包强制一起构建。4.4 Foxy、Humble、Iron版本差异ROS2版本迭代很快接口定义的核心逻辑一致但有些细节不一样Python API从Foxy到Humblerclpy的create_publisher、create_subscription签名基本稳定。C代码生成Foxy生成的C头文件是包名/msg/消息名.hpp更早版本可能用.hpp但eloquent以上都统一了。CMake最低版本Humble要求CMake 3.8Ubuntu官方源的版本一般没问题。rosidl_generate_interfaces从长期使用的角度该宏的用法一直保留但内部工具链有变化不必深究。如果你在Ubuntu 20.04用Foxy或者Ubuntu 22.04用Humble本文的配置方案都适用。如果是更新的Iron或Jazzy最好查一下对应官方迁移指南不过基本思路一致。5. 别把自定义消息想得太重serv和action同理消息定义不止用于话题服务接口定义流程几乎一模一样。在srv目录下建一个文件比如CalculateArea.srvfloat32 width float32 height --- float32 area---上面是请求下面是响应。编译配置只需要在rosidl_generate_interfaces里加上这个srv文件路径代码引用方式为from my_interfaces.srv import CalculateArea创建一个服务端节点时回调函数接收request和response两个对象def handle_calculate(request, response): response.area request.width * request.height return response这和话题消息在使用形式上不同但接口定义和构建依赖的思路完全一样。如果你后续要搞动作库.action原理也相通只是文件里多了一个分段。我建议新手先用一到两个msg项目练手跑通“定义-编译-发布-订阅-调试”闭环。自定义消息真正提升的是整个团队的“可读性”一个人乐呵乐呵的项目也许无所谓但项目一旦有交接清晰的接口就是最好的文档。最后说一个个人习惯我会在msg文件里给每个字段写注释不只是给人看更是给自己一个月后看。ROS2的消息描述文件支持#注释写清楚每个字段的单位和取值范围真的能省下很多排查时间。这也是我玩自定义接口这几年觉得性价比最高的“隐藏功能”。本文还有配套的精品资源点击获取

相关新闻

从“土豆服务器”到稳定性工程:容量规划、瓶颈排查与降级策略

从“土豆服务器”到稳定性工程:容量规划、瓶颈排查与降级策略

2026/9/8 6:12:49

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Oracle ODBC驱动实战:instantclient-odbc-nt-11.2.0.3.0安装配置与排错指南

Oracle ODBC驱动实战:instantclient-odbc-nt-11.2.0.3.0安装配置与排错指南

2026/9/8 6:02:49

简介:Oracle ODBC驱动是Windows环境下连接Oracle数据库的核心组件,尤其面向使用PowerDesigner、ERStudio等数据建模工具的技术人员,用于解决11.2.0.3.0版本数据库的ODBC访问与数据源配置问题。压缩包共11个文件,以DLL动态库为主体…

ThreeDPoseTracker Windows 0.5.1 实战:从零搭建低成本3D动捕驱动流程

ThreeDPoseTracker Windows 0.5.1 实战:从零搭建低成本3D动捕驱动流程

2026/9/8 6:02:49

简介:面向VAM、MMD与Blender用户的Windows动作捕捉工具,ThreeDPoseTracker 0.5.1版可直接导入视频文件提取骨骼动作,生成MMD或Blender可用数据,借助插件还能转为VAM的timeline动作,适合需要低成本制作角色动画的虚拟主…

对称矩阵压缩存储下标计算全解析:从0基到上三角的公式推导与陷阱

对称矩阵压缩存储下标计算全解析:从0基到上三角的公式推导与陷阱

2026/9/8 7:12:52

对称矩阵的压缩存储,可能是数据结构里最容易出现“背了公式还做错”的考点。同一个求A[i][j]存储下标的题,换个下标起点、换一种三角区域,答案就完全不同,甚至有的题目还故意把“下标”和“第几个元素”混着问。这篇文章把对称矩阵…

2026大模型工程师必备技能:从模型部署到RAG与Agent实战

2026大模型工程师必备技能:从模型部署到RAG与Agent实战

2026/9/8 7:12:52

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

对称矩阵压缩存储下标计算全解析:公式推导到C语言验证

对称矩阵压缩存储下标计算全解析:公式推导到C语言验证

2026/9/8 7:12:52

后台经常收到这样的问题:数据结构复习到矩阵压缩存储时,明明公式背了好几遍,可一到考试,换个数、变一下“下标从 0 开始还是从 1 开始”的设定,结果就完全不一样了。尤其是对称矩阵压缩存储后的下标计算,看…

Python HTTP服务认证:标准库、Flask与Nginx三种方案

Python HTTP服务认证:标准库、Flask与Nginx三种方案

2026/9/8 7:12:52

上周给团队搭一个临时下载服务,图省事直接跑了python -m http.server 8000 --bind 0.0.0.0,然后把http://服务器IP:8000发到了群里。第二天看访问日志,里面混进来不少陌生 IP 在扫路径。那一刻我才意识到,任何暴露到网络里的 HTTP…

从设备台账到运维闭环:物联网设备管理平台核心功能拆解

从设备台账到运维闭环:物联网设备管理平台核心功能拆解

2026/9/8 7:12:52

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

轻量级AVI播放器开发:MCIWnd控件实战与避坑指南

轻量级AVI播放器开发:MCIWnd控件实战与避坑指南

2026/9/8 7:02:51

简介:基于MCIWnd控件的AVI视频播放器是一份适合Windows平台初学者的多媒体编程工程资源。它围绕MCIWnd控件,实现了通过菜单选择视频文件并在客户区左上角动态创建播放窗口的功能,帮助开发者理解视频播放器窗口生成、文件关联及播放控制条调用…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/7 20:21:46

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/8 4:55:53

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景:客户拿着一条良率曲线截图问你,这批货的良率怎么掉了三个点,是不是工艺出问题了,产生的不良会不会流到他们产线上去。你解释了半天,客户似懂非懂&#…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle,这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了,而且很有意思的是,它经常不是新手专属——很多写了好几年模型的老手,在从单机改成自定义采样器&#xf…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频,声称中国电动车不仅性能不如美国大排量车型,安全性也堪忧。然而事实恰恰相反,GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴(Euro NCAP)测试中全部斩获满分。就在特斯…

远程协作的工作台整理

远程协作的工作台整理

2026/9/8 4:23:39

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/8 3:19:39

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/8 4:00:23

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…