Skip to content

ROS 2 Launch 详细使用指南

基于源码 src/ros2/launchsrc/ros2/launch_ros 整理,版本:Foxy


目录

  1. 系统架构
  2. launch 文件结构
  3. Node Action 详解
  4. Substitution 动态替换表达式
  5. 条件执行
  6. 参数声明
  7. GroupAction 与命名空间
  8. 嵌套 launch 文件
  9. 事件处理系统
  10. 定时器与延迟执行
  11. 组件化节点
  12. 生命周期节点管理
  13. OpaqueFunction 完全自定义执行逻辑
  14. 进程 Output 控制
  15. 命令行使用
  16. 完整实战示例

一、系统架构

┌─────────────────────────────────────────────────────┐
│                   LaunchDescription                          │
│  (动作蓝图:声明要执行的 Action 列表)                      │
└─────────────────────┬───────────────────────────────┘
                       │ include_launch_description()
┌─────────────────────▼───────────────────────────────┐
│                    LaunchService                             │
│  (事件循环驱动引擎,asyncio 驱动)                          │
│  ┌──────────────────────────────────────────────┐   │
│  │              LaunchContext                           │   │
│  │  launch_configurations(配置字典)                  │   │
│  │  environment variables(环境变量)                  │   │
│  │  event handlers(事件处理器注册表)                │   │
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

LaunchService 内部使用 asyncio 事件循环,每个进程的启动、I/O、退出都通过异步事件驱动,不会阻塞主循环。

包结构对照

职责
launch 平台无关核心框架(ExecuteProcessGroupActionTimerAction 等)
launch_ros ROS 2 专用扩展(NodeLifecycleNodeComposableNodeContainer 等)
ros2launch ros2 launch CLI 命令实现

二、launch 文件结构

标准入口(被 ros2 launch 命令识别)

python
from launch import LaunchDescription

def generate_launch_description() -> LaunchDescription:
    return LaunchDescription([
        # 所有 actions 放在这里
    ])

可编程入口(适合复杂逻辑,直接用 Python 运行)

python
import sys
from launch import LaunchService, LaunchDescription

def main():
    ld = LaunchDescription()
    # 动态添加 actions
    ld.add_action(...)

    ls = LaunchService(argv=sys.argv[1:])
    ls.include_launch_description(ld)
    return ls.run()

if __name__ == '__main__':
    sys.exit(main())

注意generate_launch_description()ros2 launch 识别的固定入口名,必须返回 LaunchDescription 对象。main() 形式需要手动管理 LaunchService,适合需要在启动前打印调试信息(如 LaunchIntrospector)的场景。


三、Node Action 详解

NodeExecuteProcess 的子类,会自动将 ROS 专有参数(namenamespaceparametersremappings)转换为 --ros-args 命令行参数。

完整参数说明

python
from launch_ros.actions import Node

Node(
    package='my_pkg',            # ROS 包名(必填,除非 executable 是绝对路径)
    executable='my_node',        # 可执行文件名(必填)
    name='my_node_renamed',      # 覆盖节点名称(可选)
    namespace='/my_ns',          # 命名空间,绝对路径以 / 开头(可选)
    exec_name='label_in_log',    # 进程在日志中的标签,默认取 executable 的 basename
    parameters=[                 # 参数列表(可混合文件路径和字典)
        '/path/to/params.yaml',
        {'use_sim_time': True, 'max_speed': 1.5},
        {'nested': {'key': 'value'}},
    ],
    remappings=[                 # 话题/服务重映射(有序,顺序影响结果)
        ('chatter', 'my_chatter'),
        ('/tf', '/robot/tf'),
    ],
    arguments=['--ros-args', '--log-level', 'debug'],  # 额外命令行参数
    output='screen',             # 输出方式: 'screen' | 'log' | 'both'
    emulate_tty=True,            # 模拟 TTY,使 Python 进程输出不被缓冲
    respawn=True,                # 进程异常退出后自动重启
    respawn_delay=2.0,           # 重启前等待秒数
    prefix='gdb -ex run --args', # 进程前缀,用于 gdb/valgrind 等调试工具
    cwd='/tmp',                  # 工作目录
    env={'MY_VAR': 'value'},     # 完全替换环境变量(None 则继承当前环境)
    additional_env={'MY_VAR': 'value'},  # 在当前环境基础上追加
    shell=False,                 # 是否通过 shell 执行
    log_cmd=True,                # 打印展开后的完整命令(调试替换表达式用)
    on_exit=[...],               # 进程退出时执行的 actions
    condition=IfCondition(...),  # 条件执行
)

