ROS 2 的 launch 文件

ROS 2 的 Launch 文件支持 Python(.launch.pyXML(.launch.xmlYAML(.launch.yaml 三种格式。其中,Python 格式是官方和社区的首选,因为它提供了无与伦比的灵活性。

  • 为什么首选 Python?
    • 灵活性高:可以使用完整的 Python 逻辑,如条件判断、循环、函数调用,实现动态和复杂的启动逻辑。
    • 社区标准:已成为 ROS 2 社区的事实标准,资源和示例最丰富。
    • 功能强大:能适配从简单到复杂的所有项目场景。

注意:XML 和 YAML 格式主要用于极简单的场景,在复杂项目中不推荐使用。


一、launch 文件的核心概念

  • launch 文件是纯 Python 脚本:无需编译,ros2 launch 直接解释执行。
  • generate_launch_description():每个 .launch.py 都必须定义这个函数,它是 launch 文件的入口点,返回值是一个 LaunchDescription 对象。
  • LaunchDescription:一个动作(Actions)列表,定义了要启动的节点、要设置的环境变量、要执行的进程等。
  • Action(动作)LaunchDescription 里的每个元素。最常用的是 Node(启动一个节点),此外还有 DeclareLaunchArgument(声明启动参数)、ExecuteProcess(执行任意命令)等。

基本骨架如下:

from launch import LaunchDescription

def generate_launch_description():
    # 在这里定义你的动作 (Actions)
    return LaunchDescription([
        # 动作列表,例如启动一个节点
    ])

二、基础启动 — 启动单个节点

2.1 写法 A:完整限定名

使用 import launch_ros.actions,调用时写全名 launch_ros.actions.Node

# import 方式一:import launch_ros.actions,使用时写全名 launch_ros.actions.Node
from launch import LaunchDescription
import launch_ros.actions

def generate_launch_description():
    # 在这里定义你的动作 (Actions)
    return LaunchDescription([
        # 动作列表,例如启动一个节点
        # --- 最小示例:只指定 package + executable 两个必填字段 ---
        launch_ros.actions.Node(
            # ---- 必须的字段 ----
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)
        ),
    ])

2.2 写法 B:直接导入 Node 类(推荐)

# import 方式二(推荐):from ... import Node,使用时直接写 Node
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    # 在这里定义你的动作 (Actions)
    return LaunchDescription([
        # 动作列表,例如启动一个节点
        # --- 最小示例:只指定 package + executable ---
        Node(
            # ---- 必须的字段 ----
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)
        )
    ])

与写法 A 效果完全一样,只是代码更简洁、更符合规范。两种写法二选一即可,本文其余章节均采用写法 B。

保存与运行:把上面的内容(写法 A 或 B 任选其一)保存为 ~/ros2_launch_demo/01_basic.launch.py,然后在终端运行:

ros2 launch ~/ros2_launch_demo/01_basic.launch.py

运行后会弹出一个 turtlesim 窗口。本文的示例文件统一放在 ~/ros2_launch_demo/ 目录下,都可以直接复制粘贴保存后运行。


三、完整节点配置 — 展示所有常用字段

本节把所有字段堆在一起展示,实际使用时可按需删减。

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    # 在这里定义你的动作 (Actions)
    return LaunchDescription([
        # 动作列表,例如启动一个节点
        Node(
            # ---- 必须的字段 ----
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)

            # ---- 可选字段 ----
            name='my_turtle',             # 给节点改名(不指定则默认用 executable 的名字)
            namespace='ns1',              # 放入命名空间 → 节点全名变成 /ns1/my_turtle
                                          # 话题也随之变成 /ns1/...,用于多实例隔离
            output='screen',              # 将 stdout/stderr 打印到终端(也可选 'log' 或 'both')

            # ---- 设置 ROS 参数 ----
            #   参数会在节点启动时加载,可用 ros2 param list 查看
            #   数字会按 YAML 规则处理:字符串数字(如 '255')会自动转成 int
            parameters=[{
                'background_r': 255,      # 背景色 R 通道 (0-255)
                'background_g': 165,      # 背景色 G 通道 (0-255)
                'background_b': 0,        # 背景色 B 通道 (0-255)
            }],

            # ---- 话题重映射 (Remapping) ----
            #   每个元素是 (旧话题, 新话题),相当于把 /cmd_vel "改名为" /turtle1/cmd_vel
            remappings=[
                ('/cmd_vel', '/turtle1/cmd_vel'),
            ],

            # ---- 额外 ROS 参数(透传给 --ros-args) ----
            #   arguments 里的内容会"原样"拼接到节点命令后面(等价于 ros2 run 之后
            #   再加的参数),必须以 --ros-args 开头,用来控制 ROS 2 运行时的行为。
            #   常见用法:
            #     --log-level INFO          设置日志级别 (DEBUG/INFO/WARN/ERROR/FATAL)
            #     --params-file config.yaml 从 YAML 文件加载参数(等价于 parameters 字段)
            #     -p key:=value             直接设置单条参数
            #     -r 旧话题:=新话题         话题重映射(等价于 remappings 字段)
            #   注意:parameters / remappings 字段本质上是这些 --ros-args 的
            #   "结构化便捷写法",最终都会被转换成 --ros-args 透传给节点。
            arguments=['--ros-args', '--log-level', 'INFO'],

        ),
    ])

