2. ROS2话题通信基础

任务目标

本案例将实现一个ROS2话题通信的最小闭环:创建自定义消息类型,编写一个发布者节点周期性发送消息,编写一个订阅者节点接收并打印消息,最终通过命令行工具验证消息传递的实时性。

环境准备

需要已安装ROS2(Humble或更高版本),并完成环境变量加载(source /opt/ros/humble/setup.bash)。创建工作空间并编译自定义消息接口包。

实现步骤

1. 创建自定义消息包

# 创建ROS2工作空间
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src

# 创建自定义消息接口包
ros2 pkg create --build-type ament_cmake tutorial_interfaces

# 创建msg目录并定义消息文件
mkdir -p tutorial_interfaces/msg
cat > tutorial_interfaces/msg/Student.msg << 'EOF'
string name
int32 age
float32 score
EOF

编辑tutorial_interfaces/CMakeLists.txt,在find_package部分添加rosidl_default_generators依赖,并在文件末尾添加消息生成规则:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Student.msg"
)

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

2. 创建发布者与订阅者节点

在~/ros2_ws/src下创建功能包:

ros2 pkg create --build-type ament_python py_talker_listener

创建发布者节点py_talker_listener/py_talker_listener/talker.py:

import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Student

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.publisher = self.create_publisher(Student, 'student_info', 10)
        self.timer = self.create_timer(0.5, self.timer_callback)  # 2Hz
        self.count = 0

    def timer_callback(self):
        msg = Student()
        msg.name = f'Student_{self.count}'
        msg.age = 20 + self.count % 10
        msg.score = 80.0 + self.count % 20
        self.publisher.publish(msg)
        self.get_logger().info(f'发布: {msg.name}, 年龄: {msg.age}, 成绩: {msg.score}')
        self.count += 1

def main(args=None):
    rclpy.init(args=args)
    node = Talker()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

创建订阅者节点py_talker_listener/py_talker_listener/listener.py:

import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Student

class Listener(Node):
    def __init__(self):
        super().__init__('listener')
        self.subscription = self.create_subscription(
            Student, 'student_info', self.listener_callback, 10)
        self.receive_count = 0
        self.last_receive_time = self.get_clock().now()

    def listener_callback(self, msg):
        now = self.get_clock().now()
        interval = (now - self.last_receive_time).nanoseconds / 1e9
        self.last_receive_time = now
        self.receive_count += 1
        self.get_logger().info(
            f'收到: {msg.name}, 年龄: {msg.age}, 成绩: {msg.score:.1f}, '
            f'接收间隔: {interval:.3f}s, 累计: {self.receive_count}')

def main(args=None):
    rclpy.init(args=args)
    node = Listener()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

编辑py_talker_listener/setup.py,在entry_points中添加:

entry_points={
    'console_scripts': [
        'talker = py_talker_listener.talker:main',
        'listener = py_talker_listener.listener:main',
    ],
},

编辑py_talker_listener/package.xml,添加依赖:

<exec_depend>rclpy</exec_depend>
<exec_depend>tutorial_interfaces</exec_depend>

3. 编译与运行

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

# 终端1:运行发布者
ros2 run py_talker_listener talker

# 终端2:运行订阅者
ros2 run py_talker_listener listener

验证方法

订阅者终端应持续打印接收到的消息,且接收间隔稳定在0.5秒左右(与发布频率2Hz对应),说明消息实时传递。同时可打开第三个终端,使用ros2 topic echo /student_info查看话题数据,或使用ros2 topic hz /student_info查看实际发布频率。

预期输出示例

# 订阅者终端
[INFO] 收到: Student_0, 年龄: 20, 成绩: 80.0, 接收间隔: 0.502s, 累计: 1
[INFO] 收到: Student_1, 年龄: 21, 成绩: 81.0, 接收间隔: 0.498s, 累计: 2
[INFO] 收到: Student_2, 年龄: 22, 成绩: 82.0, 接收间隔: 0.501s, 累计: 3

通过条件:订阅者持续收到消息且间隔接近0.5秒,无报错,即验证通过。