3、ROS2基础编程:Python客户端库(rclpy)入门
好,咱们正式开始写代码了。
前面两章我们把ROS2的环境搭好,也聊了聊它的核心概念。现在,是时候让机器人真正「开口说话」了。这一章,我会带你从零开始,用Python写一个发布者节点和一个订阅者节点。
说白了,就是让两个程序通过ROS2互相喊话。一个负责发,一个负责收。
3.1 rclpy:ROS2的Python大门
rclpy,全称是ROS Client Library for Python。它是ROS2为Python开发者提供的官方库。
我个人习惯把它理解为「ROS2的Python翻译官」。你写的Python代码,通过它才能跟ROS2的底层通信机制打交道。没有它,你的Python程序就是个孤岛。
核心要点:rclpy负责初始化节点、管理通信、处理回调。所有Python节点的起点,都是 rclpy.init()。
我在项目中遇到过不少新手,上来就写 import rclpy,然后直接调用发布订阅。结果程序跑起来没反应。为什么?因为忘了初始化。记住,rclpy.init() 是第一步,就像你开车前得先拧钥匙点火一样。
3.2 创建第一个发布者节点
我们先写一个发布者。它每隔一秒发一条消息,内容就是「Hello, ROS2!」。
嗯,这里要注意:发布者节点需要三个要素——节点名称、话题名称、消息类型。
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
def __init__(self):
super().__init__('simple_publisher')
self.publisher_ = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.counter = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello, ROS2! 第 {self.counter} 条消息'
self.publisher_.publish(msg)
self.get_logger().info(f'发布了: "{msg.data}"')
self.counter += 1
def main(args=None):
rclpy.init(args=args)
node = SimplePublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
这段代码里,我做了几件事:
- 继承了
Node类,创建了一个叫simple_publisher的节点。 - 用
create_publisher创建了一个发布者,话题叫chatter,消息类型是String。 - 用
create_timer创建了一个定时器,每秒触发一次回调函数。
小技巧:队列长度参数 10 是什么意思?它表示消息缓冲区的大小。如果订阅者处理得慢,消息会暂存在这里。我曾经设成 1,结果丢消息丢到怀疑人生。一般设 10 或 100 比较稳妥。
3.3 创建第一个订阅者节点
有发的,就得有收的。订阅者节点负责监听 chatter 话题,一旦有消息进来,就打印出来。
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(f'收到了: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
node = SimpleSubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
你看,订阅者的结构和发布者很像。区别在于:
- 用
create_subscription代替了create_publisher。 - 不需要定时器,消息来了自动触发回调。
- 回调函数
listener_callback接收一个msg参数,里面就是收到的数据。
注意:订阅者创建后,一定要把 self.subscription 赋值给一个成员变量。否则Python的垃圾回收机制会把它当成临时变量给清理掉。我刚开始学的时候就踩过这个坑,节点创建了,但就是收不到消息,排查了半天才发现是这个问题。
3.4 理解回调函数
回调函数,是ROS2编程里绕不开的概念。你想想看,你的程序怎么知道「什么时候该执行什么动作」?
传统写法是写一个死循环,不断检查有没有新消息。但ROS2用了另一种方式——回调。
说白了,回调函数就是「你告诉ROS2:当某件事发生时,请调用这个函数」。比如:
- 定时器到期了 → 调用
timer_callback - 新消息到达了 → 调用
listener_callback
这样做的好处是,你的代码不用一直轮询等待。ROS2的 rclpy.spin() 会帮你管理这一切。它就像一个事件循环,不断检查有没有事件发生,有的话就调用对应的回调函数。
关键理解:回调函数是异步触发的。你永远不知道它什么时候会被调用。所以回调函数里不要做耗时操作,比如读写大文件、发网络请求。否则会阻塞整个节点,导致其他回调无法执行。
我曾经在一个项目里,把图像处理的代码直接写在了回调函数里。结果图像还没处理完,下一帧又来了,整个系统卡得跟幻灯片一样。后来我把耗时操作放到单独的线程里,回调只负责把数据丢进队列,问题就解决了。
3.5 发布-订阅模型流程图
下面这张图,展示了发布者和订阅者之间的通信关系。我画得比较简洁,但核心逻辑都在里面了。
这张图你看懂了吗?发布者把消息扔到话题上,订阅者从话题上取消息。两者完全解耦,互不知道对方的存在。这就是ROS2通信的精髓。
3.6 运行你的第一个ROS2程序
代码写好了,怎么跑起来?
- 打开终端,先source一下ROS2环境:
source /opt/ros/humble/setup.bash - 运行发布者:
python3 publisher.py - 再开一个终端,同样source环境,运行订阅者:
python3 subscriber.py
你会看到发布者终端每秒打印一条发布日志,订阅者终端同步打印接收日志。如果一切正常,恭喜你,你的第一个ROS2节点跑起来了!
调试技巧:如果收不到消息,先用 ros2 topic list 看看话题有没有创建。再用 ros2 topic echo /chatter 直接监听话题内容。这两个命令是我排查通信问题的首选工具。
嗯,这一章的内容就到这里。你学会了用rclpy创建发布者和订阅者,也理解了回调函数的工作原理。这些是ROS2编程的基石,后面的所有传感器接入、数据处理,都建立在这个基础之上。