【ROS1从入门到精通】第8篇:Launch文件编写(多节点协同管理)
🎯 本文目标:深入理解ROS Launch系统,掌握launch文件的编写技巧,学会多节点协同管理、参数配置、命名空间管理,能够设计和实现复杂的机器人系统启动方案。
📑 目录
1. Launch系统概述
1.1 为什么需要Launch文件?
在复杂的机器人系统中,通常需要同时启动多个节点,每个节点都有自己的参数配置。手动启动每个节点不仅繁琐,而且容易出错。Launch系统提供了一种声明式的方式来管理整个系统的启动。
Launch启动(简单) 手动启动(繁琐) roslaunch my_robot system.launch 自动启动roscore 启动所有节点 加载参数 设置命名空间 配置日志 终端1: roscore 终端2: rosrun pkg1 node1 终端3: rosrun pkg2 node2 终端4: rosparam load 终端5: rosrun pkg3 node3
1.2 Launch系统特点
| 特性 | 描述 |
|---|---|
| 批量启动 | 一次启动多个节点 |
| 参数管理 | 统一配置和加载参数 |
| 命名空间 | 组织节点的层次结构 |
| 重映射 | 灵活的话题和服务连接 |
| 条件逻辑 | 根据条件启动不同配置 |
| 分布式 | 支持远程机器上启动节点 |
| 日志管理 | 统一的日志输出控制 |
1.3 Launch文件格式
Launch文件使用XML格式,主要元素包括:
xml
<launch> <!-- 根元素 -->
<node> <!-- 启动节点 -->
<param> <!-- 设置参数 -->
<rosparam> <!-- 加载参数文件 -->
<include> <!-- 包含其他launch文件 -->
<arg> <!-- 定义参数变量 -->
<group> <!-- 节点分组 -->
<remap> <!-- 重映射 -->
<machine> <!-- 远程机器配置 -->
<env> <!-- 环境变量 -->
<test> <!-- 测试节点 -->
</launch>
2. Launch文件基础
2.1 基本Launch文件
xml
<!-- launch/basic_example.launch -->
<launch>
<!-- 1. 启动单个节点 -->
<node name="talker" pkg="rospy_tutorials" type="talker.py" />
<!-- 2. 带输出配置的节点 -->
<node name="listener" pkg="rospy_tutorials" type="listener.py"
output="screen" />
<!-- 3. 必需节点(如果死亡则关闭整个launch) -->
<node name="critical_node" pkg="my_package" type="critical_node"
required="true" />
<!-- 4. 重启节点 -->
<node name="unstable_node" pkg="my_package" type="unstable_node"
respawn="true" respawn_delay="10" />
<!-- 5. 自定义节点名 -->
<node name="my_custom_name" pkg="my_package" type="original_node" />
</launch>
2.2 节点属性详解
xml
<!-- launch/node_attributes.launch -->
<launch>
<!-- 完整的节点配置示例 -->
<node
pkg="my_package" <!-- 必需:包名 -->
type="my_node" <!-- 必需:可执行文件名 -->
name="custom_node_name" <!-- 必需:节点运行时名称 -->
output="screen" <!-- 输出位置:screen/log -->
respawn="true" <!-- 死亡后自动重启 -->
respawn_delay="5" <!-- 重启延迟(秒) -->
required="false" <!-- 是否为必需节点 -->
launch-prefix="gdb -ex run" <!-- 启动前缀(如调试器) -->
cwd="ROS_HOME" <!-- 工作目录:ROS_HOME/node -->
args="--param1 value1" <!-- 命令行参数 -->
machine="remote_machine" <!-- 远程机器名 -->
clear_params="true" <!-- 启动前清除私有参数 -->
ns="robot1" <!-- 命名空间 -->
>
<!-- 节点内的参数设置 -->
<param name="frequency" value="10.0" />
<param name="debug_mode" value="true" />
<!-- 节点内的重映射 -->
<remap from="input_topic" to="/global_input" />
<remap from="output_topic" to="/global_output" />
<!-- 环境变量 -->
<env name="CUSTOM_ENV" value="custom_value" />
</node>
</launch>
2.3 参数设置方式
xml
<!-- launch/parameters.launch -->
<launch>
<!-- 1. 直接设置参数 -->
<param name="robot_name" value="my_robot" />
<param name="max_speed" value="2.0" type="double" />
<param name="debug_mode" value="true" type="bool" />
<param name="sensor_ids" value="[1, 2, 3, 4]" type="yaml" />
<!-- 2. 从文件加载参数 -->
<rosparam file="$(find my_package)/config/robot_config.yaml"
command="load" />
<!-- 3. 命名空间参数 -->
<group ns="robot1">
<param name="id" value="001" />
<param name="type" value="mobile" />
</group>
<!-- 4. 节点私有参数 -->
<node name="my_node" pkg="my_package" type="my_node">
<param name="private_param" value="private_value" />
<rosparam>
nested_param:
key1: value1
key2: value2
</rosparam>
</node>
<!-- 5. 使用命令输出作为参数 -->
<param name="current_time"
command="date +%Y-%m-%d_%H-%M-%S" />
<!-- 6. 使用环境变量 -->
<param name="home_dir" value="$(env HOME)" />
<!-- 7. 二进制数据参数 -->
<param name="binary_data"
binfile="$(find my_package)/data/binary_file.dat" />
<!-- 8. 文本文件参数 -->
<param name="config_text"
textfile="$(find my_package)/config/settings.txt" />
</launch>
3. 节点管理
3.1 节点组织结构
xml
<!-- launch/node_organization.launch -->
<launch>
<!-- 使用group组织节点 -->
<group ns="perception">
<!-- 传感器处理节点 -->
<node name="camera_driver" pkg="camera_pkg" type="camera_node" />
<node name="lidar_driver" pkg="lidar_pkg" type="lidar_node" />
<group ns="processing">
<node name="image_proc" pkg="image_proc" type="image_proc_node" />
<node name="point_cloud_proc" pkg="pcl_pkg" type="pcl_node" />
</group>
</group>
<group ns="planning">
<!-- 规划节点 -->
<node name="global_planner" pkg="nav_pkg" type="global_planner" />
<node name="local_planner" pkg="nav_pkg" type="local_planner" />
</group>
<group ns="control">
<!-- 控制节点 -->
<node name="motion_controller" pkg="control_pkg" type="controller" />
<node name="servo_driver" pkg="hardware_pkg" type="servo_node" />
</group>
<!-- 全局节点(不在任何命名空间) -->
<node name="system_monitor" pkg="monitor_pkg" type="monitor_node" />
</launch>
3.2 多机器人配置
xml
<!-- launch/multi_robot.launch -->
<launch>
<!-- 定义参数 -->
<arg name="robot_count" default="3" />
<arg name="base_port" default="11311" />
<!-- Robot 1 -->
<group ns="robot1">
<param name="robot_id" value="robot_001" />
<param name="initial_x" value="0.0" />
<param name="initial_y" value="0.0" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot1" />
<arg name="port" value="$(arg base_port)" />
</include>
</group>
<!-- Robot 2 -->
<group ns="robot2">
<param name="robot_id" value="robot_002" />
<param name="initial_x" value="2.0" />
<param name="initial_y" value="0.0" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot2" />
<arg name="port" value="$(eval arg('base_port') + 1)" />
</include>
</group>
<!-- Robot 3 (条件启动) -->
<group ns="robot3" if="$(eval arg('robot_count') >= 3)">
<param name="robot_id" value="robot_003" />
<param name="initial_x" value="4.0" />
<param name="initial_y" value="0.0" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot3" />
<arg name="port" value="$(eval arg('base_port') + 2)" />
</include>
</group>
<!-- 中央协调器 -->
<node name="fleet_manager" pkg="fleet_pkg" type="fleet_manager">
<param name="robot_count" value="$(arg robot_count)" />
<rosparam>
robot_list: [robot1, robot2, robot3]
</rosparam>
</node>
</launch>
3.3 节点生命周期管理
xml
<!-- launch/lifecycle_management.launch -->
<launch>
<!-- 1. 启动顺序控制 -->
<!-- 先启动硬件驱动 -->
<node name="hardware_driver" pkg="hw_pkg" type="hw_driver"
required="true" />
<!-- 延迟启动传感器(等待硬件初始化) -->
<node name="sensor_node" pkg="sensor_pkg" type="sensor_node"
launch-prefix="bash -c 'sleep 5; $0 $@' " />
<!-- 2. 依赖管理 -->
<!-- 主节点 -->
<node name="master_node" pkg="my_pkg" type="master"
required="true" />
<!-- 从节点(依赖主节点) -->
<node name="slave_node" pkg="my_pkg" type="slave"
respawn="true"
respawn_delay="5" />
<!-- 3. 故障恢复 -->
<!-- 关键节点(失败则关闭所有) -->
<node name="critical_system" pkg="core_pkg" type="core_node"
required="true" />
<!-- 可恢复节点 -->
<node name="recoverable_node" pkg="my_pkg" type="node"
respawn="true"
respawn_delay="10" />
<!-- 有限重试节点 -->
<node name="limited_retry_node" pkg="my_pkg" type="node"
respawn="true"
respawn_delay="5"
respawn_limit="3" />
<!-- 4. 清理操作 -->
<!-- 使用roslaunch-check确保清理 -->
<node name="cleanup_node" pkg="my_pkg" type="cleanup"
launch-prefix="roslaunch-check" />
</launch>
4. 参数配置
4.1 参数文件管理
yaml
# config/robot_config.yaml
# 机器人配置文件
# 机器人信息
robot:
name: "MyRobot"
model: "MR-100"
version: "1.0.0"
# 硬件配置
hardware:
motors:
left:
port: "/dev/motor_left"
max_rpm: 100
pid:
kp: 1.0
ki: 0.1
kd: 0.05
right:
port: "/dev/motor_right"
max_rpm: 100
pid:
kp: 1.0
ki: 0.1
kd: 0.05
sensors:
lidar:
port: "/dev/ttyUSB0"
baud: 115200
range_min: 0.15
range_max: 12.0
camera:
device: "/dev/video0"
resolution: [640, 480]
fps: 30
# 导航配置
navigation:
global_planner:
algorithm: "dijkstra"
resolution: 0.05
timeout: 5.0
local_planner:
algorithm: "dwa"
max_vel_x: 1.0
max_vel_theta: 1.0
acc_lim_x: 0.5
acc_lim_theta: 0.5
# 行为配置
behaviors:
obstacle_avoidance:
enabled: true
min_distance: 0.5
auto_charging:
enabled: true
battery_threshold: 20.0
xml
<!-- launch/with_config.launch -->
<launch>
<!-- 加载参数文件到不同命名空间 -->
<!-- 1. 全局参数 -->
<rosparam file="$(find my_robot)/config/robot_config.yaml"
command="load" />
<!-- 2. 命名空间参数 -->
<group ns="robot1">
<rosparam file="$(find my_robot)/config/robot_config.yaml"
command="load" />
</group>
<!-- 3. 节点私有参数 -->
<node name="navigation" pkg="nav_pkg" type="nav_node">
<rosparam file="$(find my_robot)/config/navigation.yaml"
command="load" />
</node>
<!-- 4. 参数替换 -->
<rosparam subst_value="true">
config_path: $(find my_robot)/config
data_path: $(find my_robot)/data
log_path: $(env HOME)/.ros/log
</rosparam>
<!-- 5. 条件参数加载 -->
<arg name="use_sim" default="false" />
<rosparam file="$(find my_robot)/config/real_robot.yaml"
command="load"
unless="$(arg use_sim)" />
<rosparam file="$(find my_robot)/config/simulation.yaml"
command="load"
if="$(arg use_sim)" />
<!-- 6. 删除参数 -->
<rosparam command="delete" param="obsolete_param" />
<!-- 7. 导出参数到文件 -->
<rosparam command="dump"
file="$(find my_robot)/config/exported_params.yaml"
param="/" />
</launch>
4.2 动态参数配置
xml
<!-- launch/dynamic_params.launch -->
<launch>
<!-- 使用arg定义可变参数 -->
<arg name="robot_name" default="robot1" />
<arg name="max_speed" default="1.0" />
<arg name="enable_slam" default="true" />
<arg name="map_file" default="$(find my_maps)/map/office.yaml" />
<!-- 使用doc属性文档化参数 -->
<arg name="use_gpu" default="false"
doc="Enable GPU acceleration for perception" />
<!-- 参数值类型转换 -->
<arg name="num_robots" default="3" />
<param name="robot_count" value="$(arg num_robots)" type="int" />
<!-- 计算参数 -->
<arg name="base_frame" default="base_link" />
<arg name="sensor_frame" value="$(arg base_frame)_sensor" />
<!-- 条件参数 -->
<param name="use_slam" value="$(arg enable_slam)" />
<param name="slam_method" value="gmapping" if="$(arg enable_slam)" />
<param name="slam_method" value="none" unless="$(arg enable_slam)" />
<!-- 表达式计算 -->
<param name="doubled_speed" value="$(eval 2 * arg('max_speed'))" />
<param name="speed_limit" value="$(eval min(arg('max_speed'), 2.0))" />
<!-- 字符串拼接 -->
<param name="full_name"
value="$(eval arg('robot_name') + '_' + str(arg('num_robots')))" />
<!-- 布尔逻辑 -->
<param name="enable_advanced"
value="$(eval arg('use_gpu') and arg('enable_slam'))" />
<!-- 启动节点并传递参数 -->
<node name="robot_controller" pkg="control_pkg" type="controller">
<param name="robot_name" value="$(arg robot_name)" />
<param name="max_speed" value="$(arg max_speed)" />
<!-- 嵌套条件 -->
<group if="$(arg enable_slam)">
<param name="slam_enabled" value="true" />
<param name="map_file" value="$(arg map_file)" />
</group>
</node>
</launch>
5. 命名空间和重映射
5.1 命名空间管理
xml
<!-- launch/namespace_example.launch -->
<launch>
<!-- 1. 基本命名空间 -->
<group ns="sensors">
<node name="lidar" pkg="sensor_pkg" type="lidar_node" />
<node name="camera" pkg="sensor_pkg" type="camera_node" />
</group>
<!-- 2. 嵌套命名空间 -->
<group ns="robot1">
<group ns="sensors">
<node name="lidar" pkg="sensor_pkg" type="lidar_node" />
<!-- 完整名称: /robot1/sensors/lidar -->
</group>
<group ns="actuators">
<node name="motor" pkg="actuator_pkg" type="motor_node" />
<!-- 完整名称: /robot1/actuators/motor -->
</group>
</group>
<!-- 3. 动态命名空间 -->
<arg name="vehicle_name" default="car1" />
<group ns="$(arg vehicle_name)">
<node name="controller" pkg="control_pkg" type="controller" />
</group>
<!-- 4. 推送命名空间到包含文件 -->
<include file="$(find other_pkg)/launch/subsystem.launch"
ns="subsystem1" />
<!-- 5. 全局命名空间参数 -->
<param name="/global_param" value="global_value" />
<!-- 6. 相对命名空间参数 -->
<group ns="robot">
<param name="local_param" value="local_value" />
<!-- 实际路径: /robot/local_param -->
</group>
<!-- 7. 私有命名空间参数 -->
<node name="my_node" pkg="my_pkg" type="my_node">
<param name="~private_param" value="private_value" />
<!-- 实际路径: /my_node/private_param -->
</node>
</launch>
5.2 话题重映射
xml
<!-- launch/remapping_example.launch -->
<launch>
<!-- 1. 全局重映射 -->
<remap from="camera/image_raw" to="camera/image" />
<!-- 2. 节点级重映射 -->
<node name="image_processor" pkg="image_pkg" type="processor">
<remap from="input_image" to="/camera/image_raw" />
<remap from="output_image" to="/processed/image" />
</node>
<!-- 3. 组级重映射 -->
<group ns="robot1">
<remap from="cmd_vel" to="/robot1/cmd_vel" />
<node name="teleop" pkg="teleop_pkg" type="teleop_node">
<!-- 继承组的重映射 -->
</node>
</group>
<!-- 4. 双向通信重映射 -->
<node name="controller" pkg="control_pkg" type="controller">
<!-- 订阅 -->
<remap from="odometry_in" to="/robot/odom" />
<!-- 发布 -->
<remap from="cmd_out" to="/robot/cmd_vel" />
</node>
<!-- 5. 服务重映射 -->
<node name="service_client" pkg="my_pkg" type="client">
<remap from="get_plan" to="/planner/get_plan" />
</node>
<!-- 6. 动作重映射 -->
<node name="action_client" pkg="my_pkg" type="action_client">
<remap from="navigate" to="/navigation/navigate_to_goal" />
</node>
<!-- 7. 使用参数的重映射 -->
<arg name="input_topic" default="sensor_data" />
<arg name="output_topic" default="processed_data" />
<node name="processor" pkg="my_pkg" type="processor">
<remap from="input" to="$(arg input_topic)" />
<remap from="output" to="$(arg output_topic)" />
</node>
<!-- 8. 条件重映射 -->
<arg name="use_filtered" default="true" />
<node name="navigator" pkg="nav_pkg" type="navigator">
<remap from="scan" to="/scan_filtered" if="$(arg use_filtered)" />
<remap from="scan" to="/scan_raw" unless="$(arg use_filtered)" />
</node>
</launch>
6. 条件启动和包含
6.1 条件逻辑
xml
<!-- launch/conditional_launch.launch -->
<launch>
<!-- 定义参数 -->
<arg name="mode" default="simulation" /> <!-- simulation/real/debug -->
<arg name="enable_vision" default="true" />
<arg name="robot_type" default="mobile" /> <!-- mobile/arm/drone -->
<arg name="sensor_suite" default="basic" /> <!-- basic/advanced/full -->
<!-- 1. 简单条件 -->
<group if="$(arg enable_vision)">
<node name="camera" pkg="camera_pkg" type="camera_node" />
<node name="vision_proc" pkg="vision_pkg" type="processor" />
</group>
<!-- 2. unless条件(否定) -->
<group unless="$(arg enable_vision)">
<node name="fake_vision" pkg="mock_pkg" type="fake_vision" />
</group>
<!-- 3. 复杂表达式 -->
<group if="$(eval arg('mode') == 'simulation')">
<include file="$(find my_robot)/launch/simulation.launch" />
</group>
<group if="$(eval arg('mode') == 'real')">
<include file="$(find my_robot)/launch/hardware.launch" />
</group>
<group if="$(eval arg('mode') == 'debug')">
<node name="debugger" pkg="debug_pkg" type="debugger"
launch-prefix="gdb -ex run" />
</group>
<!-- 4. 多条件组合 -->
<group if="$(eval arg('robot_type') == 'mobile' and arg('sensor_suite') == 'full')">
<include file="$(find my_robot)/launch/mobile_full_sensors.launch" />
</group>
<!-- 5. 数值比较 -->
<arg name="num_cameras" default="2" />
<group if="$(eval arg('num_cameras') >= 1)">
<node name="camera1" pkg="camera_pkg" type="camera_node">
<param name="camera_id" value="0" />
</node>
</group>
<group if="$(eval arg('num_cameras') >= 2)">
<node name="camera2" pkg="camera_pkg" type="camera_node">
<param name="camera_id" value="1" />
</node>
</group>
<!-- 6. 字符串操作 -->
<arg name="environment" default="indoor" />
<group if="$(eval 'indoor' in arg('environment'))">
<param name="max_range" value="10.0" />
</group>
<group if="$(eval 'outdoor' in arg('environment'))">
<param name="max_range" value="50.0" />
</group>
<!-- 7. 选择性包含 -->
<arg name="planner_type" default="global" />
<include file="$(find nav_pkg)/launch/global_planner.launch"
if="$(eval arg('planner_type') == 'global')" />
<include file="$(find nav_pkg)/launch/local_planner.launch"
if="$(eval arg('planner_type') == 'local')" />
<include file="$(find nav_pkg)/launch/hybrid_planner.launch"
if="$(eval arg('planner_type') == 'hybrid')" />
</launch>
6.2 文件包含
xml
<!-- launch/include_example.launch -->
<launch>
<!-- 1. 基本包含 -->
<include file="$(find other_package)/launch/other.launch" />
<!-- 2. 带参数的包含 -->
<include file="$(find nav_package)/launch/navigation.launch">
<arg name="map_file" value="$(find my_maps)/office.yaml" />
<arg name="use_amcl" value="true" />
</include>
<!-- 3. 命名空间包含 -->
<include file="$(find sensor_package)/launch/sensors.launch"
ns="robot1" />
<!-- 4. 条件包含 -->
<arg name="include_slam" default="true" />
<include file="$(find slam_package)/launch/slam.launch"
if="$(arg include_slam)" />
<!-- 5. 清除参数的包含 -->
<include file="$(find config_package)/launch/config.launch"
clear_params="true" />
<!-- 6. 传递所有参数 -->
<arg name="robot_name" default="my_robot" />
<arg name="robot_id" default="001" />
<include file="$(find robot_package)/launch/robot.launch"
pass_all_args="true" />
<!-- 7. 嵌套包含 -->
<include file="$(find my_package)/launch/subsystem.launch">
<arg name="subsystem_id" value="1" />
<!-- 可以在include内部设置参数 -->
<param name="subsystem_param" value="value" />
<!-- 可以在include内部重映射 -->
<remap from="subsystem_topic" to="/global_topic" />
</include>
<!-- 8. 使用环境变量的包含 -->
<include file="$(env ROS_WORKSPACE)/launch/custom.launch"
if="$(optenv USE_CUSTOM false)" />
<!-- 9. 递归包含保护 -->
<arg name="included_once" default="false" />
<group unless="$(arg included_once)">
<include file="$(find my_package)/launch/this_file.launch">
<arg name="included_once" value="true" />
</include>
</group>
</launch>
7. 高级Launch特性
7.1 机器配置(分布式系统)
xml
<!-- launch/distributed_system.launch -->
<launch>
<!-- 定义远程机器 -->
<machine name="robot1_machine"
address="192.168.1.101"
env-loader="/home/robot/ros_env.sh"
default="false"
user="robot"
password="robot123"
timeout="10.0" />
<machine name="robot2_machine"
address="192.168.1.102"
env-loader="/home/robot/ros_env.sh"
default="false"
user="robot"
ssh-port="2222"
timeout="10.0" />
<machine name="server_machine"
address="192.168.1.100"
env-loader="/opt/ros/noetic/env.sh"
default="true"
user="server" />
<!-- 在不同机器上启动节点 -->
<!-- Robot 1上的节点 -->
<node name="robot1_controller"
pkg="control_pkg"
type="controller"
machine="robot1_machine" />
<node name="robot1_sensors"
pkg="sensor_pkg"
type="sensor_node"
machine="robot1_machine" />
<!-- Robot 2上的节点 -->
<node name="robot2_controller"
pkg="control_pkg"
type="controller"
machine="robot2_machine" />
<node name="robot2_sensors"
pkg="sensor_pkg"
type="sensor_node"
machine="robot2_machine" />
<!-- 服务器上的节点(默认机器) -->
<node name="fleet_manager"
pkg="fleet_pkg"
type="fleet_manager" />
<node name="mission_planner"
pkg="planning_pkg"
type="mission_planner" />
<!-- 设置ROS_MASTER_URI -->
<env name="ROS_MASTER_URI" value="http://192.168.1.100:11311" />
<!-- 为特定机器设置环境变量 -->
<machine name="gpu_machine"
address="192.168.1.103"
env-loader="/home/robot/gpu_env.sh">
<env name="CUDA_VISIBLE_DEVICES" value="0,1" />
<env name="LD_LIBRARY_PATH" value="/usr/local/cuda/lib64:$LD_LIBRARY_PATH" />
</machine>
</launch>
7.2 测试集成
xml
<!-- launch/test_integration.launch -->
<launch>
<!-- 测试专用launch文件 -->
<!-- 1. 单元测试节点 -->
<test test-name="test_controller"
pkg="my_package"
type="test_controller"
time-limit="60.0">
<param name="test_param" value="test_value" />
</test>
<!-- 2. 集成测试 -->
<test test-name="integration_test"
pkg="my_package"
type="integration_test.py"
time-limit="120.0"
retry="3">
<!-- 测试配置 -->
<rosparam>
test_config:
timeout: 30
iterations: 100
expected_result: success
</rosparam>
</test>
<!-- 3. 性能测试 -->
<test test-name="performance_test"
pkg="my_package"
type="performance_test"
time-limit="300.0"
required="true">
<param name="message_rate" value="1000" />
<param name="duration" value="60" />
</test>
<!-- 4. 启动被测试的节点 -->
<node name="node_under_test"
pkg="my_package"
type="my_node"
output="screen">
<param name="test_mode" value="true" />
</node>
<!-- 5. 模拟数据发布 -->
<node name="test_data_publisher"
pkg="rostopic"
type="rostopic"
args="pub -r 10 /test_topic std_msgs/String 'test_data'" />
<!-- 6. 使用rostest参数 -->
<arg name="coverage" default="false" />
<test test-name="coverage_test"
pkg="my_package"
type="test_with_coverage"
if="$(arg coverage)"
launch-prefix="python-coverage run">
<param name="generate_report" value="true" />
</test>
</launch>
7.3 高级参数技巧
xml
<!-- launch/advanced_params.launch -->
<launch>
<!-- 1. 参数替换和计算 -->
<arg name="base_speed" default="1.0" />
<!-- 数学运算 -->
<param name="double_speed" value="$(eval 2.0 * arg('base_speed'))" />
<param name="half_speed" value="$(eval arg('base_speed') / 2.0)" />
<param name="speed_squared" value="$(eval arg('base_speed') ** 2)" />
<!-- 2. 查找和路径操作 -->
<param name="package_path" value="$(find my_package)" />
<param name="config_file" value="$(find my_package)/config/settings.yaml" />
<!-- 匿名ID生成 -->
<param name="unique_id" value="robot_$(anon robot)" />
<!-- 3. 选项环境变量 -->
<param name="workspace" value="$(optenv ROS_WORKSPACE /home/robot/ws)" />
<param name="log_level" value="$(optenv ROS_LOG_LEVEL INFO)" />
<!-- 4. 复杂数据结构 -->
<rosparam>
robot_config:
sensors:
- name: lidar
type: laser
rate: 10
- name: camera
type: image
rate: 30
controllers:
pid:
p: [1.0, 1.0, 1.0]
i: [0.1, 0.1, 0.1]
d: [0.05, 0.05, 0.05]
</rosparam>
<!-- 5. 动态YAML生成 -->
<rosparam subst_value="true">
dynamic_config:
timestamp: "$(eval __import__('time').time())"
hostname: "$(env HOSTNAME)"
user: "$(env USER)"
ros_distro: "$(env ROS_DISTRO)"
</rosparam>
<!-- 6. 参数命名空间操作 -->
<group ns="robot$(arg robot_id)">
<rosparam command="load" file="$(find my_package)/config/robot.yaml" />
<!-- 删除特定参数 -->
<rosparam command="delete" param="deprecated_param" />
<!-- 导出参数 -->
<rosparam command="dump"
file="/tmp/robot$(arg robot_id)_params.yaml" />
</group>
</launch>
8. 调试和错误处理
8.1 调试技巧
xml
<!-- launch/debug_launch.launch -->
<launch>
<!-- 1. 输出控制 -->
<arg name="debug" default="false" />
<!-- 条件输出 -->
<node name="my_node" pkg="my_package" type="my_node"
output="screen" if="$(arg debug)"
output="log" unless="$(arg debug)" />
<!-- 2. GDB调试 -->
<node name="debug_node" pkg="my_package" type="my_node"
launch-prefix="gdb -ex run --args"
if="$(arg debug)" />
<!-- 3. Valgrind内存检查 -->
<node name="memory_check_node" pkg="my_package" type="my_node"
launch-prefix="valgrind --leak-check=full"
if="$(arg debug)" />
<!-- 4. 日志级别设置 -->
<env name="ROSCONSOLE_CONFIG_FILE"
value="$(find my_package)/config/rosconsole.config"
if="$(arg debug)" />
<!-- 5. 打印参数值(调试用) -->
<arg name="param_to_debug" default="test_value" />
<node name="echo_param" pkg="rostopic" type="rostopic"
args="echo /rosout"
if="$(arg debug)" />
<!-- 6. 启动前延迟(用于附加调试器) -->
<node name="delayed_node" pkg="my_package" type="my_node"
launch-prefix="bash -c 'sleep 5; $0 $@' "
if="$(arg debug)" />
<!-- 7. 核心转储设置 -->
<env name="ROS_BREAK_ON_ASSERT" value="1" if="$(arg debug)" />
<!-- 8. 记录所有话题 -->
<node name="recorder" pkg="rosbag" type="record"
args="-a -o /tmp/debug_bag"
if="$(arg debug)" />
<!-- 9. 系统信息节点 -->
<node name="system_info" pkg="diagnostic_aggregator"
type="aggregator_node"
if="$(arg debug)">
<rosparam>
analyzers:
system:
type: diagnostic_aggregator/GenericAnalyzer
path: System
contains: ['CPU', 'Memory', 'Disk']
</rosparam>
</node>
<!-- 10. 错误处理 -->
<arg name="continue_on_error" default="false" />
<node name="critical_node" pkg="my_package" type="critical_node"
required="$(eval not arg('continue_on_error'))" />
</launch>
8.2 常见错误处理
xml
<!-- launch/error_handling.launch -->
<launch>
<!-- 1. 参数验证 -->
<arg name="robot_id" /> <!-- 必需参数,无默认值 -->
<!-- 验证参数范围 -->
<arg name="speed" default="1.0" />
<group if="$(eval arg('speed') > 10.0 or arg('speed') < 0)">
<node name="error_reporter" pkg="my_package" type="error_node">
<param name="error_msg" value="Speed out of range!" />
</node>
</group>
<!-- 2. 文件存在性检查 -->
<arg name="config_file" default="$(find my_package)/config/default.yaml" />
<!-- 使用Python检查文件 -->
<param name="config_exists"
command="python -c "import os; print(os.path.exists('$(arg config_file)'))"" />
<!-- 3. 包存在性检查 -->
<arg name="optional_package" default="optional_pkg" />
<!-- 尝试加载可选包 -->
<include file="$(find $(arg optional_package))/launch/optional.launch"
if="$(eval __import__('rospkg').RosPack().list().count('$(arg optional_package)') > 0)" />
<!-- 4. 网络连接检查 -->
<param name="network_check"
command="ping -c 1 -W 1 192.168.1.100 > /dev/null 2>&1 && echo true || echo false" />
<!-- 5. 依赖顺序管理 -->
<!-- 确保依赖节点先启动 -->
<node name="dependency" pkg="my_package" type="dependency_node"
required="true" />
<!-- 延迟启动依赖它的节点 -->
<node name="dependent" pkg="my_package" type="dependent_node"
launch-prefix="bash -c 'sleep 2; $0 $@' " />
<!-- 6. 超时处理 -->
<test test-name="startup_test" pkg="my_package" type="startup_test"
time-limit="30.0" />
<!-- 7. 清理操作 -->
<node name="cleanup" pkg="my_package" type="cleanup_node"
launch-prefix="trap 'your_cleanup_command' EXIT; " />
</launch>
9. 实战案例:多机器人系统启动
9.1 主Launch文件
xml
<!-- launch/multi_robot_system.launch -->
<launch>
<!-- ============ 系统参数 ============ -->
<arg name="num_robots" default="3" doc="Number of robots to spawn" />
<arg name="world_name" default="warehouse" doc="Gazebo world name" />
<arg name="use_sim_time" default="true" doc="Use simulation time" />
<arg name="gui" default="true" doc="Launch GUI tools" />
<arg name="rviz" default="true" doc="Launch RViz" />
<arg name="record" default="false" doc="Record rosbag" />
<!-- ============ 仿真环境 ============ -->
<include file="$(find my_simulation)/launch/world.launch">
<arg name="world_name" value="$(arg world_name)" />
<arg name="gui" value="$(arg gui)" />
<arg name="use_sim_time" value="$(arg use_sim_time)" />
</include>
<!-- ============ 参数服务器配置 ============ -->
<param name="/use_sim_time" value="$(arg use_sim_time)" />
<rosparam file="$(find my_robot)/config/system_config.yaml" command="load" />
<!-- ============ 多机器人生成 ============ -->
<!-- Robot 1 -->
<group ns="robot1">
<param name="tf_prefix" value="robot1" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot1" />
<arg name="init_pose_x" value="0.0" />
<arg name="init_pose_y" value="0.0" />
<arg name="init_pose_yaw" value="0.0" />
</include>
</group>
<!-- Robot 2 -->
<group ns="robot2" if="$(eval arg('num_robots') >= 2)">
<param name="tf_prefix" value="robot2" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot2" />
<arg name="init_pose_x" value="2.0" />
<arg name="init_pose_y" value="0.0" />
<arg name="init_pose_yaw" value="0.0" />
</include>
</group>
<!-- Robot 3 -->
<group ns="robot3" if="$(eval arg('num_robots') >= 3)">
<param name="tf_prefix" value="robot3" />
<include file="$(find my_robot)/launch/single_robot.launch">
<arg name="robot_name" value="robot3" />
<arg name="init_pose_x" value="4.0" />
<arg name="init_pose_y" value="0.0" />
<arg name="init_pose_yaw" value="0.0" />
</include>
</group>
<!-- ============ 中央系统 ============ -->
<include file="$(find my_robot)/launch/central_system.launch">
<arg name="num_robots" value="$(arg num_robots)" />
</include>
<!-- ============ 可视化 ============ -->
<group if="$(arg rviz)">
<node name="rviz" pkg="rviz" type="rviz"
args="-d $(find my_robot)/rviz/multi_robot.rviz" />
</group>
<!-- ============ 数据记录 ============ -->
<group if="$(arg record)">
<node name="rosbag_record" pkg="rosbag" type="record"
args="-a -x '(.*)camera(.*)' -o $(find my_robot)/bags/multi_robot" />
</group>
<!-- ============ 监控和诊断 ============ -->
<include file="$(find my_robot)/launch/monitoring.launch" />
</launch>
9.2 单机器人Launch文件
xml
<!-- launch/single_robot.launch -->
<launch>
<!-- 参数 -->
<arg name="robot_name" />
<arg name="init_pose_x" default="0.0" />
<arg name="init_pose_y" default="0.0" />
<arg name="init_pose_yaw" default="0.0" />
<!-- ======== 机器人模型 ======== -->
<param name="robot_description"
command="$(find xacro)/xacro $(find my_robot_description)/urdf/robot.urdf.xacro
robot_name:=$(arg robot_name)" />
<node name="robot_state_publisher" pkg="robot_state_publisher"
type="robot_state_publisher">
<param name="tf_prefix" value="$(arg robot_name)" />
</node>
<!-- ======== 传感器驱动 ======== -->
<include file="$(find my_robot)/launch/sensors.launch">
<arg name="robot_name" value="$(arg robot_name)" />
</include>
<!-- ======== 定位 ======== -->
<node name="localization" pkg="amcl" type="amcl">
<rosparam file="$(find my_robot)/config/amcl.yaml" command="load" />
<param name="initial_pose_x" value="$(arg init_pose_x)" />
<param name="initial_pose_y" value="$(arg init_pose_y)" />
<param name="initial_pose_a" value="$(arg init_pose_yaw)" />
<remap from="scan" to="scan" />
<remap from="map" to="/map" />
</node>
<!-- ======== 导航 ======== -->
<node name="move_base" pkg="move_base" type="move_base" respawn="false">
<rosparam file="$(find my_robot)/config/costmap_common.yaml"
command="load" ns="global_costmap" />
<rosparam file="$(find my_robot)/config/costmap_common.yaml"
command="load" ns="local_costmap" />
<rosparam file="$(find my_robot)/config/costmap_local.yaml"
command="load" />
<rosparam file="$(find my_robot)/config/costmap_global.yaml"
command="load" />
<rosparam file="$(find my_robot)/config/planner.yaml"
command="load" />
<param name="base_global_planner" value="navfn/NavfnROS" />
<param name="base_local_planner" value="dwa_local_planner/DWAPlannerROS" />
<remap from="map" to="/map" />
<remap from="cmd_vel" to="cmd_vel" />
<remap from="odom" to="odom" />
</node>
<!-- ======== 行为控制 ======== -->
<node name="behavior_controller" pkg="my_robot" type="behavior_controller">
<param name="robot_name" value="$(arg robot_name)" />
<rosparam file="$(find my_robot)/config/behaviors.yaml" command="load" />
</node>
</launch>
9.3 中央控制系统
xml
<!-- launch/central_system.launch -->
<launch>
<arg name="num_robots" default="3" />
<!-- ======== 任务分配器 ======== -->
<node name="task_allocator" pkg="multi_robot_coordination"
type="task_allocator" output="screen">
<param name="num_robots" value="$(arg num_robots)" />
<rosparam>
robot_list: [robot1, robot2, robot3]
allocation_method: "auction" # auction, greedy, optimal
task_types:
- pick_and_place
- patrol
- charging
optimization_criteria:
- distance
- battery
- capability
</rosparam>
</node>
<!-- ======== 冲突解决 ======== -->
<node name="conflict_resolver" pkg="multi_robot_coordination"
type="conflict_resolver" output="screen">
<rosparam>
resolution_method: "priority" # priority, negotiation, replanning
safety_distance: 0.5
deadlock_timeout: 10.0
communication_timeout: 1.0
</rosparam>
</node>
<!-- ======== 群体行为协调 ======== -->
<node name="swarm_coordinator" pkg="multi_robot_coordination"
type="swarm_coordinator" output="screen">
<rosparam>
formation_type: "line" # line, circle, wedge, square
formation_spacing: 2.0
leader_robot: "robot1"
coordination_rate: 10.0
</rosparam>
</node>
<!-- ======== 全局地图服务器 ======== -->
<node name="map_server" pkg="map_server" type="map_server"
args="$(find my_maps)/maps/$(arg world_name).yaml">
<param name="frame_id" value="map" />
</node>
<!-- ======== 数据融合 ======== -->
<node name="data_fusion" pkg="multi_robot_coordination"
type="data_fusion_node" output="screen">
<rosparam>
fusion_topics:
- /robot1/scan
- /robot2/scan
- /robot3/scan
fusion_method: "probabilistic"
output_topic: /fused_scan
</rosparam>
</node>
<!-- ======== Web界面 ======== -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
<arg name="port" value="9090" />
</include>
<node name="web_server" pkg="my_robot_web" type="web_server.py">
<param name="port" value="8080" />
<param name="num_robots" value="$(arg num_robots)" />
</node>
</launch>
10. 总结与最佳实践
10.1 本文总结
通过本文的学习,你已经掌握了:
- ✅ Launch系统基础:理解launch文件的作用和结构
- ✅ 节点管理:批量启动、生命周期管理、错误处理
- ✅ 参数配置:静态和动态参数、参数文件管理
- ✅ 命名空间:组织节点层次结构、避免命名冲突
- ✅ 话题重映射:灵活连接节点间的通信
- ✅ 条件逻辑:根据参数动态配置系统
- ✅ 文件包含:模块化组织launch文件
- ✅ 分布式系统:多机器节点部署
- ✅ 调试技巧:问题诊断和错误处理
- ✅ 实战应用:多机器人系统启动管理
10.2 最佳实践建议
| 方面 | 建议 | 原因 |
|---|---|---|
| 文件组织 | 模块化设计,功能分离 | 提高可维护性 |
| 参数管理 | 使用参数文件,避免硬编码 | 便于配置调整 |
| 命名规范 | 统一命名空间策略 | 避免冲突,清晰结构 |
| 错误处理 | 设置关键节点为required | 快速发现问题 |
| 版本控制 | 参数化所有可变配置 | 适应不同环境 |
| 文档化 | 使用doc属性说明参数 | 提高可读性 |
| 调试支持 | 提供debug模式 | 便于问题定位 |
10.3 常见问题解决
-
找不到包或文件
xml<!-- 使用$(find package_name)确保路径正确 --> <param name="config" value="$(find my_package)/config/file.yaml" /> -
参数未生效
xml<!-- 确保参数在节点启动前设置 --> <param name="param_name" value="value" /> <node name="node" pkg="pkg" type="type" /> -
命名空间混乱
xml<!-- 使用group明确组织命名空间 --> <group ns="namespace"> <!-- 节点和参数 --> </group> -
节点启动顺序问题
xml<!-- 使用required和延迟启动控制顺序 --> <node name="first" pkg="pkg" type="type" required="true" /> <node name="second" pkg="pkg" type="type" launch-prefix="bash -c 'sleep 2; $0 $@' " />
10.4 Launch文件模板
xml
<!-- launch/template.launch -->
<launch>
<!-- ========== 文档 ========== -->
<!--
功能描述:系统启动模板
作者:开发团队
版本:1.0.0
更新:2025-12-20
-->
<!-- ========== 参数定义 ========== -->
<arg name="robot_name" default="robot1" doc="Robot name" />
<arg name="sim" default="false" doc="Use simulation" />
<arg name="debug" default="false" doc="Debug mode" />
<!-- ========== 参数验证 ========== -->
<!-- 在这里添加参数验证逻辑 -->
<!-- ========== 全局配置 ========== -->
<param name="/use_sim_time" value="$(arg sim)" />
<!-- ========== 参数加载 ========== -->
<rosparam file="$(find my_package)/config/config.yaml" command="load" />
<!-- ========== 核心节点 ========== -->
<group ns="$(arg robot_name)">
<!-- 在这里添加节点 -->
</group>
<!-- ========== 可选组件 ========== -->
<group if="$(arg debug)">
<!-- 调试工具 -->
</group>
<!-- ========== 包含文件 ========== -->
<!-- 在这里包含其他launch文件 -->
</launch>
10.5 下一步学习
在下一篇文章中,我们将学习:
- TF坐标变换:机器人的空间认知
- 坐标系管理:多坐标系的组织
- 变换发布:静态和动态变换
- TF工具使用:调试和可视化
版权声明:本文为原创文章,转载请注明出处
💡 学习建议:Launch文件是ROS系统集成的关键。建议从简单的launch文件开始,逐步增加复杂度。多实践不同的配置方式,理解各种参数和选项的作用。记住,一个好的launch文件能够大大简化系统的部署和维护。
下一篇预告:《【ROS1从入门到精通】第9篇:TF坐标变换(机器人的空间认知)》
敬请期待!