11.11. L2CAP-kanaler¶
GATT är en nyckel/värde-modell. Operationerna den erbjuder (read, write, notify, indicate) flyttar ett kort värde åt gången, och den största enskilda nyttolasten de kan bära är vad än det förhandlade MTU:t tillåter – några hundra byte i bästa fall. Det fungerar bra för sensoravläsningar, kommandoregister och statusflaggor. Det faller samman vid kilobyte eller megabyte: att dela upp en lång blob i hundratals små skrivningar kostar tur-och-retur-resor som radion är mycket snabbare än.
För massdataflöden – en infångad bildruta som kameran strömmar till en telefon, en uppdateringsavbildning över luften, en buntad export av mätningar – erbjuder BLE en alternativ väg: Logical Link Control and Adaptation Protocol, L2CAP. L2CAP ligger mellan länklagret och GATT och låter en applikation göra anspråk på sin egen anslutningsorienterade kanal ovanpå samma radiolänk. Kanalen är en kreditflödesstyrd byteväg med ett mycket större MTU per paket och ingen GATT-inramning däremellan.
11.11.1. När man ska använda L2CAP¶
L2CAP-kanaler är rätt verktyg när:
Överföringen är mer än några hundra byte.
De två ändarna båda vet att en L2CAP-kanal kommer att användas (den exponeras inte i annonseringsnyttolasten; klienten måste känna till kanalens protocol/service multiplexer-, eller PSM-, nummer utanför bandet).
Applikationen är villig att ge upp GATT-bekvämligheterna: ingen inbyggd adresserbarhet via UUID, ingen klientupptäckbarhet via standardappar, inga notifieringar.
Det vanligaste fallet i aioble-baserade applikationer är att flytta en binär blob mellan två mjukvarudelar som båda känner till PSM-konventionen – ett anpassat protokoll mellan kamera och telefon, ett par openmv-kameror som pratar med varandra, en intern väg för fast programvara-uppdatering under en peripherals GATT-tjänst.
För allt annat, stanna på GATT. En kort status, ett styrregister, en sensoravläsning – allt sådant hör hemma i en egenskap.
11.11.2. Upprätta en kanal¶
L2CAP körs ovanpå en befintlig aioble.DeviceConnection, så GAP-sidans flöde för upptäckt / annonsering / anslutning är exakt detsamma som för GATT. När båda sidor håller en anslutning lyssnar den ena sidan på en PSM och den andra sidan ansluter till den.
PSM:et är bara ett litet heltal. Bluetooth SIG reserverar botten av intervallet för standardiserad användning (0x0001-0x007F); för applikationsspecifika kanaler använd ett nummer från det dynamiska intervallet (0x0080-0x00FF för fasta PSM:er, 0x0040 och uppåt är vanligtvis fria för anpassad användning). Båda sidor måste komma överens om värdet i förväg.
MTU:t på en L2CAP-kanal är den största enskilda SDU (Service Data Unit) som någon sida levererar i en enda send() – inte BLE-länkens MTU. Aioble fragmenterar större nyttolaster automatiskt. Kamerans BLE-värd begränsar L2CAP-MTU till 1017 byte; 512 är ett rimligt standardvärde som lämnar utrymme på båda sidor utan att förbruka RAM.
På lyssnarsidan (t.ex. kameran som peripheral):
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()
På anslutarsidan (t.ex. en telefon eller 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() blockerar tills motparten ansluter (eller timeout_ms löser ut); l2cap_connect() blockerar tills lyssnaren accepterar (eller misslyckas). Båda returnerar en aioble.L2CAPChannel – själv en asynkron kontexthanterare som stänger kanalen vid utgång.
11.11.3. Sända och ta emot¶
De två huvudoperationerna på en kanal är send() (skriver byte till motparten) och recvinto() (läser in i en förallokerad buffert). Båda är coroutiner.
send()fragmenterar bufferten i MTU-stora delar och inväntar länklagrets flödesstyrningskrediter mellan dem. En lång sändning är ettawaitur applikationens perspektiv; internt kan den köa många paket och pausa varje gång motpartens mottagningskrediter tar slut.recvinto()fyller den medskickade bufferten med vad som än finns tillgängligt (upp till kanalens MTU) och returnerar antalet byte. Inväntar om inget finns tillgängligt.available()returnerarTruesynkront om det finns buffrad data redo – användbart för polling utan att avbryta.flush()inväntar tills eventuell utestående sändning har överförts fullständigt till styrenheten.
L2CAP-kanaler är strömlika i den meningen att byten anländer i ordning och utan förlust, men gränserna för en enskild send bevaras – varje SDU kommer ut ur en enda recvinto. Det är olikt TCP, där gränserna för ett send() kan smetas ut över flera recv()-anrop.
11.11.4. Hantering av frånkoppling¶
Kanalen försvinner vid tre förhållanden: någon sida anropar disconnect(), den underliggande GAP-anslutningen tappas, eller L2CAP-nivåns frånkoppling anländer. Aktiva operationer kastar aioble.L2CAPDisconnectedError. Precis som på GATT-sidan dyker detta upp som ett undantag i den coroutine som väntade, och async with channel-blocket avslutas snyggt.
Om en kanal blir oåtkomlig genom en GAP-nivå-frånkoppling loopar applikationen tillbaka till annonsering eller skanning på samma sätt som den skulle göra för en GATT-frånkoppling.
11.11.5. Minneskostnad¶
Större MTU:er och längre köer använder mer RAM på båda sidor. Ett 512-byte MTU plus en mottagningsbuffert per kanal är runt 1 KB per kanal – inte gratis på en liten kamera om flera kanaler är öppna samtidigt. Håll dig till en kanal per anslutning och välj ett MTU som matchar den förväntade meddelandestorleken; standarden på en L2CAPChannel per DeviceConnection räcker för de flesta applikationer.
L2CAP är BLE:s säkerhetsventil. GATT är det som nästan varje applikation först griper efter, och resten av det här avsnittets exempel för central / peripheral håller sig till GATT. Det kanalsmakande API:et är svaret när en applikation växer ur nyckel/värde-modellen.