11.11. Kanály L2CAP¶
GATT je model klíč/hodnota. Operace, které nabízí (read, write, notify, indicate), přenášejí najednou jednu krátkou hodnotu a největší jednotlivý užitečný obsah, který mohou nést, je to, co umožňuje vyjednané MTU – v nejlepším případě několik stovek bajtů. To funguje dobře pro hodnoty ze senzorů, příkazové registry a stavové příznaky. Selhává to u kilobajtů či megabajtů: rozdělení dlouhého blobu na stovky malých zápisů stojí přenosy tam a zpět, oproti nimž je rádio mnohem rychlejší.
Pro toky hromadných dat – zachycený snímek, který kamera streamuje do telefonu, obraz pro aktualizaci přes vzduch, dávkový export měření – nabízí BLE alternativní cestu: Logical Link Control and Adaptation Protocol, L2CAP. L2CAP se nachází mezi linkovou vrstvou a GATT a umožňuje aplikaci nárokovat si vlastní kanál orientovaný na připojení nad stejným rádiovým spojem. Kanál je bajtová cesta s řízením toku pomocí kreditů, s mnohem větším MTU na paket a bez GATT rámcování uprostřed.
11.11.1. Kdy použít L2CAP¶
Kanály L2CAP jsou tím správným nástrojem, když:
Přenos je větší než několik stovek bajtů.
Oba konce vědí, že se použije kanál L2CAP (není to vystaveno v inzertním obsahu; klient musí znát číslo protocol/service multiplexer neboli PSM kanálu mimo pásmo).
Aplikace je ochotna vzdát se výhod GATT: žádná vestavěná adresovatelnost přes UUID, žádná zjistitelnost klientem přes standardní aplikace, žádná oznámení.
Nejběžnějším případem v aplikacích založených na aioble je přesun binárního blobu mezi dvěma programy, které oba znají konvenci PSM – vlastní protokol kamera-telefon, dvojice kamer openmv komunikujících mezi sebou, interní cesta pro aktualizaci firmware pod GATT službou periferie.
Pro vše ostatní zůstaňte u GATT. Krátký stav, řídicí registr, hodnota ze senzoru – to vše patří do charakteristiky.
11.11.2. Navázání kanálu¶
L2CAP běží nad existujícím aioble.DeviceConnection, takže tok zjišťování / inzerce / připojování na straně GAP je přesně stejný jako u GATT. Jakmile obě strany drží připojení, jedna strana naslouchá na PSM a druhá se k ní připojuje.
PSM je jen malé celé číslo. Bluetooth SIG rezervuje spodní část rozsahu pro standardizované použití (0x0001-0x007F); pro kanály specifické pro aplikaci použijte číslo z dynamického rozsahu (0x0080-0x00FF pro pevná PSM, 0x0040 a výše obvykle volné pro vlastní použití). Obě strany se musí na hodnotě předem dohodnout.
MTU na kanálu L2CAP je největší jednotlivá SDU (Service Data Unit), kterou kterákoli strana doručí v jednom send() – nikoli MTU BLE spoje. Aioble větší užitečné obsahy automaticky fragmentuje. BLE host kamery omezuje MTU L2CAP na 1017 bajtů; 512 je rozumná výchozí hodnota, která ponechává prostor na obou stranách bez spalování RAM.
Na straně naslouchajícího (např. kamera jako periferie):
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()
Na straně připojujícího (např. telefon nebo centrální zařízení):
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() blokuje, dokud se protějšek nepřipojí (nebo nevyprší timeout_ms); l2cap_connect() blokuje, dokud naslouchající nepřijme (nebo neselže). Obě vracejí aioble.L2CAPChannel – sám o sobě asynchronní kontextový správce, který kanál při ukončení zavře.
11.11.3. Odesílání a příjem¶
Dvě hlavní operace na kanálu jsou send() (zapisuje bajty protějšku) a recvinto() (čte do předem alokovaného bufferu). Obě jsou korutiny.
send()fragmentuje buffer na části velikosti MTU a mezi nimi čeká na kredity řízení toku na linkové vrstvě. Dlouhé odeslání je z pohledu aplikace jedinéawait; interně může zařadit do fronty mnoho paketů a pozastavit se pokaždé, když protějšku dojdou přijímací kredity.recvinto()naplní předaný buffer tím, co je k dispozici (až do MTU kanálu), a vrátí počet bajtů. Čeká, pokud není nic k dispozici.available()vrací synchronněTrue, pokud jsou připravena bufferovaná data – užitečné pro dotazování bez pozastavení.flush()čeká, dokud nebude jakékoli nevyřízené odeslání plně přeneseno do řadiče.
Kanály L2CAP jsou podobné proudu v tom smyslu, že bajty přicházejí v pořadí a bez ztrát, ale hranice jediného send jsou zachovány – každá SDU vychází z jediného recvinto. To je na rozdíl od TCP, kde se hranice jednoho send() mohou rozetřít napříč více voláními recv().
11.11.4. Zpracování odpojení¶
Kanál zaniká za tří podmínek: kterákoli strana zavolá disconnect(), podkladové GAP připojení vypadne, nebo dorazí odpojení na úrovni L2CAP. Aktivní operace vyvolají aioble.L2CAPDisconnectedError. Stejně jako na straně GATT se to projeví jako výjimka v korutině, která čekala, a blok async with channel se čistě ukončí.
Pokud se kanál stane nedosažitelným kvůli odpojení na úrovni GAP, aplikace se vrací zpět k inzerci nebo skenování stejným způsobem, jako by to udělala u odpojení GATT.
11.11.5. Paměťové náklady¶
Větší MTU a delší fronty využívají více RAM na obou stranách. MTU 512 bajtů plus přijímací buffer pro každý kanál představuje zhruba 1 KB na kanál – na malé kameře to není zadarmo, pokud je otevřeno několik kanálů najednou. Držte se jednoho kanálu na připojení a zvolte MTU odpovídající očekávané velikosti zpráv; výchozí jeden L2CAPChannel na DeviceConnection postačí pro většinu aplikací.
L2CAP je pojistný ventil BLE. GATT je to, po čem téměř každá aplikace sáhne jako první, a zbytek příkladů centrálního a periferního zařízení v této sekci se drží GATT. API ve stylu kanálů je odpovědí, když aplikace přeroste model klíč/hodnota.