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.