11.8. Le module aioble¶
La spécification Bluetooth Core fournit un vocabulaire qui se projette sur deux modules MicroPython.
bluetooth– la liaison de bas niveau au contrôleur BLE. Synchrone, pilotée par les événements via une fonction de rappel de type IRQ, et structurée autour de tampons d’octets, de poignées (handles) et des primitives GATT brutes. Elle expose le protocole tel qu’il est, et non tel que les applications Python souhaitent le consommer.aioble– un enrobage de plus haut niveau, écrit en Python par-dessusbluetooth, qui transforme chaque opération distante en une coroutineasyncioet chaque objet BLE (services, caractéristiques, connexions, résultats de scan, canaux L2CAP) en une classe Python ergonomique. Les scans deviennent des itérateurs asynchrones ; les connexions deviennent des gestionnaires de contexte asynchrones ; les notifications deviennent des objets attendables.
11.8.1. Quand recourir au module de plus bas niveau¶
bluetooth reste la bonne réponse dans deux cas restreints :
Vous écrivez le type de code à partir duquel
aioblelui-même est construit – un nouveau schéma qui nécessite un contrôle du protocole au niveau des IRQ.Vous travaillez sur une cible matérielle où le paquet
aioblen’est pas disponible, et une fine couche d’adaptation autour du contrôleur est la seule option.
Pour toute application de caméra, aioble est la bonne réponse.
11.8.2. Les composants d’un programme aioble¶
Toute application fondée sur aioble comporte un petit ensemble d’éléments mobiles, quels que soient les rôles qu’elle joue.
Une boucle d’événements
asyncioà exécution longue. Tout dansaiobleest une coroutine, de sorte que l’application est structurée en une ou plusieurs tâches sur une seule boucle d’événements. Consultez Asyncio pour le détail de la boucle, des tâches et des exceptions.Une radio allumée.
aiobleactive la radio BLE implicitement à la première utilisation, mais elle peut aussi être contrôlée explicitement avecaioble.config()(qui transmet àbluetooth.BLE.config()après s’être assuré que la radio est en marche) et arrêtée avecaioble.stop().Un ou plusieurs rôles en cours simultanément. Du côté périphérique : un ensemble enregistré de services GATT (voir
aioble.register_services()) et une coroutineaioble.advertise()en cours d’exécution. Du côté central : un itérateuraioble.scan()en cours d’exécution ou unaioble.Device.connect()en attente. La radio multiplexe le travail ; l’application voit chaque rôle comme une tâche indépendante.
11.8.3. Un périphérique minimal¶
Le plus petit programme aioble utile – un périphérique qui annonce une unique caractéristique en lecture seule – est court
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 qui ne fait rien de plus que se connecter et lire une fois est tout aussi court
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())
Les deux programmes font environ quinze lignes et couvrent l’ensemble du flux, depuis « la radio est éteinte » jusqu’à « le travail utile est accompli ».
11.8.4. Éteindre la radio¶
Sur une caméra alimentée par batterie, la radio BLE est le plus gros poste discrétionnaire du budget. Deux leviers importent.
Le premier est implicite : aioble active la radio à la première utilisation, et la radio se met en veille automatiquement entre les événements planifiés (rafales d’annonce, événements de connexion, fenêtres de scan). Choisir des intervalles plus longs pour aioble.advertise() / aioble.scan() et convenir d’un intervalle de connexion plus long au moment du connect() maintient la radio éteinte proportionnellement plus longtemps. Le tableau des annonces dans Diffusion et balayage constitue le guide pratique sur ce point.
Le second est l’arrêt explicite
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() désactive la radio BLE sous-jacente et démantèle tout ce qui est en cours – les connexions ouvertes tombent, les scanneurs et les annonceurs s’annulent, les canaux L2CAP se ferment. Les coroutines qui attendaient ces opérations lèvent leurs exceptions habituelles (DeviceDisconnectedError et consorts), qui constituent le mécanisme de nettoyage pour lequel les blocs async with environnants ont été écrits. Appeler ensuite n’importe quelle coroutine aioble réactive la radio à froid.
Le schéma typique pour une caméra-capteur périodique alimentée par batterie est :
Se réveiller selon un calendrier (minuteur, capteur de mouvement, bouton).
Exécuter la rafale de travail BLE – annoncer, accepter une connexion, pousser la valeur, se déconnecter.
Appeler
aioble.stop()et se mettre en veille jusqu’au prochain réveil.
11.8.5. Ce que aioble ne fait pas¶
aioble couvre délibérément GATT, GAP et L2CAP – les couches qu’une application utilise. Trois éléments sont hors de son périmètre :
Tout ce qui se situe sous la couche de liaison. La sélection de canaux, le saut de fréquence, les accusés de réception de paquets et le chiffrement de la couche de liaison se produisent tous à l’intérieur du port BLE et du silicium du contrôleur ;
aioblen’expose pas de points d’accroche à ce niveau.Le Bluetooth classique.
aiobleest exclusivement BLE. Les liaisons audio, RFCOMM, A2DP et autres fonctionnalités des profils classiques ne font pas partie de l’API.Le Bluetooth Mesh. La couche de réseau maillé du Bluetooth SIG (une pile distincte par-dessus l’annonce BLE) n’est pas implémentée sur la caméra. La caméra peut annoncer et observer, mais elle ne peut pas participer aux rôles de relais / ami / proxy d’un réseau maillé.
11.8.6. Exceptions¶
Quatre types d’exceptions sortent de aioble. Chacune se déclenche depuis l’intérieur d’une coroutine qui attendait une opération lorsqu’un problème est survenu ; les blocs async with se déroulent proprement lorsqu’elles se propagent.
aioble.DeviceDisconnectedError– la liaison BLE vers le pair est tombée pendant qu’une opération GATT (read,write,notified,indicated,subscribe,exchange_mtu, …) était en cours. Levée à l’intérieur de la coroutine qui attendait. De loin l’exception la plus courante ; interceptez-la dans tout code censé se reconnecter en cas de perte.aioble.GattError– une opération GATT a atteint le pair mais s’est terminée avec un statut ATT non nul (écriture-avec-réponse rejetée, indication non acquittée, lecture-non-autorisée, …). Le code de statut se trouve dans l’attribut_statusde l’exception.aioble.L2CAPDisconnectedError– le canal L2CAP est tombé pendant qu’unsend(), unrecvinto()ou unflush()était en cours. L’un ou l’autre côté a pu fermer le canal, ou la connexion GAP sous-jacente a disparu.aioble.L2CAPConnectionError– levée parl2cap_connect()lorsque l’écouteur a refusé ou que le contrôleur a échoué l’établissement du canal. Le code de statut Bluetooth est le premier argument positionnel.
Les opérations qui prennent un timeout_ms explicite (les appels de connexion / découverte / lecture / écriture / appairage, plus timeout() en tant qu’enrobage) lèvent en outre asyncio.TimeoutError depuis asyncio lorsque l’échéance s’écoule avant que l’opération ne se termine.