【ROS2】用launch批量拉起节点(以python为例)Basic
概述
在 ROS2 中,主流的 Launch 文件是用 Python 编写的
要一次性拉起多个功能包中的多个节点,最标准的做法是创建一个专门的启动功能包(通常命名为 xxx_bringup),在这个包里集中管理所有的 Launch 文件和参数配置
举例
假设我们要同时启动 package_a 中的 node_a 和 package_b 中的 node_b,具体流程如下:
第一步:编写 Python Launch 文件
假设在一个名为 my_robot_bringup 的功能包下,新建了一个 launch 文件夹,并在其中创建了 multi_nodes.launch.py 文件:
# 导入所需的库
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
# 1. 定义要启动的第一个节点 (来自 package_a)
node_a = Node(
package='package_a', # 功能包的名称
executable='node_a_exe', # 可执行文件的名称 (CMakeLists/setup.py中配置的名称)
name='custom_node_a', # [可选] 重命名节点
output='screen' # 将该节点的日志输出到终端,方便调试
)
# 2. 定义要启动的第二个节点 (来自 package_b)
node_b = Node(
package='package_b',
executable='node_b_exe',
name='custom_node_b',
output='screen',
# [进阶] 你甚至可以直接在这里传递参数或进行话题重映射
parameters=[{'my_param': 100}],
remappings=[('/old_topic', '/new_topic')]
)
# 3. 将所有节点添加到 LaunchDescription 中并返回
return LaunchDescription([
node_a,
node_b
])
第二步:⭐配置编译文件(以Python 功能包为例)
ROS2 通过编译规则查找 Launch 文件,不配置就找不到
setup.py 的路径声明
打开 setup.py,确保引入了相关的路径库,并在 data_files 列表中添加 launch 文件的安装路径:
import os
from glob import glob
from setuptools import setup
package_name = 'my_robot_bringup'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# 👇 添加这一行,作用是把 launch 文件夹下的所有 launch 文件拷贝到安装目录
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
],
# ... 省略其他配置
)
package.xml的依赖声明
Bringup 功能包需要依赖它要启动的普通包。它自身可能连一行 rclpy 代码都没写,但如果它的 launch 文件要拉起 package_a 和 package_b,通常会在 package.xml 中使用执行依赖(<exec_depend>),确保运行时这些包是存在的:
<exec_depend>package_a</exec_depend>
<exec_depend>package_b</exec_depend>
<exec_depend>ros2launch</exec_depend>
第三步:编译并运行
配置好之后,回到工作空间根目录,进行编译和环境声明:
# 1. 编译工作空间(可以只编译你的 bringup 包节省时间)
colcon build --packages-select my_robot_bringup
# 2. 刷新环境变量
source install/setup.bash
# 3. 运行 Launch 文件!
ros2 launch my_robot_bringup multi_nodes.launch.py
执行完毕后,你就能在终端看到来自不同功能包的多个节点被一同拉起,并且它们的日志会混合输出在同一个终端里。
对比普通功能包和bringup功能包
在 ROS2 中,区分普通功能包(Standard Package)和 Bringup 功能包的核心思想是关注点分离(Separation of Concerns)
1. 核心定位与内容的区别
| 对比维度 | 普通功能包 (Standard Package) | Bringup 功能包 (Bringup Package) |
|---|---|---|
| 主要职责 | 实现具体功能(如:雷达驱动、图像识别、运动控制)。 | 统筹规划,负责把多个普通功能包按特定配置组合并启动。 |
| 核心内容 | 主要是 .py 源码文件(节点代码、算法逻辑)。 | 主要是 .launch.py (启动脚本)、.yaml (参数配置)、RViz/URDF 模型。 |
| 复用性 | 极高。例如一个 USB 摄像头包,可以用在无人车上,也可以用在机械臂上。 | 极低。通常专门针对某一款特定的机器人(例如 turtlebot3_bringup)。 |
| 目录结构特点 | 拥有完整的 包名/ 源码目录,里面全是 Python 代码。 | 重点在于 launch/、config/、rviz/ 等资源文件夹。通常没有真实的节点逻辑代码。 |
2. 配置文件上的本质区别
由于它们的作用不同,在 Python 格式的包中,它们的 setup.py 和 package.xml 侧重点完全相反。
区别一:setup.py 的配置重点
- 普通功能包:重点在
entry_points(注册可执行文件)
普通包需要让 ROS2 知道去哪里执行你的 Python 节点逻辑。它的data_files通常很简单,但必须在console_scripts中注册节点。
# 普通功能包的 setup.py
setup(
# ... 省略基础信息 ...
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
entry_points={
'console_scripts': [
# 注册节点:可执行文件名 = 包名.文件名:主函数
'camera_node = my_camera_pkg.camera_node:main',
],
},
)
- Bringup 功能包:重点在
data_files(安装非代码资源)
Bringup 包通常没有节点逻辑,所以console_scripts往往是空的。它的核心任务是通过data_files把launch和config(YAML 参数)文件夹原封不动地拷贝到系统安装目录中。
# Bringup 功能包的 setup.py
import os
from glob import glob
# ...
setup(
# ... 省略基础信息 ...
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# 核心区别 👇:安装 launch 文件夹下的所有文件
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
# 核心区别 👇:安装 config 文件夹下的参数文件
(os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))),
],
entry_points={
'console_scripts': [
# 通常为空,因为没有具体的节点代码要执行
],
},
)
区别二:package.xml 的依赖声明
- 普通功能包: 依赖具体的编程库。它需要声明
<depend>rclpy</depend>、<depend>sensor_msgs</depend>等,因为它要调用这些 API 写代码。 - Bringup 功能包: 依赖它要启动的普通包
总结来说:
开发普通功能包,你是在造砖头(写 console_scripts);开发 Bringup 功能包,你是在用砖头盖房子(写 data_files 拷贝配置文件)
更多推荐


所有评论(0)