保存与运行:保存为 ~/ros2_launch_demo/02_full.launch.py,运行:

ros2 launch ~/ros2_launch_demo/02_full.launch.py

这次节点名变成了 /ns1/my_turtle(命名空间 + 改名生效),背景色是橙色。


四、多节点启动 + 命名空间隔离

规则:

  • 同一 package + 同一 executable + 同一 name → 需要不同 namespace
  • 同一 package + 同一 executable + 同一 namespace → 需要不同 name

4.1 写法 A:节点直接内联在 LaunchDescription 列表中

from launch import LaunchDescription
import launch_ros.actions  # 写法 A:import 包,使用时写全名 launch_ros.actions.Node

def generate_launch_description():
    return LaunchDescription([
        # ---- 第一个节点:放入 turtlesim1 命名空间 ----
        launch_ros.actions.Node(
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)
            namespace='turtlesim1',       # 节点全名变成 /turtlesim1/turtle1
                                          # 通过 namespace 隔离,两个同类型节点可以共存
            name='turtle1',               # 节点名
            parameters=[{'background_r': 255, 'background_g': 165, 'background_b': 0}],  # 设置背景色参数
        ),
        # ---- 第二个节点:放入 turtlesim2 命名空间 ----
        launch_ros.actions.Node(
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名
            namespace='turtlesim2',       # 与第一个节点同名,靠不同 namespace 区分
            name='turtle1',               # 节点名(与第一个节点相同)
            parameters=[{'background_r': 0, 'background_g': 255, 'background_b': 100}],  # 设置背景色参数
        ),
    ])

4.2 写法 B:先把每个节点赋给变量,再放入列表(便于复用/修改)

from launch import LaunchDescription
from launch_ros.actions import Node  # 写法 B:直接导入 Node 类,使用时写 Node

def generate_launch_description():
    # 先把每个节点赋给变量,再统一放入列表(便于复用/修改)
    turtlesim1_node = Node(           # 第一个节点:放入 turtlesim1 命名空间
        package='turtlesim',          # 节点所在的功能包名
        executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)
        namespace='turtlesim1',       # 节点全名变成 /turtlesim1/turtle1
                                      # 通过 namespace 隔离,两个同类型节点可以共存
        name='turtle1',                # 节点名
        parameters=[{'background_r': 255, 'background_g': 165, 'background_b': 0}],  # 设置背景色参数
    )
    turtlesim2_node = Node(           # 第二个节点:放入 turtlesim2 命名空间
        package='turtlesim',          # 节点所在的功能包名
        executable='turtlesim_node',  # 可执行文件名
        namespace='turtlesim2',       # 与第一个节点同名,靠不同 namespace 区分
        name='turtle1',               # 节点名(与第一个节点相同)
        parameters=[{'background_r': 0, 'background_g': 255, 'background_b': 100}],  # 设置背景色参数
    )
    return LaunchDescription([
        turtlesim1_node,              # 组装到 LaunchDescription
        turtlesim2_node
    ])

保存与运行:保存为 ~/ros2_launch_demo/03_multi.launch.py(写法 A、B 任选其一),运行:

ros2 launch ~/ros2_launch_demo/03_multi.launch.py

会同时弹出两个 turtlesim 窗口,分别对应 /turtlesim1/turtle1/turtlesim2/turtle1


五、节点参数配置 — 命令行传参与 YAML 参数文件

通过 DeclareLaunchArgument 声明可配置参数,再用 LaunchConfiguration 获取其值,即可在启动时动态传入参数:

ros2 launch ~/ros2_launch_demo/04_param_single.launch.py background_r:=200

5.1 单参数版

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument      # 定义可配置参数
from launch.substitutions import LaunchConfiguration  # 参数获取

def generate_launch_description():
    # 1. 声明一个名为 background_r 的启动参数
    background_r_arg = DeclareLaunchArgument(
        'background_r',          # 参数名称(必需)
        default_value='150',     # 默认值(可选,若不提供则用户必须传入,且必须是字符串)
        description='背景色 R 通道 (0-255)'  # 描述信息(可选,用于 --show-args)
    )

    # 2. 获取参数的实际值(占位符)
    background_r = LaunchConfiguration('background_r')

    # 3. 定义 turtlesim 节点,使用 background_r 参数
    turtlesim_node = Node(
        namespace='ns1',             # 放入指定的命名空间(可选)
        package='turtlesim',         # 节点所在的功能包名
        executable='turtlesim_node', # 要运行的可执行文件名
        name='my_turtle',            # 给这个节点起一个新名字(可选)
        output='screen',             # 将日志打印到屏幕(而不是日志文件)
        parameters=[{'background_r': background_r}],  # 设置参数
    )

    # 4. 组装到 LaunchDescription
    return LaunchDescription([
        background_r_arg,
        turtlesim_node
    ])

