11.8. Modulen aioble

Bluetooth Core-specifikationen ger ett vokabulär som mappar mot två MicroPython-moduler.

  • bluetooth – den lågnivå-bindning till BLE-styrenheten. Synkron, händelsedriven genom ett IRQ-liknande återanrop, och strukturerad kring bytebuffertar, handtag och de nakna GATT-primitiverna. Den exponerar protokollet som det är, inte som Python-applikationer vill konsumera det.

  • aioble – en högre nivås omslutning, skriven i Python ovanpå bluetooth, som förvandlar varje fjärroperation till en asyncio-coroutine och varje BLE-objekt (tjänster, egenskaper, anslutningar, skanningsresultat, L2CAP-kanaler) till en ergonomisk Python-klass. Skanningar blir asynkrona iteratorer; anslutningar blir asynkrona kontexthanterare; notifieringar blir await-bara.

11.8.1. När man ska gripa efter lågnivåmodulen

bluetooth är fortfarande rätt svar för två snäva fall:

  • Du skriver den sorts kod som aioble själv är byggd av – ett nytt mönster som behöver IRQ-nivåkontroll över protokollet.

  • Du kör på ett hårdvarumål där paketet aioble inte är tillgängligt, och ett tunt skikt runt styrenheten är det enda alternativet.

För varje kameraapplikation är aioble rätt svar.

11.8.2. Delar av ett aioble-program

Varje aioble-baserad applikation har en liten uppsättning rörliga delar, oavsett vilka roller den spelar.

  • En långkörande asyncio-händelseloop. Allt i aioble är en coroutine, så applikationen är strukturerad som en eller flera uppgifter på en enda händelseloop. Se Asyncio för loopen, uppgifterna och undantagen i detalj.

  • En radio som är på. aioble aktiverar BLE-radion implicit vid första användning, men den kan också styras explicit med aioble.config() (som vidarebefordrar till bluetooth.BLE.config() efter att ha säkerställt att radion är uppe) och stängas ner med aioble.stop().

  • En eller flera roller i luften samtidigt. På peripheral-sidan: en registrerad uppsättning GATT-tjänster (se aioble.register_services()) och en körande aioble.advertise()-coroutine. På central-sidan: en körande aioble.scan()-iterator eller en väntande aioble.Device.connect(). Radion multiplexar arbetet; applikationen ser varje roll som en oberoende uppgift.

11.8.3. En minimal peripheral

Det minsta användbara aioble-programmet – en peripheral som annonserar en enda skrivskyddad egenskap – är 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())

En central som inte gör mer än att ansluta och läsa en gång är likaledes 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())

Båda programmen är ungefär femton rader och de täcker hela flödet från ”radion är av” till ”nyttigt arbete gjort”.

11.8.4. Stänga av radion

På en batteridriven kamera är BLE-radion det största skönsmässiga uttaget på budgeten. Två rattar spelar roll.

Den första är implicit: aioble aktiverar radion vid första användning, och radion sover mellan schemalagda händelser (annonseringsskurar, anslutningshändelser, skanningsfönster) automatiskt. Att välja längre intervall på aioble.advertise() / aioble.scan() och komma överens om ett längre anslutningsintervall vid connect()-tillfället håller radion av proportionellt mer av tiden. Annonseringstabellen i Annonsering och skanning är den praktiska guiden här.

Den andra är explicit nedstängning:

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() avaktiverar den underliggande BLE-radion och river ner allt i luften – öppna anslutningar tappas, skannrar och annonsörer avbryts, L2CAP-kanaler stängs. Coroutiner som väntade på dessa operationer kastar sina vanliga undantag (DeviceDisconnectedError med flera), vilket är den uppstädningsmekanism som de omgivande async with-blocken skrevs för. Att anropa någon aioble-coroutine efteråt aktiverar radion igen från kallt.

Det typiska mönstret för en periodisk batteridriven sensorkamera är:

  • Vakna enligt ett schema (timer, rörelsesensor, knapp).

  • Kör BLE-arbetsskuren – annonsera, acceptera en anslutning, pusha värdet, koppla från.

  • Anropa aioble.stop() och sov tills nästa uppvaknande.

11.8.5. Vad aioble inte gör

aioble täcker avsiktligt GATT, GAP och L2CAP – de lager en applikation använder. Tre delar ligger utanför omfånget:

  • Allt under länklagret. Kanalval, frekvenshoppning, paketbekräftelser och länklagerkryptering sker allt inuti BLE-porten och styrenhetens kisel; aioble exponerar inga krokar på den nivån.

  • Klassisk Bluetooth. aioble är endast för BLE. Ljudlänkar, RFCOMM, A2DP och andra klassisk-profil-funktioner är inte en del av API:et.

  • Bluetooth Mesh. Bluetooth SIG:s mesh-nätverkslager (en separat stack ovanpå BLE-annonsering) är inte implementerat på kameran. Kameran kan annonsera och observera, men den kan inte delta i ett mesh-nätverks roller som relä / vän / proxy.

11.8.6. Undantag

Fyra undantagstyper kommer ut ur aioble. Var och en avfyras inifrån en coroutine som inväntade en operation när något gick fel; async with-block rullas tillbaka snyggt när de propagerar.

  • aioble.DeviceDisconnectedError – BLE-länken till motparten tappades medan en GATT-operation (read, write, notified, indicated, subscribe, exchange_mtu, …) var i luften. Kastas inuti vilken coroutine som än väntade. Det överlägset vanligaste undantaget; fånga det i all kod som ska återansluta vid förlust.

  • aioble.GattError – en GATT-operation nådde motparten men slutfördes med en ATT-status skild från noll (write-with-response avvisad, indicate inte bekräftad, read-not-permitted, …). Statuskoden finns på undantagets _status-attribut.

  • aioble.L2CAPDisconnectedError – L2CAP-kanalen tappades medan en send(), recvinto() eller flush() var i luften. Någon sida kan ha stängt kanalen, eller så försvann den underliggande GAP-anslutningen.

  • aioble.L2CAPConnectionError – kastas av l2cap_connect() när lyssnaren vägrade eller styrenheten misslyckades med kanaluppsättningen. Bluetooth-statuskoden är det första positionella argumentet.

Operationer som tar ett explicit timeout_ms (anropen för connect / discovery / read / write / pair, plus timeout() som ett omslag) kastar dessutom asyncio.TimeoutError från asyncio när tidsgränsen löper ut innan operationen slutförs.