ROS 2 Launch 详细使用指南
基于源码
src/ros2/launch与src/ros2/launch_ros整理,版本:Foxy
目录
- 系统架构
- launch 文件结构
- Node Action 详解
- Substitution 动态替换表达式
- 条件执行
- 参数声明
- GroupAction 与命名空间
- 嵌套 launch 文件
- 事件处理系统
- 定时器与延迟执行
- 组件化节点
- 生命周期节点管理
- OpaqueFunction 完全自定义执行逻辑
- 进程 Output 控制
- 命令行使用
- 完整实战示例
一、系统架构
┌─────────────────────────────────────────────────────┐
│ LaunchDescription │
│ (动作蓝图:声明要执行的 Action 列表) │
└─────────────────────┬───────────────────────────────┘
│ include_launch_description()
┌─────────────────────▼───────────────────────────────┐
│ LaunchService │
│ (事件循环驱动引擎,asyncio 驱动) │
│ ┌──────────────────────────────────────────────┐ │
│ │ LaunchContext │ │
│ │ launch_configurations(配置字典) │ │
│ │ environment variables(环境变量) │ │
│ │ event handlers(事件处理器注册表) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘ LaunchService 内部使用 asyncio 事件循环,每个进程的启动、I/O、退出都通过异步事件驱动,不会阻塞主循环。
包结构对照
| 包 | 职责 |
|---|---|
launch | 平台无关核心框架(ExecuteProcess、GroupAction、TimerAction 等) |
launch_ros | ROS 2 专用扩展(Node、LifecycleNode、ComposableNodeContainer 等) |
ros2launch | ros2 launch CLI 命令实现 |
二、launch 文件结构
标准入口(被 ros2 launch 命令识别)
from launch import LaunchDescription
def generate_launch_description() -> LaunchDescription:
return LaunchDescription([
# 所有 actions 放在这里
]) 可编程入口(适合复杂逻辑,直接用 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 详解
Node 是 ExecuteProcess 的子类,会自动将 ROS 专有参数(name、namespace、parameters、remappings)转换为 --ros-args 命令行参数。
完整参数说明
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)格式
# 精确匹配节点名称(精确名称优先级高于通配符)
/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 系统以通配符/**作为参数文件的键;当节点名称完全确定时,使用完整节点名作为键,可精确覆盖通配符参数。
传递参数的三种方式
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 参数
from launch.substitutions import LaunchConfiguration
LaunchConfiguration('use_sim_time')
LaunchConfiguration('use_sim_time', default='false') # 提供兜底默认值 4.2 EnvironmentVariable — 读取环境变量
from launch.substitutions import EnvironmentVariable
EnvironmentVariable('ROS_DOMAIN_ID')
EnvironmentVariable('HOME') 4.3 FindExecutable — 查找可执行文件路径
from launch.substitutions import FindExecutable
FindExecutable(name='whoami')
FindExecutable(name='python3') 4.4 PathJoinSubstitution — 路径拼接
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 模块所有符号(sin、pi、sqrt 等)及嵌套 Substitution:
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
from launch.substitutions import ThisLaunchFileDir, ThisLaunchFile
ThisLaunchFileDir() # 当前 launch 文件所在目录
ThisLaunchFile() # 当前 launch 文件的完整路径 4.7 LocalSubstitution — 访问运行时上下文局部变量
常用于事件处理器回调中访问动态上下文(如关闭原因、进程名称):
from launch.substitutions import LocalSubstitution
# 在 OnShutdown 事件处理器中读取关闭原因
LocalSubstitution('event.reason') 4.8 FindPackage(launch_ros 专属)
from launch_ros.substitutions import FindPackage
FindPackage('rviz2') # 返回 rviz2 包的 share 目录路径 Substitution 组合使用
多个 Substitution 可以放在列表中,执行时按顺序拼接为字符串:
Node(
name=['robot_', LaunchConfiguration('robot_id')], # → "robot_0"
namespace=['/fleet/', LaunchConfiguration('fleet_name')],
) 五、条件执行
所有 Action 都支持 condition 参数,条件不满足时该 Action 直接跳过。
IfCondition / UnlessCondition
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
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 无法显示该参数,且可能在运行时报错。
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='参数配置文件路径'
) 命令行传参
# 单个参数
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 配置和环境变量,使内部修改不泄漏到外部作用域。
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 文件
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 文件
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 进程退出时执行动作
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 系统会立即执行它:
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 自定义关闭时行为
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 调整超时时间:
ros2 launch my_pkg my_launch.py sigterm_timeout:=10 sigkill_timeout:=3 十、定时器与延迟执行
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)。
启动时一并加载
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',
) 向已运行的容器动态加载
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(可选) |
十二、生命周期节点管理
LifecycleNode 在 Node 基础上自动订阅 /<name>/transition_event 话题,并创建 /<name>/change_state 服务客户端,实现事件驱动的状态机管理。
完整生命周期编排示例
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。
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 均输出到屏幕和日志 |
Node(..., output='screen') # 终端可见
Node(..., output='log') # 只写日志(适合后台服务)
Node(..., output='both') # 同时输出到屏幕和日志 TTY 模拟
Python 进程默认在非 TTY 环境下使用行缓冲,导致输出不及时。emulate_tty=True 可解决此问题:
Node(..., emulate_tty=True)
# 全局禁用 TTY 模拟
from launch.actions import SetLaunchConfiguration
SetLaunchConfiguration('emulate_tty', 'false') 全局覆盖输出模式(环境变量)
OVERRIDE_LAUNCH_PROCESS_OUTPUT=screen
ros2 launch my_pkg my_launch.py 十五、命令行使用
基本用法
# 从包名启动
ros2 launch <package> <file.launch.py> [arg:=value ...]
# 从绝对路径启动
ros2 launch /abs/path/to/file.launch.py [arg:=value ...] 常用选项
# 查看支持的参数及其默认值、描述
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 嵌套、延迟启动、进程监控的完整示例:
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 | 设置全局话题重映射规则 |