保存与运行:保存为 ~/ros2_launch_demo/04_param_single.launch.py,运行(可自定义背景色):

ros2 launch ~/ros2_launch_demo/04_param_single.launch.py background_r:=200

5.2 多参数版(写法更紧凑)

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument      # 定义可配置参数
from launch.substitutions import LaunchConfiguration  # 参数获取

def generate_launch_description():
    """支持命令行传参的写法,例如:
       ros2 launch ~/ros2_launch_demo/04_param.launch.py background_r:=200 background_g:=100 background_b:=50
    """

    # 1. 声明有哪些启动参数及其默认值
    declare_bg_r = DeclareLaunchArgument('background_r', default_value='150')
    declare_bg_g = DeclareLaunchArgument('background_g', default_value='86')
    declare_bg_b = DeclareLaunchArgument('background_b', default_value='255')

    # 2. 用 LaunchConfiguration 获取占位符(此时值还未确定,运行时才替换)
    bg_r = LaunchConfiguration('background_r')
    bg_g = LaunchConfiguration('background_g')
    bg_b = LaunchConfiguration('background_b')

    # 3. 传给节点
    node = Node(
        package='turtlesim',
        executable='turtlesim_node',
        parameters=[{
            'background_r': bg_r,
            'background_g': bg_g,
            'background_b': bg_b,
        }],
    )

    # 4. 注意:LaunchDescription 中必须先列出 DeclareLaunchArgument,再列出 Node
    return LaunchDescription([
        declare_bg_r,
        declare_bg_g,
        declare_bg_b,
        node,
    ])

保存与运行:保存为 ~/ros2_launch_demo/04_param.launch.py,运行(多参数版):

ros2 launch ~/ros2_launch_demo/04_param.launch.py background_r:=200 background_g:=100 background_b:=50

5.3 从 YAML 参数文件加载(参数多时的最佳实践)

前面两小节是通过命令行传参。但当参数很多(比如几十上百个)时,每次都写命令行很痛苦。更常见的做法是:把参数集中写在一个 YAML 参数文件里,让节点启动时直接加载。

第一步:准备 YAML 参数文件 ~/ros2_launch_demo/config/turtlesim.yaml

# config/turtlesim.yaml
# ROS 2 参数文件的格式:最外层是"节点全名",下面固定是 ros__parameters 键
/turtlesim2/sim:                # 节点全名 = 命名空间/节点名(必须与 launch 里节点一致)
   ros__parameters:             # 固定写法:所有参数都放在这个键下面
      background_b: 255         # 背景色 B 通道 (0-255)
      background_g: 86          # 背景色 G 通道 (0-255)
      background_r: 150         # 背景色 R 通道 (0-255)

参数文件的顶层键是节点全名。上例对应一个 namespace='turtlesim2'name='sim' 的节点(全名 /turtlesim2/sim)。

第二步:launch 文件 ~/ros2_launch_demo/05_yaml.launch.py

# 05_yaml.launch.py
# 作用:演示从 YAML 参数文件加载参数(parameters 里放文件路径,而不是字典)
import os  # 用于拼接文件路径

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    # 1. 定位 YAML 参数文件的绝对路径
    #    os.path.dirname(__file__) 返回当前文件所在目录(本文件与 config/ 同目录)
    config = os.path.join(
        os.path.dirname(__file__),   # 当前 launch 文件所在目录
        'config',                    # config 子目录
        'turtlesim.yaml'             # 参数文件名
    )

    # 2. 启动节点,parameters 直接放"文件路径"(字符串)
    #    节点名(namespace + name)必须与 YAML 顶层键 /turtlesim2/sim 一致
    return LaunchDescription([
        Node(
            package='turtlesim',          # 节点所在的功能包名
            executable='turtlesim_node',  # 可执行文件名
            namespace='turtlesim2',       # 命名空间(与 YAML 顶层键对应)
            name='sim',                   # 节点名(与 YAML 顶层键对应)
            parameters=[config]           # 从 YAML 文件加载参数(放路径而非字典)
        ),
    ])

保存与运行:先把上面的 YAML 保存为 ~/ros2_launch_demo/config/turtlesim.yaml,再把 launch 保存为 ~/ros2_launch_demo/05_yaml.launch.py,运行:

ros2 launch ~/ros2_launch_demo/05_yaml.launch.py

运行后可用以下命令确认参数确实从 YAML 加载成功:

ros2 param get /turtlesim2/sim background_r   # 应输出 150(来自 YAML 文件)
ros2 param list /turtlesim2/sim               # 列出该节点所有参数

关键点说明

  • parameters 里放字典 {...} → 表示”直接设置参数”;放字符串路径 → 表示”从 YAML 文件加载”。两者可以混用:
    parameters=[config, {'background_r': 0}]  # 先加载 YAML,再用字典覆盖其中一个
    
  • YAML 的顶层键(节点全名)必须和 launch 里节点的命名空间 + 节点名完全一致,否则参数不会生效(ROS 会报”没有找到该节点对应的参数”)。
  • 如果节点没设置 namespace/name,则全名就是 /executable名,YAML 顶层键要写成 /turtlesim_node:

