ROS2 Humble 完全实战手册:从环境搭建到自主导航 🚀
ROS2 Humble 完全实战手册:从环境搭建到自主导航 🚀
一份面向机器人开发者的 ROS2 Humble 全栈指南,涵盖核心概念、节点通信、Gazebo 仿真和 Nav2 导航——读完就能上手搭建你的第一个智能机器人项目。
目录
- 为什么选择 ROS2 Humble
- ROS2 核心架构解密
- 环境搭建与第一个节点
- 话题、服务与动作——三大通信范式
- Gazebo 仿真实战
- Nav2 自主导航从零到跑通
- 常见问题与排错指南
- 总结与学习路径
一、为什么选择 ROS2 Humble
1.1 ROS1 的遗产与 ROS2 的诞生
2007 年,Willow Garage 发布了 ROS(Robot Operating System),这个"机器人领域的 Linux"迅速成为学术研究和工业原型的事实标准。但 ROS1 诞生于云计算和 5G 尚未普及的年代,它在设计上存在几个天生的短板:
- 单点故障:依赖中心化
roscore,master 挂了整个系统就瘫痪 - 非实时:基于 TCP 的通信无法满足硬实时场景
- 安全性弱:没有原生的加密和认证机制
- 跨平台困难:深度绑定 Ubuntu,Windows/macOS 支持惨淡
ROS2 从 2017 年 Ardent 版本起步,历经 Crystal、Dashing、Eloquent、Foxy、Galactic 多轮迭代,最终在 2022 年 5 月发布的 Humble Hawksbill 上达到了成熟稳定的里程碑。Humble 是 ROS2 第二个 LTS(长期支持)版本(首个为 Foxy Fitzroy 2020),支持周期到 2027 年 5 月——这意味你现在投入的学习,至少未来四年都不会过时。
1.2 Humble 带来了什么
| 特性 | ROS1 Noetic | ROS2 Humble |
|---|---|---|
| 通信中间件 | TCPROS/UDPROS | DDS(默认 Fast-DDS) |
| 架构模式 | 中心化 Master | 去中心化 Discovery |
| 实时性 | ❌ 不保证 | ✅ 支持 RTPS 实时配置 |
| 安全性 | ❌ | ✅ DDS-Security 加密 |
| 跨平台 | Ubuntu only | ✅ Ubuntu / Windows / macOS |
| Python 版本 | Python 2/3 混用 | Python 3.10+ |
| 组网能力 | 单机为主 | ✅ 原生多机分布式 |
| 生命周期管理 | ❌ | ✅ Managed Nodes |
| QoS 策略 | ❌ | ✅ 灵活的 QoS 配置 |
| LTS 支持 | 到 2025 | 到 2027 年 5 月 |
| 官方仿真 | Gazebo Classic | ✅ Gazebo (Ignition) Fortress |
简单说:如果你现在要从零开始学机器人开发,ROS2 Humble 是唯一正确的起点。
1.3 本文读者收益
读完这篇教程,你将能够:
- 在 Ubuntu 22.04 上搭建完整的 ROS2 Humble 开发环境
- 理解 DDS 通信机制,能独立编写 Publisher / Subscriber / Service / Action 节点
- 使用 Gazebo 仿真环境运行机器人模型
- 配置并运行 Nav2 导航栈,实现自主路径规划
- 避开新手最常踩的 10 个坑
二、ROS2 核心架构解密
2.1 去中心化:告别 roscore
ROS2 最大的架构变革是抛弃了中心化的 roscore,转而采用 DDS(Data Distribution Service)的 自动发现机制。每个节点启动时,只需要知道自己在哪个 DDS Domain 里,就可以自动发现同域内的其他节点。
┌──────────────────────────────────────────────────┐
│ DDS Domain 0 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Node A │◄──►│ Node B │◄──►│ Node C │ │
│ │ Publisher│ │Subscriber│ │Service Server│ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │
│ 所有节点对等,无需 Master — Discovery 全自动完成 │
└──────────────────────────────────────────────────┘
这种设计带来了三个直接的好处:
- 高容错:任何一个节点崩溃,其他节点不受影响
- 即插即用:新节点加入网络后自动被发现,无需重启系统
- 天然多机:同一 DDS Domain 内,跨机器的节点通信和本机一样简单
2.2 DDS:ROS2 的通信心脏
DDS 是 OMG 组织制定的分布式实时通信标准。ROS2 默认使用 eProsima 的 Fast-DDS(以前叫 Fast-RTPS),但你也可以换成 CycloneDDS 或 RTI Connext。
理解 DDS,只需要抓住三个核心概念:
- Participant(参与者):对应 ROS2 的 Context / Node,是 DDS 世界里的"公民"
- Topic(主题):和 ROS1 的 Topic 类似,是数据的逻辑通道
- DataWriter / DataReader:对应 ROS2 的 Publisher / Subscriber
而 DDS 为 ROS2 带来的杀手级特性是 QoS(Quality of Service),它让你可以精确控制通信行为:
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, HistoryPolicy
# 定义一个"可靠 + 持久"的 QoS 配置
qos = QoSProfile(
reliability=ReliabilityPolicy.RELIABLE, # 确保送达
durability=DurabilityPolicy.TRANSIENT_LOCAL, # 后加入的订阅者也能收到历史消息
history=HistoryPolicy.KEEP_LAST,
depth=10
)
常见的 QoS 组合场景:
| 场景 | Reliability | Durability | Depth |
|---|---|---|---|
| 传感器数据流(激光雷达) | BEST_EFFORT | VOLATILE | 1-5 |
| 关键指令(运动控制) | RELIABLE | VOLATILE | 1 |
| 参数/配置 | RELIABLE | TRANSIENT_LOCAL | 10 |
| 状态信息(电池、温度) | RELIABLE | VOLATILE | 5 |
💡 新手提示:ROS2 默认的 QoS 是
KEEP_LAST(10) + RELIABLE + VOLATILE,绝大多数场景不需要修改。只有当你遇到"丢消息"或"延迟太高"时才需要调优。
2.3 节点生命周期(Managed Nodes)
ROS1 的节点只有"活着"和"死了"两种状态。ROS2 引入了 生命周期节点,一个节点可以经历多个阶段:
Unconfigured → Inactive → Active → (Error) → Finalized
这对于生产环境至关重要——你可以先加载配置、再激活节点,确保启动过程可控。
三、环境搭建与第一个节点
3.1 系统要求与安装
ROS2 Humble 官方支持 Ubuntu 22.04 LTS(Jammy)。让我们从零开始:
# 1. 设置 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
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 \
$(. /etc/os-release && echo $UBUNTU_CODENAME) main" | \
sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# 3. 安装 ROS2 Humble Desktop 完整版
sudo apt update
sudo apt install ros-humble-desktop -y
# 4. 安装开发工具
sudo apt install python3-colcon-common-extensions \
python3-rosdep python3-vcstool ros-dev-tools -y
# 5. 初始化 rosdep
sudo rosdep init
rosdep update
安装完成后,在 ~/.bashrc 中添加环境变量:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
验证安装:
# 启动一个 talker 节点
ros2 run demo_nodes_cpp talker
如果能持续输出 Hello World 消息,说明安装成功。
3.2 工作空间与包结构
ROS2 的标准工作空间布局如下:
~/ros2_ws/
├── src/ # 源码目录
│ ├── my_robot_msgs/ # 自定义消息包
│ │ ├── msg/
│ │ ├── CMakeLists.txt
│ │ └── package.xml
│ ├── my_robot_bringup/ # 启动文件包
│ │ ├── launch/
│ │ └── package.xml
│ └── my_robot_control/ # 控制节点包
│ ├── src/
│ ├── CMakeLists.txt
│ └── package.xml
├── build/ # 编译中间产物(colcon build 自动生成)
├── install/ # 安装目录(colcon build 自动生成)
└── log/ # 编译日志
创建一个工作空间:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build --symlink-install
--symlink-install 参数让你修改 Python 脚本后无需重新编译,开发效率飙升。
3.3 Hello World:第一个节点
在 ~/ros2_ws/src 下创建一个 Python 包:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python py_hello_world
编辑 ~/ros2_ws/src/py_hello_world/py_hello_world/talker.py:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class HelloTalker(Node):
"""一个简单的发布者节点——每秒发送一条消息"""
def __init__(self):
super().__init__('hello_talker')
self.publisher = self.create_publisher(String, 'chatter', 10)
self.timer = self.create_timer(1.0, self.timer_callback)
self.count = 0
self.get_logger().info('Hello Talker 节点已启动 🚀')
def timer_callback(self):
msg = String()
msg.data = f'你好,ROS2 世界!这是第 {self.count} 条消息'
self.publisher.publish(msg)
self.get_logger().info(f'📤 发布: "{msg.data}"')
self.count += 1
def main(args=None):
rclpy.init(args=args)
node = HelloTalker()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
同时创建 listener.py:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class HelloListener(Node):
"""一个简单的订阅者节点——接收并打印消息"""
def __init__(self):
super().__init__('hello_listener')
self.subscription = self.create_subscription(
String, 'chatter', self.listener_callback, 10)
self.get_logger().info('Hello Listener 节点已就绪 👂')
def listener_callback(self, msg):
self.get_logger().info(f'📥 收到: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
node = HelloListener()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
在 setup.py 中注册入口点:
entry_points={
'console_scripts': [
'talker = py_hello_world.talker:main',
'listener = py_hello_world.listener:main',
],
},
编译并运行:
cd ~/ros2_ws
colcon build --symlink-install --packages-select py_hello_world
source install/setup.bash
# 终端1:启动 talker
ros2 run py_hello_world talker
# 终端2:启动 listener
ros2 run py_hello_world listener
你应该看到 listener 终端开始接收消息——恭喜,你已经跨过了 ROS2 的第一道门槛!
四、话题、服务与动作——三大通信范式
ROS2 提供了三种标准的节点通信方式,各有各的适用场景:
4.1 话题(Topic)——"广播电台"
适用场景:传感器数据流、状态广播、实时控制指令
话题是 ROS2 中最基础的通信方式:一个 Publisher 不断发布消息,任意多个 Subscriber 可以同时接收。它是单向、异步、多对多的。
C++ 版本的 Publisher 示例:
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/laser_scan.hpp"
class LaserPublisher : public rclcpp::Node {
public:
LaserPublisher() : Node("laser_publisher") {
publisher_ = this->create_publisher<sensor_msgs::msg::LaserScan>("scan", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(100),
std::bind(&LaserPublisher::publish_scan, this));
}
private:
void publish_scan() {
auto msg = sensor_msgs::msg::LaserScan();
msg.header.stamp = this->now();
msg.header.frame_id = "laser_frame";
msg.angle_min = -1.57;
msg.angle_max = 1.57;
msg.angle_increment = 0.0175;
msg.range_min = 0.1;
msg.range_max = 30.0;
// 填充实际距离数据...
publisher_->publish(msg);
}
rclcpp::Publisher<sensor_msgs::msg::LaserScan>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
4.2 服务(Service)——"电话呼叫"
适用场景:请求-响应模式,如获取地图、设置参数、触发拍照
服务是同步、一对一的通信:一个 Client 发送请求,等待 Server 处理并返回响应。请求期间 Client 会阻塞(或使用异步回调)。
Python 版 Service Server:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class AdderServer(Node):
def __init__(self):
super().__init__('adder_server')
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():
rclpy.init()
node = AdderServer()
rclpy.spin(node)
rclpy.shutdown()
调用服务:
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 42, b: 58}"
4.3 动作(Action)——"外卖订单"
适用场景:长时间运行的任务,如导航到目标点、抓取物体、机械臂运动
动作是 ROS2 中最复杂也最强大的通信机制。它本质上是一对"话题 + 服务"的组合,包含三个通道:
| 通道 | 方向 | 作用 |
|---|---|---|
| Goal | Client → Server | 发送任务目标 |
| Feedback | Server → Client | 持续反馈进度 |
| Result | Server → Client | 任务完成后的最终结果 |
以导航为例,Action Client 发送目标位姿后,可以实时获取剩余距离、预计时间等反馈,还能随时取消任务:
from action_msgs.msg import GoalStatus
from nav2_msgs.action import NavigateToPose
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
class NavClient(Node):
def __init__(self):
super().__init__('nav_client')
self.action_client = ActionClient(self, NavigateToPose, 'navigate_to_pose')
def send_goal(self, x, y, yaw):
goal_msg = NavigateToPose.Goal()
goal_msg.pose.header.frame_id = 'map'
goal_msg.pose.pose.position.x = x
goal_msg.pose.pose.position.y = y
goal_msg.pose.pose.orientation.z = yaw
self.action_client.wait_for_server()
self.send_goal_future = self.action_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().error('目标被拒绝 ❌')
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 feedback_callback(self, feedback_msg):
distance = feedback_msg.feedback.distance_remaining
self.get_logger().info(f'📏 剩余距离: {distance:.2f} m')
def get_result_callback(self, future):
status = future.result().status
if status == GoalStatus.STATUS_SUCCEEDED:
self.get_logger().info('🎯 导航成功!')
else:
self.get_logger().error(f'导航失败,状态码: {status}')
五、Gazebo 仿真实战
5.1 安装 Gazebo Fortress
ROS2 Humble 不再使用 Gazebo Classic,而是搭配新一代的 Gazebo Fortress(也叫 Ignition Gazebo):
# 安装 Gazebo Fortress
sudo apt install ros-humble-ros-gz -y
# 验证安装
gz sim --version
5.2 创建一个带传感器的机器人
我们使用 URDF(Unified Robot Description Format)描述一个差速驱动机器人,搭载激光雷达和摄像头:
<?xml version="1.0"?>
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 底盘 -->
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.15"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.4 0.8 1.0"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.15"/>
</geometry>
</collision>
<inertial>
<mass value="5.0"/>
<inertia ixx="0.1" ixy="0.0" ixz="0.0"
iyy="0.1" iyz="0.0" izz="0.2"/>
</inertial>
</link>
<!-- 左轮 -->
<link name="left_wheel">
<visual>
<geometry>
<cylinder radius="0.05" length="0.03"/>
</geometry>
</visual>
<collision>
<geometry>
<cylinder radius="0.05" length="0.03"/>
</geometry>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.0001" ixy="0.0" ixz="0.0"
iyy="0.0001" iyz="0.0" izz="0.0001"/>
</inertial>
</link>
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="-0.15 0.18 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 激光雷达 -->
<link name="laser_frame">
<visual>
<geometry>
<cylinder radius="0.03" length="0.05"/>
</geometry>
</visual>
</link>
<joint name="laser_joint" type="fixed">
<parent link="base_link"/>
<child link="laser_frame"/>
<origin xyz="0.15 0 0.1" rpy="0 0 0"/>
</joint>
<!-- Gazebo 激光雷达插件 -->
<gazebo reference="laser_frame">
<sensor type="gpu_lidar" name="lidar_sensor">
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1</resolution>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.05</min>
<max>15.0</max>
<resolution>0.01</resolution>
</range>
</ray>
<plugin
filename="libgazebo_ros_lidar_sensor.so"
name="lidar_controller">
<ros>
<namespace>/my_robot</namespace>
</ros>
<topic>scan</topic>
<frame_name>laser_frame</frame_name>
</plugin>
</sensor>
</gazebo>
<!-- 差速驱动插件 -->
<gazebo>
<plugin
filename="libgazebo_ros_diff_drive.so"
name="diff_drive_controller">
<ros>
<namespace>/my_robot</namespace>
</ros>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.36</wheel_separation>
<wheel_radius>0.05</wheel_radius>
<command_topic>cmd_vel</command_topic>
<odometry_topic>odom</odometry_topic>
</plugin>
</gazebo>
</robot>
5.3 在 Gazebo 中启动机器人
编写 launch 文件 ~/ros2_ws/src/my_robot_bringup/launch/sim.launch.py:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess
def generate_launch_description():
return LaunchDescription([
# 启动 Gazebo Fortress
ExecuteProcess(
cmd=['gz', 'sim', 'empty.sdf', '-r'],
output='screen'
),
# 生成机器人
Node(
package='ros_gz_sim',
executable='create',
arguments=[
'-name', 'my_robot',
'-topic', 'robot_description',
'-x', '0.0', '-y', '0.0', '-z', '0.1'
],
output='screen'
),
# 启动 robot_state_publisher
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': '...'}],
output='screen'
),
# 启动 RViz2
Node(
package='rviz2',
executable='rviz2',
output='screen'
)
])
启动仿真:
ros2 launch my_robot_bringup sim.launch.py
你会看到 Gazebo 窗口中出现一个蓝色小机器人,用键盘或手柄可以控制它在虚拟世界中移动——仿真世界的大门已经打开!
六、Nav2 自主导航从零到跑通
6.1 Nav2 架构速览
Nav2(Navigation2)是 ROS2 生态中最成熟的导航框架,它把导航任务拆解成多个独立节点,各司其职:
核心模块:
| 模块 | 作用 | 关键节点 |
|---|---|---|
| Planner Server | 全局路径规划(A* / Smac / NavFn) | planner_server |
| Controller Server | 局部轨迹跟踪(DWB / MPPI / RPP) | controller_server |
| Behavior Tree | 任务编排与状态机管理 | bt_navigator |
| AMCL | 蒙特卡洛定位 | amcl |
| Costmap 2D | 障碍物代价地图生成 | global_costmap / local_costmap |
| Smoother Server | 路径平滑优化 | smoother_server |
| Waypoint Follower | 多点导航 | waypoint_follower |
6.2 安装与配置
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup -y
创建一个导航配置包,核心是 nav2_params.yaml:
amcl:
ros__parameters:
alpha1: 0.2
alpha2: 0.2
alpha3: 0.2
alpha4: 0.2
base_frame_id: "base_link"
global_frame_id: "map"
odom_frame_id: "odom"
robot_model_type: "differential"
laser_model_type: "likelihood_field"
laser_max_range: 15.0
laser_min_range: 0.05
max_particles: 2000
min_particles: 500
update_min_d: 0.1
update_min_a: 0.1
bt_navigator:
ros__parameters:
global_frame: map
robot_base_frame: base_link
default_bt_xml_filename: "navigate_to_pose_w_replanning_and_recovery.xml"
planner_server:
ros__parameters:
expected_planner_frequency: 1.0
planner_plugins: ["GridBased"]
GridBased:
plugin: "nav2_smac_planner/SmacPlannerHybrid"
tolerance: 0.25
downsample_costmap: false
angle_quantization_bins: 72
controller_server:
ros__parameters:
controller_plugins: ["FollowPath"]
FollowPath:
plugin: "dwb_core::DWBLocalPlanner"
debug_trajectory_details: true
min_vel_x: -0.1
max_vel_x: 0.5
max_vel_theta: 1.5
min_speed_xy: 0.0
max_speed_xy: 0.5
critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign",
"PathAlign", "PathDist", "GoalDist"]
PathDist.scale: 32.0
GoalDist.scale: 24.0
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
global_frame: odom
robot_base_frame: base_link
robot_radius: 0.22
resolution: 0.05
width: 3
height: 3
plugins: ["obstacle_layer", "inflation_layer"]
inflation_layer:
inflation_radius: 0.35
obstacle_layer:
observation_sources: "scan"
scan:
topic: /scan
max_obstacle_height: 2.0
marking: true
clearing: true
6.3 启动导航并发送目标
# 启动完整的 Nav2 导航栈
ros2 launch nav2_bringup navigation_launch.py \
params_file:=./config/nav2_params.yaml \
use_sim_time:=true
# 启动 RViz 并发送导航目标
ros2 launch nav2_bringup rviz_launch.py
在 RViz 中点击 "2D Goal Pose" 按钮,在地图上点击一个目标点,你会看到:
- 绿色线条——Planner 生成的全局路径
- 红色短线——Controller 实时规划的局部轨迹
- 机器人开始移动,最终停在目标点附近
6.4 编程发送导航目标
#!/usr/bin/env python3
import math
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped
from nav2_msgs.action import NavigateToPose
from rclpy.action import ActionClient
class WaypointNavigator(Node):
"""按顺序导航到多个目标点"""
def __init__(self):
super().__init__('waypoint_navigator')
self.client = ActionClient(self, NavigateToPose, 'navigate_to_pose')
# 定义巡逻点(x, y, yaw)
self.waypoints = [
(2.0, 1.0, 0.0),
(4.0, 3.0, math.pi/2),
(1.0, 4.0, math.pi),
(0.0, 0.0, 0.0),
]
self.current_wp = 0
def send_next_goal(self):
if self.current_wp >= len(self.waypoints):
self.get_logger().info('🏁 所有巡逻点已完成!')
return
x, y, yaw = self.waypoints[self.current_wp]
goal = NavigateToPose.Goal()
goal.pose.header.frame_id = 'map'
goal.pose.pose.position.x = x
goal.pose.pose.position.y = y
goal.pose.pose.orientation.z = math.sin(yaw / 2)
goal.pose.pose.orientation.w = math.cos(yaw / 2)
self.get_logger().info(f'📍 发送目标 {self.current_wp+1}/{len(self.waypoints)}: ({x}, {y})')
self.client.wait_for_server()
future = self.client.send_goal_async(goal)
future.add_done_callback(self.goal_done)
def goal_done(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().error('目标被拒绝')
return
result_future = goal_handle.get_result_async()
result_future.add_done_callback(self.result_done)
def result_done(self, future):
self.current_wp += 1
self.send_next_goal()
def main():
rclpy.init()
navigator = WaypointNavigator()
navigator.send_next_goal()
rclpy.spin(navigator)
七、常见问题与排错指南
Q1:ros2 命令找不到?
# 确认 source 了 setup 文件
source /opt/ros/humble/setup.bash
# 检查是否能找到 ros2
which ros2 # 应输出 /opt/ros/humble/bin/ros2
Q2:节点间无法通信?
使用 ROS2 自带的诊断工具:
# 检查话题列表
ros2 topic list
# 检查节点图
ros2 node list
rqt_graph # 可视化节点连接关系
# 检查话题信息
ros2 topic info /chatter
# 使用 echo 查看实际消息
ros2 topic echo /chatter
Q3:Gazebo 仿真启动后机器人不动?
检查 /cmd_vel 话题是否正确连接:
ros2 topic echo /cmd_vel
# 手动发送控制指令测试
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.0}}"
Q4:Nav2 导航时机器人疯狂转圈?
通常是 Costmap 参数不合理导致的。检查:
robot_radius是否和实际机器人尺寸匹配inflation_radius不应过大(建议 0.3-0.5m)- 激光雷达的
min_range是否设得过大,导致近距离障碍物不被检测
Q5:DDS 发现不到其他机器上的节点?
# 检查 DDS 配置
echo $ROS_DOMAIN_ID # 所有机器必须相同
# 如果使用 Fast-DDS,检查多播是否正常
# 在 /etc/hosts 中添加对方 IP
# 或配置 CycloneDDS 的 XML 配置文件
Q6:编译时 ament_cmake 报错?
# 清理 build 目录后重新编译
rm -rf ~/ros2_ws/build ~/ros2_ws/install
colcon build --symlink-install
Q7:Python 节点 import 报错?
确保 setup.py 中的 entry_points 和 package.xml 中的 <exec_depend> 一致,并且:
# 重新 source 安装目录
source ~/ros2_ws/install/setup.bash
# 检查包是否被正确发现
ros2 pkg list | grep my_package
八、总结与学习路径
核心要点回顾
- ROS2 Humble = 未来四年的标准:作为 ROS2 第二个 LTS 版本(首个为 Foxy Fitzroy 2020),它是学习和生产的唯一选择
- DDS 是灵魂:去中心化 + QoS 让 ROS2 在可靠性、实时性、安全性上全面超越 ROS1
- 三大通信范式:Topic(数据流)、Service(请求响应)、Action(长任务)覆盖所有场景
- 仿真先行:Gazebo + RViz2 组合让你无需硬件就能开发和测试
- Nav2 开箱即用:只要配好参数,复杂导航功能一键启动
推荐学习路径
📅 第 1 周:安装 + Hello World + Topic/Service/Action 练习
📅 第 2 周:URDF 建模 + Gazebo 仿真 + 键盘遥控
📅 第 3 周:TF2 坐标变换 + 传感器数据处理
📅 第 4 周:Nav2 导航栈 + SLAM 建图
📅 第 5-6 周:完整项目实战(如仓储巡检机器人)
延伸资源
本文由 MarkShareX AI 自动创作,分类:ROS2,方向:ROS2 Humble