11.8. Das aioble-Modul¶
Die Bluetooth-Core-Spezifikation liefert ein Vokabular, das sich auf zwei MicroPython-Module abbildet.
bluetooth– die Low-Level-Anbindung an den BLE-Controller. Synchron, ereignisgesteuert über einen IRQ-artigen Callback und um Bytepuffer, Handles und die nackten GATT-Primitive herum strukturiert. Es legt das Protokoll so offen, wie es ist, nicht so, wie Python-Anwendungen es nutzen möchten.aioble– ein höherrangiger Wrapper, in Python aufbluetoothaufbauend, der jede Remote-Operation in eineasyncio-Coroutine und jedes BLE-Objekt (Dienste, Charakteristiken, Verbindungen, Scan-Ergebnisse, L2CAP-Kanäle) in eine ergonomische Python-Klasse verwandelt. Scans werden zu async-Iteratoren; Verbindungen werden zu asynchronen Kontextmanagern; Benachrichtigungen werden awaitable.
11.8.1. Wann man zum Low-Level-Modul greifen sollte¶
bluetooth ist nach wie vor die richtige Antwort für zwei eng umrissene Fälle:
Du schreibst genau die Art von Code, aus der
aiobleselbst aufgebaut ist – ein neues Muster, das IRQ-seitige Kontrolle über das Protokoll benötigt.Du läufst auf einem Hardwareziel, auf dem das
aioble-Paket nicht verfügbar ist, und ein dünner Shim um den Controller herum ist die einzige Option.
Für jede Kameraanwendung ist aioble die richtige Antwort.
11.8.2. Bestandteile eines aioble-Programms¶
Jede aioble-basierte Anwendung besteht aus einem kleinen Satz beweglicher Teile, unabhängig davon, welche Rollen sie spielt.
Eine langlaufende
asyncio-Ereignisschleife. Alles inaiobleist eine Coroutine, sodass die Anwendung als eine oder mehrere Tasks auf einer einzigen Ereignisschleife strukturiert ist. Siehe Asyncio für die Schleife, Tasks und Exceptions im Detail.Ein eingeschaltetes Funkmodul.
aiobleaktiviert das BLE-Funkmodul implizit bei der ersten Nutzung, es kann aber auch explizit mitaioble.config()gesteuert (das anbluetooth.BLE.config()weiterleitet, nachdem sichergestellt wurde, dass das Funkmodul aktiv ist) und mitaioble.stop()heruntergefahren werden.Eine oder mehrere Rollen gleichzeitig im Einsatz. Auf der Peripheral-Seite: ein registrierter Satz von GATT-Diensten (siehe
aioble.register_services()) und eine laufendeaioble.advertise()-Coroutine. Auf der Central-Seite: ein laufenderaioble.scan()-Iterator oder ein ausstehendesaioble.Device.connect(). Das Funkmodul multiplext die Arbeit; die Anwendung sieht jede Rolle als unabhängige Task.
11.8.3. Ein minimales Peripheral¶
Das kleinste nützliche aioble-Programm – ein Peripheral, das eine einzelne schreibgeschützte Charakteristik bewirbt – ist kurz:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A) # Environmental Sensing
TEMP_UUID = bluetooth.UUID(0x2A6E) # Temperature
service = aioble.Service(SERVICE_UUID)
temp = aioble.Characteristic(service, TEMP_UUID, read=True)
aioble.register_services(service)
async def main():
while True:
conn = await aioble.advertise(
interval_us=250000,
name="openmv-temp",
services=[SERVICE_UUID],
)
async with conn:
await conn.disconnected()
asyncio.run(main())
Ein Central, das nichts weiter tut, als sich zu verbinden und einmal zu lesen, ist ähnlich kurz:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A)
TEMP_UUID = bluetooth.UUID(0x2A6E)
async def main():
device = None
async with aioble.scan(duration_ms=5000, active=True) as scanner:
async for result in scanner:
if SERVICE_UUID in result.services():
device = result.device
break
if device is None:
return
async with await device.connect() as conn:
service = await conn.service(SERVICE_UUID)
char = await service.characteristic(TEMP_UUID)
print(await char.read())
asyncio.run(main())
Beide Programme umfassen etwa fünfzehn Zeilen und decken den gesamten Ablauf von „Funkmodul ist aus“ bis „nützliche Arbeit erledigt“ ab.
11.8.4. Das Funkmodul ausschalten¶
Auf einer batteriebetriebenen Kamera ist das BLE-Funkmodul der größte frei wählbare Verbraucher im Budget. Zwei Stellschrauben sind wichtig.
Die erste ist implizit: aioble aktiviert das Funkmodul bei der ersten Nutzung, und das Funkmodul schläft automatisch zwischen geplanten Ereignissen (Advertising-Bursts, Verbindungsereignissen, Scan-Fenstern). Längere Intervalle bei aioble.advertise() / aioble.scan() zu wählen und sich zum Zeitpunkt von connect() auf ein längeres Verbindungsintervall zu einigen, hält das Funkmodul proportional länger ausgeschaltet. Die Advertising-Tabelle in Advertising und Scanning ist hier der praktische Leitfaden.
Die zweite ist die explizite Abschaltung:
import aioble
await do_burst_of_ble_work()
aioble.stop() # radio deactivated; in-flight tasks unwound
await asyncio.sleep(60) # sleep with the radio off
# ... next aioble call brings the radio back up automatically
aioble.stop() deaktiviert das zugrunde liegende BLE-Funkmodul und baut alles Laufende ab – offene Verbindungen brechen ab, Scanner und Advertiser werden abgebrochen, L2CAP-Kanäle geschlossen. Coroutinen, die auf diese Operationen gewartet haben, lösen ihre üblichen Exceptions aus (DeviceDisconnectedError und Verwandte), was der Aufräummechanismus ist, für den die umgebenden async with-Blöcke geschrieben wurden. Der Aufruf irgendeiner aioble-Coroutine danach aktiviert das Funkmodul wieder aus dem Kaltstart heraus.
Das typische Muster für eine periodische, batteriebetriebene Sensor-Cam ist:
Nach einem Zeitplan aufwachen (Timer, Bewegungssensor, Taster).
Den Schub an BLE-Arbeit ausführen – bewerben, eine Verbindung annehmen, den Wert pushen, trennen.
aioble.stop()aufrufen und bis zum nächsten Aufwachen schlafen.
11.8.5. Was aioble nicht tut¶
aioble deckt bewusst GATT, GAP und L2CAP ab – die Schichten, die eine Anwendung nutzt. Drei Teile liegen außerhalb des Geltungsbereichs:
Alles unterhalb der Link-Schicht. Kanalauswahl, Frequenzsprung, Paketbestätigungen und Verschlüsselung auf Link-Schicht-Ebene geschehen alle innerhalb des BLE-Ports und des Controller-Siliziums;
aioblelegt auf dieser Ebene keine Hooks offen.Classic Bluetooth.
aiobleist ausschließlich BLE. Audioverbindungen, RFCOMM, A2DP und andere Classic-Profil-Funktionen sind nicht Teil der API.Bluetooth Mesh. Die Mesh-Networking-Schicht der Bluetooth SIG (ein separater Stack auf BLE-Advertising) ist auf der Kamera nicht implementiert. Die Cam kann bewerben und beobachten, aber sie kann nicht an den Relay-/Friend-/Proxy-Rollen eines Mesh-Netzwerks teilnehmen.
11.8.6. Exceptions¶
Vier Exception-Typen kommen aus aioble. Jeder wird aus einer Coroutine heraus ausgelöst, die auf eine Operation gewartet hat, als etwas schiefging; async with-Blöcke werden sauber abgewickelt, wenn sie propagieren.
aioble.DeviceDisconnectedError– die BLE-Verbindung zum Peer brach ab, während eine GATT-Operation (read,write,notified,indicated,subscribe,exchange_mtu, …) im Gange war. Wird innerhalb der jeweils wartenden Coroutine ausgelöst. Die mit Abstand häufigste Exception; fange sie in jedem Code ab, der sich bei Verlust neu verbinden soll.aioble.GattError– eine GATT-Operation erreichte den Peer, wurde aber mit einem ATT-Status ungleich null abgeschlossen (write-with-response abgelehnt, indicate nicht bestätigt, read-not-permitted, …). Der Statuscode steht im Attribut_statusder Exception.aioble.L2CAPDisconnectedError– der L2CAP-Kanal brach ab, während einsend(),recvinto()oderflush()im Gange war. Entweder hat eine der Seiten den Kanal geschlossen, oder die zugrunde liegende GAP-Verbindung ist verschwunden.aioble.L2CAPConnectionError– wird vonl2cap_connect()ausgelöst, wenn der Listener ablehnte oder der Controller den Kanalaufbau scheitern ließ. Der Bluetooth-Statuscode ist das erste Positionsargument.
Operationen, die ein explizites timeout_ms annehmen (die Aufrufe connect / discovery / read / write / pair sowie timeout() als Wrapper), lösen zusätzlich asyncio.TimeoutError aus asyncio aus, wenn die Frist verstreicht, bevor die Operation abgeschlossen ist.