一、 Launch功能概述
在 ROS 2 中,Launch 系统是用于自动启动、管理和配置多个节点(Node)及其依赖项的核心机制。与 ROS 1 主要依赖 XML 编写 .launch 文件不同,ROS 2 引入了基于 Python 动态脚本的 Launch 系统(同时也支持 XML 和 YAML),极大地提升了复杂的逻辑控制、条件判断和跨节点参数注入的能力。
1. Python Launch 的主要功能
-
多节点启动 :避免手动在多个终端逐个运行
ros2 run命令。 -
灵活的参数与 Remap 注入:统一管理节点的命名空间(Namespace)、话题重映射(Remap)以及 YAML 参数配置文件。
-
流程与生命周期控制:
-
支持节点的条件启动(如
IfCondition,UnlessCondition)。 -
支持事件监听(Event Handlers),例如:当"节点 A 启动成功"或"节点 B 退出"时触发后续动作。
-
原生支持 ROS 2 Lifecycle Node(生命周期节点) 的状态切换管理。
-
-
多语言格式支持 :支持 Python (功能最全、最推荐)、XML 和 YAML 三种格式。
| 特性 | Python (.launch.py) | XML (.launch.xml) | YAML (.launch.yaml) |
|---|---|---|---|
| 推荐程度 | 官方首选(主线标准) | 简单场景推荐 | 较少使用 |
| 表达能力 | 极强(支持完整 Python 语法、条件分支、事件处理、逻辑运算) | 中等(支持基础参数替换、节点启动、嵌套包含) | 较弱(语法结构固定) |
| 编写难度 | 代码较冗长,存在一定的学习曲线 | 直观简短,语法类似 ROS 1 | 极简,但格式缩进要求严格 |
| 典型场景 | 复杂机器人系统、带有条件逻辑和事件监听的启动 | 纯静态节点堆叠、从 ROS 1 迁移的轻量项目 | 简单的配置驱动型启动 |
2. Python Launch 的核心架构组件
编写一个 Python Launch 文件(generate_launch_description())通常包含以下核心元素:
-
LaunchDescription:主入口容器,用于收集并返回所有要执行的 Launch Actions。 -
Node(Action) :定义要启动的节点,包含package(功能包名)、executable(可执行文件名)、name(节点运行名)、parameters(参数文件/字典)和remappings(话题重映射列表)。 -
DeclareLaunchArgument:定义外部传入的 Launch 参数(例如可在命令行通过ros2 launch package_name file.launch.py use_sim_time:=true传入)。 -
LaunchConfiguration:用于在代码中动态引用通过DeclareLaunchArgument声明的变量值。 -
IncludeLaunchDescription:用于嵌套调用并运行其他的.launch.py文件。 -
ExecuteProcess:用于在 Launch 时直接执行标准的 Shell 命令或外部二进制程序(如运行rviz2或命令行工具)。 -
PythonLaunchDescriptionSource:解析并加载指定路径下的Python 格式
.launch.py文件,专门配合IncludeLaunchDescription动作(Action)来实现 Launch 文件的嵌套调用。 -
get_package_share_directory:根据功能包名称,自动动态获取该包在系统中的
share安装路径。
3. 标准 Python Launch 代码示例
python
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition
from launch_ros.actions import Node
def generate_launch_description():
# 1. 获取功能包 share 路径与配置文件位置
pkg_share = get_package_share_directory('my_robot_bringup')
default_param_file = os.path.join(pkg_share, 'config', 'params.yaml')
# 2. 声明外部命令行参数 (Launch Arguments)
use_sim_time_arg = DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation (Gazebo) clock if true'
)
rviz_arg = DeclareLaunchArgument(
'use_rviz',
default_value='true',
description='Whether to start RViz2'
)
# 3. 配置要启动的 Node (雷达驱动节点)
driver_node = Node(
package='my_lidar_driver',
executable='lidar_node',
name='lidar_node',
output='screen',
parameters=[
default_param_file, # 注入 YAML 配置文件
{'use_sim_time': LaunchConfiguration('use_sim_time')} # 动态注入属性
],
remappings=[
('/raw_scan', '/scan') # 话题重映射
]
)
# 4. 条件启动 RViz2 节点
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
condition=IfCondition(LaunchConfiguration('use_rviz')), # 条件分支控制
output='screen'
)
# 5. 返回 LaunchDescription 容器
return LaunchDescription([
use_sim_time_arg,
rviz_arg,
driver_node,
rviz_node
])
二、代码解析
1. 多节点启动
python
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='learning_topic', # 节点所在的功能包
executable='topic_helloworld_pub', # 节点的可执行文件
),
Node( # 配置一个节点的启动
package='learning_topic', # 节点所在的功能包
executable='topic_helloworld_sub', # 节点的可执行文件名
),
])
2. 命令行参数配置
python
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
rviz_config = os.path.join( # 找到配置文件的完整路径
get_package_share_directory('learning_launch'),
'rviz',
'turtle_rviz.rviz'
)
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='rviz2', # 节点所在的功能包
executable='rviz2', # 节点的可执行文件名
name='rviz2', # 对节点重新命名
arguments=['-d', rviz_config] # 加载命令行参数
)
])
3. 资源重映射
python
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
namespace='turtlesim1', # 节点所在的命名空间
executable='turtlesim_node', # 节点的可执行文件名
name='sim' # 对节点重新命名
),
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
namespace='turtlesim2', # 节点所在的命名空间
executable='turtlesim_node', # 节点的可执行文件名
name='sim' # 对节点重新命名
),
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
executable='mimic', # 节点的可执行文件名
name='mimic', # 对节点重新命名
remappings=[ # 资源重映射列表
('/input/pose', '/turtlesim1/turtle1/pose'), # 将/input/pose话题名修改为/turtlesim1/turtle1/pose
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'), # 将/output/cmd_vel话题名修改为/turtlesim2/turtle1/cmd_vel
]
)
])
4. ROS参数设置
python
from launch import LaunchDescription # launch文件的描述类
from launch.actions import DeclareLaunchArgument # 声明launch文件内使用的Argument类
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
background_r_launch_arg = DeclareLaunchArgument(
'background_r', default_value=TextSubstitution(text='0') # 创建一个Launch文件内参数(arg)background_r
)
background_g_launch_arg = DeclareLaunchArgument(
'background_g', default_value=TextSubstitution(text='84') # 创建一个Launch文件内参数(arg)background_g
)
background_b_launch_arg = DeclareLaunchArgument(
'background_b', default_value=TextSubstitution(text='122') # 创建一个Launch文件内参数(arg)background_b
)
return LaunchDescription([ # 返回launch文件的描述信息
background_r_launch_arg, # 调用以上创建的参数(arg)
background_g_launch_arg,
background_b_launch_arg,
Node( # 配置一个节点的启动
package='turtlesim',
executable='turtlesim_node', # 节点所在的功能包
name='sim', # 对节点重新命名
parameters=[{ # ROS参数列表
'background_r': LaunchConfiguration('background_r'), # 创建参数background_r
'background_g': LaunchConfiguration('background_g'), # 创建参数background_g
'background_b': LaunchConfiguration('background_b'), # 创建参数background_b
}]
),
])
5. 加载参数文件
python
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
config = os.path.join( # 找到参数文件的完整路径
get_package_share_directory('learning_launch'),
'config',
'turtlesim.yaml'
)
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
executable='turtlesim_node', # 节点的可执行文件名
namespace='turtlesim2', # 节点所在的命名空间
name='sim', # 对节点重新命名
parameters=[config] # 加载参数文件
)
])
6. Launch文件包含
在复杂的机器人系统中,launch文件会有很多,此时可以使用类似编程中的include机制,让launch文件互相包含。
python
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch.actions import IncludeLaunchDescription # 节点启动的描述类
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import GroupAction # launch文件中的执行动作
from launch_ros.actions import PushRosNamespace # ROS命名空间配置
def generate_launch_description(): # 自动生成launch文件的函数
parameter_yaml = IncludeLaunchDescription( # 包含指定路径下的另外一个launch文件
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/parameters_nonamespace.launch.py'])
)
parameter_yaml_with_namespace = GroupAction( # 对指定launch文件中启动的功能加上命名空间
actions=[
PushRosNamespace('turtlesim2'),
parameter_yaml]
)
return LaunchDescription([ # 返回launch文件的描述信息
parameter_yaml_with_namespace
])