3、第一个ROS2节点:使用Python编写发布者节点,理解rclpy初始化、节点创建、spin循环
好,咱们直接动手写代码。很多初学者喜欢先背理论,我个人的习惯是——先跑通一个节点,再回头理解它。你想想看,能看到终端里不断打印消息,那种成就感比干啃文档强多了。
3.1 从零搭建一个Python发布者
先别急着写,咱们捋一下思路。一个发布者节点,说白了就是:
- 告诉ROS2系统:“我要上线了”
- 给自己起个名字(节点名)
- 创建一个“喇叭”(发布器),定时喊话
- 让ROS2的后台服务一直运行,别让程序退出
嗯,就这么简单。下面是我在项目中常用的模板代码:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
def __init__(self):
super().__init__('my_publisher')
self.publisher_ = self.create_publisher(String, 'topic_chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.get_logger().info('发布者节点已启动!')
def timer_callback(self):
msg = String()
msg.data = 'Hello ROS2, 这是第 %d 次发布' % self.count
self.publisher_.publish(msg)
self.get_logger().info('发布了: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
node = SimplePublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
这段代码我用了好几年,基本没大改过。你把它保存成 publisher_node.py,然后 python3 publisher_node.py 就能跑。不过记得先 source 一下ROS2的环境。
ModuleNotFoundError: No module named 'rclpy',别慌。八成是你忘了 source 环境。运行 source /opt/ros/humble/setup.bash 就好。我刚开始学的时候,这个错误卡了我半小时。
3.2 拆解rclpy初始化的秘密
rclpy.init() 这行代码,很多人觉得就是个“仪式感”。其实不然。它背后做了三件重要的事:
- 初始化通信底层:打开DDS(数据分发服务)的通道,就像给网络接口插上网线
- 解析命令行参数:比如
--ros-args --remap __name:=new_node这种重命名操作 - 设置全局上下文:为后续所有节点提供共享资源,比如日志系统、参数服务器
说白了,rclpy.init() 就是给ROS2运行时“通电”。没有它,后面的节点创建全是白搭。
rclpy.init() 之前不要创建任何节点对象。我见过有人把节点创建写在init前面,结果程序直接崩溃。顺序很重要。
3.3 节点创建:你的第一个ROS2“进程”
创建节点时,super().__init__('my_publisher') 里的字符串就是节点名。ROS2里节点名必须唯一,不能重复。如果你开了两个同名节点,第二个会启动失败。
节点内部,我习惯把初始化逻辑都放在 __init__ 里。比如创建发布器、定时器、订阅器。这样结构清晰,后期维护也方便。
这里有个细节:create_publisher 的第三个参数是队列大小。10表示缓冲区最多存10条消息。如果发布速度远大于订阅速度,消息会被丢弃。我在项目中遇到过,如果队列设得太小(比如1),高频发布时订阅端会丢消息。建议根据你的发布频率来调:
| 发布频率 | 推荐队列大小 | 说明 |
|---|---|---|
| 1 Hz(每秒1次) | 10 | 够用,丢消息概率低 |
| 10 Hz | 100 | 防止突发流量 |
| 100 Hz 以上 | 1000 或更大 | 注意内存占用 |
3.4 spin循环:为什么程序不会自己退出?
你可能会问:为什么最后要加个 rclpy.spin(node)?
其实很简单。ROS2节点是事件驱动的。它需要一直“监听”有没有新消息、定时器有没有触发、服务请求有没有到来。如果没有spin,程序执行完 main() 就直接退出了,节点就“死”了。
rclpy.spin() 本质上是一个无限循环,它会:
- 检查是否有待处理的回调函数(比如定时器回调、订阅回调)
- 如果有,就执行它
- 如果没有,就阻塞等待
嗯,你可以把它理解成ROS2的“心脏”。心脏不跳,节点就凉了。
rclpy.spin_once() 配合你自己的循环。我写过一个小工具,用 spin_once 实现了“按q退出”的功能,挺实用的。
3.5 知识体系总览
下面这张图,是我自己总结的发布者节点核心流程。你看一眼,基本就全明白了:
你看,整个流程就是一条直线:初始化 → 创建节点 → 创建发布器 → 创建定时器 → 进入spin循环。没有复杂的跳转,也没有隐藏的陷阱。
3.6 调试小技巧:如何确认节点在运行?
代码写完了,怎么知道它真的在跑?我教你三招:
- 看终端日志:如果看到
[INFO] [my_publisher]: 发布了: "Hello ROS2",说明节点正常 - 用命令行工具:打开另一个终端,运行
ros2 node list,如果看到/my_publisher,说明节点注册成功 - 查看话题:运行
ros2 topic echo /topic_chatter,应该能看到发布的消息
ros2 topic list 监控话题,一个跑 ros2 topic echo 看消息内容。这样任何异常都能第一时间发现。
好了,第一个ROS2节点就讲到这里。记住:先跑通,再理解。代码写出来能跑,比什么都重要。