4、订阅者节点实战:用Python写一个会“听”的节点

好,咱们接着聊。上一节我们写了个发布者,一个劲儿地往外发消息。那谁来收呢?当然就是订阅者了。这一节,我们就来亲手写一个Python订阅者节点,看看它是怎么“听到”消息的。

说白了,订阅者就是ROS2里的“听众”。它订阅某个话题,一旦有消息发布出来,它就会被唤醒,然后执行我们预先写好的回调函数。这个机制,是整个ROS2通信的灵魂。

核心要点:订阅者节点 = 创建节点 + 订阅话题 + 定义回调函数。就这么三步,缺一不可。

4.1 先搭个架子:最简单的订阅者

我个人习惯,写任何节点之前,先把框架搭好。就像盖房子,先立柱子,再填砖。来看代码:

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimpleSubscriber(Node):
    def __init__(self):
        super().__init__('simple_subscriber')
        self.subscription = self.create_subscription(
            String,
            'chatter',
            self.listener_callback,
            10
        )
        self.subscription  # 防止被垃圾回收

    def listener_callback(self, msg):
        self.get_logger().info('收到消息: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)
    simple_subscriber = SimpleSubscriber()
    rclpy.spin(simple_subscriber)
    simple_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

嗯,这段代码看着眼熟吧?跟发布者很像,但有个关键区别——create_subscription。它需要四个参数:消息类型、话题名、回调函数、QoS设置。

你想想看,回调函数就是那个listener_callback。一旦有消息到达,ROS2就会自动调用它。我在项目中遇到过,很多新手会在这里犯迷糊:回调函数里能不能做耗时操作?我的建议是——尽量别。回调函数应该轻量、快速,否则会阻塞整个节点的消息处理。

小技巧:如果你需要在回调里做复杂处理,可以把消息放到队列里,另起一个线程去处理。这样就不会卡住消息接收了。

4.2 回调函数机制:到底是怎么工作的?

为什么会这样?回调函数到底是怎么被调用的?

其实,ROS2底层维护了一个事件循环。当你调用rclpy.spin()时,节点就进入了这个循环。它不停地检查有没有新消息到达,如果有,就立刻调用对应的回调函数。

我曾经调试过一个奇怪的问题:订阅者明明收到了消息,但回调函数就是没执行。查了半天,发现是rclpy.spin()被放在了另一个线程里,而主线程已经退出了。嗯,这里要注意——spin()必须在一个活着的线程里运行,否则节点就“死”了。

回调函数的执行顺序呢?默认是先进先出。但如果你设置了不同的QoS,顺序可能会变。我们后面会细说。

4.3 消息过滤:不是所有消息都要收

实际项目中,话题上的消息可能很密集。比如一个激光雷达,每秒发几千条消息。你的订阅者如果全收,CPU可能直接爆表。

那怎么办?消息过滤就派上用场了。ROS2提供了几种过滤方式:

  • 时间戳过滤:只接收某个时间窗口内的消息
  • 内容过滤:只接收满足特定条件的消息(ROS2 Humble及以上版本支持)
  • 频率过滤:通过QoS设置,控制接收频率

我个人最常用的是频率过滤。比如,一个话题每秒发100条消息,但我只需要每秒处理10条。那我就在QoS里设置一个合适的队列深度,让ROS2自动帮我丢弃多余的消息。

注意:消息过滤不是万能的。如果你需要精确控制接收哪些消息,最好在回调函数里自己加判断逻辑。比如:

def listener_callback(self, msg):
    if msg.data.startswith('重要'):
        self.process(msg)
    else:
        self.get_logger().debug('忽略非重要消息')
  

4.4 QoS设置:让你的订阅者更“聪明”

QoS,全称Quality of Service,服务质量。说白了,就是告诉ROS2:你希望怎么收消息?

ROS2的QoS设置比ROS1灵活得多。它有几个关键参数:

参数 说明 常用值
history 保留多少条历史消息 KEEP_LAST / KEEP_ALL
depth 队列深度(仅KEEP_LAST时有效) 10 / 100 / 1000
reliability 可靠性策略 RELIABLE / BEST_EFFORT
durability 持久性策略 VOLATILE / TRANSIENT_LOCAL

我在项目中遇到过这样一个场景:一个传感器节点发布数据,但订阅者启动得比较晚。如果使用默认的QoS(VOLATILE),订阅者就收不到之前发布的消息。后来我把durability改成TRANSIENT_LOCAL,问题就解决了。

你想想看,这就是QoS的威力。它不只是“快”或“慢”的问题,而是决定了节点之间如何“握手”。

经验之谈:如果你不确定该用哪种QoS,就用ROS2提供的预设配置。比如rclpy.qos.QoSProfile.default()或者rclpy.qos.qos_profile_sensor_data。这些预设配置经过了大量测试,一般不会出问题。

4.5 实战:写一个带QoS的订阅者

光说不练假把式。我们来写一个稍微复杂点的订阅者,手动设置QoS:

from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy

class AdvancedSubscriber(Node):
    def __init__(self):
        super().__init__('advanced_subscriber')
        
        # 自定义QoS
        qos_profile = QoSProfile(
            history=HistoryPolicy.KEEP_LAST,
            depth=20,
            reliability=ReliabilityPolicy.BEST_EFFORT
        )
        
        self.subscription = self.create_subscription(
            String,
            'chatter',
            self.listener_callback,
            qos_profile
        )

    def listener_callback(self, msg):
        self.get_logger().info('收到: %s' % msg.data)

看到没?我把reliability设成了BEST_EFFORT。这意味着如果网络不好,丢几条消息也无所谓。适合传感器数据这种“丢了就丢了”的场景。

但如果你做的是控制指令,那就得用RELIABLE。我曾经因为用错了QoS,导致机器人收到残缺的控制指令,差点撞墙。嗯,从那以后,我对QoS的选择就格外小心了。

4.6 知识体系:一张图看懂订阅者

说了这么多,我们来画张图,把订阅者的核心逻辑串起来:

订阅者节点核心逻辑 发布者节点 话题 (chatter) 订阅者节点 回调函数 QoS设置 消息过滤 发布消息 触发回调 订阅者收到消息后,自动调用回调函数 QoS决定消息的可靠性、持久性和队列深度 消息过滤决定哪些消息被接收

这张图很清楚地展示了订阅者的工作流程:发布者往话题上发消息,订阅者通过QoS设置和消息过滤,决定接收哪些消息,然后触发回调函数进行处理。

4.7 避坑指南:我踩过的那些坑

最后,分享几个我实际踩过的坑,希望能帮你少走弯路:

  • 回调函数里别sleep:我曾经在回调里加了time.sleep(1),结果整个节点卡死。记住,回调函数要快!
  • QoS不匹配:发布者和订阅者的QoS必须兼容。比如一个用RELIABLE,一个用BEST_EFFORT,就可能收不到消息。
  • 忘记spin:没有rclpy.spin(),节点就是个空壳,永远不会处理消息。
  • 话题名拼写错误:这个最坑。发布者和订阅者的话题名差一个字母,就收不到消息。我至少在这上面浪费过半小时。

调试小技巧:用ros2 topic list查看当前所有话题,用ros2 topic echo /话题名直接看消息内容。这两个命令能帮你快速定位问题。

好了,订阅者节点就讲到这里。记住三个关键词:回调函数、消息过滤、QoS设置。掌握了它们,你就掌握了ROS2通信的“听”功。

可可精品资料库