aioble --- 异步 BLE

aioble 是围绕 bluetooth 模块构建的高级、对 asyncio 友好的封装。它为扫描、连接、广播、GATT 服务和 L2CAP 通道提供了简洁的协程。

所有远程操作(connect、disconnect、客户端读/写、服务器 indicate、l2cap recv/send、pair)都是可等待的,并支持超时。

支持的角色:

  • 广播者(advertiser) --- 为常见字段生成广播和扫描响应负载,自动将负载拆分到广播和扫描响应中,可无限期或按固定时长进行广播。

  • 外围设备(Peripheral) --- 等待来自中心设备的连接,等待 MTU 交换。

  • 观察者(scanner) --- 被动和主动扫描,将同一设备的广播和扫描响应负载组合起来,从广播负载中解析常见字段。

  • 中心设备(Central) --- 连接到外围设备,发起 MTU 交换。

  • GATT 客户端 --- 发现服务 / 特征 / 描述符(可选按 UUID);对特征和描述符执行读 / 写 / 带响应的写;订阅通知和指示(通过 CCCD);等待通知和指示。

  • GATT 服务器 --- 注册服务 / 特征 / 描述符;等待对特征和描述符的写入;拦截读取请求;发送通知和指示(并等待响应)。

  • L2CAP --- 接受和连接面向连接的 L2CAP 通道,管理通道流控制。

  • 安全性 --- 基于 JSON 的密钥 / 机密管理,发起配对,查询加密 / 认证状态。

示例

扫描附近的 BLE 设备,并在每个设备被发现时将其打印出来::

import aioble
import asyncio

async def find_devices():
    async with aioble.scan(duration_ms=5000, active=True) as scanner:
        async for result in scanner:
            print(result.device.addr_hex(), result.rssi, result.name())

asyncio.run(find_devices())

作为 中心设备 连接到广播心率服务的外围设备,并订阅其测量值通知::

import aioble
import asyncio
import bluetooth

_HR_SERVICE = bluetooth.UUID(0x180D)
_HR_MEASUREMENT = bluetooth.UUID(0x2A37)

async def connect_and_read():
    device = None
    async with aioble.scan(duration_ms=5000, active=True) as scanner:
        async for result in scanner:
            if _HR_SERVICE in result.services():
                device = result.device
                break
    if device is None:
        return

    async with await device.connect() as conn:
        service = await conn.service(_HR_SERVICE)
        char = await service.characteristic(_HR_MEASUREMENT)
        await char.subscribe(notify=True)
        while True:
            data = await char.notified()
            print("notify:", data)

asyncio.run(connect_and_read())

作为 外围设备:注册一个 GATT 服务,对其进行广播,并向连接进来的任何设备推送通知::

import aioble
import asyncio
import bluetooth
import struct

_ENV_SERVICE = bluetooth.UUID(0x181A)
_TEMP_CHAR = bluetooth.UUID(0x2A6E)

def encode_temperature(deg_c):
    # Bluetooth Temperature (0x2A6E) is sint16 little-endian, 0.01 degC units.
    return struct.pack("<h", round(deg_c * 100))

service = aioble.Service(_ENV_SERVICE)
temp_char = aioble.Characteristic(service, _TEMP_CHAR, read=True, notify=True)
aioble.register_services(service)

async def peripheral_task():
    while True:
        connection = await aioble.advertise(
            interval_us=250000,
            name="openmv-sensor",
            services=[_ENV_SERVICE],
            appearance=0x0300,
        )
        print("connected:", connection.device.addr_hex())
        async with connection:
            while connection.is_connected():
                temp_char.write(encode_temperature(23.68), send_update=True)
                await asyncio.sleep(1)

asyncio.run(peripheral_task())

模块级函数

aioble.config(*args, **kwargs) Any

转发到 bluetooth.BLE.config(),并先确保 BLE 射频处于激活状态。

args

可选的单个待查询参数名。

kwargs

用于设置配置值的关键字参数。

aioble.stop() None

停用底层 BLE 射频,并运行所有已注册的子模块关闭处理程序。调用之后,扫描器、广播器、连接和 L2CAP 通道都将被拆除。

