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 yksi await sovelluksen 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 synkronisesti True, 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.