11.8. Il modulo aioble¶
La specifica Bluetooth Core fornisce un vocabolario che si mappa su due moduli MicroPython.
bluetooth– il binding di basso livello al controller BLE. Sincrono, guidato da eventi attraverso una callback in stile IRQ e strutturato attorno a buffer di byte, handle e le primitive GATT grezze. Espone il protocollo cosi com’e, non come le applicazioni Python vorrebbero consumarlo.aioble– un wrapper di piu alto livello, scritto in Python soprabluetooth, che trasforma ogni operazione remota in una coroutineasyncioe ogni oggetto BLE (servizi, caratteristiche, connessioni, risultati di scan, canali L2CAP) in un’ergonomica classe Python. Gli scan diventano iteratori asincroni; le connessioni diventano context manager asincroni; le notifiche diventano awaitable.
11.8.1. Quando ricorrere al modulo di livello piu basso¶
bluetooth rimane la risposta giusta per due casi ristretti:
Stai scrivendo il tipo di codice di cui
aioblestesso e fatto – un nuovo pattern che richiede controllo a livello IRQ sul protocollo.Stai operando su un target hardware in cui il pacchetto
aioblenon e disponibile, e un sottile shim attorno al controller e l’unica opzione.
Per ogni applicazione della camera, aioble e la risposta giusta.
11.8.2. Componenti di un programma aioble¶
Ogni applicazione basata su aioble ha un piccolo insieme di parti mobili, indipendentemente dai ruoli che svolge.
Un event loop
asyncioa lunga durata. Tutto inaioblee una coroutine, quindi l’applicazione e strutturata come uno o piu task su un singolo event loop. Vedi Asyncio per i dettagli su loop, task ed eccezioni.Una radio accesa.
aiobleattiva la radio BLE implicitamente al primo utilizzo, ma puo anche essere controllata esplicitamente conaioble.config()(che inoltra abluetooth.BLE.config()dopo essersi assicurato che la radio sia attiva) e spenta conaioble.stop().Uno o piu ruoli attivi contemporaneamente. Sul lato peripheral: un insieme registrato di servizi GATT (vedi
aioble.register_services()) e una coroutineaioble.advertise()in esecuzione. Sul lato central: un iteratoreaioble.scan()in esecuzione o unaaioble.Device.connect()in sospeso. La radio multiplexa il lavoro; l’applicazione vede ogni ruolo come un task indipendente.
11.8.3. Un peripheral minimale¶
Il piu piccolo programma aioble utile – un peripheral che pubblicizza una singola caratteristica in sola lettura – e breve:
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())
Un central che non fa altro che connettersi e leggere una volta e altrettanto breve:
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())
Entrambi i programmi sono di circa quindici righe e coprono l’intero flusso da «radio spenta» a «lavoro utile svolto».
11.8.4. Spegnere la radio¶
Su una camera alimentata a batteria la radio BLE e il maggior consumo discrezionale sul budget. Contano due manopole.
La prima e implicita: aioble attiva la radio al primo utilizzo, e la radio dorme automaticamente tra gli eventi pianificati (raffiche di advertising, eventi di connessione, finestre di scan). Scegliere intervalli piu lunghi su aioble.advertise() / aioble.scan() e concordare un intervallo di connessione piu lungo al momento di connect() mantiene la radio spenta proporzionalmente piu a lungo. La tabella di advertising in Advertising e scanning e la guida pratica in questo caso.
La seconda e lo spegnimento esplicito
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() disattiva la radio BLE sottostante e smantella tutto cio che e attivo – le connessioni aperte cadono, scanner e advertiser vengono annullati, i canali L2CAP si chiudono. Le coroutine in attesa su quelle operazioni sollevano le loro consuete eccezioni (DeviceDisconnectedError e simili), che e il meccanismo di pulizia per cui i blocchi async with circostanti sono stati scritti. Chiamare in seguito una qualsiasi coroutine aioble riattiva la radio da fredda.
Il pattern tipico per una cam sensore periodica alimentata a batteria e:
Risvegliarsi su una pianificazione (timer, sensore di movimento, pulsante).
Eseguire la raffica di lavoro BLE – pubblicizzare, accettare una connessione, inviare il valore, disconnettere.
Chiamare
aioble.stop()e andare in sleep fino al risveglio successivo.
11.8.5. Cosa aioble non fa¶
aioble copre deliberatamente GATT, GAP e L2CAP – i livelli che un’applicazione utilizza. Tre componenti sono fuori dall’ambito:
Tutto cio che e al di sotto del link layer. Selezione del canale, frequency hopping, acknowledgement dei pacchetti e cifratura a livello di link avvengono tutti all’interno della porta BLE e del silicio del controller;
aioblenon espone hook a quel livello.Il Bluetooth classico.
aioblee solo BLE. Collegamenti audio, RFCOMM, A2DP e altre funzionalita dei profili classici non fanno parte dell’API.Bluetooth Mesh. Il livello di rete mesh del Bluetooth SIG (uno stack separato sopra l’advertising BLE) non e implementato sulla camera. La cam puo pubblicizzare e osservare, ma non puo partecipare ai ruoli relay / friend / proxy di una rete mesh.
11.8.6. Eccezioni¶
Da aioble escono quattro tipi di eccezione. Ognuna scatta dall’interno di una coroutine che era in attesa di un’operazione quando qualcosa e andato storto; i blocchi async with si smontano in modo pulito quando si propagano.
aioble.DeviceDisconnectedError– il collegamento BLE al peer e caduto mentre un’operazione GATT (read,write,notified,indicated,subscribe,exchange_mtu, …) era attiva. Sollevata all’interno della coroutine che era in attesa. Di gran lunga l’eccezione piu comune; intercettala in qualsiasi codice che debba riconnettersi in caso di perdita.aioble.GattError– un’operazione GATT ha raggiunto il peer ma si e completata con uno stato ATT diverso da zero (write-with-response rifiutata, indicate non riconosciuta, read-not-permitted, …). Il codice di stato si trova nell’attributo_statusdell’eccezione.aioble.L2CAPDisconnectedError– il canale L2CAP e caduto mentre unasend(),recvinto()oflush()era attiva. Uno dei due lati puo aver chiuso il canale, oppure la connessione GAP sottostante e scomparsa.aioble.L2CAPConnectionError– sollevata dal2cap_connect()quando il listener ha rifiutato o il controller ha fatto fallire la configurazione del canale. Il codice di stato Bluetooth e il primo argomento posizionale.
Le operazioni che accettano un esplicito timeout_ms (le chiamate di connect / discovery / read / write / pair, piu timeout() come wrapper) sollevano inoltre asyncio.TimeoutError da asyncio quando la scadenza trascorre prima che l’operazione si completi.