ROS2节点话题服务通信开发 - 第3节
3. 第一个ROS2节点
3. 第一个ROS2节点:用Python写一个发布者
好,咱们开始动手写代码了。
前面聊了那么多概念,什么节点、话题、通信,其实说白了,ROS2里最核心的单元就是节点。你写一个节点,它就能干活。今天我们就来写第一个节点——一个简单的发布者。
3.1 先搭个架子:创建功能包
在写代码之前,得先有个“家”。ROS2里,代码都放在功能包里。我个人习惯用命令行创建,干净利落。
ros2 pkg create --build-type ament_python my_first_pkg
这条命令会生成一个叫 my_first_pkg 的Python功能包。里面有个 my_first_pkg 文件夹,我们的代码就放那。
3.2 写一个最简单的发布者
在 my_first_pkg/my_first_pkg/ 目录下,新建一个文件,叫 talker.py。
嗯,这里要注意:ROS2的节点,本质上就是一个Python类。我们继承 Node 类,然后重写它的初始化方法。
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class TalkerNode(Node):
def __init__(self):
super().__init__('talker')
self.publisher_ = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.get_logger().info('Talker节点已启动!')
def timer_callback(self):
msg = String()
msg.data = '你好,ROS2!'
self.publisher_.publish(msg)
self.get_logger().info(f'发布了:{msg.data}')
def main(args=None):
rclpy.init(args=args)
node = TalkerNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
代码不长,但每个部分都有讲究。我拆开来讲。
3.3 理解节点初始化
你看 __init__ 方法里,第一行是 super().__init__('talker')。这行干了什么?
说白了,就是给这个节点起个名字,叫 talker。ROS2里每个节点必须有唯一的名字。我在项目中遇到过,两个节点同名,结果后启动的那个直接报错退出了。所以名字别乱起。
接着是创建发布者:
self.publisher_ = self.create_publisher(String, 'chatter', 10)
这里三个参数:
- 消息类型:
String,来自std_msgs.msg。ROS2里消息类型很多,字符串是最基础的。 - 话题名:
chatter。订阅者也得用这个名字才能收到。 - 队列大小:
10。如果订阅者处理不过来,消息会暂存10条。再多就丢了。
然后是一个定时器:
self.timer = self.create_timer(1.0, self.timer_callback)
定时器每隔1秒调用一次 timer_callback。为什么用定时器?因为节点不能自己一直循环,得靠ROS2的事件循环来驱动。你想想看,如果自己写个 while True,那整个节点就卡死了,其他回调都进不来。
3.4 spin机制:让节点活起来
看 main 函数里最关键的一行:
rclpy.spin(node)
这个 spin 是什么意思?
我刚开始学的时候也困惑。其实 spin 就是“旋转等待”。它会让节点一直运行,监听各种事件——定时器到了、收到消息了、服务请求来了等等。一旦有事件,它就调用对应的回调函数。
没有 spin,节点就是个空壳,啥也不干。有了 spin,节点才真正“活”过来。
spin 是阻塞的。它后面的代码要等节点关闭才会执行。所以一般把 spin 放在最后。
我曾经犯过一个错:在 spin 后面又写了些逻辑,结果发现永远跑不到。后来才明白,spin 不结束,后面的代码就没机会。
3.5 运行看看效果
写完了怎么跑?先编译:
colcon build --packages-select my_first_pkg
source install/setup.bash
然后运行:
ros2 run my_first_pkg talker
你会看到终端每隔1秒打印一条信息:
[INFO] [talker]: Talker节点已启动!
[INFO] [talker]: 发布了:你好,ROS2!
[INFO] [talker]: 发布了:你好,ROS2!
...
如果想看话题数据,另开一个终端:
ros2 topic echo /chatter
你会看到同样的消息。这说明发布者正常工作。
3.6 核心逻辑流程图
下面这张图,把整个流程串起来了。你看一遍,应该就全明白了。
3.7 几个容易踩的坑
写这个节点的时候,有几个地方容易出问题。我列出来,你写的时候注意一下。
| 问题 | 表现 | 原因 | 解决 |
|---|---|---|---|
| 节点名重复 | 启动报错 | ROS2不允许同名节点 | 改个名字 |
忘记 spin |
节点一闪而过 | main函数直接结束了 | 加上 rclpy.spin() |
| 话题名拼错 | 订阅者收不到 | 大小写或符号不一致 | 仔细核对 |
| 队列太小 | 消息丢失 | 订阅者处理慢 | 调大队列 |
ros2 topic list 和 ros2 topic echo 看看话题有没有数据。这是最直接的排查方法。
好了,第一个节点就写完了。你把它跑起来,看到终端里不断打印消息,那种感觉还是挺有成就感的。记住,节点初始化就是搭骨架,spin就是让骨架动起来。这两个概念搞懂了,后面的内容就顺了。