参数文件(YAML)格式

yaml
# 精确匹配节点名称(精确名称优先级高于通配符)
/my_namespace/my_node_name:
  ros__parameters:
    param_a: 42
    param_b: "hello"
    nested_param:
      sub_key: 3.14

# 通配符(匹配所有节点)
/**:
  ros__parameters:
    use_sim_time: false

Node 未指定 name 时,launch 系统以通配符 /** 作为参数文件的键;当节点名称完全确定时,使用完整节点名作为键,可精确覆盖通配符参数。

传递参数的三种方式

python
Node(
    parameters=[
        # 方式 1:YAML 文件路径(字符串或 Substitution)
        '/abs/path/to/params.yaml',
        PathJoinSubstitution([FindPackage('my_pkg'), 'config', 'params.yaml']),

        # 方式 2:Python 字典(在运行时写入临时 YAML 文件)
        {'use_sim_time': LaunchConfiguration('use_sim_time')},

        # 方式 3:单个 Parameter 对象(通过 -p key:=value 传递)
        Parameter('log_level', 'info'),
    ]
)

四、Substitution——动态替换表达式

Substitution 是惰性求值的占位符,在 Action 真正执行时才被解析为字符串,可以任意嵌套组合。

4.1 LaunchConfiguration — 读取 launch 参数

python
from launch.substitutions import LaunchConfiguration

LaunchConfiguration('use_sim_time')
LaunchConfiguration('use_sim_time', default='false')  # 提供兜底默认值

4.2 EnvironmentVariable — 读取环境变量

python
from launch.substitutions import EnvironmentVariable

EnvironmentVariable('ROS_DOMAIN_ID')
EnvironmentVariable('HOME')

4.3 FindExecutable — 查找可执行文件路径

python
from launch.substitutions import FindExecutable

FindExecutable(name='whoami')
FindExecutable(name='python3')

4.4 PathJoinSubstitution — 路径拼接

python
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackage

PathJoinSubstitution([
    FindPackage('my_pkg'),   # 返回包的 share 目录
    'config',
    'my_params.yaml'
])

4.5 PythonExpression — 执行 Python 表达式

表达式在沙盒中执行,可使用 math 模块所有符号(sinpisqrt 等)及嵌套 Substitution:

python
from launch.substitutions import PythonExpression, LaunchConfiguration

# 逻辑运算
PythonExpression([
    "'true' if '", LaunchConfiguration('use_gpu'), "' == 'true' else 'false'"
])

# 数学运算
PythonExpression(['1.0 / ', LaunchConfiguration('hz')])

# 字符串包含判断
PythonExpression([
    "'", LaunchConfiguration('mode'), "' in ['debug', 'verbose']"
])

4.6 ThisLaunchFileDir / ThisLaunchFile

python
from launch.substitutions import ThisLaunchFileDir, ThisLaunchFile

ThisLaunchFileDir()  # 当前 launch 文件所在目录
ThisLaunchFile()     # 当前 launch 文件的完整路径

4.7 LocalSubstitution — 访问运行时上下文局部变量

常用于事件处理器回调中访问动态上下文(如关闭原因、进程名称):

python
from launch.substitutions import LocalSubstitution

# 在 OnShutdown 事件处理器中读取关闭原因
LocalSubstitution('event.reason')

4.8 FindPackage(launch_ros 专属)

python
from launch_ros.substitutions import FindPackage

FindPackage('rviz2')   # 返回 rviz2 包的 share 目录路径

Substitution 组合使用

多个 Substitution 可以放在列表中,执行时按顺序拼接为字符串:

python
Node(
    name=['robot_', LaunchConfiguration('robot_id')],   # → "robot_0"
    namespace=['/fleet/', LaunchConfiguration('fleet_name')],
)

五、条件执行

所有 Action 都支持 condition 参数,条件不满足时该 Action 直接跳过。

IfCondition / UnlessCondition

python
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import LaunchConfiguration

# 当参数字符串为 'true'/'1'/'yes' 时执行
Node(
    package='rviz2', executable='rviz2',
    condition=IfCondition(LaunchConfiguration('launch_rviz'))
)

# 当参数字符串为 'false'/'0'/'no' 时执行
Node(
    package='fake_node', executable='fake',
    condition=UnlessCondition(LaunchConfiguration('use_real_robot'))
)

# 使用 PythonExpression 组合条件
Node(
    package='my_pkg', executable='gpu_node',
    condition=IfCondition(PythonExpression([
        "'", LaunchConfiguration('mode'), "' in ['gpu', 'turbo']"
    ]))
)

LaunchConfigurationEquals / LaunchConfigurationNotEquals

python
from launch.conditions import LaunchConfigurationEquals, LaunchConfigurationNotEquals

Node(
    package='sim_pkg', executable='gazebo',
    condition=LaunchConfigurationEquals('backend', 'gazebo')
)

Node(
    package='real_pkg', executable='hardware_driver',
    condition=LaunchConfigurationNotEquals('backend', 'sim')
)

六、参数声明——DeclareLaunchArgument

必须在 LaunchDescription 顶层无条件声明,否则 ros2 launch --show-args 无法显示该参数,且可能在运行时报错。

python
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.substitutions import FindPackage

# 必填参数(无默认值,未提供时报错)
DeclareLaunchArgument(
    'robot_name',
    description='机器人名称,用于命名空间前缀'
)

# 可选参数(有默认值)
DeclareLaunchArgument(
    'use_sim_time',
    default_value='false',
    description='是否使用仿真时钟',
    choices=['true', 'false']   # 限制合法值,传入非法值时报错
)

# 默认值可以是 Substitution(在运行时才求值)
DeclareLaunchArgument(
    'config_file',
    default_value=PathJoinSubstitution([
        FindPackage('my_pkg'), 'config', 'default.yaml'
    ]),
    description='参数配置文件路径'
)

命令行传参

bash
# 单个参数
ros2 launch my_pkg my_launch.py use_sim_time:=true

# 多个参数
ros2 launch my_pkg my_launch.py use_sim_time:=true robot_name:=robot1 launch_rviz:=false

# 查看所有可用参数
ros2 launch my_pkg my_launch.py --show-args

七、GroupAction 与命名空间

GroupAction 默认 scoped=True,通过 Push/Pop 机制自动隔离 launch 配置和环境变量,使内部修改不泄漏到外部作用域。

python
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace, Node

# 给一组节点统一加 ROS 命名空间
GroupAction([
    PushRosNamespace('robot1'),          # 在 GroupAction 结束时自动弹出
    Node(package='my_pkg', executable='sensor'),
    Node(package='my_pkg', executable='controller'),
])

# 嵌套命名空间
GroupAction([
    PushRosNamespace('fleet'),
    GroupAction([
        PushRosNamespace('robot1'),       # 最终命名空间:/fleet/robot1
        Node(package='my_pkg', executable='node_a'),
    ]),
    GroupAction([
        PushRosNamespace('robot2'),       # 最终命名空间:/fleet/robot2
        Node(package='my_pkg', executable='node_b'),
    ]),
])

# 不隔离作用域(内部修改会影响后续 actions)
GroupAction(
    actions=[
        PushRosNamespace('persistent_ns'),
        Node(package='my_pkg', executable='node_c'),
    ],
    scoped=False
)

PushRosNamespace 本质上是设置 launch_configurations['ros_namespace']Node 在执行时读取此值并与节点自身的 namespace 参数合并,计算出最终绝对命名空间。


八、嵌套 launch 文件

包含同目录下的 launch 文件

python
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import ThisLaunchFileDir

IncludeLaunchDescription(
    PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/sensors.launch.py']),
    launch_arguments={
        'use_sim_time': LaunchConfiguration('use_sim_time'),
        'sensor_config': '/etc/sensor.yaml',
    }.items()
)

包含其他包的 launch 文件

python
from launch.launch_description_sources import AnyLaunchDescriptionSource
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackage

# AnyLaunchDescriptionSource 自动识别 .py / .xml / .yaml 格式
IncludeLaunchDescription(
    AnyLaunchDescriptionSource([
        FindPackage('nav2_bringup'), '/launch/navigation_launch.py'
    ]),
    launch_arguments=[
        ('map', '/maps/office.yaml'),
        ('use_sim_time', LaunchConfiguration('use_sim_time')),
    ]
)

注意事项

  • 传入 launch_arguments 的键必须与被包含文件中 DeclareLaunchArgument 声明的名称一致,否则会被设置为全局 launch 配置(不报错,但可能产生意外行为)。
  • 被包含文件中无默认值的必填参数,若调用者未传入,会在执行时抛出 RuntimeError

九、事件处理系统

Launch 的事件系统是整个框架的核心,所有进程生命周期管理都通过事件驱动实现。

9.1 可用事件处理器

事件处理器 触发时机 关键参数
OnProcessStart 进程启动后 target_action, on_start
OnProcessExit 进程退出后 target_action, on_exit
OnProcessIO 进程产生 stdout/stderr 输出时 target_action, on_stdout, on_stderr
OnShutdown 整个 launch 系统关闭时 on_shutdown
OnExecutionComplete action 执行完成时 target_action, on_completion
OnStateTransition (launch_ros) 生命周期节点状态转换时 target_lifecycle_node, goal_state, entities

省略 target_action 时,事件处理器监听所有该类型的进程事件。

9.2 进程退出时执行动作

python
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.event_handlers import OnProcessExit, OnProcessStart
from launch.events import Shutdown
from launch_ros.actions import Node

server = Node(package='my_pkg', executable='server')
client = Node(package='my_pkg', executable='client')

# 服务端进程启动后才启动客户端(避免竞态条件)
server_start_handler = RegisterEventHandler(
    OnProcessStart(
        target_action=server,
        on_start=[
            LogInfo(msg='服务端已启动,正在启动客户端...'),
            client,
        ]
    )
)

# 服务端退出时关闭整个 launch
server_exit_handler = RegisterEventHandler(
    OnProcessExit(
        target_action=server,
        on_exit=[
            LogInfo(msg='服务端已退出'),
            EmitEvent(event=Shutdown(reason='server exited')),
        ]
    )
)

9.3 捕获进程输出

事件处理回调可以直接返回 Action,launch 系统会立即执行它:

python
from launch.event_handlers import OnProcessIO
from launch.actions import RegisterEventHandler, EmitEvent
from launch.events import Shutdown

my_proc = ExecuteProcess(cmd=['./my_process'])

def handle_stdout(event):
    line = event.text.decode().strip()
    print(f'[捕获] {line}')
    if 'FATAL' in line:
        # 返回 Action,会被立即执行
        return EmitEvent(event=Shutdown(reason=f'进程报错: {line}'))

RegisterEventHandler(OnProcessIO(
    target_action=my_proc,
    on_stdout=handle_stdout,
    on_stderr=handle_stdout,
))

9.4 自定义关闭时行为

python
from launch.event_handlers import OnShutdown
from launch.substitutions import LocalSubstitution

RegisterEventHandler(
    OnShutdown(
        on_shutdown=[
            LogInfo(msg=[
                '收到关闭请求,原因:',
                LocalSubstitution('event.reason'),
            ]),
            # 在此执行清理 actions...
        ]
    )
)

9.5 进程关闭信号链

ExecuteProcess(以及 Node)内置三阶段信号链,不需要手动实现:

收到 Shutdown 事件

    ├─→ 发送 SIGINT(来自用户 Ctrl+C)

    ├─→ 等待 sigterm_timeout 秒(默认 5s)
    │       └─→ 超时则发送 SIGTERM

    └─→ 再等待 sigkill_timeout 秒(默认 5s)
            └─→ 超时则发送 SIGKILL

调整超时时间:

bash
ros2 launch my_pkg my_launch.py sigterm_timeout:=10 sigkill_timeout:=3

十、定时器与延迟执行

python
from launch.actions import TimerAction
from launch_ros.actions import Node

# 5 秒后启动节点
TimerAction(
    period=5.0,
    actions=[
        Node(package='my_pkg', executable='delayed_node')
    ]
)

# 使用 LaunchConfiguration 作为延迟时间
TimerAction(
    period=LaunchConfiguration('startup_delay', default='2.0'),
    actions=[...]
)

# cancel_on_shutdown=False:收到 Shutdown 事件后不取消
# 适用于关机清理逻辑
TimerAction(
    period=1.0,
    actions=[cleanup_action],
    cancel_on_shutdown=False,
)

TimerAction 是一次性的(one-shot),触发后不会重复。计时器基于 asyncio,精度受事件循环负载影响。


十一、组件化节点(Composable Nodes)

多个组件节点共享同一进程(component_container),消除进程间通信开销,可启用零拷贝(intra-process communication)。

启动时一并加载

python
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode

container = ComposableNodeContainer(
    name='image_pipeline_container',   # 必填
    namespace='/',                     # 必填
    package='rclcpp_components',
    executable='component_container',
    composable_node_descriptions=[
        ComposableNode(
            package='image_proc',
            plugin='image_proc::RectifyNode',
            name='rectify_node',
            namespace='camera',
            remappings=[('image', 'image_raw')],
            parameters=[{'use_sim_time': True}],
        ),
        ComposableNode(
            package='image_proc',
            plugin='image_proc::ResizeNode',
            name='resize_node',
            namespace='camera',
            extra_arguments=[{'use_intra_process_comms': True}],  # 启用零拷贝
        ),
    ],
    output='screen',
)

向已运行的容器动态加载

python
from launch_ros.actions import LoadComposableNodes
from launch_ros.descriptions import ComposableNode

LoadComposableNodes(
    target_container='/image_pipeline_container',
    composable_node_descriptions=[
        ComposableNode(
            package='my_pkg',
            plugin='my_pkg::MyPlugin',
            name='dynamic_node',
        ),
    ],
)

ComposableNode 参数说明

参数 说明
package 插件所在 ROS 包(必填)
plugin 插件类名,完整命名空间(必填)如 image_proc::RectifyNode
name 节点名称(可选)
namespace 命名空间(可选)
parameters 参数列表,同 Node(可选)
remappings 重映射规则,同 Node(可选)
extra_arguments 容器专用参数,如 use_intra_process_comms(可选)

十二、生命周期节点管理

LifecycleNodeNode 基础上自动订阅 /<name>/transition_event 话题,并创建 /<name>/change_state 服务客户端,实现事件驱动的状态机管理。

完整生命周期编排示例

python
from launch import LaunchDescription
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.events import matches_action
from launch_ros.actions import LifecycleNode
from launch_ros.events.lifecycle import ChangeState
from launch_ros.event_handlers import OnStateTransition
import lifecycle_msgs.msg

def generate_launch_description():
    talker = LifecycleNode(
        name='talker', namespace='',
        package='lifecycle', executable='lifecycle_talker', output='screen'
    )

    # inactive → activate
    on_inactive = RegisterEventHandler(
        OnStateTransition(
            target_lifecycle_node=talker,
            goal_state='inactive',
            entities=[
                LogInfo(msg="talker 已到达 inactive,正在激活..."),
                EmitEvent(event=ChangeState(
                    lifecycle_node_matcher=matches_action(talker),
                    transition_id=lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE,
                )),
            ]
        )
    )

    # active → 启动 listener
    on_active = RegisterEventHandler(
        OnStateTransition(
            target_lifecycle_node=talker,
            goal_state='active',
            entities=[
                LogInfo(msg="talker 已激活,启动 listener..."),
                LifecycleNode(
                    name='listener', namespace='',
                    package='lifecycle', executable='lifecycle_listener', output='screen'
                ),
            ]
        )
    )

    # 触发 configure 转换
    configure_event = EmitEvent(event=ChangeState(
        lifecycle_node_matcher=matches_action(talker),
        transition_id=lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE,
    ))

    return LaunchDescription([
        on_inactive,    # 必须在节点启动前注册
        on_active,
        talker,
        configure_event,
    ])

生命周期状态机

未配置 (Unconfigured)
    │ TRANSITION_CONFIGURE

非活跃 (Inactive)
    │ TRANSITION_ACTIVATE

活跃 (Active)
    │ TRANSITION_DEACTIVATE

非活跃 (Inactive)
    │ TRANSITION_CLEANUP

未配置 (Unconfigured)
    │ TRANSITION_UNCONFIGURED_SHUTDOWN

终止 (Finalized)

十三、OpaqueFunction — 完全自定义执行逻辑

OpaqueFunction 接受一个 Python 函数,在执行时传入完整的 LaunchContext,可以访问所有运行时状态,动态生成并返回 actions。

python
from launch.actions import OpaqueFunction
from launch.launch_context import LaunchContext
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node

def setup_nodes(context: LaunchContext):
    robot_count = int(LaunchConfiguration('robot_count').perform(context))
    config_dir = LaunchConfiguration('config_dir').perform(context)

    nodes = []
    for i in range(robot_count):
        nodes.append(Node(
            package='my_pkg',
            executable='robot_controller',
            namespace=f'robot_{i}',
            name='controller',
            parameters=[f'{config_dir}/robot_{i}.yaml'],
        ))
    return nodes

# OpaqueFunction 会执行函数并将返回的 actions 加入执行队列
OpaqueFunction(function=setup_nodes)

# 也可以传递额外参数
def setup_with_extra(context, prefix, count):
    return [Node(package='my_pkg', executable='node',
                 namespace=f'{prefix}_{i}') for i in range(count)]

OpaqueFunction(
    function=setup_with_extra,
    args=('robot', 3)
)

十四、进程 Output 控制

output 参数

效果
'log' 默认。stdout 输出到日志文件,stderr 同时输出到屏幕和日志
'screen' stdout 和 stderr 均输出到屏幕
'both' stdout 和 stderr 均输出到屏幕和日志
python
Node(..., output='screen')   # 终端可见
Node(..., output='log')      # 只写日志(适合后台服务)
Node(..., output='both')     # 同时输出到屏幕和日志

TTY 模拟

Python 进程默认在非 TTY 环境下使用行缓冲,导致输出不及时。emulate_tty=True 可解决此问题:

python
Node(..., emulate_tty=True)

# 全局禁用 TTY 模拟
from launch.actions import SetLaunchConfiguration
SetLaunchConfiguration('emulate_tty', 'false')

全局覆盖输出模式(环境变量)

bash
OVERRIDE_LAUNCH_PROCESS_OUTPUT=screen 
ros2 launch my_pkg my_launch.py

十五、命令行使用

基本用法

bash
# 从包名启动
ros2 launch <package> <file.launch.py> [arg:=value ...]

# 从绝对路径启动
ros2 launch /abs/path/to/file.launch.py [arg:=value ...]

常用选项

bash
# 查看支持的参数及其默认值、描述
ros2 launch my_pkg my_launch.py --show-args

# 打印 LaunchDescription 结构(不实际执行)
ros2 launch my_pkg my_launch.py --print-description

# 调试模式(更详细的系统日志)
ros2 launch my_pkg my_launch.py --debug

# 调整进程关闭超时(秒)
ros2 launch my_pkg my_launch.py sigterm_timeout:=10 sigkill_timeout:=3

# 为所有进程添加前缀(用于调试工具)
ros2 launch my_pkg my_launch.py 'launch-prefix:=gdb -ex run --args'
ros2 launch my_pkg my_launch.py 'launch-prefix:=valgrind --leak-check=full'

# 非交互模式(禁用 Ctrl+C 交互处理,适合 CI)
ros2 launch my_pkg my_launch.py --noninteractive

十六、完整实战示例

一个包含参数声明、条件节点、命名空间分组、子 launch 嵌套、延迟启动、进程监控的完整示例:

python
from launch import LaunchDescription
from launch.actions import (
    DeclareLaunchArgument, GroupAction,
    IncludeLaunchDescription, RegisterEventHandler,
    TimerAction, EmitEvent, LogInfo, OpaqueFunction,
)
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.events import Shutdown
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import (
    LaunchConfiguration, PathJoinSubstitution,
    PythonExpression, ThisLaunchFileDir,
)
from launch_ros.actions import Node, PushRosNamespace, ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
from launch_ros.substitutions import FindPackage


def generate_launch_description():

    # ─── 1. 参数声明 ────────────────────────────────────────────────
    args = [
        DeclareLaunchArgument(
            'robot_name', default_value='my_robot',
            description='机器人名称,用于 ROS 命名空间'
        ),
        DeclareLaunchArgument(
            'use_sim_time', default_value='false', choices=['true', 'false'],
            description='是否使用仿真时钟'
        ),
        DeclareLaunchArgument(
            'launch_rviz', default_value='true', choices=['true', 'false'],
            description='是否启动 RViz2'
        ),
        DeclareLaunchArgument(
            'config_dir',
            default_value=PathJoinSubstitution([FindPackage('my_pkg'), 'config']),
            description='配置文件目录'
        ),
    ]

    # ─── 2. 常用变量 ────────────────────────────────────────────────
    robot_name   = LaunchConfiguration('robot_name')
    use_sim_time = LaunchConfiguration('use_sim_time')
    config_dir   = LaunchConfiguration('config_dir')

    # ─── 3. 主节点组(带命名空间隔离) ──────────────────────────────
    controller_node = Node(
        package='my_pkg', executable='controller',
        name='controller',
        parameters=[
            PathJoinSubstitution([config_dir, 'ctrl.yaml']),
            {'use_sim_time': PythonExpression(["'", use_sim_time, "' == 'true'"])},
        ],
        output='screen',
        respawn=True,
        respawn_delay=2.0,
    )

    main_group = GroupAction([
        PushRosNamespace(robot_name),
        controller_node,
        Node(
            package='my_pkg', executable='sensor_node',
            name='sensor',
            remappings=[('raw_data', 'processed_data')],
            output='screen',
        ),
    ])

    # ─── 4. 图像处理组件容器 ────────────────────────────────────────
    image_container = ComposableNodeContainer(
        name='image_container',
        namespace='/',
        package='rclcpp_components',
        executable='component_container',
        composable_node_descriptions=[
            ComposableNode(
                package='image_proc',
                plugin='image_proc::RectifyNode',
                name='rectify',
                extra_arguments=[{'use_intra_process_comms': True}],
            ),
        ],
        output='screen',
    )

    # ─── 5. 可选 RViz ───────────────────────────────────────────────
    rviz_node = Node(
        package='rviz2', executable='rviz2',
        name='rviz2',
        condition=IfCondition(LaunchConfiguration('launch_rviz')),
        arguments=['-d', PathJoinSubstitution([config_dir, 'view.rviz'])],
        output='screen',
    )

    # ─── 6. 延迟 3 秒后包含传感器子 launch ─────────────────────────
    delayed_sensors = TimerAction(
        period=3.0,
        actions=[
            IncludeLaunchDescription(
                PythonLaunchDescriptionSource([
                    ThisLaunchFileDir(), '/sensors.launch.py'
                ]),
                launch_arguments={
                    'use_sim_time': use_sim_time,
                }.items(),
            )
        ]
    )

    # ─── 7. 控制器退出时关闭整个 launch ─────────────────────────────
    watchdog = RegisterEventHandler(
        OnProcessExit(
            target_action=controller_node,
            on_exit=[
                LogInfo(msg='控制器已退出,关闭 launch 系统...'),
                EmitEvent(event=Shutdown(reason='controller exited')),
            ]
        )
    )

    return LaunchDescription(
        args + [
            main_group,
            image_container,
            rviz_node,
            delayed_sensors,
            watchdog,
        ]
    )

附录:全量 Action 速查表

launch 包(通用)

Action 说明
ExecuteProcess 启动任意系统进程
Node (launch_ros) 启动 ROS 节点
IncludeLaunchDescription 嵌套包含另一个 launch 文件
GroupAction 将多个 action 打组,支持作用域隔离
DeclareLaunchArgument 声明命令行可传入的参数
SetLaunchConfiguration 在运行时设置 launch 配置变量
SetEnvironmentVariable 设置环境变量
UnsetEnvironmentVariable 删除环境变量
RegisterEventHandler 注册事件监听器
UnregisterEventHandler 注销事件监听器
EmitEvent 手动触发一个事件
LogInfo 打印 INFO 级别日志
TimerAction 延迟指定秒数后执行一组 actions(一次性)
OpaqueFunction 执行任意 Python 函数,动态返回 actions
Shutdown 触发整个 launch 系统关闭

launch_ros 包(ROS 专用)

Action 说明
Node 启动 ROS 节点
LifecycleNode 启动生命周期节点,支持状态机事件驱动
ComposableNodeContainer 启动 rclcpp 组件容器
LoadComposableNodes 向已运行容器动态加载组件节点
PushRosNamespace 压入 ROS 命名空间(在 GroupAction 结束时自动弹出)
SetParameter 设置 ROS 全局参数
SetRemap 设置全局话题重映射规则

基于 VitePress 构建