aioble --- 非同步 BLE#

aioble 是對 bluetooth 模組所做的高階、對 asyncio 友善的封裝。它為掃描、連線、廣播、GATT 服務以及 L2CAP 通道提供了清晰的協程介面。

所有遠端操作(連線、斷線、用戶端讀取/寫入、伺服端 indicate、l2cap recv/send、配對)皆可 await 並支援逾時設定。

支援的角色:

  • Broadcaster(廣播者) --- 為常見欄位產生廣告與掃描回應酬載、自動將酬載拆分到廣告與掃描回應之間、無限期或固定時長進行廣播。

  • Peripheral(周邊端) --- 等待來自 central 的連線、等待 MTU 交換。

  • Observer(掃描者) --- 被動與主動掃描、為同一裝置合併廣告與掃描回應酬載、從廣告酬載解析常見欄位。

  • Central(中央端) --- 連線至 peripheral、發起 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())

central 身分連線至廣播 Heart Rate 服務的 peripheral,並訂閱其測量值通知::

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())

作為 peripheral:註冊一個 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#

非同步協程,開始廣播並等待傳入的 central 連線。傳回代表已連線 central 的 DeviceConnection,或在逾時時引發 asyncio.TimeoutError

interval_us

廣播間隔,以微秒為單位。

adv_data

原始廣告酬載。若未設定,adv_data 會從其餘的關鍵字引數建構而成。

resp_data

原始掃描回應酬載。若有需要,會自動填入由 adv_data 溢位的內容。

connectable

若為 True,則此為可連線的廣告。

limited_disc

使用受限可探索(limited-discoverable)旗標,而非一般旗標。

br_edr

設定 BR/EDR 支援旗標。

name

可選的完整本地名稱,用以嵌入。

services

要廣播的 bluetooth.UUID 可迭代物件。

appearance

16 位元外觀值(請參閱 Bluetooth 指定編號)。

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 在建立通道失敗時引發。第一個引數為 Bluetooth 狀態碼。

類別#

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

在交換前要設定於底層 BLE 介面的可選偏好 MTU。

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 Secure Connections。

mitm

要求中間人(man-in-the-middle)防護。

io

IO 能力常數(例如 3 表示無輸入/輸出)。

timeout_ms

配對的逾時時間。

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

非同步。在指定的 PSM 上監聽,並在遠端開啟後傳回一個 L2CAPChannel

psm

要監聽的 Protocol/Service Multiplexer。

mtu

最大接收大小,以位元組為單位。

timeout_ms

等待遠端連線的最長時間。

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

非同步。在指定的 PSM 上對遠端開啟一個 L2CAP 通道。

psm

要連接的 Protocol/Service Multiplexer。

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

若有提供,則僅產生公司 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/indicate。

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

要 indicate 的酬載,或 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,傳回讀取的位元組數。若通道為空則 await 新資料。

buf

用以填入資料的預先配置緩衝區。

timeout_ms

等待的最長時間。None 表示永久等待。

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

非同步。在通道上傳送 buf,將較大的酬載分段為 MTU 大小的區塊。會視需要 await 流量控制額度。

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

等待的最長時間。