11.11. Канали L2CAP¶
GATT — це модель «ключ/значення». Операції, які вона надає (читання, запис, сповіщення, індикація), переміщують одне коротке значення за раз, а найбільший одиничний пакет, який вони можуть переносити, визначається узгодженим значенням MTU — у кращому разі кілька сотень байт. Це добре працює для показань датчиків, регістрів команд і статусних прапорів. Але на кілобайтах чи мегабайтах усе розсипається: розбиття довгої плями на сотні малих записів коштує зайвих обмінів, а радіоканал значно швидший за це.
Для потоків об’ємних даних — захопленого кадру, який камера передає на телефон, образу оновлення прошивки по повітрю, пакетного експорту вимірювань — BLE пропонує альтернативний шлях: Logical Link Control and Adaptation Protocol, L2CAP. L2CAP знаходиться між канальним рівнем і GATT та дозволяє застосунку зайняти власний з’єднано-орієнтований канал поверх того самого радіозв’язку. Канал є байтовим шляхом з управлінням потоком на основі кредитів, набагато більшим MTU на пакет і без GATT-фреймінгу посередині.
11.11.1. Коли використовувати L2CAP¶
Канали L2CAP — правильний інструмент, коли:
Обсяг передачі перевищує кілька сотень байт.
Обидва кінці знають, що буде використовуватися канал L2CAP (він не відображається в рекламному пакеті; клієнт має знати мультиплексор протоколу/сервісу каналу, або PSM, заздалегідь).
Застосунок готовий відмовитися від зручностей GATT: немає вбудованої адресації за UUID, немає виявлення клієнтами через стандартні застосунки, немає сповіщень.
Найпоширенішим випадком у застосунках на основі aioble є передача двійкової плями між двома програмними компонентами, які обидва знають про конвенцію PSM — власний протокол камера-до-телефону, пара камер openmv, що спілкуються між собою, внутрішній шлях оновлення мікропрограми під GATT-сервісом периферійного пристрою.
Для всього іншого залишайтеся на GATT. Короткий статус, регістр керування, показання датчика — все це належить характеристиці.
11.11.2. Встановлення каналу¶
L2CAP працює поверх наявного aioble.DeviceConnection, тому процес виявлення/реклами/підключення на стороні GAP точно такий самий, як і для GATT. Як тільки обидві сторони мають підключення, одна сторона слухає на PSM, інша — підключається до нього.
PSM — це просто мале ціле число. Bluetooth SIG резервує нижній діапазон для стандартизованого використання (0x0001-0x007F); для каналів конкретного застосунку використовуйте число з динамічного діапазону (0x0080-0x00FF для фіксованих PSM, 0x0040 і далі зазвичай вільні для власного використання). Обидві сторони мають заздалегідь погодити значення.
MTU на каналі L2CAP — це найбільший одиничний SDU (Service Data Unit), який кожна зі сторін доставить за один send() — не MTU BLE-зв’язку. Aioble автоматично фрагментує більші пакети. BLE-хост камери обмежує MTU L2CAP до 1017 байт; 512 — розумний за замовчуванням, що залишає місце з обох боків без зайвих витрат RAM.
На стороні слухача (наприклад, камера як периферійний пристрій):
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()
На стороні з’єднувача (наприклад, телефон або центральний пристрій):
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() блокується до підключення партнера (або спрацьовування timeout_ms); l2cap_connect() блокується до прийняття слухачем (або невдачі). Обидва повертають aioble.L2CAPChannel — сам по собі асинхронний контекстний менеджер, який закриває канал при виході.
11.11.3. Надсилання та отримання¶
Дві основні операції на каналі: send() (записує байти до партнера) і recvinto() (читає в попередньо виділений буфер). Обидві є корутинами.
send()фрагментує буфер на частини розміром MTU і очікує між ними кредити управління потоком канального рівня. Тривале відправлення — це одинawaitз точки зору застосунку; внутрішньо воно може ставити в чергу багато пакетів і призупинятися, коли кредити прийому партнера вичерпуються.recvinto()заповнює переданий буфер тим, що є (до MTU каналу), і повертає кількість байт. Чекає, якщо нічого не доступно.available()синхронно повертаєTrue, якщо є буферизовані дані — корисно для опитування без призупинення.flush()чекає, поки будь-яке незавершене надсилання не буде повністю передано до контролера.
Канали L2CAP подібні до потоків у тому сенсі, що байти надходять по порядку і без втрат, але межі одного send зберігаються — кожен SDU виходить з одного recvinto. Це відрізняється від TCP, де межі одного send() можуть розмиватися в кількох викликах recv().
11.11.4. Обробка від’єднання¶
Канал зникає за трьох умов: будь-яка сторона викликає disconnect(), базове GAP-з’єднання обривається, або надходить відключення на рівні L2CAP. Активні операції генерують виняток aioble.L2CAPDisconnectedError. Як і на стороні GATT, це проявляється як виняток у корутині, яка очікувала, і блок async with channel завершується чисто.
Якщо канал стає недосяжним через відключення на рівні GAP, застосунок повертається до реклами або сканування так само, як при відключенні GATT.
11.11.5. Витрати пам’яті¶
Більші MTU і довші черги використовують більше RAM з обох боків. MTU 512 байт плюс буфер прийому на канал займає близько 1 КБ на канал — це не безкоштовно на маленькій камері, якщо одночасно відкрито кілька каналів. Тримайтеся одного каналу на з’єднання і вибирайте MTU, який відповідає очікуваному розміру повідомлення; типово одного L2CAPChannel на DeviceConnection достатньо для більшості застосунків.
L2CAP — це запобіжний клапан BLE. GATT — це те, до чого майже кожен застосунок звертається першим, і решта прикладів центрального/периферійного пристрою в цьому розділі залишаються на GATT. API на основі каналів — це відповідь, коли застосунок переростає модель «ключ/значення».