常见坑

  • 节点名对不上:YAML 顶层键写的是 /turtlesim2/sim,但 launch 里节点没写 namespace='turtlesim2'name='sim' → 参数静默不加载。用 ros2 param list 检查是否为空即可发现。
  • 功能包里的 YAML 忘了安装:如果把 YAML 放进功能包并用 get_package_share_directory() 定位(生产环境推荐),记得像第九节那样在 setup.py / CMakeLists.txt安装 config 目录,否则启动时报”参数文件不存在”。本功能包的 setup.py 已有 glob('config/*.yaml') 安装规则。
  • 生产环境定位方式:示例用 os.path.dirname(__file__) 方便快速测试;功能包里应改用:
    from ament_index_python.packages import get_package_share_directory
    config = os.path.join(
        get_package_share_directory('launch_tutorial'), 'config', 'turtlesim.yaml')
    

三种设置节点参数的方式对比

方式 写法 适用场景
命令行传参(5.1 / 5.2 节) DeclareLaunchArgument + parameters=[{'key': LaunchConfiguration('key')}] 参数少、需要每次启动临时改
直接写死 parameters=[{'key': value}] 参数少且固定不变
YAML 参数文件(本节) parameters=[文件路径] 参数多、需集中管理

六、条件启动 (Conditional Launch)

使用 IfCondition 可以根据命令行参数值决定是否启动某个节点。

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.conditions import IfCondition  # 条件启动
from launch.substitutions import LaunchConfiguration  # 获取命令行参数值

def generate_launch_description():
    # 读取命令行参数 launch_turtlesim(默认 true),如:ros2 launch 05_condition.launch.py launch_turtlesim1:=false
    launch_turtlesim1 = LaunchConfiguration('launch_turtlesim1', default='true')
    launch_turtlesim2 = LaunchConfiguration('launch_turtlesim2', default='true')
    return LaunchDescription([
        # ---- 第一个节点 ----
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='turtle1',       # 节点改名为 turtle1
            # IfCondition 会把 "true"/"1" 视为真、"false"/"0" 视为假,直接传参即可
            condition=IfCondition(launch_turtlesim1),
        ),
        # ---- 第二个节点(改名为 turtle2,与第一个节点区分开)----
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='turtle2',       # 节点改名为 turtle2
            condition=IfCondition(launch_turtlesim2),
        ),
    ])

保存与运行:保存为 ~/ros2_launch_demo/05_condition.launch.py,运行:

# 默认启动两个乌龟
ros2 launch ~/ros2_launch_demo/05_condition.launch.py

# 只启动 turtle1,不启动 turtle2
ros2 launch ~/ros2_launch_demo/05_condition.launch.py launch_turtlesim2:=false

七、运行 launch 文件

# 基本运行(以第五节的多参数版为例)
ros2 launch ~/ros2_launch_demo/04_param.launch.py

# 覆盖启动参数(对应第五节)
ros2 launch ~/ros2_launch_demo/04_param.launch.py background_r:=200

# 查看可用的启动参数及其默认值/描述
ros2 launch ~/ros2_launch_demo/04_param.launch.py --show-args

八、常用字段速查表

字段 说明 示例
package 功能包名(必填) 'turtlesim'
executable 可执行文件名(必填) 'turtlesim_node'
name 节点名(重命名) 'my_node'/ns1/my_node
namespace 命名空间(隔离多实例) 'ns1'
parameters 参数列表 [{'key': value}]
remappings 话题重映射 [('/旧', '/新')]
arguments 透传给节点的额外 ROS 命令行参数(需以 --ros-args 开头,如设置日志级别/加载参数文件) ['--ros-args', '--log-level', 'DEBUG']
output 日志输出位置 'screen' | 'log' | 'both'
condition 条件启动 IfCondition(LaunchConfiguration(...))
respawn 节点退出后自动重启 True | False
respawn_delay 重启前的等待秒数 3.0

关于 executable 名字的来源executable 填的是节点程序的可执行文件名,它取决于节点包的类型:

  • C++ 包ament_cmake):在 CMakeLists.txt 中用 add_executable() 定义并安装;
  • Python 包ament_python):在 setup.pyconsole_scripts 中定义入口点。

本文统一使用 turtlesim 包自带的 C++ 节点程序 turtlesim_node,所以直接填 'turtlesim_node' 即可。


九、如何让功能包安装 launch 文件

上面的例子都是在某个已有的功能包里写 launch 文件。如果你在自己的 Python 功能包ament_python)里放了 launch 文件,还需要在 setup.py声明安装规则,否则 ros2 launch 会报”文件找不到”(launch 文件没有被复制进 install/ 目录)。

setup.py 中增加以下配置(这是最关键的一行):

import os                        # 用于拼接安装路径
from glob import glob            # 用于匹配 launch 目录下的文件
from setuptools import setup     # 打包配置

