概述

在 ROS2 中,主流的 Launch 文件是用 Python 编写的

要一次性拉起多个功能包中的多个节点,最标准的做法是创建一个专门的启动功能包(通常命名为 xxx_bringup,在这个包里集中管理所有的 Launch 文件和参数配置

举例

假设我们要同时启动 package_a 中的 node_apackage_b 中的 node_b,具体流程如下:

第一步:编写 Python Launch 文件

假设在一个名为 my_robot_bringup 的功能包下,新建了一个 launch 文件夹,并在其中创建了 multi_nodes.launch.py 文件:

# 导入所需的库
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    
    # 1. 定义要启动的第一个节点 (来自 package_a)
    node_a = Node(
        package='package_a',       # 功能包的名称
        executable='node_a_exe',   # 可执行文件的名称 (CMakeLists/setup.py中配置的名称)
        name='custom_node_a',      # [可选] 重命名节点
        output='screen'            # 将该节点的日志输出到终端,方便调试
    )

    # 2. 定义要启动的第二个节点 (来自 package_b)
    node_b = Node(
        package='package_b',
        executable='node_b_exe',
        name='custom_node_b',
        output='screen',
        # [进阶] 你甚至可以直接在这里传递参数或进行话题重映射
        parameters=[{'my_param': 100}], 
        remappings=[('/old_topic', '/new_topic')]
    )

    # 3. 将所有节点添加到 LaunchDescription 中并返回
    return LaunchDescription([
        node_a,
        node_b
    ])


第二步:⭐配置编译文件(以Python 功能包为例)

ROS2 通过编译规则查找 Launch 文件,不配置就找不到

setup.py 的路径声明

打开 setup.py,确保引入了相关的路径库,并在 data_files 列表中添加 launch 文件的安装路径:

import os
from glob import glob
from setuptools import setup

package_name = 'my_robot_bringup'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        # 👇 添加这一行,作用是把 launch 文件夹下的所有 launch 文件拷贝到安装目录
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
    ],
    # ... 省略其他配置
)

package.xml的依赖声明

Bringup 功能包需要依赖它要启动的普通包。它自身可能连一行 rclpy 代码都没写,但如果它的 launch 文件要拉起 package_a 和 package_b,通常会在 package.xml 中使用执行依赖(<exec_depend>),确保运行时这些包是存在的:

<exec_depend>package_a</exec_depend>
<exec_depend>package_b</exec_depend>
<exec_depend>ros2launch</exec_depend>

第三步:编译并运行

配置好之后,回到工作空间根目录,进行编译和环境声明:

# 1. 编译工作空间(可以只编译你的 bringup 包节省时间)
colcon build --packages-select my_robot_bringup

# 2. 刷新环境变量
source install/setup.bash

# 3. 运行 Launch 文件!
ros2 launch my_robot_bringup multi_nodes.launch.py

执行完毕后,你就能在终端看到来自不同功能包的多个节点被一同拉起,并且它们的日志会混合输出在同一个终端里。


对比普通功能包和bringup功能包

在 ROS2 中,区分普通功能包(Standard Package)和 Bringup 功能包的核心思想是关注点分离(Separation of Concerns)

1. 核心定位与内容的区别

对比维度普通功能包 (Standard Package)Bringup 功能包 (Bringup Package)
主要职责实现具体功能(如:雷达驱动、图像识别、运动控制)。统筹规划,负责把多个普通功能包按特定配置组合并启动。
核心内容主要是 .py 源码文件(节点代码、算法逻辑)。主要是 .launch.py (启动脚本)、.yaml (参数配置)、RViz/URDF 模型。
复用性极高。例如一个 USB 摄像头包,可以用在无人车上,也可以用在机械臂上。极低。通常专门针对某一款特定的机器人(例如 turtlebot3_bringup)。
目录结构特点拥有完整的 包名/ 源码目录,里面全是 Python 代码。重点在于 launch/config/rviz/ 等资源文件夹。通常没有真实的节点逻辑代码。

2. 配置文件上的本质区别

由于它们的作用不同,在 Python 格式的包中,它们的 setup.pypackage.xml 侧重点完全相反。

区别一:setup.py 的配置重点
  • 普通功能包:重点在 entry_points (注册可执行文件)
    普通包需要让 ROS2 知道去哪里执行你的 Python 节点逻辑。它的 data_files 通常很简单,但必须在 console_scripts 中注册节点。
# 普通功能包的 setup.py
setup(
    # ... 省略基础信息 ...
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    entry_points={
        'console_scripts': [
            # 注册节点:可执行文件名 = 包名.文件名:主函数
            'camera_node = my_camera_pkg.camera_node:main', 
        ],
    },
)

  • Bringup 功能包:重点在 data_files (安装非代码资源)
    Bringup 包通常没有节点逻辑,所以 console_scripts 往往是空的。它的核心任务是通过 data_fileslaunchconfig(YAML 参数)文件夹原封不动地拷贝到系统安装目录中。
# Bringup 功能包的 setup.py
import os
from glob import glob
# ...
setup(
    # ... 省略基础信息 ...
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        # 核心区别 👇:安装 launch 文件夹下的所有文件
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
        # 核心区别 👇:安装 config 文件夹下的参数文件
        (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))),
    ],
    entry_points={
        'console_scripts': [
            # 通常为空,因为没有具体的节点代码要执行
        ],
    },
)

区别二:package.xml 的依赖声明
  • 普通功能包: 依赖具体的编程库。它需要声明 <depend>rclpy</depend><depend>sensor_msgs</depend> 等,因为它要调用这些 API 写代码。
  • Bringup 功能包: 依赖它要启动的普通包

总结来说:
开发普通功能包,你是在造砖头(写 console_scripts);开发 Bringup 功能包,你是在用砖头盖房子(写 data_files 拷贝配置文件)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