用Python和Bluez 5.50在Ubuntu 20.04上,手把手教你做个能被手机发现的BLE广播设备
用Python和Bluez 5.50在Ubuntu 20.04上打造可发现的BLE广播设备
在物联网和智能硬件快速发展的今天,蓝牙低功耗(BLE)技术因其低功耗、低成本的特点,成为连接智能设备的首选方案之一。想象一下,你正在开发一个智能温湿度传感器,或者一个可穿戴健康监测设备,如何让用户的手机能够快速发现并识别你的设备?BLE广播正是实现这一功能的核心技术。
本文将带你从零开始,在Ubuntu 20.04系统上使用Python和Bluez 5.50蓝牙协议栈,创建一个能够被手机发现的BLE广播设备。不同于复杂的理论讲解,我们将聚焦于实际操作,确保你能够在30分钟内完成从环境搭建到手机验证的完整流程。无论你是物联网开发者、Python爱好者,还是对BLE技术好奇的硬件工程师,这篇指南都将为你提供清晰的实践路径。
1. 环境准备与工具安装
在开始编码之前,我们需要确保开发环境已经正确配置。Ubuntu 20.04是一个理想的开发平台,因为它提供了对Bluez蓝牙协议栈的良好支持。以下是需要准备的软件和工具:
- 操作系统:Ubuntu 20.04 LTS(建议使用实体机或虚拟机)
- 蓝牙适配器:确保你的电脑配备了兼容的蓝牙硬件(USB蓝牙适配器也可)
- Python环境:Python 3.8或更高版本
- 必要软件包:Bluez 5.50及相关开发工具
打开终端,执行以下命令安装所需依赖:
sudo apt update sudo apt install -y bluez libbluetooth-dev python3-dev python3-pip sudo pip3 install dbus-python pygobject安装完成后,验证Bluez版本:
bluetoothctl --version你应该看到类似bluetoothctl: 5.50的输出。如果版本不一致,可能需要手动编译安装Bluez 5.50,但这超出了本文的范围。
提示:如果遇到权限问题,请确保你的用户账户已加入
lp和bluetooth用户组:sudo usermod -a -G lp,bluetooth $USER
2. BLE广播基础与工作原理
在深入代码之前,理解BLE广播的基本概念至关重要。BLE设备通过广播通道向外发送数据包,这些数据包可以包含设备名称、服务UUID、制造商特定数据等信息。手机等扫描设备会监听这些广播,从而发现附近的BLE设备。
一个典型的BLE广播包包含以下部分:
| 字段 | 描述 | 示例 |
|---|---|---|
| 设备名称 | 可读的设备标识符 | "MySensor" |
| 服务UUID | 设备提供的服务标识 | "180A" (设备信息服务) |
| 制造商数据 | 厂商自定义数据 | 0xFFFF + [0x01,0x02] |
| 发射功率 | 信号强度指示 | -70dBm |
| 连接标志 | 指示设备是否可连接 | 0x01 |
广播模式主要分为三种:
- 可连接广播:允许设备建立BLE连接
- 不可连接广播:仅发送数据,不允许连接
- 扫描响应:当扫描设备请求更多信息时的额外响应
我们的示例将创建一个不可连接的广播设备,专注于广播数据的发送。
3. 编写Python广播程序
现在,让我们创建一个完整的Python脚本,实现BLE广播功能。我们将使用dbus-python库与Bluez交互。创建一个名为ble_advertiser.py的文件,内容如下:
#!/usr/bin/env python3 import dbus import dbus.mainloop.glib from gi.repository import GLib import sys # DBus接口定义 BLUEZ_SERVICE_NAME = 'org.bluez' DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1' class Advertisement(dbus.service.Object): def __init__(self, bus, index, advertising_type): self.path = '/org/bluez/example/advertisement' + str(index) self.bus = bus self.ad_type = advertising_type self.service_uuids = None self.manufacturer_data = None self.local_name = None self.include_tx_power = False dbus.service.Object.__init__(self, bus, self.path) def add_service_uuid(self, uuid): if not self.service_uuids: self.service_uuids = [] self.service_uuids.append(uuid) def add_manufacturer_data(self, manufacturer_id, data): if not self.manufacturer_data: self.manufacturer_data = dbus.Dictionary({}, signature='qv') self.manufacturer_data[manufacturer_id] = dbus.Array(data, signature='y') def add_local_name(self, name): self.local_name = name @dbus.service.method(DBUS_PROP_IFACE, in_signature='s', out_signature='a{sv}') def GetAll(self, interface): if interface != LE_ADVERTISEMENT_IFACE: raise InvalidArgsException() properties = dict() properties['Type'] = self.ad_type if self.service_uuids: properties['ServiceUUIDs'] = dbus.Array(self.service_uuids, signature='s') if self.manufacturer_data: properties['ManufacturerData'] = self.manufacturer_data if self.local_name: properties['LocalName'] = dbus.String(self.local_name) if self.include_tx_power: properties['Includes'] = dbus.Array(['tx-power'], signature='s') return properties def register_ad_cb(): print("Advertisement registered successfully") def register_ad_error_cb(error): print("Failed to register advertisement: " + str(error)) sys.exit(1) def find_adapter(bus): remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'), DBUS_OM_IFACE) objects = remote_om.GetManagedObjects() for path, interfaces in objects.items(): if LE_ADVERTISING_MANAGER_IFACE in interfaces: return path return None def main(): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() adapter = find_adapter(bus) if not adapter: print("找不到支持BLE广播的蓝牙适配器") return # 开启蓝牙适配器 adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), DBUS_PROP_IFACE) adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1)) # 创建广告对象 ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), LE_ADVERTISING_MANAGER_IFACE) advertisement = Advertisement(bus, 0, "peripheral") advertisement.add_service_uuid("180A") # 设备信息服务 advertisement.add_manufacturer_data(0xFFFF, [0x01, 0x02, 0x03, 0x04]) advertisement.add_local_name("PythonBLE") advertisement.include_tx_power = True # 注册广告 ad_manager.RegisterAdvertisement(advertisement.get_path(), {}, reply_handler=register_ad_cb, error_handler=register_ad_error_cb) try: # 启动事件循环 mainloop = GLib.MainLoop() print("BLE广播已启动,按Ctrl+C停止") mainloop.run() except KeyboardInterrupt: mainloop.quit() if __name__ == "__main__": main()让我们分解这段代码的关键部分:
Advertisement类:封装了BLE广播数据,包括:
- 服务UUID(
add_service_uuid方法) - 制造商数据(
add_manufacturer_data方法) - 设备名称(
add_local_name方法)
- 服务UUID(
DBus交互:
find_adapter函数查找可用的蓝牙适配器adapter_props.Set启用蓝牙适配器ad_manager.RegisterAdvertisement注册广播数据
主循环:
GLib.MainLoop()保持程序运行,持续发送广播
注意:运行此脚本需要root权限或蓝牙组权限,建议使用:
sudo python3 ble_advertiser.py
4. 使用手机验证广播设备
程序运行后,我们需要验证手机是否能够发现我们的BLE设备。以下是使用nRF Connect(一款流行的BLE调试工具)的步骤:
- 在手机应用商店下载安装nRF Connect
- 打开应用,确保手机蓝牙已开启
- 点击"Scan"按钮开始扫描附近的BLE设备
- 你应该在设备列表中看到名为"PythonBLE"的设备
- 点击设备查看详细信息,确认广播数据与代码设置一致
在nRF Connect中,你应该能看到以下信息:
- 设备名称:PythonBLE
- 服务UUID:180A
- 制造商数据:FFFF -> [01,02,03,04]
- TX Power:显示信号强度值
如果一切正常,恭喜你!你已经成功创建了一个可被手机发现的BLE广播设备。这个基础框架可以扩展为各种实际应用,比如:
- 智能信标(iBeacon/Eddystone)
- 设备状态指示灯
- 简单的数据广播传感器
5. 高级自定义与故障排除
掌握了基础广播功能后,你可能希望进一步定制广播数据或解决可能出现的问题。以下是几个常见场景的解决方案:
自定义广播间隔
广播间隔影响设备被发现的速度和功耗。要修改广播间隔,可以在注册广告后添加:
adapter_props.Set("org.bluez.Adapter1", "AdvertisingInterval", dbus.UInt16(0x0800))常见间隔值:
- 0x0020 (20ms) - 高频率,快速发现
- 0x0800 (1.28s) - 平衡发现速度与功耗
- 0x4000 (10.24s) - 低功耗
常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 手机看不到设备 | 蓝牙未启用/适配器问题 | 检查bluetoothctl状态,确认适配器已启用 |
| 广告注册失败 | 权限不足/接口不支持 | 使用sudo运行,或确认适配器支持BLE |
| 数据不完整 | 广播包过长 | BLE广播包最多31字节,减少数据量 |
| 频繁断开 | 信号干扰 | 检查环境,避免2.4GHz频段干扰源 |
扩展广播数据
BLE 5.0引入了扩展广播功能,支持更大的数据包。要使用扩展广播,需要:
- 确认硬件支持BLE 5.0
- 修改广告类型为"peripheral"为"broadcast"
- 设置扩展广播参数:
advertisement = Advertisement(bus, 0, "broadcast") adapter_props.Set("org.bluez.Adapter1", "AdvertisingFeatures", dbus.UInt16(0x10))6. 实际应用案例
让我们看一个实际应用场景:创建一个室内温湿度监测信标。我们可以修改广播数据来包含环境信息:
# 模拟温湿度数据 temperature = 25 # 摄氏度 humidity = 60 # 百分比 # 在Advertisement类中添加 advertisement.add_manufacturer_data(0xFFFF, [ 0x01, # 数据类型标识 temperature, # 温度值 humidity # 湿度值 ])这样,手机APP无需连接设备就能获取基本环境数据,大大降低了功耗。实际产品中,这些数据可以来自真实的传感器读取。
另一个常见应用是资产跟踪。通过在广播包中包含设备唯一标识,可以实现简单的物品追踪系统:
device_id = [0x12, 0x34, 0x56, 0x78] # 4字节设备ID advertisement.add_manufacturer_data(0xFFFF, device_id)7. 性能优化与最佳实践
为了确保BLE广播的稳定性和效率,请考虑以下建议:
广播数据精简:
- 优先使用1字节UUID(如"180A"而非"0000180A-0000-1000-8000-00805F9B34FB")
- 合并相关数据到制造商特定字段
功耗优化:
- 根据应用场景调整广播间隔
- 考虑使用BLE 5.0的周期性广播功能
兼容性考虑:
- 测试不同手机型号的扫描行为
- 避免使用保留的UUID和制造商ID
安全注意事项:
- 广播数据是公开的,不要包含敏感信息
- 如需安全通信,应建立加密连接
# 示例:优化后的广播设置 advertisement.add_service_uuid("180D") # 心率服务(2字节表示) advertisement.add_manufacturer_data(0xFFFF, [ 0x01, # 数据版本 0x80, # 电池电量(80%) 0x64 # 信号强度 ])通过本文的实践,你已经掌握了使用Python和Bluez创建BLE广播设备的核心技能。在实际项目中,我发现最常遇到的问题通常是权限和版本兼容性,因此建议在部署前在不同硬件上进行充分测试。