aioble.scan(duration_ms: int, interval_us: int | None = None, window_us: int | None = None, active: bool = False) scan

返回一个 scan 异步上下文管理器 / 异步迭代器,它为每个发现的唯一设备(或为已知设备的每条新广播数据)生成 ScanResult 实例。

duration_ms

扫描时长,以毫秒为单位。传入 0 以无限期扫描,直到上下文管理器退出。

interval_us

扫描间隔,以微秒为单位。默认为 1,280,000。

window_us

扫描窗口,以微秒为单位(必须小于或等于 interval_us)。默认为 11,250。

active

如果为 True,则执行主动扫描(请求扫描响应数据)。默认为 False

aioble.advertise(interval_us: int, adv_data: bytes | None = None, resp_data: bytes | None = None, connectable: bool = True, limited_disc: bool = False, br_edr: bool = False, name: str | None = None, services: list | None = None, appearance: int = 0, manufacturer: tuple | None = None, timeout_ms: int | None = None) DeviceConnection

异步协程,开始广播并等待传入的中心设备连接。返回表示已连接中心设备的 DeviceConnection,或在超时时引发 asyncio.TimeoutError

interval_us

广播间隔,以微秒为单位。

adv_data

原始广播负载。如果未设置,则 adv_data 由其余关键字参数构建。

resp_data

原始扫描响应负载。如有需要,会自动填充从 adv_data 溢出的部分。

connectable

如果为 True,则这是一个可连接的广播。

limited_disc

使用受限可发现标志而非通用标志。

br_edr

设置 BR/EDR 支持标志。

name

可选的待嵌入的完整本地名称。

services

要广播的 bluetooth.UUID 可迭代对象。

appearance

16 位外观值(参见蓝牙分配编号)。

manufacturer

要作为厂商专有数据广播的 (company_id, data_bytes) 元组。

timeout_ms

在此毫秒数内无连接时停止广播。None 表示一直广播直到连接成功。

aioble.register_services(*services: Service) None

向 GATT 服务器注册一个或多个 Service 对象(及其特征和描述符)。必须在启动 advertise 之前调用一次。后续调用会替换之前的注册。

services

一个或多个 Service 实例。

模块级常量

aioble.ADDR_PUBLIC

公共 BLE 设备地址类型(0)。

aioble.ADDR_RANDOM

随机 BLE 设备地址类型(1)。

异常

exception aioble.GattError

当远程 GATT 操作(读 / 写 / indicate)以非零状态完成时引发。状态码可通过 _status 属性获取。

exception aioble.DeviceDisconnectedError

在异步操作(例如 read、write、notified)内部,当底层连接在等待期间断开时引发。

exception aioble.L2CAPDisconnectedError

当在已断开的通道上(或被其中断地)尝试 L2CAP 通道 send/recv/flush 操作时引发。

exception aioble.L2CAPConnectionError

当建立通道失败时由 DeviceConnection.l2cap_connect 引发。蓝牙状态码是第一个参数。

class aioble.Device(addr_type: int, addr: bytes | str)

通过地址表示一个远程 BLE 设备。当 addr_typeaddr 都匹配时,两个 Device 实例比较结果相等。用作发起连接的句柄。

addr_type

ADDR_PUBLICADDR_RANDOM 之一。

addr

bytes 表示的六字节地址,或以冒号分隔的十六进制字符串(例如 "aa:bb:cc:dd:ee:ff")。

addr_type

构造该设备时所用的地址类型。

addr

原始的六字节设备地址。

addr_hex() str

以冒号分隔的十六进制字符串形式返回地址。

connect(timeout_ms: int = 10000, scan_duration_ms: int | None = None, min_conn_interval_us: int | None = None, max_conn_interval_us: int | None = None) Awaitable[DeviceConnection]

异步。向该设备发起 GAP 连接,并返回得到的 DeviceConnection。会取消任何进行中的扫描。

timeout_ms

等待连接完成的时长。

scan_duration_ms

连接前的初始扫描时长(与控制器相关)。

min_conn_interval_us / max_conn_interval_us