package_name = 'my_package'      # 功能包名(与目录名一致)

setup(
    # 其他配置参数 ...
    data_files=[
        # 其他需要安装的数据文件 ...
        # 安装所有 launch 文件(最关键的一行)
        (os.path.join('share', package_name), glob('launch/*.launch.py'))
    ]
)

要点说明:

  • glob('launch/*.launch.py'):把 launch/ 目录下所有以 .launch.py 结尾的文件收集起来(注意是 .launch,不是下划线 _launch)。
  • os.path.join('share', package_name):安装目标路径,即 share/<包名>/ 下,ros2 launch 会去那里查找 launch 文件。
  • 配置好后必须重新构建并 source 工作区:
cd ~/ros_ws
colcon build --packages-select my_package
source install/setup.bash
ros2 launch my_package my_launch.launch.py

9.1 C++ 包(ament_cmake)的调整

如果你用的是 C++ 功能包(构建类型为 ament_cmake),则在 CMakeLists.txt 中通过 install() 命令把 launch/ 目录整体安装到 share/<包名>/ 下。在文件末尾、ament_package() 之前加上:

# 安装 launch 目录到 share/<包名>/launch
install(DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)

也可以用 install(FILES ...) 逐个指定(launch 文件不多时更清晰):

install(FILES
  launch/my_launch.launch.py
  DESTINATION share/${PROJECT_NAME}/launch
)

要点说明:

  • DESTINATION share/${PROJECT_NAME}:安装目标路径,${PROJECT_NAME} 会自动替换成包名(即 project(...) 里定义的名字),与 Python 包的 os.path.join('share', package_name) 等价。
  • install(DIRECTORY launch ...) 会把整个 launch 目录(含里面的所有文件)复制过去,是最省事的方式;install(FILES ...) 则只装你列出的那几份。
  • 同样,改完 CMakeLists.txt 后要重新构建
cd ~/ros_ws
colcon build --packages-select my_package
source install/setup.bash
ros2 launch my_package my_launch.launch.py

