在机器人开发领域,ROS 早已不是新鲜词。过去几年,ROS1 在学术研究和原型验证中占据绝对主流,但到了真实产品落地阶段,它的通信实时性、多机协同、安全机制等短板越来越明显。ROS2 正是在这种背景下开始逐步取代 ROS1,成为新项目、新课程、新工具链的首选。不过对于零基础读者来说,ROS2 的上手门槛并不低:环境安装涉及大量系统依赖,工作空间和功能包概念又和传统软件开发不一样,话题、服务、动作三种通信方式也容易混淆。
这篇文章会围绕 ROS2 零基础入门最常见的几个环节展开:从环境搭建开始,到创建工作空间和功能包,再通过可运行的代码案例把话题通信、服务通信、动作通信全部串起来。无论你是刚接触机器人的学生,还是准备转行机器人开发的工程师,都可以按照本文步骤从零跑通。最终你会理解 ROS2 的节点如何组织、消息如何在节点之间传递,并且能独立创建自己的功能包。
1. 为什么选择 ROS2?
1.1 ROS2 与 ROS1 的本质区别
ROS1 的通信架构依赖一个 Master 节点来管理所有节点之间的连接。如果 Master 挂了,整个系统就会失去协调能力。这一点在实验室单机场景下问题不大,但一旦进入多机器人协作、车间部署、车机环境,稳定性就会成为致命伤。
ROS2 则去掉了中心化 Master,底层通信改为 DDS(Data Distribution Service)标准。DDS 是一种工业级的数据分发中间件,支持实时通信、自动发现、QoS 控制,节点之间可以直接发现对方,不需要中心节点转达。简单说,ROS2 把“通信可靠性”从上层应用下沉到了通信中间件层,这让整个系统更适合真实机器人项目。
另一个重要变化是 ROS2 支持 Python、C++ 双语言平等开发。ROS1 对 Python 的支持虽然也存在,但很多核心工具链和通信接口对 C++ 更友好。ROS2 中 Python 节点和 C++ 节点可以混用,通信完全透明,这对新手非常友好。
1.2 本文学习目标与适合人群
本文适合以下三类读者:
- 没有 ROS 基础,但会一点 Python,想快速入门 ROS2 的初学者。
- 用过 ROS1,想看 ROS2 环境、工作空间、功能包和通信方式有哪些变化的老开发者。
- 已经安装过 ROS2,但对话题、服务、动作三种通信机制理解不深,想通过代码加深理解的同学。
读完这篇文章后,你应该能独立完成以下事情:
- 在 Ubuntu 上安装 ROS2 Humble。
- 创建自己的工作空间和功能包。
- 用 Python 写出话题发布者、订阅者。
- 用 Python 写出服务端、客户端。
- 用 Python 实现动作通信的 server 与 client。
- 掌握 ROS2 常用调试命令。
文章示例基于 Python,因为 Python 的代码结构更适合讲清楚通信原理。掌握概念后,转写 C++ 会容易很多。
2. ROS2 环境搭建
2.1 版本选择:ROS2 Humble 还是别的?
ROS2 的发行版本很多,常见的有 Foxy、Galactic、Humble、Iron、Jazzy。对零基础学习者,最推荐的是ROS2 Humble Hawksbill,因为 Humble 是长期支持版本,支持周期较长,社区资料多,网上遇到的坑基本都能搜到解决方案。
需要特别注意,ROS2 每个版本对应的 Ubuntu 版本是固定的。Humble 官方推荐 Ubuntu 22.04(Jammy)。如果你的系统是 Ubuntu 20.04,则需要安装 Foxy;如果是 Ubuntu 24.04,则对应 Jazzy。版本不匹配会出现大量依赖冲突,这是新手最容易踩的坑。
本文以 Ubuntu 22.04 + ROS2 Humble 为例。如果你的环境不同,安装步骤中的 apt 源、包名需要对应调整。
2.2 Ubuntu 22.04 下安装 ROS2 Humble
在安装开始之前,建议先更新系统软件源和软件包索引:
sudo apt update sudo apt upgrade -yROS2 官方安装方式是把安装源添加到 apt 中,然后通过 apt 安装。首先安装基础工具:
sudo apt install curl gnupg lsb-release -y接着导入 ROS2 的 GPG 公钥:
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg然后添加 ROS2 apt 源。这里使用系统自动识别的 Ubuntu 版本代号,不需要手动填:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null再次更新 apt 索引:
sudo apt update接下来安装桌面版 ROS2。桌面版包含了常用的 rviz2、demo 程序、示例等功能,学习和调试非常方便:
sudo apt install ros-humble-desktop python3-argcomplete -y安装过程比较长,取决于网络速度。安装完成后,可以安装 colcon 构建工具。colcon 是 ROS2 官方推荐的编译工具,相当于 ROS1 中的 catkin:
sudo apt install python3-colcon-common-extensions -y这里解释一下为什么要用 colcon:工作空间中有多个功能包时,colcon 可以批量编译,并且会生成build、install、log目录,方便管理。
2.3 配置环境变量与验证安装
ROS2 安装完成后,还需要在每次打开终端时加载环境变量。你可以手动输入:
source /opt/ros/humble/setup.bash为了避免每个新终端都要手动 source,建议写入~/.bashrc:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc验证是否安装成功:
ros2 --help如果能看到命令帮助,说明 ROS2 已经安装成功。更直观的验证方式是运行小海龟程序:
sudo apt install ros-humble-turtlesim -y打开第一个终端,启动海龟仿真器:
ros2 run turtlesim turtlesim_node打开第二个终端,启动键盘控制节点:
ros2 run turtlesim turtle_teleop_key此时应该能看到一只小海龟出现在窗口里,按方向键可以控制它移动。这是一个非常经典的 ROS2 运行验证方式,虽然简单,但它已经用到了“节点”和“话题”的概念,后面会详细解释。
如果你的终端提示找不到ros2命令,优先检查环境变量是否加载成功,执行echo $ROS_DISTRO,如果输出为空或错误,则说明 source 没有生效。
2.4 Windows 用户:WSL2 环境说明
如果你使用的是 Windows 系统,最稳妥的方法不是原生安装 ROS2,而是通过 WSL2 安装 Ubuntu 22.04。在 Windows 中安装 WSL2 后,在 Ubuntu 子系统内部按照上面的步骤安装 ROS2 Humble。
WSL2 下 ROS2 的图形界面工具(如 rviz2、turtlesim)需要 WSLg 支持,较新的 Windows 10/11 版本默认自带。如果你在使用过程中看不到图形窗口,可以先更新 WSL 内核,或者检查 Windows 版本是否满足要求。
另外,在 WSL2 中运行硬件相关的机器人驱动可能会遇到 USB 设备透传问题,这一点在实际项目阶段需要单独处理。对于学习通信机制和写功能包来说,WSL2 已经足够。
3. 工作空间与功能包
3.1 工作空间目录结构
在 ROS2 中,工作空间是一个存放所有功能包的顶层目录。通常命名为ros2_ws。一个标准工作空间包含 4 个子目录:
src:存放功能包源码。build:编译过程中生成的中间文件。install:编译完成后的安装文件和环境变量。log:编译和运行日志。
创建标准工作空间:
mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build编译完成后,工作空间中会自动生成build、install、log目录。记住一个原则:不要把源码直接放在build或install中,所有功能包都放在src下,源码和编译产物分离,便于管理和清理。
3.2 创建第一个功能包
功能包是 ROS2 代码复用和发布的基本单元。一个功能包可以包含节点源码、配置文件、launch 文件、消息接口等。创建功能包时,需要先进入工作空间的src目录:
cd ~/ros2_ws/src ros2 pkg create my_pkg --build-type ament_python --dependencies rclpy std_msgs解释一下参数:
my_pkg:功能包名称。--build-type ament_python:指定 Python 构建类型。--dependencies rclpy std_msgs:声明依赖的库。rclpy是 ROS2 的 Python 客户端库,std_msgs包含常见的标准消息类型。
创建完成后,my_pkg目录结构大致如下:
my_pkg ├── package.xml ├── resource ├── setup.cfg ├── setup.py ├── test └── my_pkg └── __init__.py其中my_pkg/my_pkg是实际的 Python 包目录,后续编写的节点代码都放在这里。setup.py负责声明可执行程序的入口,package.xml负责声明依赖信息。
3.3 编译与 source
功能包代码写完后,需要回到工作空间根目录编译:
cd ~/ros2_ws colcon build编译成功后,需要 source 当前工作空间的 setup 文件,这样才能让终端找到新功能包里的可执行程序:
source install/setup.bash建议把这条命令也写入~/.bashrc,这样每次打开新终端都能自动加载:
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc这里需要提醒新手:每次修改功能包代码后,都要重新执行colcon build才能让改动生效,否则ros2 run运行的还是旧版本代码。
4. 通信机制:节点、话题、服务、动作
4.1 核心概念速览
在 ROS2 中,一个独立运行的程序被称为“节点”。节点之间通过不同的通信机制交换数据。最常用的有三种:
- 话题通信:节点持续发布或订阅数据,适合传感器数据、状态信息等高频单向数据流。
- 服务通信:客户端发起请求,服务端返回响应,适合一次性调用,比如请求某个位置的坐标。
- 动作通信:客户端发起目标,服务端持续返回反馈,最终返回结果,适合执行时间长、需要取消的任务。
简单理解:话题是“广播”,服务是“问答”,动作是“交办任务并持续汇报进度”。
4.2 通信机制怎么选
实际项目里,三种通信方式经常会混用。
话题用于高频、单向、无需应答的数据。例如激光雷达扫描数据、摄像头图像、机器人当前速度,都会用话题发布。
服务用于一次性的请求-响应。例如打开机械臂的夹爪、校准陀螺仪、查询当前地图名称,这类操作不需要持续反馈,服务就够用了。
动作用于需要多次反馈的长时间任务。例如让机器人导航到某个目标点、让机械臂执行一段复杂轨迹,这类任务可能执行几十秒甚至几分钟,需要实时反馈进度,并且允许中途取消,这时候就必须使用动作通信。
4.3 rqt_graph 可视化理解
可以用一个可视化工具 rqt_graph 查看节点和话题的连接关系。安装:
sudo apt install ros-humble-rqt-graph -y启动 turtlesim 并让小海龟运动后,打开新终端运行:
rqt_graph你会看到两个节点和一个话题连接线。话题的名字和连接方向都一目了然,这对排查通信问题非常有帮助。以后如果发现节点之间收不到数据,第一步就应该打开 rqt_graph 看连接是否存在。
5. 话题通信实战
5.1 发布者节点实现
话题通信中,发布者负责把数据发送到指定话题,订阅者负责从同一话题接收数据。
现在我们在my_pkg中创建一个发布者节点。文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/publisher_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/publisher_node.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, 'chatter', 10) self.timer = self.create_timer(0.5, self.timer_callback) self.count = 0 def timer_callback(self): msg = String() msg.data = f'Hello ROS2: {self.count}' self.publisher_.publish(msg) self.get_logger().info(f'Publishing: {msg.data}') self.count += 1 def main(args=None): rclpy.init(args=args) node = SimplePublisher() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这段代码的核心逻辑是:
- 继承
Node,并将节点命名为simple_publisher。 - 通过
create_publisher创建发布者,消息类型为String,话题名为chatter,队列长度为 10。 - 通过
create_timer创建一个 0.5 秒触发一次的定时器。 - 每次定时器触发,就发布一条字符串消息,并打印日志。
最后那个if __name__ == '__main__':是为了让这个文件也能被命令行直接执行。不过 ROS2 的推荐方式是使用setup.py配置入口,然后通过ros2 run启动。
5.2 订阅者节点实现
发布者写完,再写一个订阅者节点,文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/subscriber_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/subscriber_node.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, 'chatter', self.listener_callback, 10 ) def listener_callback(self, msg): self.get_logger().info(f'I heard: {msg.data}') def main(args=None): rclpy.init(args=args) node = SimpleSubscriber() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()订阅者的关键点是回调函数listener_callback,只要话题上来了新消息,这个回调函数就会被自动执行。注意create_subscription需要指定消息类型、话题名、回调函数和 QoS 队列长度。
这里有一个新手容易搞混的地方:不要在主线程里反复手动读取话题数据,ROS2 是事件驱动模型,收到消息后回调函数会先执行,所以只需要等待rclpy.spin(node)把程序阻塞住,不断处理回调即可。
5.3 配置入口并编译运行
为了让ros2 run能找到这两个节点,需要在setup.py中配置入口。编辑文件:
~/ros2_ws/src/my_pkg/setup.py在entry_points中添加:
entry_points={ 'console_scripts': [ 'simple_publisher = my_pkg.publisher_node:main', 'simple_subscriber = my_pkg.subscriber_node:main', ], },回到工作空间根目录编译:
cd ~/ros2_ws colcon build source install/setup.bash打开两个终端。终端 1 运行订阅者,终端 2 运行发布者:
ros2 run my_pkg simple_subscriberros2 run my_pkg simple_publisher终端 1 中应该能看到发布者发来的每一条消息,例如:
[INFO] I heard: Hello ROS2: 0 [INFO] I heard: Hello ROS2: 1话题通信已经跑通了。
5.4 使用 CLI 工具验证话题
除了写代码,ROS2 提供了一系列命令行工具,排查问题非常方便。
查看当前话题列表:
ros2 topic list查看某个话题的消息类型:
ros2 topic info /chatter实时监听话题中的数据:
ros2 topic echo /chatter手动向话题发布数据:
ros2 topic pub /chatter std_msgs/msg/String "{data: 'Hello CLI'}" --once这些命令是话题调试的利器。尤其当你写了一个复杂系统却收不到数据时,先用 CLI 工具确认话题是否存在、消息是否真正发布,可以快速缩小问题范围。
6. 服务通信实战
6.1 服务通信接口介绍
服务通信使用客户端-服务端模式。客户端发送请求,服务端处理请求并返回响应。ROS2 中服务的接口文件后缀为.srv,格式分成上下两部分,中间用---分隔,上半部分是请求,下半部分是响应。
本文使用example_interfaces/srv/AddTwoInts.srv,这是一个内置的示例服务接口,内容等价于:
int64 a int64 b --- int64 sum也就是说,客户端传入两个整数a和b,服务端返回它们的和sum。
6.2 服务端节点实现
在my_pkg中创建服务端节点,文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/service_server_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/service_server_node.py import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts class AddTwoIntsService(Node): def __init__(self): super().__init__('add_two_ints_service') self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback) def add_two_ints_callback(self, request, response): response.sum = request.a + request.b self.get_logger().info( f'Received: {request.a} + {request.b} = {response.sum}' ) return response def main(args=None): rclpy.init(args=args) node = AddTwoIntsService() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这里最需要注意的是回调函数的写法:request和response都由框架自动创建,回调返回的是填充后的response。这个返回值会被自动发送给客户端,不需要手动处理网络传输。
6.3 客户端节点实现
客户端节点比服务端稍复杂一点,因为它需要等待服务上线,并通过异步方式发送请求。文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/service_client_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/service_client_node.py import rclpy from rclpy.node import Node from example_interfaces.srv import AddTwoInts 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 future = self.cli.call_async(self.req) rclpy.spin_until_future_complete(self, future) return future.result() def main(args=None): rclpy.init(args=args) node = AddTwoIntsClient() response = node.send_request(5, 3) node.get_logger().info(f'Service result: {response.sum}') node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()服务端启动后,客户端会先循环等待服务可用,避免出现“服务没起来,请求直接失败”的情况。call_async是异步调用,它会立即返回一个 future,后续通过rclpy.spin_until_future_complete阻塞直到拿到结果。
6.4 运行验证
在setup.py的entry_points中加入服务端和客户端入口:
'add_two_ints_server = my_pkg.service_server_node:main', 'add_two_ints_client = my_pkg.service_client_node:main',重新编译:
cd ~/ros2_ws colcon build source install/setup.bash先启动服务端:
ros2 run my_pkg add_two_ints_server然后启动客户端:
ros2 run my_pkg add_two_ints_client客户端会打印结果:
[INFO] Service result: 8服务端终端中也会收到一条日志。这说明服务通信成功。
也可以用命令行直接请求服务,不需要写客户端代码。新开终端执行:
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 10, b: 20}"这个命令非常适合测试服务端是否正常。
7. 动作通信实战
7.1 动作通信接口与适用场景
动作通信是最接近真实机器人任务的一种交互方式。它包含目标、反馈、结果三部分,客户端下发目标,服务端在执行过程中不断发布反馈,最后返回结果。整个过程还支持取消任务。
本文使用example_interfaces/action/Fibonacci.action。这个接口内容等价于:
int32 order --- int32[] sequence --- int32[] partial_sequence目标是计算斐波那契数列的前order项,服务端在计算过程中不断反馈已经算出的部分序列,最后返回完整结果。
在实际项目中,动作通信常用于导航、机械臂规划这类长时间任务。
7.2 动作服务端节点实现
在my_pkg中创建动作服务端节点,文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/action_server_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/action_server_node.py import rclpy import rclpy.action from rclpy.node import Node from example_interfaces.action import Fibonacci class FibonacciActionServer(Node): def __init__(self): super().__init__('fibonacci_action_server') self._action_server = rclpy.action.create_server( self, Fibonacci, 'fibonacci', self.execute_callback ) def execute_callback(self, goal_handle): self.get_logger().info('Executing goal...') feedback_msg = Fibonacci.Feedback() feedback_msg.partial_sequence = [0, 1] for i in range(1, goal_handle.request.order): feedback_msg.partial_sequence.append( feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i - 1] ) goal_handle.publish_feedback(feedback_msg) goal_handle.succeed() result = Fibonacci.Result() result.sequence = feedback_msg.partial_sequence return result def main(args=None): rclpy.init(args=args) node = FibonacciActionServer() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()动作服务端的核心是execute_callback。这个回调函数接收一个goal_handle,通过它可以发布反馈、标记目标成功或失败。在长时间任务中,建议在循环里周期性调用publish_feedback,这样客户端才能实时看到进度。
7.3 动作客户端节点实现
动作客户端在发送目标之后,需要等待服务端接受目标,同时接收反馈,最后等待最终结果。文件路径为:
~/ros2_ws/src/my_pkg/my_pkg/action_client_node.py代码如下:
# 文件路径:~/ros2_ws/src/my_pkg/my_pkg/action_client_node.py import rclpy import rclpy.action from rclpy.node import Node from example_interfaces.action import Fibonacci class FibonacciActionClient(Node): def __init__(self): super().__init__('fibonacci_action_client') self._action_client = rclpy.action.create_client( self, Fibonacci, 'fibonacci' ) def send_goal(self, order): goal_msg = Fibonacci.Goal() goal_msg.order = order self._action_client.wait_for_server() future = self._action_client.send_goal_async( goal_msg, feedback_callback=self.feedback_callback ) 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('Goal rejected.') return self.get_logger().info('Goal accepted.') result_future = goal_handle.get_result_async() result_future.add_done_callback(self.get_result_callback) def feedback_callback(self, feedback_msg): self.get_logger().info( f'Received feedback: {feedback_msg.feedback.partial_sequence}' ) def get_result_callback(self, future): result = future.result().result self.get_logger().info(f'Result: {result.sequence}') rclpy.shutdown() def main(args=None): rclpy.init(args=args) node = FibonacciActionClient() node.send_goal(10) rclpy.spin(node) if __name__ == '__main__': main()动作客户端的回调比较多,理解起来比话题复杂。可以这样记忆:发送目标后,先触发“目标响应回调”,判断服务端是否接受目标;接受后,后续每一次服务端发布反馈都会触发“反馈回调”;最终任务完成,触发“结果回调”。整个过程是一个完整的异步状态机。
7.4 运行验证
在setup.py的entry_points中添加:
'fibonacci_action_server = my_pkg.action_server_node:main', 'fibonacci_action_client = my_pkg.action_client_node:main',编译并 source:
cd ~/ros2_ws colcon build source install/setup.bash先启动动作服务端:
ros2 run my_pkg fibonacci_action_server再启动动作客户端:
ros2 run my_pkg fibonacci_action_client客户端会依次打印反馈序列,最终打印结果:
[INFO] Received feedback: [0, 1, 1] [INFO] Received feedback: [0, 1, 1, 2] [INFO] Received feedback: [0, 1, 1, 2, 3] ... [INFO] Result: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]也可以用命令行发送动作目标:
ros2 action send_goal /fibonacci example_interfaces/action/Fibonacci "{order: 5}" --feedback加--feedback参数可以实时显示反馈,这对调试动作通信非常方便。
8. 常见问题与排查思路
8.1 高频报错与解决方式
新手在 ROS2 环境搭建和通信代码编写中,最常遇到下面这些问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
找不到命令ros2 | 环境变量没有加载 | 执行source /opt/ros/humble/setup.bash,并检查~/.bashrc |
ros2 pkg create报 Package not found | 功能包名冲突或语法错误 | 确认包名符合命名规范,不要使用已存在的相同包名 |
| 编译时报模块找不到 | 依赖没有安装或未 source 工作空间 | 安装对应依赖,并重新执行source install/setup.bash |
| 话题收不到消息 | 话题名不一致、节点没有运行、QoS 不匹配 | 用ros2 topic list和rqt_graph检查连接关系 |
| 服务调用超时 | 服务端没有启动或服务名错误 | 先启动服务端,用ros2 service list查询服务名 |
| 动作目标一直不被接受 | 动作服务端没有运行或 action 类型不匹配 | 检查动作服务名称和类型,使用ros2 action list查看 |
| 在 WSL2 中看不到图形窗口 | WSLg 异常 | 更新 WSL 内核,或检查 Windows 系统版本 |
8.2 调试工具与排查思路
遇到通信问题,不要盲目改代码,建议按顺序排查:
第一步,确认节点都在运行:
ros2 node list如果节点不在列表中,检查是否 source 了工作空间,是否编译成功。
第二步,确认话题、服务、动作都在列表中:
ros2 topic list ros2 service list ros2 action list第三步,查看消息类型是否匹配:
ros2 topic info /chatter ros2 service type /add_two_ints ros2 action type /fibonacci第四步,直接使用 CLI 工具发布或请求,判断问题在发送端还是接收端。
这套流程基本可以解决 90% 的通信问题。
9. 工程实践与开发建议
9.1 命名与目录规划
功能包名称建议使用小写字母和下划线,例如my_pkg、robot_navigation,不要使用大写字母或中划线。节点名称一般和功能包名区分开,表达功能含义,比如camera_node、lidar_node。
与 ROS1 相比,ROS2 的命名空间和话题重映射能力更强。同一个功能包可以启动多个节点实例,通过不同命名空间区分数据流,这在多机器人场景中非常实用。
目录规划上,一个功能包内部可以按作用拆分:
my_pkg/ ├── my_pkg/ │ ├── publisher_node.py │ ├── subscriber_node.py │ ├── service_server_node.py │ ├── service_client_node.py │ └── action_server_node.py ├── launch/ ├── config/ ├── package.xml └── setup.py节点文件多以后,把 launch 和 config 单独放在目录中,是工程化的重要一步。
9.2 尽量使用 launch 文件
实际项目中通常有多个节点需要同时启动。如果每个节点都开一个终端,既不便于维护,也不能保证启动顺序。ROS2 的 launch 文件可以统一管理节点启动。
在功能包下新建launch目录,创建一个 launch 文件:
# 文件路径:~/ros2_ws/src/my_pkg/launch/talker_listener.launch.py from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_pkg', executable='simple_publisher', name='publisher', output='screen' ), Node( package='my_pkg', executable='simple_subscriber', name='subscriber', output='screen' ), ])在setup.py的data_files中加入 launch 目录:
import os from glob import glob data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')), ],之后只需要一条命令启动多个节点:
ros2 launch my_pkg talker_listener.launch.pylaunch 文件支持参数传递、条件判断、命名空间设置,是 ROS2 大型项目不可缺少的一部分。
9.3 QoS 与日志规范
ROS2 的 QoS(Quality of Service)策略决定了消息传输的可靠性。默认情况下,话题使用keep_last(10)策略,这条策略表示发送端缓冲区最多保留 10 条历史消息。在传感器数据传输或命令控制中,需要根据实际场景调整 QoS。
新手最容易遇到的问题就是:发布者和订阅者的 QoS 不匹配,导致消息传输失败或降级。如果通信不稳,优先检查双方的 QoS 设置,例如:
self.publisher_ = self.create_publisher(String, 'chatter', 10)这里的10就是 QoS 队列深度。在关键控制场景中,建议使用reliable传输;在高频传感器场景中,建议使用best_effort,否则会引入不必要的延迟。
日志方面,ROS2 提供了统一的日志系统,节点中打印日志应该使用self.get_logger(),而不是直接print。这样日志可以集成到 ROS2 的日志框架中,便于按级别过滤和控制。
10. 总结与后续学习路线
到这里,你已经完成了 ROS2 的第一个完整闭环:安装环境、创建工作空间、创建功能包,并分别实现了话题、服务、动作三种通信方式的节点代码。这四件事是 ROS2 开发的基本功,无论以后做什么机器人项目,都离不开这些基础能力。
下一步可以继续学习三个方向:
第一个方向是硬件接入。把单片机、激光雷达、摄像头接入 ROS2,需要掌握传感器驱动包的使用方法,以及消息类型如何转换。建议从摄像头和雷达开始,因为这些传感器的 ROS2 驱动比较成熟。
第二个方向是机器人建模与仿真。学习 URDF 文件格式、Gazebo 仿真环境、rviz2 可视化,在仿真中验证算法,再迁移到真机,可以大幅降低开发风险。
第三个方向是导航和运动控制。学习 Nav2 导航框架,理解地图、定位、路径规划、代价地图等概念,这些都是机器人项目落地时最常用的模块。
如果你现在还在犹豫要不要继续学,不妨先不要看太多理论。安装好 Humble 后,跑一次小海龟,再动手把 publisher 和 subscriber 这两段代码改成你想要的数据类型。等你亲手把一个自定义话题跑通,ROS2 的大门就已经打开了。