11.8. Az aioble modul

A Bluetooth Core specifikáció olyan szókészletet ad, amely két MicroPython modulra képeződik le.

  • bluetooth – az alacsony szintű kötés a BLE-vezérlőhöz. Szinkron, IRQ-stílusú visszahíváson keresztül eseményvezérelt, és bájtpufferek, fogantyúk és a puszta GATT-primitívek köré strukturált. A protokollt úgy teszi elérhetővé, ahogy van, nem úgy, ahogy a Python-alkalmazások szeretnék fogyasztani.

  • aioble – egy magasabb szintű csomagoló, Pythonban írva a bluetooth tetejére, amely minden távoli műveletet asyncio coroutine-ná, és minden BLE-objektumot (szolgáltatások, karakterisztikák, kapcsolatok, beolvasási eredmények, L2CAP-csatornák) ergonomikus Python-osztállyá alakít. A beolvasásokból aszinkron iterátorok lesznek; a kapcsolatokból aszinkron kontextuskezelők; az értesítések megvárhatóvá válnak.

11.8.1. Mikor nyúlj az alacsonyabb szintű modulhoz

A bluetooth még mindig a helyes válasz két szűk esetben:

  • Olyan kódot írsz, amilyenből maga az aioble épül fel – egy új mintát, amelyhez IRQ-szintű vezérlés szükséges a protokoll felett.

  • Olyan hardvercélon futsz, ahol az aioble csomag nem elérhető, és a vezérlő köré épített vékony köztesréteg az egyetlen lehetőség.

Minden kameraalkalmazáshoz az aioble a helyes válasz.

11.8.2. Egy aioble program darabjai

Minden aioble-alapú alkalmazásnak van egy kis mozgó alkatrészkészlete, függetlenül attól, hogy milyen szerepeket játszik.

  • Egy hosszan futó asyncio eseményhurok. Az aioble-ben minden coroutine, így az alkalmazás egy vagy több feladatként van strukturálva egyetlen eseményhurkon. Lásd a Asyncio oldalt a hurok, a feladatok és a kivételek részletes leírásához.

  • Egy bekapcsolt rádió. Az aioble az első használatkor implicit módon aktiválja a BLE-rádiót, de explicit módon is vezérelhető az aioble.config() hívással (amely a bluetooth.BLE.config() hívásnak továbbít, miután biztosította, hogy a rádió fent van), és leállítható az aioble.stop() hívással.

  • Egy vagy több szerep egyszerre folyamatban. A peripheral oldalon: a GATT-szolgáltatások regisztrált készlete (lásd az aioble.register_services() hívást) és egy futó aioble.advertise() coroutine. A central oldalon: egy futó aioble.scan() iterátor vagy egy függőben lévő aioble.Device.connect(). A rádió multiplexeli a munkát; az alkalmazás minden szerepet független feladatként lát.

11.8.3. Egy minimális peripheral

A legkisebb hasznos aioble program – egy peripheral, amely egyetlen csak olvasható karakterisztikát hirdet – rövid:

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

Egy central, amely nem tesz többet, mint hogy csatlakozik és egyszer olvas, hasonlóan rövid:

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

Mindkét program körülbelül tizenöt sor, és lefedik a teljes folyamatot a „rádió ki van kapcsolva” állapottól a „hasznos munka elvégezve” állapotig.

11.8.4. A rádió kikapcsolása

Egy elemmel táplált kamerán a BLE-rádió a legnagyobb diszkrecionális fogyasztó a keretben. Két szabályozó számít.

Az első implicit: az aioble az első használatkor aktiválja a rádiót, és a rádió automatikusan alszik az ütemezett események (hirdetési kitörések, kapcsolati események, beolvasási ablakok) között. Hosszabb intervallumok választása az aioble.advertise() / aioble.scan() hívásokon, és hosszabb kapcsolati intervallumban való megegyezés a connect() időpontjában arányosan több ideig tartja kikapcsolva a rádiót. A hirdetési táblázat a Hirdetés és pásztázás oldalon a gyakorlati útmutató ehhez.

