11.8. Modul aioble#

Specifikace Bluetooth Core poskytuje slovník, který se mapuje na dva moduly MicroPython.

  • bluetoothnízkoúrovňová vazba na BLE řadič. Synchronní, řízená událostmi přes callback ve stylu IRQ a strukturovaná kolem bajtových bufferů, handlů a holých GATT primitiv. Vystavuje protokol takový, jaký je, nikoli takový, jak ho chtějí konzumovat aplikace v Pythonu.

  • aioble – vyšší úrovňový obal napsaný v Pythonu nad bluetooth, který každou vzdálenou operaci proměňuje na korutinu asyncio a každý BLE objekt (služby, charakteristiky, připojení, výsledky skenování, kanály L2CAP) na ergonomickou třídu v Pythonu. Skenování se stává asynchronními iterátory; připojení se stávají asynchronními kontextovými správci; oznámení se stávají awaitable.

11.8.1. Kdy sáhnout po nízkoúrovňovém modulu#

bluetooth je stále tou správnou odpovědí pro dva úzké případy:

  • Píšete druh kódu, ze kterého je vystavěn sám aioble – nový vzor, který potřebuje řízení protokolu na úrovni IRQ.

  • Běžíte na hardwarové platformě, kde balíček aioble není k dispozici, a tenká mezivrstva kolem řadiče je jedinou možností.

Pro každou kamerovou aplikaci je aioble tou správnou odpovědí.

11.8.2. Části programu aioble#

Každá aplikace založená na aioble má malou množinu pohyblivých částí, bez ohledu na to, jaké role hraje.

  • Dlouho běžící smyčka událostí asyncio. Vše v aioble je korutina, takže aplikace je strukturována jako jedna nebo více úloh na jediné smyčce událostí. Podrobnosti o smyčce, úlohách a výjimkách viz Asyncio.

  • Zapnuté rádio. aioble aktivuje BLE rádio implicitně při prvním použití, ale lze ho také řídit explicitně pomocí aioble.config() (které předává volání bluetooth.BLE.config() poté, co zajistí, že je rádio v provozu) a vypnout pomocí aioble.stop().

  • Jedna nebo více rolí najednou v běhu. Na straně periferie: registrovaná sada GATT služeb (viz aioble.register_services()) a běžící korutina aioble.advertise(). Na straně centrálního zařízení: běžící iterátor aioble.scan() nebo čekající aioble.Device.connect(). Rádio práci multiplexuje; aplikace vidí každou roli jako nezávislou úlohu.

11.8.3. Minimální periferie#

Nejmenší užitečný program aioble – periferie, která inzeruje jedinou charakteristiku pouze pro čtení – je krátký:

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

Centrální zařízení, které nedělá nic víc než se připojí a jednou přečte, je podobně krátké:

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

Oba programy mají kolem patnácti řádků a pokrývají celý tok od „rádio je vypnuté“ po „užitečná práce hotova“.

11.8.4. Vypnutí rádia#

Na kameře napájené z baterie je BLE rádio největším volitelným odběrem z rozpočtu. Záleží na dvou regulátorech.

První je implicitní: aioble aktivuje rádio při prvním použití a rádio mezi naplánovanými událostmi (inzertní dávky, události připojení, skenovací okna) automaticky usíná. Volba delších intervalů u aioble.advertise() / aioble.scan() a dohoda na delším intervalu připojení v čase connect() udržuje rádio vypnuté úměrně více času. Praktickým vodítkem je zde tabulka inzerce v Vysílání (advertising) a skenování.

Druhým je explicitní vypnutí:

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() deaktivuje podkladové BLE rádio a strhne vše, co je v běhu – otevřená připojení vypadnou, skenery a inzerující se zruší, kanály L2CAP se zavřou. Korutiny, které na tyto operace čekaly, vyvolají své obvyklé výjimky (DeviceDisconnectedError a podobné), což je úklidový mechanismus, pro který byly okolní bloky async with napsány. Volání jakékoli korutiny aioble poté znovu aktivuje rádio ze studeného stavu.

Typický vzor pro periodickou senzorovou kameru napájenou z baterie je:

  • Probudit se podle plánu (časovač, pohybový senzor, tlačítko).

  • Spustit dávku BLE práce – inzerovat, přijmout připojení, odeslat hodnotu, odpojit.

  • Zavolat aioble.stop() a spát do dalšího probuzení.

11.8.5. Co aioble nedělá#

aioble záměrně pokrývá GATT, GAP a L2CAP – vrstvy, které aplikace používá. Tři části jsou mimo rozsah:

  • Cokoli pod linkovou vrstvou. Výběr kanálu, frekvenční skákání, potvrzování paketů a šifrování na linkové vrstvě se vše odehrává uvnitř BLE portu a křemíku řadiče; aioble na této úrovni nevystavuje žádné háčky.

  • Klasický Bluetooth. aioble je pouze pro BLE. Audio spoje, RFCOMM, A2DP a další funkce klasických profilů nejsou součástí API.

  • Bluetooth Mesh. Vrstva síťování typu mesh od Bluetooth SIG (samostatný stack nad BLE inzercí) není na kameře implementována. Kamera může inzerovat a pozorovat, ale nemůže se účastnit rolí relay / friend / proxy v mesh síti.

11.8.6. Výjimky#

Z aioble vychází čtyři typy výjimek. Každá vystřelí zevnitř korutiny, která čekala na operaci, když se něco pokazilo; bloky async with se při jejich propagaci čistě rozvinou.

  • aioble.DeviceDisconnectedError – BLE spoj k protějšku vypadl, zatímco byla v běhu GATT operace (read, write, notified, indicated, subscribe, exchange_mtu, …). Vyvolána uvnitř té korutiny, která čekala. Zdaleka nejběžnější výjimka; zachyťte ji v jakémkoli kódu, který se má při ztrátě znovu připojit.

  • aioble.GattError – GATT operace dosáhla protějšku, ale dokončila se s nenulovým ATT stavem (write-with-response odmítnuto, indicate nepotvrzeno, read-not-permitted, …). Stavový kód je na atributu _status výjimky.

  • aioble.L2CAPDisconnectedError – kanál L2CAP vypadl, zatímco bylo v běhu send(), recvinto() nebo flush(). Kanál mohla zavřít kterákoli ze stran, nebo zaniklo podkladové GAP připojení.

  • aioble.L2CAPConnectionError – vyvolána l2cap_connect(), když naslouchající odmítl nebo řadič selhal při sestavení kanálu. Stavový kód Bluetooth je prvním poziční argumentem.

Operace, které berou explicitní timeout_ms (volání connect / discovery / read / write / pair, plus timeout() jako obal), navíc vyvolají asyncio.TimeoutError z asyncio, když časový limit uplyne dříve, než se operace dokončí.