11.11. L2CAP-kanavat#
GATT on avain/arvo-malli. Sen tarjoamat operaatiot (read, write, notify, indicate) siirtävät yhden lyhyen arvon kerrallaan, ja suurin yksittäinen hyötykuorma, jonka ne voivat kantaa, on neuvotellun MTU:n salliman verran – enimmillään muutama sata tavua. Tämä toimii hyvin sensorilukemille, komentorekistereille ja tilalipuille. Se pettää kilo- tai megatavuilla: pitkän blobin pilkkominen sadoiksi pieniksi kirjoituksiksi maksaa edestakaisia matkoja, joita radio on paljon nopeampi.
Massadatavirroille – kameran puhelimelle suoratoistama otettu kehys, langaton päivityskuva, mittausten eräajettu vienti – BLE tarjoaa vaihtoehtoisen reitin: Logical Link Control and Adaptation Protocol, L2CAP. L2CAP sijaitsee linkkikerroksen ja GATT:n välissä ja antaa sovelluksen vaatia oman yhteyssuuntautuneen kanavansa saman radioyhteyden päälle. Kanava on luottovirtaohjattu tavupolku, jolla on paljon suurempi pakettikohtainen MTU eikä GATT-kehystystä välissä.
11.11.1. Milloin L2CAP:ia kannattaa käyttää#
L2CAP-kanavat ovat oikea työkalu, kun:
Siirto on suurempi kuin muutama sata tavua.
Molemmat päät tietävät, että L2CAP-kanavaa käytetään (sitä ei paljasteta mainoshyötykuormassa; asiakkaan on tiedettävä kanavan protocol/service multiplexer- eli PSM-numero kaistan ulkopuolelta).
Sovellus on valmis luopumaan GATT:n mukavuuksista: ei sisäänrakennettua osoitettavuutta UUID:llä, ei asiakkaan löydettävyyttä vakiosovellusten kautta, ei ilmoituksia.
Yleisin tapaus aioble-pohjaisissa sovelluksissa on binäärisen blobin siirtäminen kahden ohjelmiston välillä, jotka molemmat tuntevat PSM-sopimuksen – mukautettu kameran ja puhelimen välinen protokolla, kaksi keskenään keskustelevaa openmv-kameraa, sisäinen laiteohjelmiston päivitysreitti peripheraalin GATT-palvelun alla.
Kaikessa muussa pysy GATT:ssa. Lyhyt tila, ohjausrekisteri, sensorilukema – kaikki nuo kuuluvat ominaisuuteen.
11.11.2. Kanavan muodostaminen#
L2CAP ajetaan olemassa olevan aioble.DeviceConnection-yhteyden päällä, joten GAP-puolen löytö- / mainostus- / yhdistämisvirta on täsmälleen sama kuin GATT:lla. Kun molemmat puolet pitävät yhteyttä, toinen puoli kuuntelee PSM:ää ja toinen puoli yhdistyy siihen.
PSM on vain pieni kokonaisluku. Bluetooth SIG varaa alueen pohjan standardoituun käyttöön (0x0001-0x007F); sovelluskohtaisille kanaville käytä numeroa dynaamiselta alueelta (0x0080-0x00FF kiinteille PSM:ille, 0x0040 eteenpäin tyypillisesti vapaa mukautettuun käyttöön). Molempien puolten on sovittava arvosta etukäteen.
L2CAP-kanavan MTU on suurin yksittäinen SDU (Service Data Unit), jonka kumpikaan puoli toimittaa yhdessä send()-kutsussa – ei BLE-linkin MTU. Aioble pilkkoo suuremmat hyötykuormat automaattisesti. Kameran BLE-isäntä rajaa L2CAP-MTU:n 1017 tavuun; 512 on järkevä oletusarvo, joka jättää tilaa molemmille puolille polttamatta RAM-muistia.
Kuuntelijapuolella (esim. kamera peripheraalina):
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()
Yhdistäjäpuolella (esim. puhelin tai 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() estää, kunnes vertaislaite yhdistyy (tai timeout_ms laukeaa); l2cap_connect() estää, kunnes kuuntelija hyväksyy (tai epäonnistuu). Molemmat palauttavat aioble.L2CAPChannel-olion – joka on itse asynkroninen kontekstinhallinta, joka sulkee kanavan poistuttaessa.
11.11.3. Lähettäminen ja vastaanottaminen#
Kanavan kaksi pääoperaatiota ovat send() (kirjoittaa tavuja vertaislaitteelle) ja recvinto() (lukee esivaratuun puskuriin). Molemmat ovat korutiineja.
send()pilkkoo puskurin MTU:n kokoisiin paloihin ja odottaa linkkikerroksen virtaohjausluottoja niiden välissä. Pitkä lähetys on yksiawaitsovelluksen näkökulmasta; sisäisesti se voi jonottaa monta pakettia ja keskeytyä aina, kun vertaislaitteen vastaanottoluotot loppuvat.recvinto()täyttää annetun puskurin sillä, mitä on saatavilla (kanavan MTU:hun asti) ja palauttaa tavumäärän. Odottaa, jos mitään ei ole saatavilla.available()palauttaa synkronisestiTrue, jos puskuroitua dataa on valmiina – hyödyllistä kyselyyn ilman keskeyttämistä.flush()odottaa, kunnes mahdollinen kesken oleva lähetys on lähetetty kokonaan ohjaimelle.
L2CAP-kanavat ovat virtamaisia siinä mielessä, että tavut saapuvat järjestyksessä ja häviöttä, mutta yksittäisen send-kutsun rajat säilytetään – jokainen SDU tulee ulos yhdestä recvinto-kutsusta. Tämä eroaa TCP:stä, jossa yhden send()-kutsun rajat voivat levitä useaan recv()-kutsuun.
11.11.4. Yhteyden katkeamisen käsittely#
Kanava katoaa kolmessa tilanteessa: jompikumpi puoli kutsuu disconnect(), taustalla oleva GAP-yhteys katkeaa, tai L2CAP-tason katkaisu saapuu. Aktiiviset operaatiot nostavat aioble.L2CAPDisconnectedError-poikkeuksen. Kuten GATT-puolella, tämä ilmenee poikkeuksena siinä korutiinissa, joka oli odottamassa, ja async with channel -lohko poistuu siististi.
Jos kanavasta tulee tavoittamaton GAP-tason katkaisun kautta, sovellus palaa takaisin mainostamaan tai skannaamaan samalla tavalla kuin se tekisi GATT-katkaisun yhteydessä.
11.11.5. Muistikustannus#
Suuremmat MTU:t ja pidemmät jonot käyttävät enemmän RAM-muistia molemmilla puolilla. 512 tavun MTU plus kanavakohtainen vastaanottopuskuri on noin 1 KB kanavaa kohti – ei ilmaista pienellä kameralla, jos useita kanavia on auki yhtä aikaa. Pidättäydy yhdessä kanavassa yhteyttä kohti ja valitse MTU, joka vastaa odotettua viestin kokoa; oletus yksi L2CAPChannel DeviceConnection-yhteyttä kohti riittää useimpiin sovelluksiin.
L2CAP on BLE:n varoventtiili. GATT on se, mihin lähes jokainen sovellus tarttuu ensin, ja tämän osion muut central- / peripheral-esimerkit pysyvät GATT:ssa. Kanavatyylinen API on vastaus, kun sovellus kasvaa avain/arvo-mallia suuremmaksi.