用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广播设备的核心技能。在实际项目中,我发现最常遇到的问题通常是权限和版本兼容性,因此建议在部署前在不同硬件上进行充分测试。
更多推荐


所有评论(0)