4. 使用自定义消息类型

任务目标

在ROS2中定义包含基本类型(int32、float32、string)和定长/变长数组的自定义消息,分别用C++发布节点和Python订阅节点(或反向)传递该消息,验证跨语言序列化与通信正确性。

实现步骤

  1. 创建消息包并定义.msg文件
  2. 编译生成语言绑定代码
  3. 编写C++发布节点与Python订阅节点
  4. 运行并验证接收数据与发送数据一致

1. 创建消息包

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake custom_msg_pkg
mkdir -p custom_msg_pkg/msg

在custom_msg_pkg/msg/SensorData.msg中定义消息:

# 基本类型
int32 device_id
float32 temperature
string status
# 定长数组(3个元素)
float32[3] accelerometer
# 变长数组
int32[] readings

2. 配置编译规则

编辑custom_msg_pkg/CMakeLists.txt,添加:

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/SensorData.msg"
  DEPENDENCIES builtin_interfaces
)

编辑custom_msg_pkg/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>

3. 编译消息包

cd ~/ros2_ws
colcon build --packages-select custom_msg_pkg
source install/setup.bash

4. C++发布节点

在custom_msg_pkg/src/publisher.cpp中:

#include "rclcpp/rclcpp.hpp"
#include "custom_msg_pkg/msg/sensor_data.hpp"
#include <vector>

using namespace std::chrono_literals;

class SensorPublisher : public rclcpp::Node {
public:
    SensorPublisher() : Node("sensor_publisher") {
        publisher_ = this->create_publisher<custom_msg_pkg::msg::SensorData>("sensor_topic", 10);
        timer_ = this->create_wall_timer(1s, [this]() { publish_data(); });
    }

private:
    void publish_data() {
        auto msg = custom_msg_pkg::msg::SensorData();
        msg.device_id = 42;
        msg.temperature = 23.5f;
        msg.status = "ok";
        msg.accelerometer = {1.0f, 2.0f, 3.0f};
        msg.readings = {10, 20, 30, 40};
        RCLCPP_INFO(this->get_logger(), "Publishing: id=%d temp=%.1f status=%s accel=[%.1f,%.1f,%.1f] readings=[%d,%d,%d,%d]",
                    msg.device_id, msg.temperature, msg.status.c_str(),
                    msg.accelerometer[0], msg.accelerometer[1], msg.accelerometer[2],
                    msg.readings[0], msg.readings[1], msg.readings[2], msg.readings[3]);
        publisher_->publish(msg);
    }
    rclcpp::Publisher<custom_msg_pkg::msg::SensorData>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<SensorPublisher>());
    rclcpp::shutdown();
    return 0;
}

在CMakeLists.txt末尾添加:

add_executable(sensor_publisher src/publisher.cpp)
ament_target_dependencies(sensor_publisher rclcpp custom_msg_pkg)
install(TARGETS sensor_publisher DESTINATION lib/${PROJECT_NAME})

5. Python订阅节点

创建custom_msg_pkg/scripts/subscriber.py:

import rclpy
from rclpy.node import Node
from custom_msg_pkg.msg import SensorData

class SensorSubscriber(Node):
    def __init__(self):
        super().__init__('sensor_subscriber')
        self.subscription = self.create_subscription(
            SensorData, 'sensor_topic', self.callback, 10)

    def callback(self, msg):
        self.get_logger().info(
            f'Received: id={msg.device_id} temp={msg.temperature:.1f} '
            f'status={msg.status} accel={list(msg.accelerometer)} '
            f'readings={list(msg.readings)}')

def main(args=None):
    rclpy.init(args=args)
    node = SensorSubscriber()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

在CMakeLists.txt中添加安装规则:

install(PROGRAMS scripts/subscriber.py DESTINATION lib/${PROJECT_NAME})

6. 编译与运行

cd ~/ros2_ws
colcon build --packages-select custom_msg_pkg
source install/setup.bash

# 终端1:运行发布节点
ros2 run custom_msg_pkg sensor_publisher

# 终端2:运行订阅节点
ros2 run custom_msg_pkg subscriber.py

验证方法

观察两个终端输出:发布节点打印的Publishing信息与订阅节点打印的Received信息应完全一致,包括数组元素顺序与数值。若一致,说明自定义消息在C++与Python间序列化/反序列化正确。

预期输出示例(发布端):

[INFO] Publishing: id=42 temp=23.5 status=ok accel=[1.0,2.0,3.0] readings=[10,20,30,40]

预期输出示例(订阅端):

[INFO] Received: id=42 temp=23.5 status=ok accel=[1.0, 2.0, 3.0] readings=[10, 20, 30, 40]

常见问题

  • 找不到消息头文件:确认colcon build后已source install/setup.bash,且包名与include路径大小写正确(sensor_data.hpp对应SensorData.msg)。
  • Python无法import:确认Python节点通过ros2 run执行(自动设置PYTHONPATH),或手动source install/setup.bash。
  • 数组长度不匹配:定长数组float32[3]赋值时必须恰好3个元素;变长数组int32[]长度不限。