11.11. Canaux L2CAP¶
GATT est un modèle clé/valeur. Les opérations qu’il propose (lecture, écriture, notification, indication) déplacent une seule courte valeur à la fois, et la plus grande charge utile unique qu’elles peuvent transporter correspond à ce que permet la MTU négociée – quelques centaines d’octets au mieux. Cela fonctionne bien pour les relevés de capteurs, les registres de commande et les indicateurs d’état. Cela s’effondre dès qu’il s’agit de kilo-octets ou de méga-octets : découper un long blob en des centaines de petites écritures coûte des allers-retours par rapport auxquels la radio est bien plus rapide.
Pour les flux de données en masse – une trame capturée que la caméra diffuse vers un téléphone, une image de mise à jour par voie hertzienne, un export groupé de mesures – le BLE offre un chemin alternatif : le Logical Link Control and Adaptation Protocol, L2CAP. L2CAP se situe entre la couche de liaison et GATT et permet à une application de réclamer son propre canal orienté connexion par-dessus la même liaison radio. Le canal est un chemin d’octets contrôlé par flux de crédits, doté d’une MTU par paquet bien plus grande et sans encadrement GATT intermédiaire.
11.11.1. Quand utiliser L2CAP¶
Les canaux L2CAP sont l’outil approprié lorsque :
Le transfert dépasse quelques centaines d’octets.
Les deux extrémités savent toutes deux qu’un canal L2CAP sera utilisé (il n’est pas exposé dans la charge utile d’annonce ; le client doit connaître le numéro de protocol/service multiplexer du canal, ou PSM, par un canal externe).
L’application est prête à renoncer aux commodités de GATT : pas d’adressabilité intégrée par UUID, pas de découvrabilité côté client via les applications standard, pas de notifications.
Le cas le plus courant dans les applications fondées sur aioble est le déplacement d’un blob binaire entre deux logiciels qui connaissent tous deux la convention de PSM – un protocole personnalisé caméra-vers-téléphone, une paire de caméras openmv communiquant entre elles, un chemin interne de mise à jour du micrologiciel sous le service GATT d’un périphérique.
Pour tout le reste, restez sur GATT. Un court état, un registre de commande, un relevé de capteur – tout cela relève d’une caractéristique.
11.11.2. Établir un canal¶
L2CAP s’exécute par-dessus une aioble.DeviceConnection existante, de sorte que le flux de découverte / annonce / connexion côté GAP est exactement le même que pour GATT. Une fois que les deux côtés détiennent une connexion, l’un écoute sur un PSM, l’autre s’y connecte.
Le PSM n’est qu’un petit entier. Le Bluetooth SIG réserve le bas de la plage à un usage normalisé (0x0001-0x007F) ; pour les canaux spécifiques à une application, utilisez un numéro de la plage dynamique (0x0080-0x00FF pour les PSM fixes, généralement libre à partir de 0x0040 pour un usage personnalisé). Les deux côtés doivent s’accorder au préalable sur la valeur.
La MTU d’un canal L2CAP est la plus grande SDU (Service Data Unit) unique que l’un ou l’autre côté délivrera en un seul send() – et non la MTU de la liaison BLE. Aioble fragmente automatiquement les charges utiles plus grandes. L’hôte BLE de la caméra plafonne la MTU L2CAP à 1017 octets ; 512 est une valeur par défaut raisonnable qui laisse de la marge des deux côtés sans consommer trop de RAM.
Côté écouteur (par exemple la caméra en tant que périphérique)
async def serve_l2cap(connection, image_bytes):
channel = await connection.l2cap_accept(psm=0x80, mtu=512)
async with channel:
# image_bytes is a bytearray -- e.g. csi0.snapshot().bytearray()
# or a compressed JPEG buffer. send() fragments into MTU-sized
# chunks automatically and awaits flow-control credits between.
await channel.send(image_bytes)
await channel.flush()
Côté connecteur (par exemple un téléphone ou un central)
async def open_l2cap(connection, total_bytes):
channel = await connection.l2cap_connect(psm=0x80, mtu=512)
async with channel:
image_bytes = bytearray(total_bytes)
view = memoryview(image_bytes)
received = 0
while received < total_bytes:
n = await channel.recvinto(view[received:])
if n == 0:
break
received += n
return image_bytes
l2cap_accept() se bloque jusqu’à ce que le pair se connecte (ou que timeout_ms se déclenche) ; l2cap_connect() se bloque jusqu’à ce que l’écouteur accepte (ou échoue). Les deux retournent un aioble.L2CAPChannel – lui-même un gestionnaire de contexte asynchrone qui ferme le canal à la sortie.
11.11.3. Envoi et réception¶
Les deux principales opérations sur un canal sont send() (écrit des octets vers le pair) et recvinto() (lit dans un tampon préalloué). Les deux sont des coroutines.
send()fragmente le tampon en morceaux de la taille de la MTU et attend entre eux les crédits de contrôle de flux de la couche de liaison. Un long envoi représente un seulawaitdu point de vue de l’application ; en interne, il peut mettre de nombreux paquets en file d’attente et se mettre en pause chaque fois que les crédits de réception du pair sont épuisés.recvinto()remplit le tampon passé avec tout ce qui est disponible (jusqu’à la MTU du canal) et retourne le nombre d’octets. Se met en attente si rien n’est disponible.available()retourneTruede manière synchrone s’il y a des données en mémoire tampon prêtes – utile pour interroger sans se suspendre.flush()se met en attente jusqu’à ce que tout envoi en cours ait été entièrement transmis au contrôleur.
Les canaux L2CAP sont de type flux dans le sens où les octets arrivent dans l’ordre et sans perte, mais les limites d’un seul send sont préservées – chaque SDU sort d’un seul recvinto. Cela diffère du TCP, où les limites d’un seul send() peuvent s’étaler sur plusieurs appels recv().
11.11.4. Gestion des déconnexions¶
Le canal disparaît dans trois situations : l’un des côtés appelle disconnect(), la connexion GAP sous-jacente tombe, ou la déconnexion au niveau L2CAP arrive. Les opérations actives lèvent aioble.L2CAPDisconnectedError. Comme du côté GATT, cela se manifeste sous forme d’exception dans la coroutine qui attendait, et le bloc async with channel se termine proprement.
Si un canal devient inaccessible à cause d’une déconnexion au niveau GAP, l’application revient en boucle vers l’annonce ou le scan, de la même manière qu’elle le ferait pour une déconnexion GATT.
11.11.5. Coût mémoire¶
Des MTU plus grandes et des files d’attente plus longues consomment davantage de RAM des deux côtés. Une MTU de 512 octets plus un tampon de réception par canal représentent environ 1 Ko par canal – ce n’est pas gratuit sur une petite caméra si plusieurs canaux sont ouverts en même temps. Tenez-vous-en à un canal par connexion et choisissez une MTU correspondant à la taille de message attendue ; la valeur par défaut d’un L2CAPChannel par DeviceConnection suffit à la plupart des applications.
L2CAP est la soupape de sécurité du BLE. GATT est ce vers quoi presque toutes les applications se tournent en premier, et les autres exemples central / périphérique de cette section s’en tiennent à GATT. L’API à saveur canal est la réponse lorsqu’une application dépasse le modèle clé/valeur.