4. 使用自定义消息类型
任务目标
在ROS2中定义包含基本类型(int32、float32、string)和定长/变长数组的自定义消息,分别用C++发布节点和Python订阅节点(或反向)传递该消息,验证跨语言序列化与通信正确性。
实现步骤
- 创建消息包并定义
.msg文件 - 编译生成语言绑定代码
- 编写C++发布节点与Python订阅节点
- 运行并验证接收数据与发送数据一致
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[]长度不限。