4. 第一个ROS2节点:用Python写一个“Hello World”节点,理解节点生命周期
好,咱们开始动手写代码了。
说实话,我当年学ROS1的时候,第一个节点也是“Hello World”。那时候踩了不少坑,比如节点跑起来却看不到输出,或者节点一启动就自己退出了。后来才明白,这些问题的根源,都是对节点生命周期理解不到位。
今天咱们就用Python写一个最简的ROS2节点。你跟着我走一遍,保证能跑起来。
4.1 先搭个架子:创建功能包
在ROS2里,节点不是孤零零的。它得待在一个功能包里。就像你写Python代码,得先建个文件夹一样。
打开终端,执行这几条命令:
cd ~/ros2_ws/src
ros2 pkg create hello_pkg --build-type ament_python --dependencies rclpy
解释一下:
hello_pkg是功能包的名字,你可以随便起--build-type ament_python告诉系统这是Python包--dependencies rclpy引入ROS2的Python客户端库
命令跑完后,你会看到 hello_pkg 文件夹里自动生成了不少文件。别慌,咱们只关心 hello_pkg/hello_pkg/ 这个目录,里面有个 __init__.py 文件。待会儿咱们就在这旁边新建一个Python文件。
4.2 写一个最简节点:Hello World
在 hello_pkg/hello_pkg/ 目录下,新建一个文件,叫 hello_node.py。
然后敲入以下代码:
import rclpy
from rclpy.node import Node
class HelloNode(Node):
def __init__(self):
super().__init__('hello_node')
self.get_logger().info('Hello, ROS2! 节点已启动')
def main(args=None):
rclpy.init(args=args)
node = HelloNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
代码不长,但每一行都有讲究。咱们拆开来看:
- 导入库:
import rclpy和from rclpy.node import Node。这是必须的,没有它们,ROS2就认不出你的节点。 - 定义类:
class HelloNode(Node)。你的节点本质上是一个类,继承自Node。所有ROS2的功能,比如发布、订阅、日志,都从这个基类来。 - 构造函数:
__init__里调用了super().__init__('hello_node')。这个'hello_node'就是节点的名字。注意,名字不能重复,否则会冲突。 - 日志输出:
self.get_logger().info(...)。这是ROS2的日志系统,比print强多了。它会自动加上时间戳、节点名等信息。 - 主函数:
main()里做了四件事:初始化、创建节点、挂起、清理。顺序不能乱。
rclpy.spin(node) 这行是关键。它让节点一直活着,等待事件发生。没有它,节点执行完 __init__ 就退出了,你啥也看不到。
4.3 配置入口点
代码写好了,但ROS2还不知道怎么启动它。你得告诉系统:这个节点的主函数在哪里。
打开 hello_pkg/setup.py 文件,找到 entry_points 部分,改成这样:
entry_points={
'console_scripts': [
'hello_node = hello_pkg.hello_node:main',
],
},
格式是固定的:'命令名 = 包名.文件名:函数名'。这里 hello_node 是你在终端里要敲的命令名,后面是路径。
hello_pkg.hello_node,不是 hello_pkg/hello_node。Python里用点号,不是斜杠。写错了编译能过,但运行时会报模块找不到。
4.4 编译并运行
回到工作空间根目录,编译:
cd ~/ros2_ws
colcon build --packages-select hello_pkg
编译成功后,记得 source 一下:
source install/setup.bash
然后运行节点:
ros2 run hello_pkg hello_node
如果一切顺利,你会看到终端里打印出:
[INFO] [1700000000.123456789] [hello_node]: Hello, ROS2! 节点已启动
看到这行输出,恭喜你,第一个ROS2节点跑起来了!
4.5 理解节点生命周期
节点不是一直傻傻地活着。它有明确的生命周期。咱们刚才的代码,其实展示了四个阶段:
| 阶段 | 对应代码 | 说明 |
|---|---|---|
| 初始化 | rclpy.init() |
启动ROS2通信层,分配全局资源 |
| 创建 | HelloNode() |
实例化节点,注册到ROS2网络中 |
| 旋转 | rclpy.spin() |
进入事件循环,等待并处理消息 |
| 销毁 | destroy_node() + shutdown() |
释放资源,优雅退出 |
你想想看,为什么要有 spin 这一步?
说白了,ROS2节点本质上是事件驱动的。它得一直监听有没有新消息、有没有定时器触发。如果没有 spin,节点就像个一次性程序,跑完就没了。而 spin 就是那个让节点“活”起来的循环。
spin。结果节点启动后,只打印了一行日志就退出了。我查了半天,最后发现是 spin 没写。嗯,从那以后,我写节点第一件事就是检查 spin 在不在。
4.6 验证节点是否在运行
节点跑起来后,你可以打开另一个终端,用ROS2的命令行工具看看它:
ros2 node list
你会看到输出:
/hello_node
这就是你的节点。前面有个斜杠,表示它是全局唯一的。
还可以查看节点信息:
ros2 node info /hello_node
它会告诉你这个节点有哪些发布者、订阅者、服务等。目前咱们的节点啥都没有,所以列表是空的。别急,后面几章咱们会慢慢给它加功能。
4.7 小结
今天咱们干了三件事:
- 创建了一个Python功能包
- 写了一个能打印“Hello World”的节点
- 理解了节点的四个生命周期阶段
这个节点虽然简单,但它是你ROS2之旅的起点。后面所有的发布、订阅、服务、动作,都建立在这个基础之上。
下一章,咱们会在这个节点的基础上,加上发布者和订阅者,让两个节点真正“聊”起来。到时候你会发现,ROS2的通信机制其实没那么神秘。
HelloNode 的构造函数,让它在启动时打印当前时间。提示:可以用 self.get_clock().now() 获取ROS2时间。