一个完整的简单的launch文件配置过程
1.编写launch文件
2.配置package.xml
3.配置setup.py(python包)
4.配置CMakeList(C++包)
5.编译+运行
# 在 ROS 2 的 Python 启动文件中,这些导入语句用于引入各类启动模块,以构建和配置节点启动流程
from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',namespace='turtlesim1',executable='turtlesim_node',name='sim'),Node(package='turtlesim',namespace='turtlesim2',executable='turtlesim_node',name='sim'),Node(package='turtlesim',executable='mimic',name='mimic',remappings=[('/input/pose', '/turtlesim1/turtle1/pose'),('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),])])
package.xml加入以下内容:
<exec_depend>ros2launch</exec_depend>
CMakeList: to the end of the file (but before ament_package()
).
# Install launch files.
install(DIRECTORYlaunchDESTINATION share/${PROJECT_NAME}/
)
package.list:
import os
from glob import glob
# Other imports ...package_name = 'py_launch_example'setup(# Other parameters ...data_files=[# ... Other data files# Include all launch files.(os.path.join('share', package_name, 'launch'), glob('launch/*'))]
)
替换表达式
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():colors = {'background_r': '200'}return LaunchDescription([# 使用PathJoinSubstitution和FindPackageShare动态构建启动文件路径IncludeLaunchDescription(PathJoinSubstitution([FindPackageShare('launch_tutorial'),'launch','example_substitutions.launch.py']),# 传递给被包含启动文件的参数launch_arguments={'turtlesim_ns': 'turtlesim2','use_provided_red': 'True','new_background_r': colors['background_r'],}.items())])
在 Python 中,.items()
是字典(dict
)对象的一个方法,用于将字典转换为可迭代的键值对元组列表。
又如:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PythonExpression
from launch_ros.actions import Nodedef generate_launch_description():# turtlesim_ns = LaunchConfiguration('turtlesim_ns')use_provided_red = LaunchConfiguration('use_provided_red')new_background_r = LaunchConfiguration('new_background_r')return LaunchDescription([DeclareLaunchArgument('turtlesim_ns',default_value='turtlesim1'),DeclareLaunchArgument('use_provided_red',default_value='False'),DeclareLaunchArgument('new_background_r',default_value='200'),Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim'),ExecuteProcess(cmd=[['ros2 service call ',turtlesim_ns,'/spawn ','turtlesim/srv/Spawn ','"{x: 2, y: 2, theta: 0.2}"']],shell=True),ExecuteProcess(cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ','120']],shell=True),TimerAction(period=2.0,actions=[ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True),],)])
为什么需要 LaunchConfiguration?
简单说,LaunchConfiguration
就像一个 “参数占位符”。当你在 Launch 文件中声明了一个启动参数(比如DeclareLaunchArgument('turtlesim_ns')
),LaunchConfiguration
可以引用这个参数的值,让你在后续的节点、动作配置中动态使用它,而不用写死具体数值。
比如你声明了一个参数background_r
,默认值为 200,但用户启动时可能会通过命令行修改为 255。LaunchConfiguration('background_r')
就能自动获取用户输入的最新值,无需修改 Launch 文件代码。
命令行进程调用:
ExecuteProcess(cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ','120']],shell=True),
条件执行:
TimerAction(period=2.0,actions=[ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[['ros2 param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True),],)
格式:
TimerAction(period = .... # 多少时间后actions =[ExecuteProcess(condition = IfCondition(...... # 具体条件),cmd = [ [........... # 命令行指令]],shell = True),],)
这段代码的作用是:在启动后 2 秒,检查特定条件是否满足,如果满足则执行一条 ROS 2 命令修改 turtlesim 模拟器的背景红色值。
ROS 2 中的事件处理器(Event Handlers)使用示例
from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument,EmitEvent,ExecuteProcess,LogInfo,RegisterEventHandler,TimerAction
)
from launch.conditions import IfCondition
from launch.event_handlers import (OnExecutionComplete,OnProcessExit,OnProcessIO,OnProcessStart,OnShutdown
)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable,FindExecutable,LaunchConfiguration,LocalSubstitution,PythonExpression
)
from launch_ros.actions import Nodedef generate_launch_description():turtlesim_ns = LaunchConfiguration('turtlesim_ns')use_provided_red = LaunchConfiguration('use_provided_red')new_background_r = LaunchConfiguration('new_background_r')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')turtlesim_node = Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim')spawn_turtle = ExecuteProcess(cmd=[[FindExecutable(name='ros2'),' service call ',turtlesim_ns,'/spawn ','turtlesim/srv/Spawn ','"{x: 2, y: 2, theta: 0.2}"']],shell=True)change_background_r = ExecuteProcess(cmd=[[FindExecutable(name='ros2'),' param set ',turtlesim_ns,'/sim background_r ','120']],shell=True)change_background_r_conditioned = ExecuteProcess(condition=IfCondition(PythonExpression([new_background_r,' == 200',' and ',use_provided_red])),cmd=[[FindExecutable(name='ros2'),' param set ',turtlesim_ns,'/sim background_r ',new_background_r]],shell=True)return LaunchDescription([turtlesim_ns_launch_arg,use_provided_red_launch_arg,new_background_r_launch_arg,turtlesim_node,RegisterEventHandler(OnProcessStart(target_action=turtlesim_node,on_start=[LogInfo(msg='Turtlesim started, spawning turtle'),spawn_turtle])),RegisterEventHandler(OnProcessIO(target_action=spawn_turtle,on_stdout=lambda event: LogInfo(msg='Spawn request says "{}"'.format(event.text.decode().strip())))),RegisterEventHandler(OnExecutionComplete(target_action=spawn_turtle,on_completion=[LogInfo(msg='Spawn finished'),change_background_r,TimerAction(period=2.0,actions=[change_background_r_conditioned],)])),RegisterEventHandler(OnProcessExit(target_action=turtlesim_node,on_exit=[LogInfo(msg=(EnvironmentVariable(name='USER'),' closed the turtlesim window')),EmitEvent(event=Shutdown(reason='Window closed'))])),RegisterEventHandler(OnShutdown(on_shutdown=[LogInfo(msg=['Launch was asked to shutdown: ', LocalSubstitution('event.reason')])])),])
详解:
这个启动文件创建了一个 turtlesim 模拟器,并实现了以下功能:
- 启动 turtlesim 节点后,自动生成一只新乌龟
- 捕获生成乌龟的服务响应并打印日志
- 乌龟生成完成后,修改模拟器背景颜色
- 2 秒后,根据参数条件再次修改背景颜色
- 当 turtlesim 窗口关闭时,自动终止整个启动过程
- 捕获系统关闭事件并打印关闭原因
1.节点与命令定义
turtlesim_node = Node(package='turtlesim',namespace=turtlesim_ns,executable='turtlesim_node',name='sim'
)
spawn_turtle = ExecuteProcess(...) # 生成乌龟的服务调用
change_background_r = ExecuteProcess(...) # 修改背景颜色的命令
change_background_r_conditioned = ExecuteProcess(...) # 条件性修改背景颜色
这些组件定义了要执行的基本操作,但它们的执行时机由事件处理器控制。
2.事件处理器注册
RegisterEventHandler(OnProcessStart(target_action=turtlesim_node,on_start=[LogInfo(...), spawn_turtle])
)
- OnProcessStart:当
turtlesim_node
启动时,触发生成乌龟的命令。
RegisterEventHandler(OnProcessIO(target_action=spawn_turtle,on_stdout=lambda event: LogInfo(...))
)
- OnProcessIO:捕获
spawn_turtle
命令的标准输出,提取服务响应信息。
当执行spawn_turtle
命令(即调用turtlesim/srv/Spawn
服务)时,服务响应会通过标准输出返回。例如:
ros2 service call /turtlesim1/spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2}"
服务成功响应后,控制台会打印:
[INFO] [launch]: Spawn request says "Created turtle [turtle2]"
这表明新乌龟已成功生成,并且名称为turtle2
。
RegisterEventHandler(OnExecutionComplete(target_action=spawn_turtle,on_completion=[LogInfo(...), change_background_r, TimerAction(...)])
)
- OnExecutionComplete:当
spawn_turtle
命令完成后,修改背景颜色,并设置一个 2 秒后的延迟动作。
RegisterEventHandler(OnProcessExit(target_action=turtlesim_node,on_exit=[LogInfo(...), EmitEvent(event=Shutdown(...))])
)
- OnProcessExit:当
turtlesim_node
退出时,触发系统关闭事件。
RegisterEventHandler(OnShutdown(on_shutdown=[LogInfo(...)])
)
- OnShutdown:捕获系统关闭事件,打印关闭原因。
3.编译执行:
ros2 launch launch_tutorial example_event_handlers.launch.py turtlesim_ns:='turtlesim3' use_provided_red:='True' new_background_r:=200
管理大型工程
顶层管理:
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():launch_dir = PathJoinSubstitution([FindPackageShare('launch_tutorial'), 'launch'])return LaunchDescription([IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_1.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_2.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'broadcaster_listener.launch.py']),launch_arguments={'target_frame': 'carrot1'}.items()),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'mimic.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'fixed_broadcaster.launch.py'])),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_rviz.launch.py'])),])
在launch文件设置参数:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('background_r', default_value='0'),DeclareLaunchArgument('background_g', default_value='84'),DeclareLaunchArgument('background_b', default_value='122'),Node(package='turtlesim',executable='turtlesim_node',name='sim',parameters=[{'background_r': LaunchConfiguration('background_r'),'background_g': LaunchConfiguration('background_g'),'background_b': LaunchConfiguration('background_b'),}]),])
通过YAML文件设置参数:
from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='turtlesim_node',namespace='turtlesim2',name='sim',parameters=[PathJoinSubstitution([FindPackageShare('launch_tutorial'), 'config', 'turtlesim.yaml'])],),])
/turtlesim2/sim:ros__parameters:background_b: 255background_g: 86background_r: 150
整个/turtlesim2/sim
代表的是 “位于turtlesim2
命名空间下、名为sim
的turtlesim
节点”,其后的ros__parameters
则是该节点的具体参数配置(如背景色的 RGB 值)
命名空间 :
当启动文件需要包含大量节点时,手动为每个节点单独设置命名空间会变得繁琐且易出错。PushRosNamespace
提供了一种更优雅的解决方案,让你可以为一组节点批量设置命名空间。
首先remove the namespace='turtlesim2'
line from the turtlesim_world_2.launch.py
。然后按照以下修改:
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace...GroupAction(actions=[PushROSNamespace('turtlesim2'),IncludeLaunchDescription(PathJoinSubstitution([launch_dir, 'turtlesim_world_2.launch.py'])),]),
那么在turtlesim_world_2.launch.py中的每一个节点都有一个turtlesim2前缀的命令空间。
节点复用
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfigurationfrom launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('target_frame', default_value='turtle1',description='Target frame name.',),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster1',parameters=[{'turtlename': 'turtle1'}],),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster2',parameters=[{'turtlename': 'turtle2'}],),Node(package='turtle_tf2_py',executable='turtle_tf2_listener',name='listener',parameters=[{'target_frame': LaunchConfiguration('target_frame')}],),])
话题重映射
from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='mimic',name='mimic',remappings=[('/input/pose', '/turtle2/pose'),('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),])])
启动rviz,并按照文件配置:
from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():return LaunchDescription([Node(package='rviz2',executable='rviz2',name='rviz2',arguments=['-d', PathJoinSubstitution([FindPackageShare('turtle_tf2_py'), 'rviz', 'turtle_rviz.rviz'])],),])
动态配置节点名:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable, LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('node_prefix',default_value=[EnvironmentVariable('USER'), '_'],description='prefix for node name'),Node(package='turtle_tf2_py',executable='fixed_frame_tf2_broadcaster',name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],),])
- 声明一个启动参数
node_prefix
,默认值为当前系统用户的用户名加上下划线(例如:doubao_) - 启动一个节点,节点名称由
node_prefix
和固定后缀fixed_broadcaster
组成(例如:doubao_fixed_broadcaster)
更新setup.py:
import os
from glob import glob
from setuptools import setup
...data_files=[...(os.path.join('share', package_name, 'launch'),glob('launch/*')),(os.path.join('share', package_name, 'config'),glob('config/*.yaml')),(os.path.join('share', package_name, 'rviz'),glob('config/*.rviz')),],