11.8. De aioble-module

De Bluetooth Core-specificatie geeft een vocabulaire dat op twee MicroPython-modules afgebeeld kan worden.

  • bluetooth – de low-level binding met de BLE-controller. Synchroon, gebeurtenisgestuurd via een IRQ-achtige callback, en opgebouwd rond bytebuffers, handles en de kale GATT-primitieven. Het stelt het protocol bloot zoals het is, niet zoals Python-toepassingen het willen consumeren.

  • aioble – een hoger niveau wrapper, geschreven in Python bovenop bluetooth, die elke externe bewerking omzet in een asyncio-coroutine en elk BLE-object (services, characteristics, verbindingen, scanresultaten, L2CAP-kanalen) in een ergonomische Python-klasse. Scans worden async-iterators; verbindingen worden async-contextmanagers; notificaties worden awaitable.

11.8.1. Wanneer naar de low-level module te grijpen

bluetooth is nog steeds het juiste antwoord voor twee specifieke gevallen:

  • Je schrijft het soort code waaruit aioble zelf is opgebouwd – een nieuw patroon dat IRQ-niveau-controle over het protocol nodig heeft.

  • Je draait op een hardware-target waar het aioble-pakket niet beschikbaar is, en een dunne shim rond de controller is de enige optie.

Voor elke cameratoepassing is aioble het juiste antwoord.

11.8.2. Onderdelen van een aioble-programma

Elke op aioble gebaseerde toepassing heeft een kleine set bewegende onderdelen, ongeacht welke rollen het speelt.

  • Een langlopende asyncio-event-loop. Alles in aioble is een coroutine, dus de toepassing is gestructureerd als een of meer taken op één event-loop. Zie Asyncio voor de loop, taken en exceptions in detail.

  • Een radio die aanstaat. aioble activeert de BLE-radio impliciet bij eerste gebruik, maar deze kan ook expliciet worden bestuurd met aioble.config() (die doorstuurt naar bluetooth.BLE.config() nadat is verzekerd dat de radio aanstaat) en worden uitgeschakeld met aioble.stop().

  • Een of meer rollen tegelijk in de lucht. Aan de peripheral-zijde: een geregistreerde set GATT-services (zie aioble.register_services()) en een draaiende aioble.advertise()-coroutine. Aan de central-zijde: een draaiende aioble.scan()-iterator of een in behandeling zijnde aioble.Device.connect(). De radio multiplext het werk; de toepassing ziet elke rol als een onafhankelijke taak.

11.8.3. Een minimale peripheral

Het kleinste bruikbare aioble-programma – een peripheral die een enkele alleen-lezen characteristic adverteert – is kort:

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

Een central die niets meer doet dan verbinden en één keer lezen is even kort:

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 programma’s zijn ongeveer vijftien regels en ze dekken de hele stroom van “radio is uit” tot “nuttig werk gedaan”.

11.8.4. De radio uitzetten

Op een camera op batterijvoeding is de BLE-radio de grootste discretionaire belasting van het budget. Twee knoppen doen ertoe.

De eerste is impliciet: aioble activeert de radio bij eerste gebruik, en de radio slaapt automatisch tussen geplande gebeurtenissen door (advertentiebursts, verbindingsgebeurtenissen, scanvensters). Het kiezen van langere intervallen bij aioble.advertise() / aioble.scan() en het overeenkomen van een langer verbindingsinterval bij connect() houdt de radio proportioneel langer uit. De advertentietabel in Adverteren en scannen is hier de praktische gids.

De tweede is expliciete uitschakeling:

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() deactiveert de onderliggende BLE-radio en breekt alles af wat in de lucht is – open verbindingen vallen weg, scanners en adverteerders annuleren, L2CAP-kanalen sluiten. Coroutines die op die bewerkingen stonden te wachten, werpen hun gebruikelijke exceptions (DeviceDisconnectedError en aanverwanten), wat het opruimmechanisme is waarvoor de omringende async with-blokken zijn geschreven. Het aanroepen van een aioble-coroutine daarna activeert de radio opnieuw vanuit koude staat.

Het typische patroon voor een periodieke sensorcamera op batterijvoeding is:

  • Ontwaak volgens een schema (timer, bewegingssensor, knop).

  • Voer de burst van BLE-werk uit – adverteer, accepteer een verbinding, push de waarde, verbreek.

  • Roep aioble.stop() aan en slaap tot het volgende ontwaken.

11.8.5. Wat aioble niet doet

aioble dekt bewust GATT, GAP en L2CAP – de lagen die een toepassing gebruikt. Drie onderdelen vallen buiten het bereik:

  • Alles onder de linklaag. Kanaalselectie, frequency hopping, pakketbevestigingen en versleuteling op linklaagniveau gebeuren allemaal binnen de BLE-port en het siliconen van de controller; aioble stelt geen hooks op dat niveau bloot.

  • Classic Bluetooth. aioble is uitsluitend BLE. Audioverbindingen, RFCOMM, A2DP en andere classic-profiel-functies maken geen deel uit van de API.

  • Bluetooth Mesh. De mesh-netwerklaag van de Bluetooth SIG (een aparte stack bovenop BLE-advertising) is niet geïmplementeerd op de camera. De camera kan adverteren en observeren, maar kan niet deelnemen aan de relay- / friend- / proxy-rollen van een mesh-netwerk.

11.8.6. Exceptions

Er komen vier exception-types uit aioble. Elk vuurt af vanuit een coroutine die op een bewerking stond te wachten toen er iets misging; async with-blokken wikkelen zich netjes af wanneer ze propageren.

  • aioble.DeviceDisconnectedError – de BLE-link naar de peer viel weg terwijl een GATT-bewerking (read, write, notified, indicated, subscribe, exchange_mtu, …) in de lucht was. Geworpen binnen welke coroutine ook stond te wachten. Verreweg de meest voorkomende exception; vang hem in alle code die opnieuw moet verbinden bij verlies.

  • aioble.GattError – een GATT-bewerking bereikte de peer maar voltooide met een ATT-status ongelijk aan nul (write-with-response geweigerd, indicate niet bevestigd, read-not-permitted, …). De statuscode staat op het _status-attribuut van de exception.

  • aioble.L2CAPDisconnectedError – het L2CAP-kanaal viel weg terwijl een send(), recvinto() of flush() in de lucht was. Een van beide zijden kan het kanaal hebben gesloten, of de onderliggende GAP-verbinding viel weg.

  • aioble.L2CAPConnectionError – geworpen door l2cap_connect() wanneer de luisteraar weigerde of de controller de kanaalopzet liet mislukken. De Bluetooth-statuscode is het eerste positionele argument.

Bewerkingen die een expliciete timeout_ms accepteren (de connect- / discovery- / read- / write- / pair-aanroepen, plus timeout() als wrapper) werpen daarnaast asyncio.TimeoutError uit asyncio wanneer de deadline verstrijkt voordat de bewerking voltooid is.