You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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端正常观测效果:
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个核心错误,导致无法获取实时数据:

  1. 通知回调传参错误:start_notify要求传入的第二个参数是可直接调用的函数对象,代码中写的notification_handler(value)是提前执行函数、把返回值None传给了注册接口,设备推送新数据时根本没有可触发的回调逻辑,打印的永远是第一次读取到的固定0值。
  2. 读写逻辑冲突冗余:同时混用read_gatt_char主动读取和start_notify订阅通知逻辑,还在1秒周期的循环里反复启动、停止通知。BLE通知的机制是订阅后设备主动推送新采样值,不需要反复启停订阅,也不需要循环主动读;反复启停会导致订阅还没收到新数据就被断开,只能拿到特征寄存器的初始默认0值。
  3. 事件循环写法混乱:同时使用asyncio.run()和手动loop.run_forever()两套事件循环逻辑,还在协程未启动时就把全局loop对象传入main函数,会导致协程调度异常,部分BLE操作无法获取实时返回值。
  4. 特征属性匹配错误:多数Arduino BLE示例中,传感器特征仅开启了通知(Notify)权限,没有开启读(Read)权限,主动调用read_gatt_char只会返回特征初始化时的默认0值,不会触发传感器采样逻辑。LightBlue能看到动态值,是因为调试工具默认会自动订阅支持通知的特征,接收设备主动推送的新数据,而非靠主动读取。
修复方案
  1. 移除冗余的循环read_gatt_char逻辑和反复启停通知的代码,仅在连接建立后注册一次通知回调,保持订阅状态持续接收推送
  2. 修正回调函数格式:bleak的通知回调固定接收sender(特征对象)、data(字节数据)两个参数,注册时直接传函数名,不要加括号提前执行
  3. 清理混乱的事件循环逻辑,统一使用asyncio.run()做协程调度,移除重复的loop启动代码
  4. 若必须使用主动读取方式,需要在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:54:31