11.14. Підсумок

Ви пройшли шлях від Bluetooth Low Energy на рівні радіозв’язку до Python API, що використовується для його керування:

  • Мотивація – BLE є відповіддю, коли камера хоче спілкуватись з чимось поблизу без будь-якої інфраструктури між ними. Телефон у тій же кімнаті, носимий пристрій на зап’ясті, маяк на стіні. Короткий радіус дії, не потрібно підключатись до мережі, майже без споживання енергії.

  • Радіозв’язок – 2,4 ГГц, 40 каналів: три для реклами, 37 для даних з’єднання, з псевдовипадковою послідовністю перемикання і адаптивним уникненням зашумлених каналів. Короткі пакети, радіомодулі, що здебільшого сплять.

  • Рівень каналу зв’язку – формування пакетів, адресація, планування з’єднань, повторна передача та шифрування на рівні каналу зв’язку. Нічого з цього не налаштовується з Python; все це проявляється через параметри з’єднання і MTU.

  • Generic Access Profile (GAP) – виявлення і управління з’єднаннями. Чотири ролі: peripheral і broadcaster (рекламують себе), central і observer (сканують). Корисні навантаження реклами несуть локальне ім’я, UUID сервісів, зовнішній вигляд і специфічні для виробника дані – 31 байт плюс необов’язкові 31 байти відповіді на сканування. Інтервал з’єднання, латентність периферійного пристрою і таймаут нагляду визначають, як відчувається відкрите з’єднання.

  • Generic Attribute Profile (GATT) – дерево сервісів, кожен з яких містить характеристики, кожна з яких може містити дескриптори, ідентифіковані за UUID (16-бітними для стандартів Bluetooth SIG, 128-бітними для власних). П’ять операцій: read і write (витяг, ініційований клієнтом), notify і indicate (push, ініційований сервером, з підпискою через Client Characteristic Configuration Descriptor). Розмір корисного навантаження обмежений узгодженим MTU.

  • Python APIaioble перетворює кожен шаблон BLE на корутину asyncio. Периферійний пристрій – це aioble.advertise(), що циклічно обробляє з’єднання, з об’єктами Service / Characteristic, побудованими один раз і зафіксованими за допомогою aioble.register_services(). Центральний пристрій – це aioble.scan() для знаходження вузла, connect() для відкриття каналу зв’язку, service() і characteristic() для обходу віддаленого дерева GATT, а потім read() / write() / subscribe() / notified() для фактичних даних. Відключення проявляються як aioble.DeviceDisconnectedError всередині корутини, що очікувала.

  • Канали L2CAP – аварійний вихід для масових потоків байтів, що не відповідають моделі ключ/значення GATT. aioble.DeviceConnection.l2cap_accept() / l2cap_connect() відкривають канал для кожного застосунку поверх GAP-з’єднання, з контрольованою кредитами передачею/прийомом і більшим MTU, ніж може забезпечити GATT.

  • Сполучення і шифрування – BLE-канали зв’язку є публічними за замовчуванням. aioble.DeviceConnection.pair() ініціює обмін ключами, що генерує зашифрований канал зв’язку; bond=True (за замовчуванням) зберігає ключі, щоб наступні з’єднання пропустили процедуру погодження. Без mitm=True і доступної можливості вводу-виводу шифрування захищає від пасивних підслуховувачів, але не від активного перенаправлення під час початкового сполучення.

Цього достатньо для написання застосунків для камери, що публікують стан як периферійний пристрій, зчитують дані датчиків як центральний пристрій, передають поточні значення на телефон через BLE, захищають канал зв’язку за допомогою кроку сполучення і зв’язування, а також – для рідкісного випадку масової передачі – переходять з GATT до L2CAP-каналу.

11.14.1. Усунення несправностей

Збої BLE здебільшого виникають через невідповідність очікувань двох сторін, і інспектор на стороні телефону є найшвидшим способом виявити, чиї очікування помилкові. Стандартним інструментом є nRF Connect for Mobile (Nordic Semiconductor, безкоштовний для Android та iOS): він сканує, підключається, обходить базу даних GATT, читає і записує характеристики, підписується на сповіщення – тому поведінку камери можна тестувати в ізоляції, не написавши супутній застосунок.

Типові причини збоїв:

  • «Мій пристрій з’являється у сканері, але не підключається.» Найчастіше рекламний пакет має connectable=False (режим broadcaster), або попереднє з’єднання ще відкрите і камера вже пройшла aioble.advertise(). Додайте оператори print навколо виклику advertise для підтвердження.

  • «exchange_mtu(512) виконався, але мої сповіщення все одно обмежені 20 байтами.» Узгоджений MTU – це min(local, peer) – телефон або бібліотека центрального пристрою могла не запросити більший MTU зі свого боку, тому з’єднання залишається на 23. Перевірте mtu після повернення з exchange_mtu(). Також зверніть увагу, що exchange_mtu() працює лише один раз на з’єднання; викликайте його перед першою великою операцією.

  • «Сполучення не вдається з загальною помилкою.» Дві типові причини: невідповідність можливостей вводу-виводу (запит mitm=True на камері, що оголошує io=3 / немає вводу немає виводу – немає способу підтвердити числовий код, тому процедура сполучення переривається), і суттєво неправильний час на камері, коли це вимагається вузлом. Встановіть час за допомогою ntptime.settime() перед першою спробою сполучення.

  • «Сповіщення ніколи не надходять до клієнта.» Дві речі для перевірки, по порядку: (a) чи оголошено характеристику з notify=True? – біт властивості має бути встановлений на стороні сервера; (b) чи викликав клієнт subscribe()? – без запису Client Characteristic Configuration Descriptor (CCCD) сервер вважає, що жоден клієнт не хоче сповіщень, і мовчки відкидає їх.

  • «Рекламоване ім’я усічене або відсутнє.» Рекламний пакет має 31 байт, і поля flags + service-UUID + appearance кожне забирає байти з верхньої частини. Довге name= плюс кілька UUID сервісів переповнює пакет. Або скоротіть ім’я, або використовуйте активне сканування, щоб відповідь на сканування (ще 31 байт) несла переповнення. nRF Connect відображає обидві половини окремо, що робить розподіл очевидним.

  • «L2CAP connect негайно генерує виключення.» Зазвичай це невідповідність PSM – обидві сторони мають погодити один і той самий номер PSM позасмуговим способом. L2CAPConnectionError несе код статусу Bluetooth як перший аргумент; статус 2 («PSM not supported») є характерною ознакою.

  • «Зв’язані з’єднання все ще ініціюють повну процедуру сполучення при кожному повторному підключенні.» aioble.security.load_secrets() не було викликано при запуску. Без цього збережені ключі знаходяться у флеш-пам’яті, але ніколи не завантажуються в пам’ять, тому ідентифікатор вузла невідомий і сполучення запускається з нуля кожного разу.

Якщо все інше не допомагає, низькорівневий модуль bluetooth надає зворотний виклик IRQ, що спрацьовує для кожної базової події; підписка на нього на короткий час і виведення подій є еквівалентом трасування Wireshark для сторони камери.

11.14.2. Використання цього довідника пізніше

Розглядайте розділи про Bluetooth як довідковий матеріал; звертатись до точного формату рекламного корисного навантаження периферійного пристрою або потоку сканування і підписки центрального пристрою – саме для цього вони призначені. Довідкові сторінки aioble — Async BLE і bluetooth — низькорівневий Bluetooth перераховують кожен метод, прапорець і константу в одному місці, коли питання полягає лише у «якому точному імені цього виклику».