11.14. Заключение¶
Вы прошли путь по Bluetooth Low Energy от радиоканала до Python API, используемого для управления им:
Мотивация – BLE является ответом, когда камера хочет связаться с чем-то поблизости без какой-либо инфраструктуры между ними. Телефон в той же комнате, носимое устройство на запястье, маяк на стене. Малая дальность, никакой сети для подключения, почти нулевое энергопотребление.
Радиоканал – 2,4 ГГц, 40 каналов: три для оповещения, 37 для данных соединения, перескок по псевдослучайной последовательности с адаптивным избеганием зашумлённых каналов. Короткие пакеты, большую часть времени спящие радиомодули.
Канальный уровень – формирование пакетов, адресация, планирование соединений, повторная передача и шифрование канального уровня. Ничего из этого не настраивается из Python; всё это проявляется в параметрах соединения и MTU.
Generic Access Profile (GAP) – обнаружение и управление соединениями. Четыре роли: периферийное устройство и широковещатель (оповещают), центральное устройство и наблюдатель (сканируют). Полезная нагрузка оповещения несёт локальное имя, UUID сервисов, внешний вид и данные, специфичные для производителя – 31 байт плюс необязательный 31-байтовый ответ на сканирование. Интервал соединения, задержка периферийного устройства и тайм-аут наблюдения определяют, каким воспринимается открытое соединение.
Generic Attribute Profile (GATT) – дерево сервисов, каждый из которых содержит характеристики, каждая из которых при необходимости содержит дескрипторы, идентифицируемые по UUID (16-битные для стандартов Bluetooth-SIG, 128-битные для пользовательских). Пять операций: чтение и запись (вытягивание, инициируемое клиентом), уведомление и индикация (выталкивание, инициируемое сервером, с подпиской через Client Characteristic Configuration Descriptor). Размер полезной нагрузки ограничен согласованным MTU.
Python API –
aiobleпревращает каждый шаблон 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(режим широковещателя), или предыдущее соединение всё ещё открыто, и камера уже прошла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 байт, а поля флагов + UUID сервиса + внешнего вида каждое отнимают байты сверху. Длинное
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 — асинхронный BLE и bluetooth — низкоуровневый Bluetooth перечисляют каждый метод, флаг и константу в одном месте, когда вопрос просто «как точно называется этот вызов».