ROS2基础与环境搭建:ROS2安装、工作空间创建、话题与服务通信机制入门

说实话,每次带新人入门ROS2,我都能看到他们被环境搭建这一关卡住。我自己当年也踩过不少坑——装到一半发现源不对,编译时缺依赖,跑demo时节点死活连不上。嗯,这一章咱们就把这些坑提前填上。

ROS2跟ROS1最大的区别,说白了就是去中心化了。没有master节点,节点之间直接通信。你想想看,这对机械臂这种实时性要求高的系统有多重要?一个节点挂了,整个系统不至于瘫痪。

2.1 ROS2的安装与配置

我个人习惯用Humble版本,稳定,社区活跃,文档也全。咱们就以Ubuntu 22.04 + ROS2 Humble为例。

版本选择建议: 如果你用的是Ubuntu 20.04,选Foxy;Ubuntu 22.04选Humble;Ubuntu 24.04选Iron。别混着来,我见过有人Ubuntu 22.04硬装Foxy,结果一堆依赖冲突。

2.1.1 安装步骤

  1. 设置locale:确保系统支持UTF-8
  2. 添加ROS2源:用官方源或镜像源
  3. 安装基础包:ros-humble-desktop
  4. 安装开发工具:colcon、rosdep等
  5. 配置环境变量: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的devel
  • log/:编译日志,出问题时翻翻这里

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 本章知识体系

下面这张图把本章的核心内容串起来了。我每次给团队新人培训时都会画一遍,你一看就明白。

ROS2基础环境与通信机制 ROS2安装 Ubuntu 22.04 + Humble 工作空间 colcon build 环境配置 setup.bash + rosdep 话题通信 发布者 → 话题 → 订阅者 一对多,实时数据流 服务通信 客户端 → 服务端 → 响应 一对一,请求-响应 机械臂联调实战 话题发布关节状态 · 服务控制夹爪 · 导航地图构建 环境搭建是基础,话题和服务是核心工具,最终都服务于机械臂的联调实战

这张图把本章内容分成了三层:底层是环境搭建,中间是通信机制,顶层是应用目标。你先把环境搭好,再掌握话题和服务,后面做机械臂联调就水到渠成了。

我的建议: 别急着往下看,先把本章的代码跑一遍。发布者和订阅者跑通了,服务端和客户端跑通了,你对ROS2的理解就上了一个台阶。我在带项目时,新人只要能独立跑通这两个示例,后面学起来就快多了。