3. ROS2基础通信实战:话题、服务、动作的编程实现

大家好,我是你们的自动驾驶感知工程师讲师。今天咱们来聊聊ROS2里最核心的三个通信方式——话题、服务、动作。说实话,这三个东西搞明白了,ROS2开发你就入门了一大半。

我记得刚接触ROS2那会儿,总觉得通信机制有点绕。后来在项目里踩了不少坑,才慢慢摸清门道。今天我就把这些经验掰开揉碎了讲给你听。

3.1 话题(Topic)——最常用的异步通信

话题是ROS2里最基础的通信方式。说白了,就是发布者往一个“频道”里发数据,订阅者从这个“频道”里收数据。发布者和订阅者互不认识,完全解耦。

核心特点:异步、一对多、实时性高

我在项目中遇到过一个问题:摄像头采集的图像数据量很大,如果用服务通信,每帧图像都要等响应,帧率根本跑不起来。用话题就完美解决了——发布者只管发,订阅者只管收,谁也不等谁。

3.1.1 发布者编程示例

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.pub = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello ROS2: {self.count}'
        self.pub.publish(msg)
        self.get_logger().info(f'发布: {msg.data}')
        self.count += 1

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

小技巧:队列长度参数(这里的10)很关键。我一般设成10-20,太大浪费内存,太小容易丢消息。

3.1.2 订阅者编程示例

class Listener(Node):
    def __init__(self):
        super().__init__('listener')
        self.sub = self.create_subscription(
            String, 'chatter', self.callback, 10)

    def callback(self, msg):
        self.get_logger().info(f'收到: {msg.data}')

你想想看,订阅者只需要知道话题名字和消息类型,就能收到数据。这种松耦合的设计,在自动驾驶系统里特别实用。比如激光雷达节点发布点云数据,感知模块、定位模块、规划模块都能同时订阅,各取所需。

3.2 服务(Service)——同步请求-响应

服务跟话题不一样。它是同步的,客户端发一个请求,服务端处理完再返回响应。说白了,就是一问一答的模式。

核心特点:同步、一对一、有响应

我曾经在调试一个相机标定程序时,需要临时查询相机参数。用话题的话,得一直监听,太浪费。用服务就刚刚好——需要的时候发个请求,拿到结果就完事。

3.2.1 服务端编程示例

from example_interfaces.srv import AddTwoInts

class ServiceServer(Node):
    def __init__(self):
        super().__init__('service_server')
        self.srv = self.create_service(
            AddTwoInts, 'add_two_ints', self.add_callback)

    def add_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info(
            f'收到: {request.a} + {request.b} = {response.sum}')
        return response

3.2.2 客户端编程示例

class ServiceClient(Node):
    def __init__(self):
        super().__init__('service_client')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('等待服务...')
        self.req = AddTwoInts.Request()

    def send_request(self, a, b):
        self.req.a = a
        self.req.b = b
        future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, future)
        return future.result()

注意:服务调用是阻塞的。如果服务端处理时间太长,客户端会一直等。我建议超时时间设成5秒,超过就报错重试。

3.3 动作(Action)——带反馈的长时间任务

动作是ROS2里最复杂的通信方式。它结合了话题和服务的优点——既有服务的请求-响应模式,又有话题的实时反馈能力。

核心特点:异步、可取消、带反馈、适合长时间任务

嗯,这里要注意。动作特别适合自动驾驶里的规划控制任务。比如车辆变道,你发一个“变道”的动作请求,它一边执行一边给你反馈进度,执行完了告诉你结果。如果发现障碍物,还能中途取消。

3.3.1 动作定义文件

# action/Drive.action
int32 target_speed
int32 target_lane
---
bool success
string message
---
float32 progress

3.3.2 动作服务端编程示例

from rclpy.action import ActionServer
from your_package.action import Drive

class DriveActionServer(Node):
    def __init__(self):
        super().__init__('drive_action_server')
        self.action_server = ActionServer(
            self, Drive, 'drive', self.execute_callback)

    async def execute_callback(self, goal_handle):
        self.get_logger().info(f'收到目标: 速度={goal_handle.request.target_speed}')
        
        feedback_msg = Drive.Feedback()
        feedback_msg.progress = 0.0
        
        for i in range(10):
            if goal_handle.is_cancel_requested:
                goal_handle.canceled()
                return Drive.Result(success=False, message='任务取消')
            
            feedback_msg.progress = (i + 1) * 10.0
            goal_handle.publish_feedback(feedback_msg)
            await asyncio.sleep(1.0)
        
        goal_handle.succeed()
        return Drive.Result(success=True, message='任务完成')

3.4 三种通信方式对比

特性 话题 服务 动作
通信模式 异步 同步 异步
是否支持反馈 否 否 是
是否可取消 否 否 是
适用场景 传感器数据、状态发布 参数查询、简单计算 导航、机械臂控制
实时性 高 中 中

3.5 知识体系结构图

ROS2三种通信方式 ROS2通信机制 话题 (Topic) 发布-订阅模式 异步通信 一对多 传感器数据 服务 (Service) 请求-响应模式 同步通信 一对一 参数查询 动作 (Action) 目标-结果模式 异步+反馈 可取消 长时间任务 选择建议 实时数据流 → 话题 | 简单查询 → 服务 | 复杂任务 → 动作

3.6 实战经验总结

我个人习惯在项目开始前,先画一个通信拓扑图。把每个节点用什么通信方式标清楚,这样后面写代码不会乱。

我的建议:

  • 传感器数据、状态信息 → 用话题
  • 临时查询、参数设置 → 用服务
  • 导航、控制、机械臂 → 用动作

我曾经在一个项目里,把本该用话题的传感器数据硬生生用成了服务。结果每帧图像都要等响应,帧率从30fps掉到了2fps。后来改成话题,问题立刻解决。你想想看,选对通信方式有多重要。

避坑指南:我曾经在调试多节点通信时,发现话题收不到数据。查了半天,原来是话题名字拼写错了。ROS2不会报错,只会默默收不到消息。所以一定要用 ros2 topic list 和 ros2 topic echo 来验证。

好了,三种通信方式就讲到这里。代码示例我都跑过,可以直接用。建议你动手敲一遍,遇到问题再回来翻翻。实践出真知嘛。

可可精品资料库