可选的连接间隔上下界,以微秒为单位。

class aioble.DeviceConnection

到某个 Device 的活动 GAP 连接。由 Device.connect()advertise 返回。支持用作 async with 上下文管理器,退出时自动断开连接。

请勿直接构造。

device

底层的 Device

encrypted

一旦链路被加密(例如配对之后)即为 True

authenticated

如果链路已认证(受 MITM 保护的配对)则为 True

bonded

如果配对产生了绑定密钥则为 True

key_size

协商出的加密密钥大小(以字节为单位),如果未加密则为 False

mtu

exchange_mtu 之后协商出的 ATT MTU,在设置之前为 None

is_connected() bool

返回连接是否仍然处于活动状态。

disconnect(timeout_ms: int = 2000) Awaitable[None]

异步。断开连接并等待断开连接 IRQ。

timeout_ms

等待断开连接的最长时间。

disconnected(timeout_ms: int | None = None, disconnect: bool = False) Awaitable[None]

异步。等待连接被任意一方终止。如果 disconnectTrue,则先主动断开连接。

timeout_ms

等待的最长时间。None 表示永远等待。

disconnect

如果为 True,则发起断开连接。

timeout(timeout_ms: int | None) DeviceTimeout

返回一个上下文管理器,当超时到期(引发 asyncio.TimeoutError)或设备断开连接(引发 DeviceDisconnectedError)时,取消其主体。

timeout_ms

超时时间,以毫秒为单位,None 表示无超时。

exchange_mtu(mtu: int | None = None, timeout_ms: int = 1000) Awaitable[int]

异步。发起 ATT MTU 交换并返回协商出的 MTU。

mtu

可选的首选 MTU,在交换之前设置到底层 BLE 接口上。

timeout_ms

交换的超时时间。

service(uuid: bluetooth.UUID, timeout_ms: int = 2000) Awaitable[ClientService | None]

异步。发现与 uuid 匹配的单个远程服务,如果未找到则为 None

services(uuid: bluetooth.UUID | None = None, timeout_ms: int = 2000) ClientDiscover

返回远程 ClientService 对象的异步迭代器。配合 async for 使用并将循环运行至完成。

uuid

可选的 UUID 过滤器。None 返回每一个服务。

timeout_ms

每次发现的超时时间。

pair(bond: bool = True, le_secure: bool = True, mitm: bool = False, io: int = 3, timeout_ms: int = 20000) Awaitable[None]

异步。在此连接上发起配对。完成时更新 encrypted / authenticated / bonded / key_size 属性。

bond

持久化配对密钥。

le_secure

使用 LE 安全连接。

mitm

要求中间人保护。

io

IO 能力常量(例如 3 表示无输入/输出)。

timeout_ms

配对的超时时间。

l2cap_accept(psm: int, mtu: int, timeout_ms: int | None = None) Awaitable[L2CAPChannel]

异步。在给定的 PSM 上监听,一旦远端打开通道便返回一个 L2CAPChannel

psm

要监听的协议/服务多路复用器(PSM)。

mtu

最大接收大小,以字节为单位。

timeout_ms

等待远端连接的最长时间。

l2cap_connect(psm: int, mtu: int, timeout_ms: int = 1000) Awaitable[L2CAPChannel]

异步。在给定的 PSM 上向远端打开一个 L2CAP 通道。

psm

要连接的协议/服务多路复用器(PSM)。

mtu

最大接收大小,以字节为单位。

timeout_ms

连接超时时间。

class aioble.ScanResult

scan 期间发现的单个设备。当有新的广播数据到达时,会重新生成同一个实例。

请勿直接构造。

device

底层的 Device

rssi

最后报告的 RSSI,以 dBm 为单位。

adv_data

原始广播负载(bytesNone)。

resp_data

原始扫描响应负载(bytesNone),如果启用了主动扫描。

connectable

如果最近一条广播是可连接的,则为 True

name() str | None

从负载中解码完整(或缩短)的广播本地名称,如果不存在则为 None

services() Iterator[bluetooth.UUID]

