第2章 ROS2基础与环境搭建
ROS2,说白了就是机器人开发的操作系统级框架。从ROS1迁移到ROS2,不只是版本号变了,底层通信、实时性、跨平台支持都做了大手术。我最早接触ROS2 Humble时,第一感觉是「这玩意儿比ROS1清爽多了」,但坑也不少。这一章咱们就把环境搭好,把Topic、Service、Action这三个通信机制玩明白。
2.1 安装ROS2 Humble
我建议你直接用Ubuntu 22.04 LTS,搭配Humble Hawksbill,这是目前最稳定的组合。别问我为什么不用最新版——稳定压倒一切,做SLAM系统最怕底层库突然不兼容。
打开终端,逐条执行以下命令。我习惯每执行一条就确认一下输出,别一股脑全复制。
# 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
安装完成后,记得把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
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
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}')
2.6 三种通信机制对比
| 特性 | Topic | Service | Action |
|---|---|---|---|
| 通信模式 | 发布/订阅 | 请求/响应 | 目标/反馈/结果 |
| 同步/异步 | 异步 | 同步(可异步调用) | 异步 |
| 是否可取消 | 否 | 否 | 是 |
| 适用场景 | 传感器数据流、状态广播 | 查询、配置、触发 | 长时间任务、导航、建图 |
| QoS支持 | 是 | 有限 | 是 |
嗯,到这里ROS2的基础环境就搭好了,三种通信机制也实战了一遍。你想想看,Topic就像群聊,Service像打电话问事,Action像发快递——有单号、有物流跟踪、还能取消。搞懂了这些,后面做SLAM系统时,通信这块就不会再卡壳了。