11.11. Canale L2CAP¶
GATT este un model cheie/valoare. Operațiunile pe care le oferă (citire, scriere, notificare, indicare) transferă o singură valoare scurtă la un moment dat, iar cea mai mare sarcină utilă unică pe care o pot transporta este oricât permite MTU-ul negociat – câteva sute de octeți în cel mai bun caz. Acest lucru funcționează bine pentru citirile de senzor, registrele de comandă și indicatorii de stare. Se prăbușește la kiloocteți sau megaocteți: împărțirea unui blob lung în sute de scrieri mici costă dus-întorsuri pe care radioul le face mult mai lent.
Pentru fluxurile de date în masă – un cadru capturat pe care camera îl transmite către un telefon, o imagine de actualizare prin aer, un export grupat de măsurători – BLE oferă o cale alternativă: Logical Link Control and Adaptation Protocol, L2CAP. L2CAP se află între stratul de legătură și GATT și permite unei aplicații să-și revendice propriul canal orientat pe conexiune deasupra aceleiași legături radio. Canalul este o cale de octeți cu control al fluxului bazat pe credite, cu un MTU per pachet mult mai mare și fără cadrare GATT la mijloc.
11.11.1. Când să folosești L2CAP¶
Canalele L2CAP sunt instrumentul potrivit atunci când:
Transferul este mai mare de câteva sute de octeți.
Ambele capete știu că va fi folosit un canal L2CAP (acesta nu este expus în sarcina utilă de publicitate; clientul trebuie să cunoască numărul protocol/service multiplexer, sau PSM, al canalului în afara benzii).
Aplicația este dispusă să renunțe la avantajele GATT: fără adresabilitate încorporată după UUID, fără posibilitatea de descoperire de către client prin aplicații standard, fără notificări.
Cel mai frecvent caz în aplicațiile bazate pe aioble este transferul unui blob binar între două componente software care cunosc ambele convenția PSM – un protocol personalizat cameră-telefon, o pereche de camere openmv care comunică între ele, o cale internă de actualizare a firmware-ului sub serviciul GATT al unui periferic.
Pentru orice altceva, rămâi la GATT. O stare scurtă, un registru de control, o citire de senzor – toate acestea aparțin unei caracteristici.
11.11.2. Stabilirea unui canal¶
L2CAP rulează deasupra unei aioble.DeviceConnection existente, deci fluxul de descoperire / publicitate / conectare de pe partea GAP este exact același ca pentru GATT. Odată ce ambele părți dețin o conexiune, una ascultă pe un PSM, iar cealaltă se conectează la el.
PSM-ul este doar un întreg mic. Bluetooth SIG rezervă partea inferioară a intervalului pentru utilizare standardizată (0x0001-0x007F); pentru canale specifice aplicației folosește un număr din intervalul dinamic (0x0080-0x00FF pentru PSM-uri fixe, de la 0x0040 în sus de obicei liber pentru utilizare personalizată). Ambele părți trebuie să convină asupra valorii în prealabil.
MTU-ul pe un canal L2CAP este cel mai mare SDU unic (Service Data Unit) pe care oricare dintre părți îl va livra într-un singur send() – nu MTU-ul legăturii BLE. Aioble fragmentează automat sarcinile utile mai mari. Gazda BLE a camerei limitează MTU-ul L2CAP la 1017 octeți; 512 este o valoare implicită rezonabilă care lasă loc pe ambele părți fără a consuma RAM.
Pe partea ascultătorului (de exemplu, camera ca periferic):
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()
Pe partea conectorului (de exemplu, un telefon sau 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() blochează până când partenerul se conectează (sau timeout_ms se declanșează); l2cap_connect() blochează până când ascultătorul acceptă (sau eșuează). Ambele returnează un aioble.L2CAPChannel – el însuși un gestionar de context asincron care închide canalul la ieșire.
11.11.3. Trimitere și recepție¶
Cele două operațiuni principale pe un canal sunt send() (scrie octeți către partener) și recvinto() (citește într-un tampon (buffer) prealocat). Ambele sunt corutine.
send()fragmentează tamponul (buffer) în bucăți de dimensiunea MTU-ului și așteaptă creditele de control al fluxului de la stratul de legătură între ele. O trimitere lungă este un singurawaitdin perspectiva aplicației; intern, poate pune în coadă multe pachete și se întrerupe ori de câte ori creditele de recepție ale partenerului se epuizează.recvinto()umple tamponul (buffer) transmis cu orice este disponibil (până la MTU-ul canalului) și returnează numărul de octeți. Așteaptă dacă nu este nimic disponibil.available()returneazăTrueîn mod sincron dacă există date tamponate gata – util pentru interogare fără suspendare.flush()așteaptă până când orice trimitere în curs a fost complet transmisă către controler.
Canalele L2CAP sunt asemănătoare unui flux în sensul că octeții ajung în ordine și fără pierderi, dar limitele unui singur send sunt păstrate – fiecare SDU iese dintr-un singur recvinto. Acest lucru este diferit de TCP, unde limitele unui singur send() se pot întinde pe mai multe apeluri recv().
11.11.4. Gestionarea deconectării¶
Canalul dispare în trei condiții: oricare dintre părți apelează disconnect(), conexiunea GAP subiacentă se întrerupe, sau sosește deconectarea la nivel L2CAP. Operațiunile active generează aioble.L2CAPDisconnectedError. Ca și în cazul părții GATT, acest lucru apare ca o excepție în corutina care aștepta, iar blocul async with channel iese în mod curat.
Dacă un canal devine inaccesibil printr-o deconectare la nivel GAP, aplicația se întoarce în buclă la publicitate sau scanare în același mod în care ar face-o pentru o deconectare GATT.
11.11.5. Costul de memorie¶
MTU-urile mai mari și cozile mai lungi folosesc mai mult RAM pe ambele părți. Un MTU de 512 octeți plus un tampon (buffer) de recepție per canal înseamnă aproximativ 1 KB per canal – nu este gratuit pe o cameră mică dacă mai multe canale sunt deschise simultan. Rămâi la un singur canal per conexiune și alege un MTU care se potrivește dimensiunii mesajului așteptat; valoarea implicită de un singur L2CAPChannel per DeviceConnection este suficientă pentru majoritatea aplicațiilor.
L2CAP este supapa de siguranță a BLE. GATT este ceea ce aproape orice aplicație folosește mai întâi, iar restul exemplelor central / periferic din această secțiune rămân la GATT. API-ul de tip canal este răspunsul atunci când o aplicație depășește modelul cheie/valoare.