13 KiB
70 · Launch 文件系统(完全指南)
目标:能写 ROS2 Python launch 文件,理解参数覆盖、嵌套、生命周期、条件启动。
目录
- 1. 什么是 launch
- 2. 最小 launch 文件
- 3. 启动方式
- 4. 顶层 Actions
- 5. 事件处理 / 生命周期
- 6. 条件启动
- 7. 命名空间 / 多机器人
- 8. 常用模式(实战)
- 9. ROS2 launch CLI
- 10. 常见坑
- 11. 在本仓库里跑
- 12. 进阶:复杂 launch + 自定义 Action
1. 什么是 launch
ROS1 用 XML 启动多节点繁琐。ROS2 的 launch = Python 脚本:
- 一键启多个节点 + 设参数
- 跨包嵌套复用
- 条件启动、按机器切换配置
- 生命周期管理(启动顺序、超时、失败重试)
2. 最小 launch 文件
# src/<pkg>/launch/my_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
"""ROS2 launch 框架会调这个函数生成 LaunchDescription 实例。"""
return LaunchDescription([
Node(
package='py_pubsub', # 包名
executable='talker', # 入口名(setup.py entry_points)
name='talker_py', # 节点名(可重命名避免冲突)
output='screen', # stdout/stderr 直打到终端
parameters=[ # 参数 dict
{'period_ms': 500, 'topic': 'chatter'},
],
remappings=[ # topic 重映射
('/chatter', '/my_chatter'),
],
),
])
入口约定:函数名必须是 generate_launch_description(),返回 LaunchDescription。
3. 启动方式
# 装包后,ROS 自动索引 launch 到 share/<pkg>/launch/
colcon build --packages-select py_pubsub
# 用包名 + launch 文件名(去 .py 后缀)
ros2 launch py_pubsub my_launch.py
# 加参数(覆盖 LaunchConfiguration)
ros2 launch py_pubsub my_launch.py topic:=hello period_ms:=200
ROS2 找 launch 文件路径:<install>/share/<pkg>/launch/。launch 文件必须装到 share//launch/,否则报 "file not found"。
4. 顶层 Actions
4.1 Node
Node(
package=...,
executable=...,
name=...,
namespace='/', # 命名空间(可分多机器人)
output='screen', # 'screen' / 'log' / 'both'
parameters=[{...}], # 参数 dict
remappings=[(...)], # topic 重映射
arguments=[...], # 透传给 executable 的命令行参数
respawn=False, # 崩溃是否重启
respawn_delay=0,
)
4.2 DeclareLaunchArgument 声明命令行参数
from launch.actions import DeclareLaunchArgument
DeclareLaunchArgument(
'topic', # 参数名
default_value='chatter', # 默认值
description='Topic name', # 说明(ros2 launch --help 显示)
choices=['chatter', 'hello'], # (可选)限定取值
)
4.3 LaunchConfiguration 取值
from launch.substitutions import LaunchConfiguration
topic = LaunchConfiguration('topic')
Node(
...,
parameters=[{'topic': topic}],
)
4.4 IncludeLaunchDescription 嵌套
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
IncludeLaunchDescription(
PythonLaunchDescriptionSource(<other_launch.py>),
launch_arguments={
'topic': 'chatter',
'period_ms': '500',
}.items(),
)
用 FindPackageShare + PathJoinSubstitution(跨包推荐):
from launch_ros.substitutions import FindPackageShare
from launch.substitutions import PathJoinSubstitution
pkg_share = FindPackageShare('py_pubsub')
launch_path = PathJoinSubstitution([pkg_share, 'launch', 'pubsub_launch.py'])
IncludeLaunchDescription(PythonLaunchDescriptionSource(launch_path))
4.5 ExecuteProcess 跑 shell 命令
from launch.actions import ExecuteProcess
ExecuteProcess(
cmd=['xacro', 'arm.xacro'],
output='screen',
)
⚠️
Command(['cat', '/path'])偶尔会丢空格变成'cat/path', 建议launch 启动前open().read()读文件,不要 cat。
4.6 OpaqueFunction 任意函数
from launch.actions import OpaqueFunction
def my_setup(context, *args, **kwargs):
# 自定义逻辑
return [...]
LaunchDescription([
OpaqueFunction(function=my_setup),
Node(...),
])
4.7 TimerAction 延迟启动
from launch.actions import TimerAction
LaunchDescription([
Node(package='srv', executable='server'),
TimerAction(period=2.0, actions=[
Node(package='client', executable='client'),
]),
])
5. 事件处理 / 生命周期
5.1 启动顺序
ROS2 launch 按 LaunchDescription 里 action 的顺序同步启动(默认)。
5.2 事件 handler
from launch import LaunchDescription
from launch.event_handlers import OnProcessExit
from launch.actions import RegisterEventHandler, LogInfo
def generate_launch_description():
server = Node(package='srv', executable='server')
return LaunchDescription([
server,
RegisterEventHandler(
OnProcessExit(
target_action=server,
on_exit=[LogInfo(msg='server exited, shutting down')],
)
),
])
支持的事件: OnProcessStart / OnProcessExit / OnProcessIO 等。
6. 条件启动
from launch.conditions import IfCondition, UnlessCondition
Node(
...,
condition=IfCondition(LaunchConfiguration('use_camera')),
)
CLI 用 :=true / :=false:
ros2 launch my_pkg my.launch.py use_camera:=true
7. 命名空间 / 多机器人
7.1 namespace 隔离
Node(
package='py_pubsub',
executable='talker',
namespace='robot1', # → topic /robot1/chatter
name='talker', # → 节点名 /robot1/talker
)
7.2 push_ros_namespace
from launch.actions import PushRosNamespace
LaunchDescription([
PushRosNamespace('robot1'),
Node(...),
])
8. 常用模式(实战)
8.1 参数文件加载
from ament_index_python.packages import get_package_share_directory
import os
params_file = os.path.join(
get_package_share_directory('my_pkg'),
'config', 'params.yaml'
)
Node(
package='my_pkg',
executable='node',
parameters=[params_file],
)
8.2 多节点同包
nodes = [
Node(package='py_pubsub', executable='talker', name='talker_a', parameters=[{'topic': 'a'}]),
Node(package='py_pubsub', executable='listener', name='listener_a', parameters=[{'topic': 'a'}]),
]
8.3 跨包组合(bringup 模式)
本仓库 src/bringup/launch/full_demo_launch.py:
def _include(pkg_name, launch_file):
pkg = FindPackageShare(pkg_name)
path = PathJoinSubstitution([pkg, 'launch', launch_file])
return IncludeLaunchDescription(PythonLaunchDescriptionSource(path))
def generate_launch_description():
return LaunchDescription([
_include('py_pubsub', 'pubsub_launch.py'),
_include('cpp_pubsub', 'pubsub_launch.py'),
_include('py_srv', 'srv_launch.py'),
_include('py_action_demo', 'action_launch.py'),
_include('cpp_robot_tf2', 'robot_tf2_launch.py'),
_include('py_vision_demo', 'vision_launch.py'),
])
8.4 读 URDF 文件内容
import os
from ament_index_python.packages import get_package_share_directory
pkg_share = get_package_share_directory('cpp_robot_tf2')
urdf_path = os.path.join(pkg_share, 'urdf', 'simple_arm.urdf')
with open(urdf_path, 'r', encoding='utf-8') as f:
robot_description = f.read()
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}],
)
9. ROS2 launch CLI
# 列出所有 launch 文件
ros2 launch --show-args <pkg> <file>
# 看可用参数
ros2 launch <pkg> <file> --show-args
# 调试(详细日志)
ros2 launch -d <pkg> <file>
# 传参
ros2 launch <pkg> <file> topic:=hello period_ms:=200
10. 常见坑
10.1 launch 文件找不到
setup.py的data_files必须包含'share/<pkg>/launch': ['launch/*.py']- colcon build 后 launch 文件没复制到 install/share → 检查 build log
10.2 节点名冲突
同一 ROS Domain 内节点名必须唯一。launch 里 name= 重命名,或用 namespace= 隔离。
10.3 参数失效
parameters=[{'topic': 'chatter'}] 里 topic 是字符串;如果想"取自 LaunchConfiguration":
parameters=[{'topic': LaunchConfiguration('topic')}]
10.4 Command(['cat', path]) 空格丢了
如前所述,改用 launch 启动前 open().read()。
10.5 launch 启动后节点秒退
- 节点 main 函数异常(看日志)
- 参数名拼错(节点没收到参数)
- launch
output='log'看不到错误 → 改output='screen'
10.6 launch 嵌套找不到文件
IncludeLaunchDescription 引用的 launch 文件必须在被包含包的 share/<pkg>/launch/ 下被 colcon 实际安装。
11. 在本仓库里跑
11.1 各包 launch 文件清单
src/
├── py_pubsub/launch/pubsub_launch.py (2 节点,纯 Python)
├── cpp_pubsub/launch/pubsub_launch.py (2 节点,C++ + topic 命令行参数)
├── py_srv/launch/srv_launch.py (1 service)
├── py_action_demo/launch/action_launch.py (1 action server)
├── cpp_robot_tf2/launch/robot_tf2_launch.py (3 节点,URDF + TF)
├── py_vision_demo/launch/vision_launch.py (2 节点,cv_bridge)
├── py_params/launch/params_launch.py (1 节点 + YAML)
├── cpp_custom_interface/(无 launch,直接 ros2 run)
├── py_lifecycle_composable/launch/lifecycle_launch.py
├── cpp_qos_demo/(无 launch,直接 ros2 run)
├── py_overlay_dds/(无 launch,直接 ros2 run)
└── bringup/launch/
├── all_launch.py (4 demo 合一)
├── pubsub_launch.py (4 节点,Topic 跨包跨语言)
├── service_launch.py
├── action_launch.py
├── robot_launch.py (嵌套 cpp_robot_tf2)
├── vision_launch.py (嵌套 py_vision_demo)
└── full_demo_launch.py (11 节点,跨所有包)
11.2 看可用参数
docker exec ros2_dev bash -lc "source /root/ros2_ws/install/setup.bash && ros2 launch bringup full_demo_launch.py --show-args"
12. 进阶:复杂 launch + 自定义 Action
12.1 复杂 launch 模板
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import (
DeclareLaunchArgument,
OpaqueFunction,
RegisterEventHandler,
LogInfo,
)
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.substitutions import (
LaunchConfiguration, Command, FindExecutable, PathJoinSubstitution,
)
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def launch_setup(context, *args, **kwargs):
# 任何动态逻辑
config_file = LaunchConfiguration('config_file').perform(context)
if not os.path.exists(config_file):
return [LogInfo(msg=f'config not found: {config_file}')]
return [...]
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument('config_file', default_value='config.yaml'),
DeclareLaunchArgument('enable_ai', default_value='true'),
Node(
package='my_pkg',
executable='main_node',
parameters=[LaunchConfiguration('config_file')],
condition=IfCondition(LaunchConfiguration('enable_ai')),
),
OpaqueFunction(function=launch_setup),
])
接下来读
| 主题 | 文档 |
|---|---|
| Topic | 20-topics.md |
| Service | 30-services.md |
| Action | 40-actions.md |
| colcon / ament 包构建 | 80-package-build.md |
| 测试策略 | 90-testing.md |
📖 阅读路径导航
💡 这是仓库
doc/下所有文档的推荐阅读顺序。返回 README 总导航⏱ 本文预计阅读时间: 50 分钟 📍 当前位置: 第 18 / 24 篇
- ⏮ 上一篇: 机器人模型描述
- ⏭ 下一篇: colcon / ament 包构建