2. ROS2基础与环境搭建
说实话,每次带新人入门ROS2,我都会想起自己当年被ROS1的“环境变量地狱”支配的恐惧。ROS2的出现,确实让很多坑被填平了。但话说回来,该踩的坑一个都不会少,只是换了个地方踩而已。
这一章,咱们就一起把ROS2的开发环境搭起来。我保证,跟着我的节奏走,你至少能少走一半弯路。
2.1 ROS2的安装与配置
ROS2的版本选择,我个人建议直接用Humble Hawksbill。为什么?因为它是LTS版本,支持到2027年。我在项目里吃过非LTS版本的亏——项目还没做完,官方就停止维护了,那叫一个尴尬。
安装前的准备
先确认你的Ubuntu版本。ROS2 Humble对应的是Ubuntu 22.04。如果你用的是20.04,那就选Galactic版本。别搞混了,否则依赖关系会让你怀疑人生。
安装步骤(以Humble为例)
# 1. 设置locale
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
# 2. 添加ROS2源
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# 3. 安装ROS2
sudo apt update
sudo apt upgrade
sudo apt install ros-humble-desktop
# 4. 安装开发工具
sudo apt install ros-dev-tools
安装完成后,记得配置环境变量。我个人习惯把它写到 ~/.bashrc 里,这样每次打开终端就不用重新source了。
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
2.2 工作空间创建
工作空间,说白了就是你放代码的地方。ROS2用的是colcon来编译,跟ROS1的catkin_make有点像,但更现代一些。
创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build
第一次编译会生成几个文件夹:build、install、log。其中install里放着编译好的可执行文件和库文件。每次编译完,记得source一下:
source install/setup.bash
你想想看,如果你忘了source,那系统就找不到你新写的节点。我刚开始用ROS2时,经常编译完发现节点跑不起来,debug了半天才发现是忘了source。嗯,这种低级错误,犯过一次就记住了。
2.3 发布-订阅模型
发布-订阅模型,是ROS2最核心的通信机制。说白了,就是一个节点发消息,另一个节点收消息,中间通过话题(Topic)来传递。
我画了一张图,帮你理解这个模型:
你看这张图,发布者只管往话题里发消息,订阅者只管从话题里收消息。两者完全解耦,互不知道对方的存在。这就是ROS2通信的优雅之处。
写一个简单的发布者
# talker.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker')
self.publisher = self.create_publisher(String, 'chatter', 10)
timer_period = 0.5 # 0.5秒发一次
self.timer = self.create_timer(timer_period, 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)
talker = Talker()
rclpy.spin(talker)
talker.destroy_node()
rclpy.shutdown()
写一个对应的订阅者
# listener.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Listener(Node):
def __init__(self):
super().__init__('listener')
self.subscription = self.create_subscription(
String, 'chatter', self.listener_callback, 10)
self.subscription # 防止被垃圾回收
def listener_callback(self, msg):
self.get_logger().info(f'收到了: {msg.data}')
def main(args=None):
rclpy.init(args=args)
listener = Listener()
rclpy.spin(listener)
listener.destroy_node()
rclpy.shutdown()
create_publisher的第三个参数是队列长度。10表示最多缓存10条消息。如果订阅者处理不过来,旧消息会被丢弃。rclpy.spin()会让节点一直运行,直到你按 Ctrl+C 停止。- 话题名称是字符串,可以任意命名。但建议用有意义的名称,比如
/joint_states、/cmd_vel。
2.4 话题与服务的基本使用
话题是“发布-订阅”模式,适合持续性的数据流。比如传感器数据、机器人状态。但有些场景需要“请求-响应”模式,比如让机器人抓个东西、查询当前位姿。这时候就该服务(Service)上场了。
话题 vs 服务
| 特性 | 话题 (Topic) | 服务 (Service) |
|---|---|---|
| 通信模式 | 发布-订阅(异步) | 请求-响应(同步) |
| 数据流向 | 单向 | 双向 |
| 适用场景 | 传感器数据、状态信息 | 指令下发、参数查询 |
| 是否阻塞 | 不阻塞 | 客户端会阻塞等待响应 |
| 典型例子 | /camera/image_raw | /gripper/close |
服务的定义
服务需要定义请求和响应的数据结构。比如我们定义一个加法服务:
# AddTwoInts.srv
int64 a
int64 b
---
int64 sum
三个横线上面是请求,下面是响应。这个文件放在 srv/ 目录下。
服务端实现
# server.py
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class Server(Node):
def __init__(self):
super().__init__('server')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
def add_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(f'收到请求: {request.a} + {request.b} = {response.sum}')
return response
def main(args=None):
rclpy.init(args=args)
server = Server()
rclpy.spin(server)
server.destroy_node()
rclpy.shutdown()
客户端实现
# client.py
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class Client(Node):
def __init__(self):
super().__init__('client')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('等待服务上线...')
self.req = AddTwoInts.Request()
def send_request(self, a, b):
self.req.a = a
self.req.b = b
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
def main(args=None):
rclpy.init(args=args)
client = Client()
response = client.send_request(3, 5)
client.get_logger().info(f'结果: 3 + 5 = {response.sum}')
client.destroy_node()
rclpy.shutdown()
2.5 编译与运行
代码写好了,怎么跑起来?用colcon编译,然后用ros2 run运行。
# 编译
cd ~/ros2_ws
colcon build --packages-select your_package_name
# 运行发布者
source install/setup.bash
ros2 run your_package_name talker
# 另一个终端运行订阅者
source install/setup.bash
ros2 run your_package_name listener
你可以在终端里看到发布者每隔0.5秒发一条消息,订阅者实时收到。这就是ROS2最基本的通信流程。
好了,ROS2的基础环境搭建和通信机制就讲到这里。这些内容看起来简单,但它们是后续所有复杂功能的基础。你想想看,如果连消息都发不出去,那避障、路径规划什么的就都无从谈起了。
下一章,我们会开始搭建机械臂的仿真环境。到时候,这些话题和服务就会真正派上用场了。
可可精品资料库