生成器,生成在 16/32/128 位服务列表字段中广播的每个 bluetooth.UUID

manufacturer(filter: int | None = None) Iterator[tuple[int, bytes]]

生成器,从厂商专有广播字段中生成 (company_id, data) 元组。

filter

如果给定,则只生成 company ID 匹配的条目。

class aioble.Service(uuid: bluetooth.UUID)

本地 GATT 服务。用一个或多个 Characteristic 实例构建一个服务,然后将其传递给 register_services

uuid

服务 UUID。

uuid

服务 UUID。

characteristics

绑定到此服务的 Characteristic 对象列表。

class aioble.Characteristic(service: Service, uuid: bluetooth.UUID, read: bool = False, write: bool = False, write_no_response: bool = False, notify: bool = False, indicate: bool = False, initial: bytes | None = None, capture: bool = False)

本地 GATT 特征。构造一个特征会自动将其追加到 service

service

所属的 Service

uuid

特征 UUID。

readwritewrite_no_responsenotifyindicate

用于选择支持的 GATT 操作的布尔值。

initial

可选的初始值(bytes)。

capture

如果为 True,写入的值会被排队(最多 10 个深度),以便快速连续的写入不会丢失。此后每次 written 调用都会返回一个 (connection, data) 元组。

uuid

特征 UUID。

flags

由构造函数构建的 GATT 属性标志位掩码。

descriptors

绑定到此特征的 Descriptor 对象列表。

read() bytes

从本地 GATT 数据库读取当前值。

write(data: bytes, send_update: bool = False) None

更新本地 GATT 数据库中的值。

data

新的值字节。

send_update

如果为 True,还会通知/指示每个已订阅的连接。

notify(connection: DeviceConnection, data: bytes | None = None) None

connection 发送 GATT Notify。

connection

目标客户端连接。

data

要发送的负载。如果为 None,则发送当前本地值。

indicate(connection: DeviceConnection, data: bytes | None = None, timeout_ms: int = 1000) Awaitable[None]

异步。向 connection 发送 GATT Indicate 并等待客户端确认。在非零状态时引发 GattError

connection

目标客户端连接。

data

要指示的负载,或为 None 以发送本地值。

timeout_ms

等待确认的最长时间。

written(timeout_ms: int | None = None) Awaitable[DeviceConnection | tuple[DeviceConnection, bytes]]

异步。等待远程写入。返回执行写入的 DeviceConnection,如果特征是用 capture=True 创建的,则返回 (connection, data)

timeout_ms

等待的最长时间。None 表示永远等待。

on_read(connection: DeviceConnection) int

当收到远程读取时被同步调用的可重写钩子。返回 0 以允许读取,或返回非零 ATT 错误码以拒绝读取。默认实现返回 0

class aioble.BufferedCharacteristic(service: Service, uuid: bluetooth.UUID, max_len: int = 20, append: bool = False, **kwargs)

一种其底层 GATT 缓冲区可配置的 Characteristic。适用于接收大于默认属性大小的值,或用于排队连续写入。

max_len

缓冲区大小,以字节为单位。

append

如果为 True,连续写入会追加到缓冲区中而不是覆盖。

其他参数转发给 Characteristic

class aioble.Descriptor(characteristic: Characteristic, uuid: bluetooth.UUID, read: bool = False, write: bool = False, initial: bytes | None = None)

本地 GATT 描述符。构造一个描述符会自动将其追加到 characteristic。从 Characteristic 继承 readwritewritten

characteristic

所属的 Characteristic

uuid

描述符 UUID。

readwrite

用于选择支持的 GATT 操作的布尔值。

initial

可选的初始值(bytes)。

class aioble.ClientService

在对端设备上发现的远程 GATT 服务。由 DeviceConnection.service() 返回,或从 DeviceConnection.services() 迭代得到。

请勿直接构造。

connection

所属的 DeviceConnection

uuid

远程服务 UUID。

characteristic(uuid: bluetooth.UUID, timeout_ms: int = 2000) Awaitable[ClientCharacteristic | None]

异步。按 UUID 发现单个特征,如果未找到则为 None

