ROS1从入门到精通 8:Launch文件编写(多节点协同管理)

【ROS1从入门到精通】第8篇:Launch文件编写(多节点协同管理)

🎯 本文目标:深入理解ROS Launch系统,掌握launch文件的编写技巧,学会多节点协同管理、参数配置、命名空间管理,能够设计和实现复杂的机器人系统启动方案。

📑 目录

  1. Launch系统概述
  2. Launch文件基础
  3. 节点管理
  4. 参数配置
  5. 命名空间和重映射
  6. 条件启动和包含
  7. 高级Launch特性
  8. 调试和错误处理
  9. 实战案例:多机器人系统启动
  10. 总结与最佳实践

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 &quot;import os; print(os.path.exists('$(arg config_file)'))&quot;" />

  <!-- 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>&amp;1 &amp;&amp; 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 本文总结

通过本文的学习,你已经掌握了:

  1. Launch系统基础:理解launch文件的作用和结构
  2. 节点管理:批量启动、生命周期管理、错误处理
  3. 参数配置:静态和动态参数、参数文件管理
  4. 命名空间:组织节点层次结构、避免命名冲突
  5. 话题重映射:灵活连接节点间的通信
  6. 条件逻辑:根据参数动态配置系统
  7. 文件包含:模块化组织launch文件
  8. 分布式系统:多机器节点部署
  9. 调试技巧:问题诊断和错误处理
  10. 实战应用:多机器人系统启动管理

10.2 最佳实践建议

方面 建议 原因
文件组织 模块化设计,功能分离 提高可维护性
参数管理 使用参数文件,避免硬编码 便于配置调整
命名规范 统一命名空间策略 避免冲突,清晰结构
错误处理 设置关键节点为required 快速发现问题
版本控制 参数化所有可变配置 适应不同环境
文档化 使用doc属性说明参数 提高可读性
调试支持 提供debug模式 便于问题定位

10.3 常见问题解决

  1. 找不到包或文件

    xml 复制代码
    <!-- 使用$(find package_name)确保路径正确 -->
    <param name="config" value="$(find my_package)/config/file.yaml" />
  2. 参数未生效

    xml 复制代码
    <!-- 确保参数在节点启动前设置 -->
    <param name="param_name" value="value" />
    <node name="node" pkg="pkg" type="type" />
  3. 命名空间混乱

    xml 复制代码
    <!-- 使用group明确组织命名空间 -->
    <group ns="namespace">
      <!-- 节点和参数 -->
    </group>
  4. 节点启动顺序问题

    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坐标变换(机器人的空间认知)》

敬请期待!

相关推荐
Blossom.1182 小时前
知识图谱与大模型融合实战:基于GNN+RAG的企业级智能问答系统
人工智能·python·深度学习·神经网络·微服务·重构·知识图谱
十铭忘2 小时前
SAM2跟踪的理解12——mask decoder
人工智能·计算机视觉
PS1232322 小时前
隔爆型防爆压力变送器的多信号输出优势
大数据·人工智能
人工智能培训2 小时前
国内外知名大模型及应用
人工智能·深度学习·神经网络·大模型·dnn·ai大模型·具身智能
bryant_meng2 小时前
【GA-Net】《GA-Net: Guided Aggregation Net for End-to-end Stereo Matching》
人工智能·深度学习·计算机视觉·立体匹配·ganet
爱学习的张大2 小时前
如何选择正确版本的CUDA和PyTorch安装
人工智能·pytorch·python
serve the people2 小时前
TensorFlow 2.0 手写数字分类教程之SparseCategoricalCrossentropy 核心原理(二)
人工智能·分类·tensorflow
大千AI助手2 小时前
DeepSeek V3.2 技术解读:一次不靠“堆参数”的模型升级
人工智能·机器学习·agent·dsa·deepseek·deepseek-v3.2·大千ai助手
十铭忘2 小时前
SAM2跟踪的理解13——mask decoder
人工智能·深度学习