前言
本项目机器人平台为幻尔智能ROS2机器人,本节开始会逐步拆解学习其示例代码,侵权即删。
一、bringup 包核心定位
在 ROS2 生态中,bringup
包是系统启动包,核心作用是「一键启动机器人全系统的核心功能」,避免手动逐个启动控制器、传感器、应用节点等繁琐操作。
1.1 bringup 包的典型内容
- 总启动文件(
.launch.py
):统筹启动所有子模块(相机、雷达、控制器等) - 自检节点(如
startup_check.py
):启动前检查硬件设备(麦克风、网卡等)是否正常 - 配置文件:定义硬件参数、启动逻辑(如环境变量判断)
- 依赖管理文件(
package.xml
、setup.py
):确保启动所需的依赖包已安装
二、核心文件逐行拆解
2.1 包身份与依赖配置:package.xml
package.xml
是 ROS2 包的「身份证」,告诉系统包的名字、依赖、维护者等信息,必须与 setup.py
同步。
2.1.1 文件结构与关键标签
XML
<?xml version="1.0"?>
<!-- XML 格式校验:确保文件符合 ROS2 规范 -->
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<!-- 根标签:format="3" 是 ROS2 主流格式 -->
<package format="3">
<!-- 1. 基础身份信息 -->
<name>bringup</name> <!-- 包名:必须唯一,与 setup.py 一致 -->
<version>0.0.0</version> <!-- 版本号:如 1.0.0(正式版)、0.1.0(测试版) -->
<description>一键启动机器人控制器、雷达、相机等核心功能</description> <!-- 功能描述(替换 TODO) -->
<maintainer email="1270161395@qq.com">ubuntu</maintainer> <!-- 维护者:名字+邮箱 -->
<license>Apache-2.0</license> <!-- 许可证:ROS2 常用 Apache-2.0(开源可商用) -->
<!-- 2. 运行依赖:启动包必须的依赖 -->
<depend>rclpy</depend> <!-- Python 节点依赖:rclpy 是 ROS2 Python 核心库 -->
<depend>controller</depend> <!-- 依赖控制器包(示例:根据实际需求添加) -->
<depend>peripherals</depend> <!-- 依赖外设包(相机、雷达等) -->
<!-- 3. 测试依赖:仅代码测试时需要 -->
<test_depend>ament_copyright</test_depend> <!-- 检查版权声明 -->
<test_depend>ament_flake8</test_depend> <!-- 检查 Python 代码格式 -->
<test_depend>python3-pytest</test_depend> <!-- 运行 Python 测试用例 -->
<!-- 4. 编译类型导出:告诉系统如何编译该包 -->
<export>
<build_type>ament_python</build_type> <!-- Python 包用 ament_python;C++ 包用 ament_cmake -->
</export>
</package>
2.1.2 关键注意事项
- 包名(
<name>
)必须与setup.py
中的package_name
完全一致,否则编译失败 - 运行依赖(
<depend>
)要写全:缺少依赖会导致启动时提示「找不到某个包」 - 许可证(
<license>
)不可省略:避免开源法律风险,优先选Apache-2.0
2.2 安装与可执行配置:setup.py
setup.py
是 Python 打包工具的配置文件,负责:① 安装 Python 代码和资源文件;② 生成终端可执行命令(如 startup_check
)。
2.2.1 完整代码与注释
python
import os # 处理文件路径
from glob import glob # 查找匹配文件(如所有 launch 文件)
from setuptools import find_packages, setup # Python 打包核心工具
package_name = 'bringup' # 包名:必须与 package.xml 一致
setup(
name=package_name,
version='0.0.0', # 版本号:与 package.xml 同步
# 1. 自动查找包内所有 Python 模块(排除 test 测试目录)
packages=find_packages(exclude=['test']),
# 2. 安装非 Python 资源文件(launch、config 等)
data_files=[
# 2.1 注册包到 ROS2 索引(必须)
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
# 2.2 安装 package.xml 到系统目录
('share/' + package_name, ['package.xml']),
# 2.3 安装所有 launch 文件到 share/bringup/launch 目录
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.*'))),
# (可选)安装配置文件:如 config 目录下的参数文件
# (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),
],
# 3. 安装依赖:Python 打包工具必须
install_requires=['setuptools'],
zip_safe=True, # ROS2 推荐 True:支持压缩安装
# 4. 包元数据:与 package.xml 同步
maintainer='ubuntu',
maintainer_email='1270161395@qq.com',
description='一键启动机器人核心功能',
license='Apache-2.0',
tests_require=['pytest'], # 测试依赖
# 5. 生成终端可执行命令(核心!)
entry_points={
'console_scripts': [
# 格式:终端命令 = 模块路径:函数名
# 示例1:启动自检节点:ros2 run bringup startup_check
'startup_check = bringup.startup_check:main',
# 示例2:启动总 launch 文件(可选):ros2 run bringup start_all
# 'start_all = bringup.script.start_all:main',
],
},
)
2.2.2 核心功能解析
data_files
资源安装 :确保launch
、config
等文件夹被安装到系统目录(install/bringup/share/bringup/
),否则启动时找不到 launch 文件。entry_points
可执行命令 :定义后,可通过ros2 run bringup 命令名
运行节点(如ros2 run bringup startup_check
),本质是映射到 Python 模块的main
函数。
2.3 系统总启动文件:bringup_launch.py
这是 bringup 包的「核心大脑」,负责根据环境变量判断路径、启动所有子模块(控制器、相机、雷达等)。
2.3.1 完整代码与注释
python
import os
from ament_index_python.packages import get_package_share_directory # 获取包路径
# 导入 Launch 核心类
from launch_ros.actions import Node
from launch.actions import ExecuteProcess, IncludeLaunchDescription, OpaqueFunction
from launch import LaunchDescription, LaunchService
from launch.launch_description_sources import PythonLaunchDescriptionSource
def launch_setup(context):
"""根据环境变量配置启动路径,返回所有要启动的节点/launch"""
# 1. 读取环境变量:判断是「编译安装版」还是「源码版」
compiled = os.environ.get('need_compile', 'False') # 默认为 False(源码版)
if compiled == 'True':
# 编译安装版:从系统安装目录获取包路径
controller_path = get_package_share_directory('controller')
peripherals_path = get_package_share_directory('peripherals')
app_path = get_package_share_directory('app')
else:
# 源码版:直接指定源码路径(开发调试用)
controller_path = '/home/ubuntu/ros2_ws/src/driver/controller'
peripherals_path = '/home/ubuntu/ros2_ws/src/peripherals'
app_path = '/home/ubuntu/ros2_ws/src/app'
# 2. 启动子模块:包含其他包的 launch 文件
# 2.1 启动控制器(如电机控制)
controller_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(controller_path, 'launch/controller.launch.py')
)
)
# 2.2 启动外设(深度相机 + 雷达)
depth_camera_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(peripherals_path, 'launch/depth_camera.launch.py')
)
)
lidar_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(peripherals_path, 'launch/lidar.launch.py')
)
)
# 2.3 启动 Web 相关服务(rosbridge 用于 Web 通信,web_video 用于视频流)
rosbridge_launch = ExecuteProcess(
cmd=['ros2', 'launch', 'rosbridge_server', 'rosbridge_websocket_launch.xml'],
output='screen' # 输出日志到终端
)
web_video_node = Node(
package='web_video_server',
executable='web_video_server',
output='screen'
)
# 2.4 启动自检节点(来自 bringup 包)
startup_check_node = Node(
package='bringup',
executable='startup_check', # 对应 setup.py 中定义的命令
output='screen'
)
# 3. 返回所有要启动的组件(顺序即启动顺序)
return [
startup_check_node, # 1. 先自检
controller_launch, # 2. 启动控制器
depth_camera_launch, # 3. 启动相机
lidar_launch, # 4. 启动雷达
rosbridge_launch, # 5. 启动 Web 服务
web_video_node # 6. 启动视频流服务
]
def generate_launch_description():
"""ROS2 launch 标准入口:返回 LaunchDescription 对象"""
return LaunchDescription([
OpaqueFunction(function=launch_setup) # 包装 launch_setup,支持 Python 逻辑
])
if __name__ == '__main__':
# 直接运行该文件时启动(非必需,通常用 ros2 launch 命令)
ld = generate_launch_description()
ls = LaunchService()
ls.include_launch_description(ld)
ls.run()
2.3.2 关键功能拆解
-
环境变量判断(
compiled
) :解决「开发时用源码路径,安装后用系统路径」的问题,通过os.environ.get('need_compile')
读取环境变量,启动前可通过export need_compile=True
切换模式。 -
IncludeLaunchDescription
:用于「嵌套启动其他包的 launch 文件」,避免在一个文件中写所有启动逻辑,模块化更强(如单独启动相机的depth_camera.launch.py
)。 -
Node
vsExecuteProcess
:Node
:启动 ROS2 节点(需指定package
和executable
);ExecuteProcess
:执行系统命令(如ros2 launch rosbridge_server ...
)。
2.4 系统自检节点:startup_check.py
自检节点是启动前的「硬件检查员」,负责检查设备(麦克风、网卡)是否正常,并用 ROS2 消息控制蜂鸣器 / OLED 显示信息。
2.4.1 核心功能与代码
python
#!/usr/bin/env python3
import os
import time
import rclpy
import psutil
import threading
from ros_robot_controller_msgs.msg import BuzzerState, OLEDState # 自定义消息
def check_mic():
"""检查麦克风设备是否存在,存在则启动语音识别"""
# 查看 /dev 目录下是否有 ring_mic 设备
mic_exists = os.popen('ls /dev/ | grep ring_mic').read() == 'ring_mic\n'
if mic_exists:
os.system("ros2 launch xf_mic_asr_offline startup_test.launch.py")
def get_wifi_info():
"""获取 Wi-Fi SSID(从设备序列号生成)和 IP 地址"""
# 1. 从设备树读取序列号(树莓派/嵌入式设备常用)
with open("/proc/device-tree/serial-number", 'r') as f:
serial = f.readlines()[0][-10:-1] # 截取后10位序列号
ssid = f'HW-{serial[:8]}' # 生成 SSID(如 HW-12345678)
# 2. 获取 wlan0 的 IP 地址
ip = '0.0.0.0'
for iface, addrs in psutil.net_if_addrs().items():
if 'wlan0' in iface:
for addr in addrs:
if addr.family == 2: # 2 代表 IPv4 地址
ip = addr.address
break
return ssid, ip
def main():
# 1. 启动麦克风检查线程(不阻塞主逻辑)
threading.Thread(target=check_mic, daemon=False).start()
# 2. 初始化 ROS2 节点
rclpy.init()
node = rclpy.create_node('startup_check')
# 3. 创建发布器:控制蜂鸣器和 OLED
buzzer_pub = node.create_publisher(BuzzerState, '/ros_robot_controller/set_buzzer', 10)
oled_pub = node.create_publisher(OLEDState, '/ros_robot_controller/set_oled', 10)
# 4. 等待 5 秒(确保其他节点启动完成)
time.sleep(5)
# 5. 控制蜂鸣器响一声(频率1900Hz,响0.2秒)
buzzer_msg = BuzzerState()
buzzer_msg.freq = 1900
buzzer_msg.on_time = 0.2
buzzer_msg.off_time = 0.01
buzzer_msg.repeat = 1
buzzer_pub.publish(buzzer_msg)
# 6. 在 OLED 显示 Wi-Fi 信息(第一行 SSID,第二行 IP)
ssid, ip = get_wifi_info()
oled_msg1 = OLEDState()
oled_msg1.index = 1 # 第一行
oled_msg1.text = f'SSID: {ssid}'
oled_pub.publish(oled_msg1)
time.sleep(0.2) # 短暂延时避免消息拥堵
oled_msg2 = OLEDState()
oled_msg2.index = 2 # 第二行
oled_msg2.text = f'IP: {ip}'
oled_pub.publish(oled_msg2)
# 7. 保持节点运行
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
三、实际应用流程(学习 / 复习重点)
3.1 编译 bringup 包
-
进入 ROS2 工作空间(如
ros2_ws
):cd ~/ros2_ws
-
编译(仅编译 bringup 包,速度更快):
colcon build --packages-select bringup
-
加载环境变量(每次新终端都需执行):
source install/setup.bash
3.2 启动 bringup 系统
-
(可选)设置环境变量(切换编译 / 源码模式):
export need_compile=False # 源码模式(开发用) # export need_compile=True # 编译安装模式(部署用)
-
启动总 launch 文件:
ros2 launch bringup bringup_launch.py
3.3 验证启动结果
-
查看所有启动的节点:
ros2 node list
应包含
startup_check
、controller_node
、depth_camera_node
等。 -
查看话题是否正常发布:
ros2 topic list
应包含
/ros_robot_controller/set_buzzer
(蜂鸣器控制)、/camera/image_raw
(相机图像)等。 -
检查机器人模型(若有):启动 RViz 并加载模型:
rviz2
- 添加
RobotModel
插件; - 设置
Fixed Frame
为base_link
; - 若能看到模型,说明启动成功。
- 添加
四、常见问题排查(复习 / 应用必备)
4.1 launch 文件启动失败
-
问题 1:找不到某个 launch 文件 排查:检查
setup.py
的data_files
是否包含launch
目录,确保launch
文件已安装到install/bringup/share/bringup/launch
。 -
问题 2:环境变量
need_compile
未设置 解决:启动前执行export need_compile=False
(源码模式)或True
(编译模式)。
4.2 节点无法运行(ros2 run bringup startup_check
报错)
-
问题 1:
startup_check
命令不存在 排查:检查setup.py
的entry_points
是否正确定义startup_check = bringup.startup_check:main
,重新编译后重试。 -
问题 2:缺少自定义消息包 报错:
from ros_robot_controller_msgs.msg import ... ImportError
解决:在package.xml
中添加依赖<depend>ros_robot_controller_msgs</depend>
,重新编译。
五、总结:bringup 包学习要点
- 核心定位:一键启动全系统,模块化管理子模块(控制器、传感器等)。
- 文件分工 :
package.xml
:管依赖和身份;setup.py
:管安装和可执行命令;*.launch.py
:管启动逻辑;startup_check.py
:管硬件自检。
- 应用关键 :编译后必须
source
环境变量,启动时注意环境变量need_compile
的模式切换。 - 排查思路 :先看节点是否启动(
ros2 node list
),再看话题是否正常(ros2 topic list
),最后定位具体模块问题。