第2章 ROS2基础与环境搭建

ROS2,说白了就是机器人开发的操作系统级框架。从ROS1迁移到ROS2,不只是版本号变了,底层通信、实时性、跨平台支持都做了大手术。我最早接触ROS2 Humble时,第一感觉是「这玩意儿比ROS1清爽多了」,但坑也不少。这一章咱们就把环境搭好,把Topic、Service、Action这三个通信机制玩明白。

ROS2 Humble 核心 Topic 通信 发布/订阅 · 异步 Service 通信 请求/响应 · 同步 Action 通信 目标/反馈/结果 工作空间 & 功能包

2.1 安装ROS2 Humble

我建议你直接用Ubuntu 22.04 LTS,搭配Humble Hawksbill,这是目前最稳定的组合。别问我为什么不用最新版——稳定压倒一切,做SLAM系统最怕底层库突然不兼容。

💡 我的习惯: 装系统时就把语言环境设成英文,避免终端出现中文乱码。locale问题曾经让我浪费了一下午。

打开终端,逐条执行以下命令。我习惯每执行一条就确认一下输出,别一股脑全复制。

# 1. 设置编码
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 $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

# 3. 安装ROS2 Humble 桌面版(推荐)
sudo apt update
sudo apt install ros-humble-desktop

# 4. 安装开发工具
sudo apt install ros-dev-tools python3-colcon-common-extensions
⚠️ 我曾经踩过的坑: 如果网络慢,换国内镜像源。但注意,ROS2的key必须从官方源获取,否则签名验证会失败。别问我怎么知道的——当年在客户现场折腾了俩小时。

安装完成后,记得把source加到.bashrc里,这样每次开终端就不用手动source了。

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc

验证一下:ros2 run demo_nodes_cpp talker,如果能听到「Hello World」循环输出,恭喜你,环境成了。

2.2 创建工作空间

工作空间就是你的项目根目录。我习惯叫ros2_ws,里面放src、build、install、log四个文件夹。colcon是ROS2的编译工具,比ROS1的catkin快不少。

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build

第一次build会生成build、install、log目录。记得source一下:

source ~/ros2_ws/install/setup.bash
🔧 实用技巧: 在.bashrc里同时source ROS2和你的工作空间,但要注意顺序——先source ROS2基础环境,再source工作空间,否则可能覆盖掉一些路径。

2.3 Topic通信实战

Topic是ROS2里最常用的通信方式,发布者只管发,订阅者只管收,双方不需要知道对方的存在。说白了就是「广播模式」。

咱们写一个简单的发布-订阅例子。先创建一个功能包:

cd ~/ros2_ws/src
ros2 pkg create topic_demo --build-type ament_python --dependencies rclpy std_msgs
cd topic_demo/topic_demo

创建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.pub = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello SLAM!'
        self.pub.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()

创建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.sub = self.create_subscription(String, 'chatter', self.callback, 10)

    def 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()

别忘了在setup.py里添加entry_points。然后编译运行:

cd ~/ros2_ws
colcon build --packages-select topic_demo
source install/setup.bash
ros2 run topic_demo publisher &
ros2 run topic_demo subscriber
📌 我的一点经验: 调试Topic时多用ros2 topic list和ros2 topic echo /chatter,比看代码日志快多了。

2.4 Service通信实战

Service是同步的请求-响应模式。比如你让机器人「给我当前位置」,它必须算完再回复你。我一般在需要确认结果的场景用Service,比如校准传感器。

创建service_demo包:

cd ~/ros2_ws/src
ros2 pkg create service_demo --build-type ament_python --dependencies rclpy example_interfaces
cd service_demo/service_demo

服务端service_server.py:

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class SimpleService(Node):
    def __init__(self):
        super().__init__('simple_service')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.callback)

    def 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 = SimpleService()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

客户端service_client.py:

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts

class SimpleClient(Node):
    def __init__(self):
        super().__init__('simple_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()
        self.req.a = 5
        self.req.b = 3
        self.future = self.cli.call_async(self.req)
        self.future.add_done_callback(self.callback)

    def callback(self, future):
        response = future.result()
        self.get_logger().info(f'结果: {response.sum}')

def main(args=None):
    rclpy.init(args=args)
    node = SimpleClient()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()
⚠️ 注意: 客户端一定要用call_async,别用阻塞的call,否则你的节点会卡死。我早期写ROS2程序时吃过这个亏,整个导航系统因为一个Service调用超时而瘫痪。

2.5 Action通信实战

Action是ROS2里最强大的通信模式,适合长时间任务。比如「导航到A点」,你可以实时收到进度反馈,还能中途取消。SLAM建图过程中的回环检测、地图优化,用Action来管理非常合适。

创建action_demo包:

cd ~/ros2_ws/src
ros2 pkg create action_demo --build-type ament_python --dependencies rclpy action_msgs
cd action_demo/action_demo

先定义Action接口(在action目录下创建Fibonacci.action):

int32 order
---
int32[] sequence
---
int32[] partial_sequence

服务端action_server.py(核心部分):

import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from action_demo.action import Fibonacci

class FibonacciActionServer(Node):
    def __init__(self):
        super().__init__('fibonacci_server')
        self._action_server = ActionServer(
            self, Fibonacci, 'fibonacci', self.execute_callback)

    async def execute_callback(self, goal_handle):
        self.get_logger().info(f'收到目标: order={goal_handle.request.order}')
        feedback_msg = Fibonacci.Feedback()
        sequence = [0, 1]
        for i in range(1, goal_handle.request.order):
            sequence.append(sequence[i] + sequence[i-1])
            feedback_msg.partial_sequence = sequence
            goal_handle.publish_feedback(feedback_msg)
        goal_handle.succeed()
        result = Fibonacci.Result()
        result.sequence = sequence
        return result

客户端action_client.py:

import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from action_demo.action import Fibonacci

class FibonacciActionClient(Node):
    def __init__(self):
        super().__init__('fibonacci_client')
        self._client = ActionClient(self, Fibonacci, 'fibonacci')
        while not self._client.wait_for_server(timeout_sec=1.0):
            self.get_logger().info('等待Action服务...')
        goal_msg = Fibonacci.Goal()
        goal_msg.order = 10
        self._send_goal_future = self._client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)
        self._send_goal_future.add_done_callback(self.goal_response_callback)

    def goal_response_callback(self, future):
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().info('目标被拒绝')
            return
        self.get_logger().info('目标被接受')
        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)

    def get_result_callback(self, future):
        result = future.result().result
        self.get_logger().info(f'最终序列: {result.sequence}')

    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        self.get_logger().info(f'当前序列: {feedback.partial_sequence}')
🚀 实战建议: 在SLAM系统中,我会把建图任务封装成Action。客户端发送「开始建图」目标,服务端实时反馈当前关键帧数量、回环检测状态,用户随时可以取消或暂停。这种模式比Topic+Service的组合优雅得多。

2.6 三种通信机制对比

特性TopicServiceAction
通信模式发布/订阅请求/响应目标/反馈/结果
同步/异步异步同步(可异步调用)异步
是否可取消否否是
适用场景传感器数据流、状态广播查询、配置、触发长时间任务、导航、建图
QoS支持是有限是

嗯,到这里ROS2的基础环境就搭好了,三种通信机制也实战了一遍。你想想看,Topic就像群聊,Service像打电话问事,Action像发快递——有单号、有物流跟踪、还能取消。搞懂了这些,后面做SLAM系统时,通信这块就不会再卡壳了。