上篇讲了launch文件的基础用法。这篇讲几个进阶技巧,都是实际项目中经常用到的。

面试的时候,如果你能说出这些细节,面试官会觉得你真的做过项目,不是只看了教程。

事件系统:节点启动顺序控制

launch文件里的节点默认是同时启动的。但有些场景需要控制顺序:比如地图服务器必须先启动并加载完地图,导航节点才能启动。

ROS2的launch事件系统可以处理这种依赖关系:

from launch import LaunchDescription
from launch.actions import EmitEvent, RegisterEventHandler
from launch.events import EventHandler
from launch_ros.event_handlers import OnProcessExit

def generate_launch_description():
    map_server = Node(
        package='nav2_map_server',
        executable='map_server',
        parameters=[{'yaml_filename': 'map.yaml'}]
    )
    
    # 地图服务器退出后启动导航
    nav_lifecycle = RegisterEventHandler(
        OnProcessExit(
            target_action=map_server,
            on_exit=[
                Node(
                    package='nav2_lifecycle_manager',
                    executable='lifecycle_manager',
                    parameters=[{'node_names': ['map_server', 'navigator']}]
                )
            ]
        )
    )
    
    return LaunchDescription([
        map_server,
        nav_lifecycle
    ])

OnProcessExit监听某个进程的退出事件,退出后执行后续动作。这在需要"先初始化再启动"的场景中很有用。

TimerAction:延迟启动

有时候不是等某个节点退出,而是单纯想延迟几秒再启动某个节点。比如等所有传感器驱动都初始化完成后再启动融合节点:

from launch.actions import TimerAction

fusion_delayed = TimerAction(
    period=5.0,
    actions=[
        Node(
            package='my_robot',
            executable='sensor_fusion',
            output='screen'
        )
    ]
)

period是延迟秒数。这个方案虽然简单粗暴,但在很多场景下够用了。比在代码里写time.sleep(5)优雅得多,因为延迟逻辑集中在launch文件里,一目了然。

ExecuteProcess:运行非ROS命令

launch文件不只是启动ROS节点。你可以通过ExecuteProcess运行任何系统命令:

from launch.actions import ExecuteProcess

# 启动Gazebo
gazebo = ExecuteProcess(
    cmd=['gazebo', '--verbose', '-s', 
         'libgazebo_ros_init.so', 'world/my_world.world'],
    output='screen'
)

# 设置环境变量
set_env = ExecuteProcess(
    cmd=['bash', '-c', 'export ROS_DOMAIN_ID=42'],
    output='screen'
)

这在需要启动第三方工具或者设置环境的时候很有用。比如启动Gazebo仿真、运行数据录制脚本、设置网络配置等。

OpaqueFunction:动态生成Launch描述

如果你需要根据运行时的条件动态决定启动哪些节点,用OpaqueFunction:

from launch.actions import OpaqueFunction

def load_config(context, *args, **kwargs):
    config_file = LaunchConfiguration('config_file').perform(context)
    # 从YAML文件读取节点列表,动态创建Node对象
    nodes = create_nodes_from_yaml(config_file)
    return nodes

这个例子从YAML配置文件动态读取要启动的节点列表。不同的配置文件可以启动完全不同的节点组合,不需要写多个launch文件。

GroupAction:分组管理

节点多的时候,可以用GroupAction把相关节点分组:

from launch.actions import GroupAction

sensor_group = GroupAction([
    Node(package='my_robot', executable='lidar_driver'),
    Node(package='my_robot', executable='camera_driver'),
    Node(package='my_robot', executable='imu_driver'),
])

nav_group = GroupAction([
    Node(package='nav2', executable='planner'),
    Node(package='nav2', executable='controller'),
])

分组之后可以对整组做操作,比如统一设置命名空间或者条件启动:

sensor_group = GroupAction(
    actions=[...],
    condition=IfCondition(LaunchConfiguration('use_real_sensors'))
)

实际项目的Launch结构

在我们项目中,launch文件分三层:

第一层是system.launch.py,总入口。它根据参数(use_sim、robot_type)决定加载哪些子系统。

第二层是子系统launch文件:sensors.launch.py、navigation.launch.py、manipulation.launch.py。每个子系统内部自己管理节点启动顺序和参数。

第三层是具体节点的launch文件,通常不直接使用,被第二层包含。

配置文件放在config/目录下,按机器人型号分:config/robot_a/、config/robot_b/。不同机器人加载不同的配置。

面试中怎么聊

面试官问launch进阶,你可以说:"ROS2 launch支持事件系统控制节点启动顺序,TimerAction延迟启动,ExecuteProcess运行系统命令,OpaqueFunction动态生成节点配置。实际项目中launch文件分三层管理,配合YAML配置文件实现不同机器人型号的快速切换。"

Launch高级功能实战

Launch文件中有一些高级但很实用的功能。ExecuteProcess可以运行非ROS的命令,比如启动Gazebo仿真器;TimerAction可以延迟启动某些节点,等底层服务准备好;GroupAction可以把一组节点放在同一个命名空间下。还有条件启动,可以根据参数决定是否启动某个组件。这些功能在构建复杂的机器人系统时非常有用。

补充一个实战技巧:在复杂的Launch系统中,用OpaqueFunction可以执行任意Python代码,适合需要根据运行时条件动态决定启动逻辑的场景。比如根据环境变量或参数文件的内容来决定启动哪些节点。

给你的建议

把你项目的launch文件按三层结构重新组织。把硬编码的参数提取到配置文件里。加上use_sim参数支持仿真和实机切换。

这些改进做一次就行,但会让后续的开发和调试效率提升很多。多花时间在launch文件的组织上,回报是非常可观的。


上一篇:第119篇 ROS2 Launch系统入门——一键启动你的机器人

下一篇预告:第121篇 ROS2消息类型——std_msgs/sensor_msgs/geometry_msgs详解

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