小结:无论 Python 还是 C++ 包,本质都是把 launch/*.launch.py 装进 install/<包名>/share/<包名>/launch/。Python 包在 setup.pydata_files 里配置,C++ 包在 CMakeLists.txtinstall() 里配置。

常见坑

  • 文件名命名:launch 文件建议统一命名为 xxx.launch.py(带点)。如果写成 xxx_launch.py(下划线),glob('launch/*.launch.py') 匹配不到,文件不会被安装,ros2 launch 同样报”文件找不到”。
  • 忘了重新构建:修改 setup.py 后要重新 colcon build,否则 install/ 里还是旧内容。
  • 忘了 package.xml 依赖:如果 launch 文件里用了 launch_ros.actions.Node,记得在 package.xml<exec_depend>ros2launch</exec_depend>

十、一个 launch 文件调用另一个 launch 文件并传递参数

实际项目中,一个 launch 文件常常需要调用另一个 launch 文件(例如先启动公共的机器人驱动、传感器驱动),并且把当前文件的参数传递给它。这用 IncludeLaunchDescription + launch_arguments 就能实现。

下面用一个完整例子演示:父 launch 文件 调用 子 launch 文件,并把背景色参数传过去。两个文件都放在同一个目录(如 ~/ros2_launch_demo/)下。

10.1 子 launch 文件:声明参数并使用

先写子文件 child.launch.py:它声明一组参数,并用 LaunchConfiguration 把参数值应用到 turtlesim 节点上。

# 子 launch 文件:child.launch.py
# 作用:声明一组参数,并用 LaunchConfiguration 把参数值传给节点
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument       # 定义可配置参数
from launch.substitutions import LaunchConfiguration   # 获取参数值
from launch_ros.actions import Node

def generate_launch_description():
    # 1. 声明三个可配置参数,并给出默认值
    declare_bg_r = DeclareLaunchArgument(
        'background_r',          # 参数名称
        default_value='150',     # 默认值(必须是字符串)
        description='背景色 R 通道 (0-255)',  # 描述信息
    )
    declare_bg_g = DeclareLaunchArgument('background_g', default_value='86')
    declare_bg_b = DeclareLaunchArgument('background_b', default_value='255')

    # 2. 用 LaunchConfiguration 获取参数的实际值(占位符,运行时才替换)
    bg_r = LaunchConfiguration('background_r')
    bg_g = LaunchConfiguration('background_g')
    bg_b = LaunchConfiguration('background_b')

    # 3. 把参数值应用到 turtlesim 节点上
    turtlesim_node = Node(
        package='turtlesim',          # 节点所在的功能包名
        executable='turtlesim_node',  # 可执行文件名(这里直接用 turtlesim 包自带的节点程序)
        parameters=[{
            'background_r': bg_r,     # 使用上面获取的参数占位符
            'background_g': bg_g,
            'background_b': bg_b,
        }],
    )

    # 4. 组装 LaunchDescription(先声明参数,再放节点)
    return LaunchDescription([
        declare_bg_r,
        declare_bg_g,
        declare_bg_b,
        turtlesim_node,
    ])

这个子文件也可以单独运行:

ros2 launch ~/ros2_launch_demo/child.launch.py background_r:=200 background_g:=100 background_b:=50

10.2 父 launch 文件:声明参数并透传给子文件

再写父文件 parent.launch.py:它自己先声明一组参数(这样命令行能直接传参),再通过 IncludeLaunchDescription 包含子文件,并用 launch_arguments 把参数值透传给子文件。

# 父 launch 文件:parent.launch.py
# 作用:声明参数 → 包含(调用)child.launch.py → 把参数传递给它
import os  # 用于拼接文件路径

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription  # 声明参数 + 包含另一个 launch 文件
from launch.launch_description_sources import PythonLaunchDescriptionSource  # 指定 Python launch 文件源
from launch.substitutions import LaunchConfiguration  # 获取参数值

def generate_launch_description():
    # 1. 定位子 launch 文件的绝对路径
    #    os.path.dirname(__file__) 返回当前文件所在目录(本文件与 child.launch.py 同目录)
    child_launch = os.path.join(
        os.path.dirname(__file__),  # 当前文件所在目录
        'child.launch.py'           # 子 launch 文件名
    )

    # 2. 父文件也声明一组参数(这样命令行可以直接传参给父文件)
    declare_bg_r = DeclareLaunchArgument('background_r', default_value='150')
    declare_bg_g = DeclareLaunchArgument('background_g', default_value='86')
    declare_bg_b = DeclareLaunchArgument('background_b', default_value='255')

    # 3. 获取参数的实际值(占位符,运行时才替换)
    bg_r = LaunchConfiguration('background_r')
    bg_g = LaunchConfiguration('background_g')
    bg_b = LaunchConfiguration('background_b')

    # 4. 包含(调用)子 launch 文件,并把父文件的参数值传递给它
    include_child = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(child_launch),  # 指定要包含的 launch 文件
        launch_arguments={                            # 传给子 launch 文件的参数(键值对)
            'background_r': bg_r,     # 把父文件的参数值透传给子文件
            'background_g': bg_g,
            'background_b': bg_b,
        }.items(),  # 注意:launch_arguments 需要 .items() 转成键值对列表
    )

    # 5. 组装 LaunchDescription(先声明参数,再放 IncludeLaunchDescription)
    return LaunchDescription([
        declare_bg_r,
        declare_bg_g,
        declare_bg_b,
        include_child,
    ])

说明:子文件里 DeclareLaunchArgument 声明的参数,父文件不一定要全部传。没传的参数会使用子文件里的默认值。

10.3 运行

两个文件都放在同一个目录下(本示例用 ~/ros2_launch_demo/),直接运行父文件即可:

# 运行父文件 → 自动包含子文件,参数默认 150/86/255(青色系)
ros2 launch ~/ros2_launch_demo/parent.launch.py

# 通过命令行给父文件传参 → 父文件透传给子文件 → 背景变红色
ros2 launch ~/ros2_launch_demo/parent.launch.py background_r:=255 background_g:=0 background_b:=0

# 传蓝色
ros2 launch ~/ros2_launch_demo/parent.launch.py background_r:=0 background_g:=0 background_b:=255

运行后 turtlesim 窗口的背景色会变成你通过命令行传给父文件的颜色。可以用 ros2 param get /turtlesim background_r 实时确认参数值。

常见坑IncludeLaunchDescription 里的路径必须写成 绝对路径。这里用 os.path.dirname(__file__) 动态获取当前文件所在目录,所以无论把两个文件放到哪里都能正确找到子文件。

小结

  • IncludeLaunchDescription(PythonLaunchDescriptionSource(路径), launch_arguments={...}.items()) 就是”调用另一个 launch 文件”的标准写法。
  • 父文件先自己声明参数DeclareLaunchArgument),再用 LaunchConfiguration 把值塞进 launch_arguments,从而把命令行参数透传给子文件——这就是”调用参数并传递给另一个 launch 文件”。
  • 子文件通过 DeclareLaunchArgument + LaunchConfiguration 接收并使用参数。
  • 参数值必须是字符串(数字也要写成 '255' 这样的字符串;LaunchConfiguration 替换后自动转成字符串)。
  • os.path.dirname(__file__) 拿到当前文件所在目录,再拼接出同目录下子 launch 文件的绝对路径,方便快速测试。
  • 生产项目中,子文件通常安装在功能包里,改用 get_package_share_directory('包名') 来定位(见第九节)。

十一、高级用法:执行任意命令(ExecuteProcess)与定时动作(TimerAction)

前几节的 Node 用来启动节点DeclareLaunchArgument 用来声明参数。但实际项目中还有两类需求:

  • 执行任意命令:启动后需要跑一条”一次性命令”,比如 ros2 service call 调用服务、ros2 param set 设置参数、运行一个脚本。
  • 延迟执行:某些动作要等节点启动完成、或等上一步参数生效后再执行,而不是一上来就抢跑。

这两类需求分别对应 ExecuteProcess(执行任意命令)和 TimerAction(定时动作)。两者都是 launch.actions 里的标准 Action,可以直接放进 LaunchDescription


11.1 ExecuteProcess —— 在 launch 中执行任意命令

作用:在 launch 启动时执行任意 shell 命令(不限于 ROS 命令)。

最小示例

from launch import LaunchDescription
from launch.actions import ExecuteProcess

def generate_launch_description():
    return LaunchDescription([
        # 执行一条简单命令
        ExecuteProcess(
            cmd=['echo', 'hello from launch'],   # 命令 + 参数,列表形式
        ),
    ])

关键参数

参数 说明 示例
cmd 要执行的命令,字符串列表 ['echo', 'hello']['ros2', 'node', 'list']
shell 是否通过 shell 执行。为 True 时可使用 |>&& 等 shell 语法 shell=True
output 命令输出打印到哪里 'screen' / 'log' / 'both'
condition 条件执行(配合 IfCondition IfCondition(...)
env 为命令设置额外环境变量 {'MY_VAR': 'value'}

两种 cmd 写法

# 写法 1:普通列表 —— 每个元素是一个参数
ExecuteProcess(
    cmd=['ros2', 'node', 'list'],
)

# 写法 2:嵌套列表 —— 常用于"多个片段拼接"(配合 Substitution 替换)
#   launch 会把内层列表逐段解析,允许混入 LaunchConfiguration 等占位符
ExecuteProcess(
    cmd=[[
        'ros2 param set ',
        turtlesim_ns,          # 这里可以是 LaunchConfiguration 占位符
        '/sim background_r ',
        '120'
    ]],
    shell=True,
)

FindExecutable 定位命令(推荐):直接写 'ros2' 依赖 PATH;更健壮的做法是用 FindExecutable(name='ros2') 动态查找其绝对路径,不依赖 PATH 环境变量:

from launch.substitutions import FindExecutable

ExecuteProcess(
    cmd=[[
        FindExecutable(name='ros2'),   # 自动找到 ros2 的绝对路径
        ' service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2}"'
    ]],
    shell=True,
)

11.2 TimerAction —— 定时延迟执行动作

作用:把一组动作延迟 period 秒后再执行。适合”等节点就绪后再发指令”的场景。

最小示例

from launch import LaunchDescription
from launch.actions import ExecuteProcess, TimerAction

def generate_launch_description():
    delayed_cmd = ExecuteProcess(
        cmd=['echo', 'delayed 3 seconds'],
    )
    return LaunchDescription([
        # 3 秒后才执行 delayed_cmd
        TimerAction(
            period=3.0,                  # 延迟秒数(浮点数)
            actions=[delayed_cmd],       # 延迟后要执行的动作列表
        ),
    ])

关键参数

参数 说明 示例
period 延迟的秒数(浮点数) 2.0
actions 到点后要执行的动作列表(可以是 ExecuteProcessNodeLogInfo 等) [cmd1, cmd2]

注意TimerActionactions 接收的是动作对象列表。如果你已经有 ExecuteProcess / Node 等动作变量,直接把它们放进来即可。period 可以用浮点(如 0.5)。


11.3 综合示例:07_example_substitutions.launch.py

下面把上面两类用法和前面的知识(参数替换、条件启动)组合起来,完成一个自动演示:启动 turtlesim → 生成海龟 → 改背景色(其中一步延迟 2 秒且带条件)。

# 子 launch 文件:07_example_substitutions.launch.py
# 作用:演示 launch 文件的高级用法 —— 参数替换(Substitutions)、
#       执行任意命令(ExecuteProcess)、条件启动(IfCondition)、定时动作(TimerAction)

# 用于创建节点(Node)
from launch_ros.actions import Node

# launch 基础
from launch import LaunchDescription
# 声明启动参数 + 执行任意命令 + 定时延迟动作
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
# 条件启动:满足条件才执行某个动作
from launch.conditions import IfCondition
# 参数替换:获取启动参数值 + 计算 Python 表达式
from launch.substitutions import LaunchConfiguration, PythonExpression


def generate_launch_description():
    # ---- 1. 用 LaunchConfiguration 获取启动参数的值(占位符,运行时才替换)----
    turtlesim_ns = LaunchConfiguration('turtlesim_ns')            # 命名空间
    use_provided_red = LaunchConfiguration('use_provided_red')    # 是否使用命令行传入的红色值
    new_background_r = LaunchConfiguration('new_background_r')    # 新的背景色 R 值

    # ---- 2. 声明三个可配置的启动参数,并给出默认值 ----
    #    注意:必须先声明,才能在命令行用 参数名:=值 覆盖
    turtlesim_ns_launch_arg = DeclareLaunchArgument(
        'turtlesim_ns',               # 参数名称
        default_value='turtlesim1'    # 默认值(必须是字符串)
    )
    use_provided_red_launch_arg = DeclareLaunchArgument(
        'use_provided_red',
        default_value='False'
    )
    new_background_r_launch_arg = DeclareLaunchArgument(
        'new_background_r',
        default_value='200'
    )

    # ---- 3. 创建 turtlesim 节点,并放入指定命名空间 ----
    #    namespace 用 LaunchConfiguration 替换 → 节点名由命令行参数决定
    turtlesim_node = Node(
        package='turtlesim',            # 节点所在的功能包名
        namespace=turtlesim_ns,         # 命名空间(命令行传 turtlesim_ns:=xxx 可覆盖)
        executable='turtlesim_node',    # 可执行文件名
        name='sim'                      # 节点名 → 全名变成 /<namespace>/sim
    )

    # ---- 4. ExecuteProcess:在 launch 中直接执行任意 shell 命令 ----
    #    在 turtlesim 中生成一只新海龟(调用 /spawn 服务)
    spawn_turtle = ExecuteProcess(
        # cmd 是一个字符串列表,逐段拼接成完整命令
        cmd=[[
            'ros2 service call ',                        # 调用服务命令
            turtlesim_ns,                                # 命名空间(替换成实际值)
            '/spawn ',                                   # 服务名
            'turtlesim/srv/Spawn ',                      # 服务类型
            '"{x: 2, y: 2, theta: 0.2}"'                 # 服务参数(YAML 格式)
        ]],
        shell=True                      # 通过 shell 执行(允许重定向、管道等)
    )

    #    无条件修改背景色 R 为 120
    change_background_r = ExecuteProcess(
        cmd=[[
            'ros2 param set ',           # 设置参数命令
            turtlesim_ns,                # 命名空间(替换成实际值)
            '/sim background_r ',        # 节点/参数名
            '120'                        # 要设置的参数值
        ]],
        shell=True
    )

    #    条件修改背景色 R 为命令行传入的值(仅在满足条件时执行)
    change_background_r_conditioned = ExecuteProcess(
        # ---- 5. IfCondition + PythonExpression:根据表达式结果决定是否执行 ----
        #    PythonExpression 会把各片段拼成表达式求值(如 "200 == 200 and True")
        condition=IfCondition(
            PythonExpression([
                new_background_r,          # 值 1(如 "200")
                ' == 200',                 # 比较
                ' and ',                   # 逻辑与
                use_provided_red           # 值 2(如 "True"/"False")
            ])
        ),
        cmd=[[
            'ros2 param set ',           # 设置参数命令
            turtlesim_ns,                # 命名空间(替换成实际值)
            '/sim background_r ',        # 节点/参数名
            new_background_r             # 命令行传入的新值(替换成实际值)
        ]],
        shell=True
    )

    # ---- 6. 组装 LaunchDescription(先声明参数,再放节点和动作)----
    return LaunchDescription([
        # 声明启动参数(必须放在使用它们的动作之前)
        turtlesim_ns_launch_arg,
        use_provided_red_launch_arg,
        new_background_r_launch_arg,
        # 启动 turtlesim 节点
        turtlesim_node,
        # 生成新海龟
        spawn_turtle,
        # 无条件把背景色 R 设为 120
        change_background_r,
        # ---- 7. TimerAction:延迟 2 秒后再执行条件修改背景色 ----
        #    保证前面步骤执行完、参数设置生效后再修改
        TimerAction(
            period=2.0,                              # 延迟 2 秒
            actions=[change_background_r_conditioned]  # 延迟后要执行的动作列表
        )
    ])

运行(保存为 ~/ros2_launch_demo/07_example_substitutions.launch.py):

# 默认:命名空间 turtlesim1,不改背景色
ros2 launch ~/ros2_launch_demo/07_example_substitutions.launch.py

# 命令行覆盖:命名空间 turtlesim3 + 满足条件(200 == 200 and True)→ 延迟 2 秒后背景色 R 设为 200
ros2 launch ~/ros2_launch_demo/07_example_substitutions.launch.py \
    turtlesim_ns:='turtlesim3' use_provided_red:='True' new_background_r:=200

执行时序(以第二行命令为例):

t=0s    启动 turtlesim 节点(/turtlesim3/sim)
t≈0s    调用 /spawn 生成新海龟(x=2, y=2)
t≈0s    ros2 param set /turtlesim3/sim background_r 120   ← 无条件先设成 120
t=2s    (TimerAction 到期)条件成立 → 再设为 200          ← 覆盖成命令行传入的值

小结

  • ExecuteProcess(cmd=列表, shell=True):在 launch 里执行任意命令。cmd 可以是普通列表或嵌套列表;嵌套列表里可混入 LaunchConfiguration 等占位符实现动态拼命令。
  • TimerAction(period=秒, actions=[...]):把一组动作延迟指定秒数再执行,常用于”等节点就绪后再操作”。
  • FindExecutable(name='xxx'):动态查找命令的绝对路径,比硬编码命令名更健壮。
  • 两者都是普通 Action,可自由与 DeclareLaunchArgumentLaunchConfigurationIfConditionPythonExpression 组合,实现复杂的自动化启动流程。