2、ROS2基础与环境搭建:ROS2安装、工作空间创建、节点与话题通信、服务与动作通信
各位同学,欢迎来到《AMR自主导航系统搭建实战》的第二讲。
上一章我们聊了AMR的整体架构。今天,咱们得先把家伙事儿备齐——把ROS2环境搭起来。ROS2是AMR的“操作系统”,所有传感器、算法、执行器都靠它串起来。我见过太多新手在环境配置上卡壳,一卡就是半天。别急,跟着我的节奏来,半小时搞定。
2.1 ROS2的安装与配置
ROS2目前主流版本是Humble Hawksbill(适配Ubuntu 22.04)。我个人习惯用这个版本,稳定,坑少。
核心原则:ROS2的安装其实就三步——设置源、安装、配置环境。别被网上那些长篇教程吓到。
2.1.1 安装步骤
- 设置软件源
sudo apt update && sudo apt install curl gnupg lsb-release 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
sudo apt update sudo apt install ros-humble-desktop这里我建议装
ros-humble-desktop,包含了常用的工具和库。如果你空间紧张,可以装ros-humble-ros-base,但后面做导航仿真可能会缺东西。 - 配置环境变量
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc嗯,这一步很多人会忘。每次打开新终端都要手动source,很烦。写到bashrc里一劳永逸。
避坑指南:我曾经在Ubuntu 20.04上装ROS2 Foxy,结果因为Python版本冲突折腾了两小时。后来我学乖了——装系统前先查好ROS2版本和Ubuntu版本的对应关系。Humble配22.04,Iron配23.04,别混搭。
2.1.2 验证安装
装完别急着走。跑个简单命令验证一下:
ros2 run demo_nodes_cpp talker
再开一个新终端:
ros2 run demo_nodes_py listener
如果看到talker在发“Hello World”,listener在收,那就成了。说白了,这就是ROS2最基础的节点通信——一个发,一个收。
2.2 工作空间创建
工作空间(Workspace)是ROS2项目的根目录。你写的所有代码、配置文件都放在里面。我习惯用colcon来构建,它是ROS2的官方构建工具。
2.2.1 创建工作空间
mkdir -p ~/amr_ws/src
cd ~/amr_ws
colcon build
这里amr_ws是我给这个课程项目起的名字。你可以改成自己的。建好之后,你会看到build、install、log三个文件夹。别手贱去删它们,那是colcon的工作目录。
个人经验:每次build之前,记得先source一下工作空间的环境:source install/setup.bash。不然你新写的节点可能找不到。我刚开始学的时候,经常忘了这步,然后一脸懵逼地查半天bug。
2.2.2 工作空间结构
一个典型的ROS2工作空间长这样:
amr_ws/
├── src/ # 源代码目录
│ ├── pkg1/ # 功能包1
│ ├── pkg2/ # 功能包2
│ └── ...
├── build/ # 编译中间文件
├── install/ # 安装后的文件
└── log/ # 编译日志
你只需要关心src目录。其他都是自动生成的。
2.3 节点与话题通信
节点(Node)是ROS2的最小执行单元。一个节点可以是一个传感器驱动、一个算法模块、或者一个控制指令。节点之间通过话题(Topic)通信——说白了,就是发布-订阅模式。
2.3.1 创建第一个节点
咱们用Python写一个简单的发布节点:
# talker.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class TalkerNode(Node):
def __init__(self):
super().__init__('talker')
self.publisher = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello, AMR! {self.i}'
self.publisher.publish(msg)
self.get_logger().info(f'Publishing: {msg.data}')
self.i += 1
def main(args=None):
rclpy.init(args=args)
node = TalkerNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
再写一个订阅节点:
# listener.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class ListenerNode(Node):
def __init__(self):
super().__init__('listener')
self.subscription = self.create_subscription(String, 'chatter', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'Received: {msg.data}')
def main(args=None):
rclpy.init(args=args)
node = ListenerNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
关键点:话题通信是异步的。发布者只管发,不管有没有人收。订阅者只管收,不管谁发的。这种松耦合设计,让AMR系统可以灵活地增减模块。
2.3.2 运行节点
把上面两个文件放到amr_ws/src下的一个功能包里,然后:
colcon build
source install/setup.bash
ros2 run your_package talker
ros2 run your_package listener
你会看到talker每秒发一条消息,listener每秒收一条。这就是最基础的节点通信。
2.4 服务与动作通信
话题通信是“发完就不管了”。但有些场景需要“一问一答”——比如让机器人去某个点,你得知道它到底到没到。这时候就需要服务(Service)和动作(Action)通信。
2.4.1 服务通信
服务是同步的。客户端发请求,服务端处理完返回响应。我举个例子——写一个加法服务:
# add_two_ints_server.py
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class AddTwoIntsServer(Node):
def __init__(self):
super().__init__('add_two_ints_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'Incoming request: a={request.a}, b={request.b}')
return response
客户端:
# add_two_ints_client.py
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class AddTwoIntsClient(Node):
def __init__(self):
super().__init__('add_two_ints_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('Service not available, waiting...')
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()
避坑指南:我曾经在写服务客户端时,忘了加wait_for_service。结果服务端还没启动,客户端就发请求了,直接崩溃。记住——客户端一定要等服务端就绪。
2.4.2 动作通信
动作是服务的升级版。它支持“取消”和“反馈”。比如让机器人导航到某个点——你可以实时看到进度(反馈),也可以中途取消任务。
动作的定义需要三个部分:目标(Goal)、反馈(Feedback)、结果(Result)。
# 动作定义文件:navigation.action
# 目标
geometry_msgs/PoseStamped target_pose
---
# 结果
bool success
string message
---
# 反馈
float32 progress
string status
动作服务器和客户端的代码比服务复杂一些,但核心逻辑是一样的。我建议你先掌握服务通信,动作通信后面做导航时再深入。
2.5 本章知识体系
为了让你更直观地理解ROS2的通信机制,我画了一张图:
这张图把ROS2的三种通信方式都串起来了。你想想看——话题就像广播,服务就像打电话,动作就像发快递(能查物流、能取消)。搞懂这三种,ROS2通信你就入门了。
2.6 本章小结
这一章我们干了三件事:装了ROS2、建了工作空间、写了三种通信方式的代码。嗯,内容不少,但都是基本功。我建议你亲手敲一遍代码,别复制粘贴。敲的过程中你会遇到各种小问题——比如缩进不对、包名写错——这些都是学习的一部分。
下一章我们会开始搭建AMR的传感器驱动。到时候你会看到,今天学的这些通信机制,会像积木一样被拼起来。
课后练习:
- 写一个发布节点,每隔0.5秒发布一次当前系统时间
- 写一个订阅节点,接收时间并打印出来
- 写一个服务,输入两个浮点数,返回它们的乘积
做完这些,ROS2通信你就彻底拿下了。