Files
2026-08-05 18:17:25 +08:00

13 KiB

70 · Launch 文件系统(完全指南)

目标:能写 ROS2 Python launch 文件,理解参数覆盖、嵌套、生命周期、条件启动。


目录


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.pydata_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 篇