ROS2基础与环境搭建:ROS2安装、工作空间创建、话题与服务通信机制入门
说实话,每次带新人入门ROS2,我都能看到他们被环境搭建这一关卡住。我自己当年也踩过不少坑——装到一半发现源不对,编译时缺依赖,跑demo时节点死活连不上。嗯,这一章咱们就把这些坑提前填上。
ROS2跟ROS1最大的区别,说白了就是去中心化了。没有master节点,节点之间直接通信。你想想看,这对机械臂这种实时性要求高的系统有多重要?一个节点挂了,整个系统不至于瘫痪。
2.1 ROS2的安装与配置
我个人习惯用Humble版本,稳定,社区活跃,文档也全。咱们就以Ubuntu 22.04 + ROS2 Humble为例。
2.1.1 安装步骤
- 设置locale:确保系统支持UTF-8
- 添加ROS2源:用官方源或镜像源
- 安装基础包:ros-humble-desktop
- 安装开发工具:colcon、rosdep等
- 配置环境变量:source /opt/ros/humble/setup.bash
# 设置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
# 添加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 $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# 安装ROS2 Humble桌面版
sudo apt update
sudo apt install ros-humble-desktop
# 安装开发工具
sudo apt install python3-colcon-common-extensions python3-rosdep
# 配置环境变量(建议加到~/.bashrc里)
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
rosdep init && rosdep update,然后 rosdep install --from-paths src --ignore-src -r -y。我曾经在实验室配了三天环境,最后发现只是少装了一个python3-vcstool。
2.2 工作空间创建与编译
ROS2的工作空间结构跟ROS1差不多,但编译工具换成了colcon。我个人觉得colcon比catkin好用多了——输出更清晰,增量编译更快。
2.2.1 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build
第一次编译会生成三个目录:build、install、log。你想想看,这比ROS1的devel目录清晰多了吧?
src/:存放你的代码包build/:编译中间文件install/:编译产物,相当于ROS1的devellog/:编译日志,出问题时翻翻这里
2.2.2 常用编译命令
| 命令 | 作用 | 我常用的场景 |
|---|---|---|
colcon build |
全量编译 | 第一次编译或改了很多包 |
colcon build --packages-select 包名 |
只编译指定包 | 只改了一个包时,省时间 |
colcon build --symlink-install |
符号链接模式 | 开发阶段,改Python代码不用重新编译 |
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug |
Debug模式 | 调试C++节点时用 |
--symlink-install,这样改Python脚本后直接运行,不用等编译。但C++代码改了还是得重新编译。
2.3 话题通信机制入门
话题(Topic)是ROS2里最基础的通信方式。一个节点发布消息,其他节点订阅。说白了就是「发布-订阅」模式。
我在做机械臂控制时,关节状态数据就是通过话题发布的。每个关节一个话题,控制器订阅所有关节话题,实时获取位置和速度。
2.3.1 话题通信的核心概念
- 发布者(Publisher):发送消息的节点
- 订阅者(Subscriber):接收消息的节点
- 话题名(Topic Name):消息的标识,比如
/joint_states - 消息类型(Message Type):数据结构,比如
sensor_msgs/JointState
2.3.2 一个简单的话题示例
咱们写一个发布者节点,每隔1秒发一个字符串消息。
# publisher.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
def __init__(self):
super().__init__('simple_publisher')
self.publisher_ = self.create_publisher(String, 'hello_topic', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.get_logger().info('发布者已启动')
def timer_callback(self):
msg = String()
msg.data = 'Hello, ROS2!'
self.publisher_.publish(msg)
self.get_logger().info(f'发布了: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = SimplePublisher()
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 SimpleSubscriber(Node):
def __init__(self):
super().__init__('simple_subscriber')
self.subscription = self.create_subscription(
String,
'hello_topic',
self.listener_callback,
10)
self.get_logger().info('订阅者已启动')
def listener_callback(self, msg):
self.get_logger().info(f'收到了: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = SimpleSubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
2.4 服务通信机制入门
话题是单向的,服务是双向的。客户端发请求,服务端回响应。你想想看,机械臂的「移动到目标位置」这种操作,用服务就比话题合适——你发一个目标位置,服务端执行完告诉你「到位了」或「失败了」。
2.4.1 服务通信的核心概念
- 服务端(Service Server):提供服务的节点
- 客户端(Service Client):调用服务的节点
- 服务名(Service Name):比如
/move_to_target - 服务类型(Service Type):定义请求和响应的数据结构
2.4.2 一个简单的服务示例
咱们写一个加法服务:客户端发两个整数,服务端返回它们的和。
# 定义服务类型 AddTwoInts.srv
int64 a
int64 b
---
int64 sum
# service_server.py
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddService(Node):
def __init__(self):
super().__init__('add_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_callback)
self.get_logger().info('服务端已启动')
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)
node = AddService()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
# service_client.py
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddClient(Node):
def __init__(self):
super().__init__('add_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.get_logger().info('客户端已启动')
def send_request(self, a, b):
req = AddTwoInts.Request()
req.a = a
req.b = b
future = self.cli.call_async(req)
rclpy.spin_until_future_complete(self, future)
return future.result()
def main(args=None):
rclpy.init(args=args)
node = AddClient()
response = node.send_request(3, 5)
node.get_logger().info(f'结果: 3 + 5 = {response.sum}')
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
wait_for_service 才解决。
2.5 话题与服务的选择策略
很多新手会问:什么时候用话题,什么时候用服务?我总结了一个简单的判断方法:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 持续发送数据(如传感器数据) | 话题 | 实时性高,支持多订阅者 |
| 请求-响应模式(如执行指令) | 服务 | 需要确认结果,有超时机制 |
| 需要多个节点同时接收 | 话题 | 一对多通信,服务是一对一 |
| 需要等待执行结果 | 服务 | 同步调用,返回响应 |
你想想看,机械臂的关节状态数据用话题发布,因为多个控制器都需要;而「夹爪夹紧」这种指令用服务,因为你要确认夹紧了才能进行下一步操作。
2.6 本章知识体系
下面这张图把本章的核心内容串起来了。我每次给团队新人培训时都会画一遍,你一看就明白。
这张图把本章内容分成了三层:底层是环境搭建,中间是通信机制,顶层是应用目标。你先把环境搭好,再掌握话题和服务,后面做机械臂联调就水到渠成了。