ROS2基础与工作空间配置
各位同学好,我是你们的机器人工程师讲师。今天咱们来聊聊ROS2的基础概念和工作空间配置。说实话,ROS2刚出来那会儿,我也有点不适应——毕竟从ROS1过渡过来,很多习惯都得改。但用久了你会发现,ROS2的设计确实更合理,更适合工业级应用。
ROS2核心概念:节点、话题、服务、动作
先说说这几个核心概念。我当年带团队做第一个ROS2项目时,有个新人把节点和话题搞混了,结果调试了一整天。嗯,这里咱们得把基础打牢。
节点(Node)
节点就是ROS2中最基本的执行单元。你可以把它理解成一个独立的小程序,专门负责某一种功能。比如一个机械臂系统,可能有视觉识别节点、运动规划节点、关节控制节点等等。每个节点各司其职,互不干扰。
我个人习惯把节点想象成「车间里的工人」——每个工人只干自己那摊活,但通过某种方式互相配合。这样做的好处是,哪个节点出了问题,单独重启它就行,不影响整个系统。
话题(Topic)
话题是节点之间通信的「广播频道」。一个节点可以往话题上发消息,其他节点可以订阅这个话题来接收消息。这是一种异步通信方式,说白了就是「你发你的,我收我的,互不等候」。
我在项目中遇到过一个问题:某个传感器节点以100Hz的频率发布数据,但处理节点跟不上这个速度。怎么办?ROS2的话题机制天然支持这种场景——处理节点按自己的节奏读取最新数据,不会因为数据积压而崩溃。
服务(Service)
服务是一种请求-响应式的通信方式。客户端发送请求,服务端处理并返回结果。这就像你打电话问客服:「我的订单到哪了?」客服查完告诉你结果。整个过程是同步的,你得等着。
服务适合那些需要即时响应的操作,比如「打开夹爪」、「查询当前关节角度」。但要注意,如果服务端处理时间太长,客户端会一直阻塞——我曾经因为这个坑导致整个控制循环卡死,后来改用动作才解决。
动作(Action)
动作是ROS2里比较高级的通信机制。它结合了话题和服务的优点:你可以发送一个目标(像服务),同时还能实时接收反馈(像话题),并且可以随时取消任务。
你想想看,机械臂抓取一个物体,这个过程可能需要几秒钟。如果用服务,客户端得傻等;如果用话题,你又没法知道任务什么时候完成。动作就是为这种场景设计的——发送目标后,你可以一边接收进度反馈,一边做其他事情。
| 通信方式 | 特点 | 适用场景 |
|---|---|---|
| 话题 | 异步、单向、多对多 | 传感器数据、状态发布 |
| 服务 | 同步、请求-响应 | 查询、简单指令 |
| 动作 | 异步、可反馈、可取消 | 长时间任务、复杂控制 |
创建工作空间与功能包
好了,概念讲完了,咱们动手实践一下。工作空间就是存放ROS2项目的根目录。我建议你给每个项目单独建一个工作空间,别把所有东西都塞在一起——这是我踩过坑后的经验。
创建工作空间
# 创建工作空间目录
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 编译工作空间(第一次需要)
colcon build
执行完 colcon build 后,你会看到 build、install、log 三个文件夹。别慌,这是正常的。记得每次打开新终端都要 source 一下环境变量:
source ~/ros2_ws/install/setup.bash
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc 一键搞定。
创建功能包
功能包是ROS2代码的组织单元。一个功能包可以包含节点、配置文件、启动文件等。创建功能包用这个命令:
cd ~/ros2_ws/src
ros2 pkg create my_first_pkg --build-type ament_python --dependencies rclpy std_msgs
参数说明:
--build-type ament_python:用Python开发(也可以用ament_cmake做C++)--dependencies rclpy std_msgs:依赖的库,rclpy是ROS2的Python客户端库,std_msgs是标准消息类型
创建成功后,你会看到功能包目录下生成了 package.xml、setup.py、my_first_pkg 等文件。这些文件定义了包的元信息和构建规则。
编写第一个发布-订阅节点
终于到写代码环节了。咱们写一个最简单的发布-订阅程序:发布节点每隔1秒发一个「Hello ROS2」的消息,订阅节点收到后打印出来。
发布节点(publisher.py)
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MyPublisher(Node):
def __init__(self):
super().__init__('my_publisher')
self.publisher_ = self.create_publisher(String, 'topic_hello', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.count = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello ROS2: {self.count}'
self.publisher_.publish(msg)
self.get_logger().info(f'发布: {msg.data}')
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = MyPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
订阅节点(subscriber.py)
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MySubscriber(Node):
def __init__(self):
super().__init__('my_subscriber')
self.subscription = self.create_subscription(
String, 'topic_hello', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'收到: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = MySubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
topic_hello 必须完全一致,包括大小写。我曾经因为把话题名写成了 Topic_Hello,结果两个节点互相找不到对方,排查了半天才发现是大小写问题。
配置入口点
在 setup.py 文件中添加入口点,这样就能用 ros2 run 命令启动了:
entry_points={
'console_scripts': [
'publisher = my_first_pkg.publisher:main',
'subscriber = my_first_pkg.subscriber:main',
],
},
编译与运行
cd ~/ros2_ws
colcon build --packages-select my_first_pkg
source install/setup.bash
# 打开两个终端,分别运行
ros2 run my_first_pkg publisher
ros2 run my_first_pkg subscriber
看到终端里交替出现「发布: Hello ROS2: 0」「收到: Hello ROS2: 0」了吗?恭喜你,第一个ROS2节点跑起来了!
知识体系总览
下面这张图帮你梳理本章的核心逻辑:
说实话,第一次跑通发布-订阅程序时,那种成就感还是很棒的。虽然只是打印了几行字,但这背后是整个ROS2通信机制的运转。后面咱们做机械臂抓取时,这些基础会反复用到。
嗯,今天就到这里。记住:多动手、多调试,遇到问题别怕,ROS2的错误信息其实挺友好的。下次见!