A második az explicit leállítás:

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

Az aioble.stop() deaktiválja az alatta lévő BLE-rádiót, és lebont mindent, ami folyamatban van – a nyitott kapcsolatok megszakadnak, a beolvasók és hirdetők megszakadnak, az L2CAP-csatornák lezáródnak. Azok a coroutine-ok, amelyek ezekre a műveletekre vártak, kiváltják a szokásos kivételeiket (DeviceDisconnectedError és társai), ami az a takarítási mechanizmus, amelyre a környező async with blokkokat írták. Bármely aioble coroutine ezt követő meghívása hidegről újra aktiválja a rádiót.

A tipikus minta egy időszakos, elemmel táplált érzékelőkamerához:

  • Ébredés ütemezésre (időzítő, mozgásérzékelő, gomb).

  • A BLE-munka kitörésének futtatása – hirdetés, kapcsolat elfogadása, érték továbbítása, lekapcsolás.

  • Az aioble.stop() meghívása és alvás a következő ébredésig.

11.8.5. Amit az aioble nem csinál

Az aioble szándékosan lefedi a GATT-ot, a GAP-ot és az L2CAP-ot – azokat a rétegeket, amelyeket egy alkalmazás használ. Három darab kívül esik a hatókörön:

  • Bármi a kapcsolati réteg alatt. A csatornaválasztás, a frekvenciaugrás, a csomagnyugtázások és a kapcsolati réteg titkosítása mind a BLE-porton és a vezérlő szilíciumán belül történik; az aioble nem tesz elérhetővé kapcsolódási pontokat azon a szinten.

  • Klasszikus Bluetooth. Az aioble csak BLE. Az audiokapcsolatok, az RFCOMM, az A2DP és más klasszikus profilú funkciók nem részei az API-nak.

  • Bluetooth Mesh. A Bluetooth SIG mesh hálózati rétege (egy különálló stack a BLE-hirdetés tetején) nincs megvalósítva a kamerán. A kamera tud hirdetni és megfigyelni, de nem tud részt venni egy mesh hálózat relay / friend / proxy szerepeiben.

11.8.6. Kivételek

Négy kivételtípus jön ki az aioble modulból. Mindegyik egy olyan coroutine belsejéből vált ki, amely egy műveletre várt, amikor valami elromlott; az async with blokkok tisztán visszacsévélődnek, amikor ezek propagálódnak.

  • aioble.DeviceDisconnectedError – a társhoz vezető BLE-kapcsolat megszakadt, miközben egy GATT-művelet (read, write, notified, indicated, subscribe, exchange_mtu, …) folyamatban volt. Abban a coroutine-ban váltódik ki, amelyik éppen várt. Messze a leggyakoribb kivétel; kapd el minden olyan kódban, amelynek veszteség esetén újra kell csatlakoznia.

  • aioble.GattError – egy GATT-művelet elérte a társat, de nem nulla ATT-állapottal fejeződött be (válasszal történő írás elutasítva, indicate nem nyugtázva, read-not-permitted, …). Az állapotkód a kivétel _status attribútumán található.

  • aioble.L2CAPDisconnectedError – az L2CAP-csatorna megszakadt, miközben egy send(), recvinto() vagy flush() művelet folyamatban volt. Vagy az egyik oldal zárta le a csatornát, vagy az alatta lévő GAP-kapcsolat szűnt meg.

  • aioble.L2CAPConnectionError – a l2cap_connect() váltja ki, amikor a figyelő elutasította, vagy a vezérlő meghiúsította a csatorna beállítását. A Bluetooth-állapotkód az első pozicionális argumentum.

Azok a műveletek, amelyek explicit timeout_ms paramétert fogadnak (a connect / discovery / read / write / pair hívások, plusz a timeout() mint csomagoló), ezenfelül asyncio.TimeoutError kivételt is kiváltanak az asyncio modulból, amikor a határidő letelik, mielőtt a művelet befejeződne.