bleak库read_gatt_char()无法读取Arduino Nano 33 BLE更新值
问题现象
- 开发目标:基于Python bleak库实现Arduino Nano 33 BLE的信号数据采集,通过读取GATT特征值获取传感器采样结果
- 异常表现:代码可正常建立BLE连接,但读取返回的字节数据始终为固定全0值,无动态变化;使用LightBlue蓝牙调试终端连接同一设备时,Arduino输出的信号值可正常动态更新
本地终端固定输出示例:
Connected: True [0, 0, 0, 0] [0, 0, 0, 0] [0, 0, 0, 0] [0, 0, 0, 0] [0, 0, 0, 0] [0, 0, 0, 0]
LightBlue端正常观测效果:
问题复现代码
import sys import asyncio import platform from bleak import BleakClient CHARACTERISTIC_UUID = "00001143-0000-1000-8000-00805f9b34fb" ADDRESS = ( "3c:22:fb:29:21:3d" if platform.system() != "Darwin" else "EF670BB4-B0D8-353D-ACCE-4405567E8391" ) def notification_handler(data): """Simple notification handler which prints the data received.""" output_numbers = list(data) print(output_numbers) async def main(address): asyncio.set_event_loop(loop) async with BleakClient(address, use_cached=False) as client: print(f"Connected: {client.is_connected}") while True: value = bytes(await client.read_gatt_char(CHARACTERISTIC_UUID)) await client.start_notify(CHARACTERISTIC_UUID, notification_handler(value)) await asyncio.sleep(1) await client.stop_notify(CHARACTERISTIC_UUID) if __name__ == "__main__": loop = asyncio.get_event_loop() asyncio.run( main( sys.argv[1] if len(sys.argv) > 1 else ADDRESS # sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID, ) ) try: asyncio.ensure_future(main(), loop=loop) loop.run_forever() except KeyboardInterrupt: print() print("User stopped program.")
问题根因
代码存在4个核心错误,导致无法获取实时数据:
- 通知回调传参错误:
start_notify要求传入的第二个参数是可直接调用的函数对象,代码中写的notification_handler(value)是提前执行函数、把返回值None传给了注册接口,设备推送新数据时根本没有可触发的回调逻辑,打印的永远是第一次读取到的固定0值。 - 读写逻辑冲突冗余:同时混用
read_gatt_char主动读取和start_notify订阅通知逻辑,还在1秒周期的循环里反复启动、停止通知。BLE通知的机制是订阅后设备主动推送新采样值,不需要反复启停订阅,也不需要循环主动读;反复启停会导致订阅还没收到新数据就被断开,只能拿到特征寄存器的初始默认0值。 - 事件循环写法混乱:同时使用
asyncio.run()和手动loop.run_forever()两套事件循环逻辑,还在协程未启动时就把全局loop对象传入main函数,会导致协程调度异常,部分BLE操作无法获取实时返回值。 - 特征属性匹配错误:多数Arduino BLE示例中,传感器特征仅开启了通知(Notify)权限,没有开启读(Read)权限,主动调用
read_gatt_char只会返回特征初始化时的默认0值,不会触发传感器采样逻辑。LightBlue能看到动态值,是因为调试工具默认会自动订阅支持通知的特征,接收设备主动推送的新数据,而非靠主动读取。
修复方案
- 移除冗余的循环
read_gatt_char逻辑和反复启停通知的代码,仅在连接建立后注册一次通知回调,保持订阅状态持续接收推送 - 修正回调函数格式:bleak的通知回调固定接收
sender(特征对象)、data(字节数据)两个参数,注册时直接传函数名,不要加括号提前执行 - 清理混乱的事件循环逻辑,统一使用
asyncio.run()做协程调度,移除重复的loop启动代码 - 若必须使用主动读取方式,需要在Arduino端给对应特征添加
BLERead权限,在读回调函数中填充最新的传感器采样值,否则主动读取永远返回初始0值
修复后的可运行代码:
import sys import asyncio import platform from bleak import BleakClient CHARACTERISTIC_UUID = "00001143-0000-1000-8000-00805f9b34fb" ADDRESS = ( "3c:22:fb:29:21:3d" if platform.system() != "Darwin" else "EF670BB4-B0D8-353D-ACCE-4405567E8391" ) def notification_handler(sender, data): """通知触发时执行的回调,打印收到的字节数据""" output_numbers = list(data) print(output_numbers) async def main(address): async with BleakClient(address, use_cached=False) as client: print(f"Connected: {client.is_connected}") # 仅注册一次通知,传入函数对象本身 await client.start_notify(CHARACTERISTIC_UUID, notification_handler) # 保持连接等待数据推送,按Ctrl+C退出 while True: await asyncio.sleep(1) if __name__ == "__main__": try: asyncio.run( main( sys.argv[1] if len(sys.argv) > 1 else ADDRESS ) ) except KeyboardInterrupt: print("\nUser stopped program.")
内容的提问来源于stack exchange,提问作者ohsean
相关产品推荐
相关产品推荐

