2、自定义消息接口:创建.msg文件、编译消息接口、在Python中使用自定义消息

好,咱们接着聊。上一节我们把话题通信的基本流程跑通了,用的都是ROS2自带的标准消息类型,比如String、Int32这些。但在实际项目中,你很快就会发现——这些标准消息根本不够用。

举个例子。我在做地图数据传输时,需要传递一张栅格地图。地图里有宽度、高度、分辨率、障碍物数据……这么多信息,你总不能拆成五六个话题分开发吧?那不乱套了?

这时候就需要自定义消息接口。说白了,就是你自己定义一个数据结构,把一堆相关的数据打包成一个整体,一次性发出去。

核心思想:自定义消息 = 你自定义的数据结构。就像C语言里的struct,Python里的class。

2.1 创建.msg文件

先说说怎么创建这个文件。我个人习惯在功能包下建一个msg文件夹,专门放消息定义文件。文件名后缀是.msg。

比如我们要定义一个地图消息,叫MapData.msg。内容长这样:

# MapData.msg
# 这是一个自定义地图消息

int32 width          # 地图宽度(像素)
int32 height         # 地图高度(像素)
float32 resolution   # 分辨率(米/像素)
int32[] data         # 障碍物数据,0表示空闲,100表示障碍
string frame_id      # 坐标系名称

嗯,这里要注意几点:

  • 字段类型:支持int32、float32、string、bool等基础类型,也支持数组(比如int32[])
  • 注释:用#开头,跟Python一样
  • 命名规范:文件名首字母大写,用驼峰命名法

小技巧:我建议每个消息文件都加上注释,说明每个字段的含义。你想想看,三个月后你自己回来看代码,没有注释真的会懵。

2.2 编译消息接口

文件创建好了,接下来要告诉ROS2:“嘿,我定义了一个新消息,帮我编译一下。”这一步很多人会忘,结果运行时找不到消息类型,报错报得一脸懵。

具体分三步走:

  1. 修改CMakeLists.txt:在find_package里加上rosidl_default_generators,在rosidl_generate_interfaces里列出你的.msg文件
  2. 修改package.xml:添加依赖rosidl_default_generators和builtin_interfaces
  3. 编译:用colcon build编译整个工作空间

CMakeLists.txt里关键部分长这样:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MapData.msg"
)

package.xml里加上:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

避坑指南:我曾经有一次忘了加member_of_group这一行,结果编译通过了,但运行时ROS2根本找不到我的消息接口。折腾了半小时才发现是这里漏了。所以,package.xml里的每一行都别省。

编译命令:

cd ~/ros2_ws
colcon build --packages-select 你的功能包名
source install/setup.bash

编译成功后,你可以用ros2 interface show命令验证一下:

ros2 interface show 你的功能包名/msg/MapData

如果能看到你定义的字段,那就说明编译成功了。

2.3 在Python中使用自定义消息

消息编译好了,怎么在Python代码里用呢?其实跟用标准消息一模一样,只是import的路径变了。

发布端示例:

import rclpy
from rclpy.node import Node
from 你的功能包名.msg import MapData

class MapPublisher(Node):
    def __init__(self):
        super().__init__('map_publisher')
        self.publisher = self.create_publisher(MapData, 'map_topic', 10)
        timer_period = 1.0
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def timer_callback(self):
        msg = MapData()
        msg.width = 100
        msg.height = 100
        msg.resolution = 0.05
        msg.data = [0] * (100 * 100)  # 全部初始化为空闲
        msg.frame_id = "map"
        self.publisher.publish(msg)
        self.get_logger().info('发布地图数据')

def main(args=None):
    rclpy.init(args=args)
    map_publisher = MapPublisher()
    rclpy.spin(map_publisher)
    map_publisher.destroy_node()
    rclpy.shutdown()

订阅端示例:

import rclpy
from rclpy.node import Node
from 你的功能包名.msg import MapData

class MapSubscriber(Node):
    def __init__(self):
        super().__init__('map_subscriber')
        self.subscription = self.create_subscription(
            MapData,
            'map_topic',
            self.listener_callback,
            10)

    def listener_callback(self, msg):
        self.get_logger().info(f'收到地图: {msg.width}x{msg.height}, 分辨率: {msg.resolution}')
        # 这里可以处理地图数据了

def main(args=None):
    rclpy.init(args=args)
    map_subscriber = MapSubscriber()
    rclpy.spin(map_subscriber)
    map_subscriber.destroy_node()
    rclpy.shutdown()

你看,代码结构跟之前用String消息几乎一模一样。唯一不同的就是import路径和消息类型。

关键点:import路径是你的功能包名.msg,不是std_msgs.msg。这个别搞混了。

2.4 本章知识体系

为了让你更直观地理解整个流程,我画了一张图:

自定义消息接口工作流程 1. 创建.msg文件 定义数据结构 2. 编译消息接口 CMakeLists.txt + package.xml 3. Python中使用 import + 发布/订阅 详细说明: • .msg文件:定义字段名和类型,支持int32/float32/string/数组等 • 编译:修改CMakeLists.txt和package.xml,用colcon build编译 • Python使用:from 功能包名.msg import 消息名,然后像普通类一样赋值 • 验证:ros2 interface show 功能包名/msg/消息名 ⚠ 常见错误:忘记source install/setup.bash、package.xml漏写依赖

2.5 总结与避坑

自定义消息接口,说白了就是三步:定义 → 编译 → 使用。流程不复杂,但细节容易出错。

我把自己踩过的坑总结一下:

  • 编译后别忘了source:每次编译完都要source install/setup.bash,否则ROS2找不到你的新消息
  • 消息类型要匹配:发布端和订阅端用的消息类型必须完全一致,包括功能包名
  • 字段命名别用关键字:比如别用type、class这些Python保留字
  • 数组长度要小心:ROS2的数组是动态长度的,但如果你在C++端用固定长度数组接收,可能会出问题

个人经验:我建议你在项目初期就把所有需要传递的数据结构定义好,做成一个单独的msg功能包。这样其他功能包都依赖它,方便管理。别像我早期那样,每个功能包各自定义消息,最后改一个字段要改七八个地方……那叫一个酸爽。

好了,自定义消息接口就讲到这里。下一节我们聊聊更复杂的情况——如何在同一个话题上传输多种不同类型的消息。嗯,到时候你就知道自定义消息有多重要了。

可可精品资料库