当机器人系统需要同时启动多个节点时,逐个在终端中运行 ros2 run 既繁琐又容易出错。Launch文件可以一次性启动多个节点,并配置它们的参数、重映射、命名空间等。
#!/usr/bin/env python3
"""基础Launch文件 - 启动传感器模拟系统"""
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, LogInfo
from launch.conditions import IfCondition, UnlessCondition
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
"""生成Launch描述"""
# ====== 声明Launch参数 ======
use_sim_arg = DeclareLaunchArgument(
'use_sim',
default_value='true',
description='是否使用仿真模式'
)
sensor_rate_arg = DeclareLaunchArgument(
'sensor_rate',
default_value='10.0',
description='传感器发布频率(Hz)'
)
use_rviz_arg = DeclareLaunchArgument(
'use_rviz',
default_value='true',
description='是否启动RViz2'
)
# 获取参数值
use_sim = LaunchConfiguration('use_sim')
sensor_rate = LaunchConfiguration('sensor_rate')
use_rviz = LaunchConfiguration('use_rviz')
# ====== 定义节点 ======
# 激光雷达节点
lidar_node = Node(
package='my_sensors_pkg',
executable='lidar_simulator',
name='lidar_sensor',
namespace='sensors',
parameters=[{
'scan_frequency': sensor_rate,
'num_readings': 360,
'obstacle_distance': 3.0,
}],
output='screen',
emulate_tty=True,
)
# 温度传感器节点
temp_node = Node(
package='my_sensors_pkg',
executable='temperature_sensor',
name='temp_sensor',
namespace='sensors',
parameters=[{
'base_temp': 25.0,
'noise_amplitude': 2.0,
'publish_rate': sensor_rate,
}],
output='screen',
)
# 障碍物检测节点
obstacle_node = Node(
package='my_nav_pkg',
executable='obstacle_detector',
name='obstacle_detector',
parameters=[{
'safe_distance': 1.0,
'stop_distance': 0.5,
}],
remappings=[
('scan', '/sensors/scan'),
('cmd_vel', '/cmd_vel'),
],
output='screen',
)
# 温度监控节点
monitor_node = Node(
package='my_nav_pkg',
executable='temperature_monitor',
name='temp_monitor',
parameters=[{
'warning_threshold': 30.0,
'critical_threshold': 35.0,
}],
remappings=[
('temperature', '/sensors/temperature'),
],
output='screen',
)
# RViz2(条件启动)
rviz_config = PathJoinSubstitution([
FindPackageShare('my_nav_pkg'), 'config', 'nav.rviz'
])
rviz_node = Node(
condition=IfCondition(use_rviz),
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', rviz_config],
output='screen',
)
# 启动日志
launch_log = LogInfo(msg='🚀 传感器系统启动中...')
return LaunchDescription([
# 声明参数
use_sim_arg,
sensor_rate_arg,
use_rviz_arg,
launch_log,
# 启动节点
lidar_node,
temp_node,
obstacle_node,
monitor_node,
rviz_node,
])
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.substitutions import FindPackageShare
# 包含另一个Launch文件
sensor_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
PathJoinSubstitution([
FindPackageShare('my_sensors_pkg'),
'launch', 'sensors.launch.py'
])
]),
launch_arguments={
'sensor_rate': '20.0',
'use_sim': 'true',
}.items()
)
from launch.actions import GroupAction
from launch_ros.actions import PushROSNamespace
# 为一组节点设置命名空间
robot1_group = GroupAction(actions=[
PushROSNamespace('robot1'),
Node(package='my_pkg', executable='sensor_node', name='lidar'),
Node(package='my_pkg', executable='nav_node', name='navigator'),
Node(package='my_pkg', executable='ctrl_node', name='controller'),
])
robot2_group = GroupAction(actions=[
PushROSNamespace('robot2'),
Node(package='my_pkg', executable='sensor_node', name='lidar'),
Node(package='my_pkg', executable='nav_node', name='navigator'),
Node(package='my_pkg', executable='ctrl_node', name='controller'),
])
from launch.actions import RegisterEventHandler, EmitEvent
from launch.event_handlers import OnProcessExit, OnProcessStart
from launch.events import Shutdown
# 节点退出时自动关闭
shutdown_on_exit = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=lidar_node,
on_exit=[
LogInfo(msg='⚠️ 激光雷达节点退出,关闭系统...'),
EmitEvent(event=Shutdown(reason='关键节点退出'))
]
)
)
# 节点启动后执行动作
on_start_log = RegisterEventHandler(
event_handler=OnProcessStart(
target_action=nav_node,
on_start=[
LogInfo(msg='✅ 导航节点已启动')
]
)
)
import os
from ament_index_python.packages import get_package_share_directory
pkg_dir = get_package_share_directory('my_nav_pkg')
# 方式1:加载整个YAML文件
nav_params = os.path.join(pkg_dir, 'config', 'nav_params.yaml')
nav_node = Node(
package='nav2_controller',
executable='controller_server',
parameters=[nav_params],
)
# 方式2:加载多个参数文件
nav_node = Node(
package='nav2_controller',
executable='controller_server',
parameters=[
nav_params,
os.path.join(pkg_dir, 'config', 'costmap_params.yaml'),
{'use_sim_time': True}, # 同时传入字典
],
)
#!/usr/bin/env python3
"""完整导航系统Launch - Nav2 + 仿真"""
import os
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
pkg_dir = get_package_share_directory('my_robot_nav')
nav2_launch_dir = get_package_share_directory('nav2_bringup')
# 参数声明
use_sim_time = DeclareLaunchArgument(
'use_sim_time', default_value='true')
map_file = DeclareLaunchArgument(
'map', default_value=os.path.join(pkg_dir, 'maps', 'my_map.yaml'))
params_file = DeclareLaunchArgument(
'params_file',
default_value=os.path.join(pkg_dir, 'config', 'nav2_params.yaml'))
# 仿真环境
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(get_package_share_directory('gazebo_ros'),
'launch', 'gazebo.launch.py')
]),
launch_arguments={'world': os.path.join(
pkg_dir, 'worlds', 'my_world.world')}.items()
)
# 机器人Spawn
spawn_robot = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=[
'-entity', 'my_robot',
'-robot_namespace', 'robot',
'-file', os.path.join(pkg_dir, 'urdf', 'robot.urdf'),
'-x', '0', '-y', '0', '-z', '0.1',
],
output='screen',
)
# Nav2导航栈
navigation = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(nav2_launch_dir, 'launch', 'navigation_launch.py')
]),
launch_arguments={
'use_sim_time': LaunchConfiguration('use_sim_time'),
'params_file': LaunchConfiguration('params_file'),
}.items()
)
# SLAM
slam = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(nav2_launch_dir, 'launch', 'slam_launch.py')
]),
launch_arguments={
'use_sim_time': LaunchConfiguration('use_sim_time'),
'slam_params_file': LaunchConfiguration('params_file'),
}.items()
)
# RViz
rviz = Node(
package='rviz2',
executable='rviz2',
arguments=['-d', os.path.join(pkg_dir, 'rviz', 'nav2.rviz')],
parameters=[{'use_sim_time': LaunchConfiguration('use_sim_time')}],
output='screen',
)
return LaunchDescription([
use_sim_time, map_file, params_file,
gazebo, spawn_robot, navigation, slam, rviz,
])
# 运行Launch文件
ros2 launch my_pkg my_launch.py
# 传递参数
ros2 launch my_pkg my_launch.py use_sim:=false sensor_rate:=20.0
# 查看Launch文件内容
ros2 launch my_pkg my_launch.py --show-args
# 从包中查找Launch文件
ros2 pkg prefix my_pkg
find $(ros2 pkg prefix my_pkg) -name "*.launch.py"
创建一个Launch文件,同时启动激光雷达、IMU和摄像头三个传感器节点,每个节点使用不同频率参数。
创建Launch文件,通过 use_rviz 参数控制是否启动RViz,通过 use_sim 参数选择真实传感器还是仿真节点。
创建多机器人Launch,使用GroupAction为两个机器人设置不同命名空间,各自运行独立的导航栈。
经验值:+200 XP