【ROS2】从零开始构建你的第一个ROS2节点:基于RCLPY的实战指南
1. ROS2节点基础概念
第一次接触ROS2的朋友可能会被"节点"这个概念搞懵。简单来说,节点就是ROS2系统中的基本功能单元,就像人体内的器官一样各司其职。举个例子,假设我们要做一个自动巡逻的小车,可能需要这些节点:
- 一个节点负责读取摄像头数据
- 一个节点处理图像识别
- 一个节点控制电机转动
- 一个节点规划巡逻路线
每个节点都专注于做好自己的事情,节点之间通过四种方式通信:
- 话题(Topics):单向数据流,像广播电台
- 服务(Services):双向请求-响应,像打电话
- 动作(Actions):长时间执行的任务,像点外卖
- 参数(Parameters):配置项,像系统设置
在终端里,我们可以用这些命令管理节点:
# 查看所有运行中的节点 ros2 node list # 查看某个节点的详细信息 ros2 node info /node_name # 运行一个节点 ros2 run package_name executable_name2. 搭建开发环境
2.1 创建工作空间
工作空间就像你的项目文件夹,建议按这个结构创建:
mkdir -p ~/ros2_ws/src cd ~/ros2_ws这个结构清晰明了:
src/:存放所有功能包源码build/:编译中间文件(自动生成)install/:安装目录(自动生成)log/:日志文件(自动生成)
2.2 安装Colcon构建工具
Colcon是ROS2的构建系统,相当于ROS1中的catkin。安装很简单:
sudo apt install python3-colcon-common-extensions验证安装成功:
colcon --help3. 创建Python功能包
3.1 初始化功能包
进入src目录创建Python包:
cd ~/ros2_ws/src ros2 pkg create my_first_pkg --build-type ament_python --dependencies rclpy参数说明:
--build-type ament_python:指定Python包--dependencies rclpy:添加ROS2 Python客户端依赖
创建完成后会生成如下结构:
my_first_pkg/ ├── my_first_pkg/ │ ├── __init__.py │ └── (你的Python代码放这里) ├── package.xml ├── resource/ └── setup.py3.2 编写第一个节点
在my_first_pkg/my_first_pkg/下创建first_node.py:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node class MyFirstNode(Node): def __init__(self): super().__init__('my_first_node') # 节点名 self.get_logger().info("Hello ROS2!") def main(args=None): rclpy.init(args=args) # 初始化ROS2 node = MyFirstNode() # 创建节点 rclpy.spin(node) # 保持节点运行 rclpy.shutdown() # 关闭ROS2 if __name__ == '__main__': main()代码解析:
- 导入rclpy库和Node基类
- 创建继承自Node的自定义节点类
- 在
__init__中初始化节点名称 main()函数是标准入口:- 初始化ROS2
- 实例化节点
- 保持节点运行
- 最后关闭ROS2
3.3 配置包信息
修改setup.py添加节点入口:
entry_points={ 'console_scripts': [ 'first_node = my_first_pkg.first_node:main', ], },这行配置告诉ROS2:
first_node:可执行命令名my_first_pkg.first_node:包名.模块名:main:入口函数
4. 编译与运行
4.1 编译功能包
回到工作空间根目录:
cd ~/ros2_ws colcon build --packages-select my_first_pkg常用编译选项:
--symlink-install:创建符号链接,修改代码无需重新编译--packages-select:只编译指定包--cmake-args:传递CMake参数
4.2 运行节点
先加载环境:
source install/setup.bash然后运行节点:
ros2 run my_first_pkg first_node你应该会看到终端输出:
[INFO] [my_first_node]: Hello ROS2!5. 调试与进阶技巧
5.1 常见问题排查
问题1:找不到包或节点
- 确保正确执行了
source install/setup.bash - 检查
setup.py中的entry_points配置是否正确
问题2:Python脚本没有执行权限
chmod +x my_first_pkg/my_first_pkg/first_node.py问题3:依赖缺失 检查package.xml确保已声明所有依赖:
<depend>rclpy</depend>5.2 日志输出技巧
ROS2节点内置了日志系统:
self.get_logger().debug("调试信息") # 默认不显示 self.get_logger().info("常规信息") # 绿色 self.get_logger().warn("警告信息") # 黄色 self.get_logger().error("错误信息") # 红色 self.get_logger().fatal("严重错误") # 紫色设置日志级别:
ros2 run my_first_pkg first_node --ros-args --log-level debug5.3 参数传递
启动时传递参数:
# 在节点类中声明参数 self.declare_parameter('my_param', 'default_value') # 获取参数值 param_value = self.get_parameter('my_param').value命令行传参:
ros2 run my_first_pkg first_node --ros-args -p my_param:=custom_value6. 扩展应用实例
6.1 创建一个发布者节点
修改first_node.py添加发布者功能:
from std_msgs.msg import String import time class MyFirstNode(Node): def __init__(self): super().__init__('my_first_node') self.publisher = self.create_publisher(String, 'greeting', 10) self.timer = self.create_timer(1.0, self.timer_callback) self.counter = 0 def timer_callback(self): msg = String() msg.data = f"Hello {self.counter} times!" self.publisher.publish(msg) self.get_logger().info(f"Published: {msg.data}") self.counter += 1这个节点会:
- 创建一个String类型的发布者
- 每秒触发一次定时器回调
- 在回调中发布消息并计数
6.2 创建一个订阅者节点
新建subscriber_node.py:
from std_msgs.msg import String class MySubscriber(Node): def __init__(self): super().__init__('my_subscriber') self.subscription = self.create_subscription( String, 'greeting', self.listener_callback, 10) def listener_callback(self, msg): self.get_logger().info(f"I heard: '{msg.data}'")别忘了在setup.py中添加新的入口点:
entry_points={ 'console_scripts': [ 'first_node = my_first_pkg.first_node:main', 'subscriber_node = my_first_pkg.subscriber_node:main', ], },6.3 测试通信
重新编译后,开三个终端分别运行:
# 终端1:运行发布者 ros2 run my_first_pkg first_node # 终端2:运行订阅者 ros2 run my_first_pkg subscriber_node # 终端3:查看话题列表 ros2 topic list你应该能看到订阅者终端不断打印接收到的消息,这就是ROS2节点间最基本的通信过程。
