ROS2 Launch进阶——参数传递、条件启动和节点编排
上篇讲了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文件的组织上,回报是非常可观的。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)