characteristics(uuid: bluetooth.UUID | None = None, timeout_ms: int = 2000) ClientDiscover

返回 ClientCharacteristic 对象的异步迭代器。配合 async for 使用并将循环运行至完成。

uuid

可选的 UUID 过滤器。

timeout_ms

每次发现的超时时间。

class aioble.ClientCharacteristic

在对端设备上发现的远程 GATT 特征。由 ClientService.characteristic() 返回,或从 ClientService.characteristics() 迭代得到。

请勿直接构造。

service

所属的 ClientService

uuid

特征 UUID。

properties

对端报告的所支持 GATT 操作的位掩码。

read(timeout_ms: int = 1000) Awaitable[bytes]

异步。发出 GATT Read 并返回值。在非零状态时引发 GattError

timeout_ms

读取超时时间。

write(data: bytes, response: bool | None = None, timeout_ms: int = 1000) Awaitable[None]

异步。发出 GATT Write。

data

要写入的值。

response

True 表示要求写响应(失败时引发 GattError)。False 表示无响应写。None(默认)会根据对端所广播的内容自动选择。

timeout_ms

写入超时时间(仅当 responseTrue 时相关)。

notified(timeout_ms: int | None = None) Awaitable[bytes]

异步。等待此特征上的下一条通知并返回其负载。如果已有通知排队,则立即返回。

timeout_ms

等待的最长时间。None 表示永远等待。

indicated(timeout_ms: int | None = None) Awaitable[bytes]

异步。等待此特征上的下一条指示并返回其负载。

timeout_ms

等待的最长时间。

subscribe(notify: bool = True, indicate: bool = False) Awaitable[None]

异步。写入客户端特征配置描述符(CCCD),以订阅(或取消订阅)通知和/或指示。

notify

启用通知。

indicate

启用指示。

descriptor(uuid: bluetooth.UUID, timeout_ms: int = 2000) Awaitable[ClientDescriptor | None]

异步。按 UUID 发现单个描述符,如果未找到则为 None

descriptors(timeout_ms: int = 2000) ClientDiscover

返回 ClientDescriptor 对象的异步迭代器。配合 async for 使用并将循环运行至完成。

class aioble.ClientDescriptor

在对端设备上发现的远程 GATT 描述符。从 ClientCharacteristic 继承 readwrite

请勿直接构造。

characteristic

所属的 ClientCharacteristic

uuid

描述符 UUID。

class aioble.L2CAPChannel

活动的面向连接的 L2CAP 通道。由 DeviceConnection.l2cap_accept()DeviceConnection.l2cap_connect() 返回。支持用作 async with 上下文管理器,退出时自动断开连接。

请勿直接构造。

our_mtu

对端在单个 SDU 中可向我们发送的最大字节数。

peer_mtu

我们在单个 SDU 中可向对端发送的最大字节数。

available() bool

同步返回:如果已缓冲的接收数据就绪(即 recvinto 不会阻塞)则为 True

recvinto(buf: bytearray, timeout_ms: int | None = None) Awaitable[int]

异步。接收数据到 buf 中,返回读取的字节数。如果通道为空,则等待新数据。

buf

用于填充的预分配缓冲区。

timeout_ms

等待的最长时间。None 表示永远等待。

send(buf: bytes, timeout_ms: int | None = None, chunk_size: int | None = None) Awaitable[None]

异步。在通道上发送 buf,将较大的负载分片为 MTU 大小的块。按需等待流控制信用额度。

buf

要发送的类字节对象。

timeout_ms

每个块等待的最长时间。

chunk_size

可选的每次调用块大小覆盖值。上限为 min(our_mtu * 2, peer_mtu)

flush(timeout_ms: int | None = None) Awaitable[None]

异步。等待任何停滞的 send 被控制器排空。

timeout_ms

等待的最长时间。

disconnect(timeout_ms: int = 1000) Awaitable[None]

异步。断开通道并等待断开连接 IRQ。

timeout_ms

等待的最长时间。

disconnected(timeout_ms: int = 1000) Awaitable[None]

异步。等待通道被任意一方断开。

timeout_ms

等待的最长时间。