BLE под микроскопом. (ATTы GATTы...) Продолжение
В этой статье мы будем разбираться с таблицей атрибутов. С её структурой и наполнением. Таблица размещается в глубинах стека и каждая её строка — это отдельный атрибут. Он может быть различным, в зависимости от того, что он описывает. Раньше я уже бегло описывал её структуру. Сегодня мы будем более подробно с ней знакомиться. Итак, каждая строка таблицы атрибутов имеет следующую структуру.
1. Указатель атрибута (Attribute Handle) — это индекс таблицы, соответствующий атрибуту
2. Тип атрибута (Attribute Type) — это UUID, который описывает его тип
3. Значение атрибута (Attribute Value) — это данные, индексируемые указателем атрибута
4. Разрешения атрибутов (Attribute Permissions) — это часть атрибута, разрешения, которые не могут быть прочитаны или записаны с использованием протокола атрибутов
Какие же объекты могут находится в этой таблице? Спецификация их строго регламентирует. Давайте же перейдем к их описанию. Опираться мы будем на самую раннюю спецификацию для BLE Core_V4.0
Определение сервисов (SERVICE DEFINITION)
В начале любого сервиса идет его объявление. Признаком того, что это именно объявление сервиса, является значение 0х2800 или 0х2801 в столбце «Attribute Type». На рисунке мы видим, что сервис может быть как первичный, так и вторичный. На мой взгляд, это некоторое излишество. Мы можем все свои сервисы делать первичными. Разница между ними скорее идеологическая. Первичный сервис инкапсулирует то, что делает устройство. Вторичный сервис помогает основной службе выполнять свои функции. Я лично не встречал ситуации, в которых это имело хоть какое-то значение.
В столбце «Attribute Value» должен находится UUID этого сервиса. Он может быть как 16-ти битный, так и 128-ми битный. Напомню, что 16-ти битные сервисы зарезервированы Bluetooth Special Interest Group (Bluetooth SIG) для наиболее распространенных устройств. Например, сервису сердечного ритма присвоено значение UUID 0x180D. На сайте Bluetooth SIG можно найти много различных документов, таких как спецификации, профили и сервисы. Все зарезервированные 16-ти битные значения можно посмотреть в документе Assigned Numbers.
Но вернемся к объявлению сервиса. Если UUID сервиса 128-ми битный, тогда он может быть любым. Например, вы можете придумать его сами или воспользоваться специальным генератором. Ну и наконец, столбец с разрешениями атрибутов «Attribute Permissions». Для сервисов разрешения атрибута должны быть доступны только для чтения и не должны требовать аутентификации или авторизации.
Определение включений (INCLUDE DEFINITION)
Этим атрибутом производится включение ранее объявленного атрибута. Он должен идти сразу за объявлением сервиса. Главное условие – нельзя включать атрибуты, которые ссылаются друг на друга. В общем мне это напоминает косвенную адресацию. Я в своей практике никогда ранее не встречал атрибута включения. Возможно, он создавался для серверов, имеющих большое количество функций и большую таблицу атрибутов. Мы чаще всего будем использовать устройства с несколькими сервисами, поэтому такие сложные конструкции с включением, вряд ли стоит использовать.
Определение характеристик (CHARACTERISTIC DEFINITION)
Следующей строчкой в таблице атрибутов, после объявления сервиса, чаще всего идет объявление характеристик. Признаком объявления характеристики является значение 0х2803 в столбце «Attribute Type». Характеристик у сервиса может быть несколько. Спецификация указывает на то, что 16-ти битные характеристики должны быть объединены в одну группу, а 128-ми битные в другую. Это скорее всего связано с экономией памяти устройств при выравнивании характеристик в ней. Следом за определением характеристики идет определение значения характеристики. Они тесно связаны и их проще рассматривать совместно. Это на мой взгляд самая запутанная часть таблицы атрибутов.
Дело в том, что каждая строчка в таблице атрибутов имеет всего четыре поля, а втиснуть в них надо много чего. В случае с сервисом, мы умещаемся в эти жесткие рамки. Но у характеристик данных больше. Вот и приходится как-то выкручиваться. На практике получается, что почти всё поле «Attribute Value» характеристики мы превращаем в отдельный атрибут. Который следует сразу за объявлением характеристики. Я, как смог, постарался нарисовать общую картинку взаимосвязей, а ниже разместил код реальной программы. Справа вверху показал, как всё это видится в эфире, из приложения nRF Connect.
Давайте разбираться. Начнем с кода программы. В таблице ble_ProfileAttrTbl[] мы видим три блока данных из которых получатся три записи в таблице атрибутов. Каждый блок содержит поле типов, разрешений, значений, а также поле handle. Чтобы не загромождать этот демонстрационный пример, я не стал приводить объявления некоторых массивов и структур. Необходимые для понимания данные я привел в качестве комментариев каждой строчки.
Итак приступаем. В самом верху таблицы задается сервис (primaryServiceUUID == 0х2800), имеющий разрешение на чтение (GATT_PERMIT_READ), и 128-ми битный UUID. По спецификации прочесть определение сервиса мы можем безо всякой идентификации и авторизации. Поля handle всех атрибутов всегда остаются пустыми. По мере того, как стек формирует внутри себя таблицу атрибутов, он самостоятельно заполняет эти поля. По значениям, лежащим в них, мы будем обращаться к сервису и характеристикам.
Далее задается характеристика (characterUUID == 0x2803). У неё так же разрешение только на чтение (GATT_PERMIT_READ), а в поле значений (one_ТxCharProps) стоит свойство записи без подтверждения (GATT_PROP_WRITE_NO_RSP). Это свойство мы будем видеть в приложении nRF Connect (картинка вверху справа) как WRITE NO RESPONSE. Остальные параметры характеристики находятся ниже, в следующем атрибуте - Characteristic Value.
Здесь в поле Attribute Type находится UUID самой характеристики, в данном случае 128-ми битный. В поле Attribute Permissions находится разрешение на запись в данную характеристику - GATT_PERMIT_WRITE. Ранее я уже описывал различие между разрешением и свойством характеристики. Кто подзабыл, почитайте здесь. Поле Attribute Handle, как и ранее, равен нулю. А в поле Attribute Value лежит значение характеристики. К примеру, число ударов сердца для характеристики Heart Rate Measurement (0x2A37). В поле Attribute Permissions как обычно лежит разрешение характеристики. У нас это разрешение записи.
Дескрипторы характеристики (Characteristic Descriptor)
В некоторых, довольно частых случаях, характеристики дополняют ещё одним или несколькими элементами – дескрипторами. Они используются для того, чтобы расширить характеристику дополнительным качеством. Дескрипторы помещаются сразу после объявление значений характеристик. Их может быть несколько. Давайте посмотрим на них.
Расширенные свойства характеристики (Characteristic Extended Properties)
Объявление расширенных свойств характеристики - это дескриптор, который определяет дополнительные свойства характеристики. Битовое поле расширенных свойств характеристики описывает дополнительные свойства, касающиеся того, как можно использовать значение характеристики или как можно получить доступ к дескрипторам характеристики. Я в своей практике не встречал этот дескриптор, поэтому описание его я делаю исключительно по спецификации. У него следующие битовые поля.
.
Пользовательское описание характеристики (Characteristic User Description)
Этот тип дескриптора очень полезен и часто встречается в практике. Он определяет строку UTF-8 переменного размера, представляющую собой пользовательское текстовое описание значения характеристики. Довольно часто мы не знаем что за характеристики содержит сервис, особенно если они 128-ми битные. Но если есть такой дескриптор, он помогает нам прояснить ситуацию. Например у всем известной iTag антипотеряшки практически каждая характеристики снабжена этим дескриптором. Я выделил на картинке данный дескриптор и дескриптор CCCD из раздела ниже. Выглядит это так.
Клиентская конфигурация характеристики (Client Characteristic Configuration)
Этот дескриптор, наверное самый часто используемый, его ещё называют CCCD (Client Characteristic Configuration Descriptor). Он позволяет клиенту настраивать поведение сервера, а именно - включать нотификацию или индикацию. Разницу между ними я уже описывал в предыдущей статье. Иногда проблема системы бывает в том, что сервер, (например датчик сердечного ритма), работает только по запросам со стороны клиента (чаще всего смартфона). Что бы изменить такое поведение, клиент может включить на сервере нотификацию и просто ожидать от него сообщений об изменении состояния. Это гораздо удобнее, чем каждый раз дергать датчик на наличие у него новых данных.
Что бы включить нотификацию, надо записать в дескриптор значение 0х0001 или 0х0002 для индикации. Для отключения записываем 0х0000 и всё будет отключено. Разумеется характеристика должна поддерживать нотификацию. Однако тут есть важная деталь. Каждый клиент имеет свой собственный экземпляр конфигурации. Т.е., используя этот дескриптор, клиент включает/отключает нотификацию только для себя. Если у сервера имеются другие клиенты, для них поведение сервера никак не изменится. Получается, что сервер, в общем случае, должен хранить в своей памяти всех клиентов и их настройки.
Серверная конфигурация характеристики (Server Characteristic Configuration)
В отличии от предыдущего дескриптора, данный позволяет изменить работу сервера сразу для всех клиентов. Существует единый экземпляр конфигурации характеристик сервера для всех. Вот его поля конфигурации.
Формат представления характеристики (Characteristic Presentation Format)
Его назначение понятно из описания. Он задает некоторую форму представления, того как будет видится характеристика для клиента. Я никогда ранее не встречал её, поэтому долго останавливаться на ней не буду. В спецификации подробно разбирается каждое её поле, но я ограничусь только их описанием.
Сводный формат характеристики (Characteristic Aggregate Format)
Этот дескриптор является расширение предыдущего, хотя может быть использован и самостоятельно. Если в определении характеристики существует более одного объявления формата представления характеристик, то также должно быть одно объявление сводного формата характеристик. Он должен включать в себя каждое объявление формата представления. В своей практике я вряд ли буду когда то его использовать.
Краткое описание типов атрибутов профиля GATT
.
Снифферы
Давайте теперь поговорим о средствах контроля. Я в своей первой статье упоминал о сниффере на основе донгла nRF51822. Приобрести его было сложно, а без таких приборов делать разработку BLE устройств весьма проблематично. На данный момент, ситуация значительно улучшилась. Можно пойти двумя путями. Первый - это приобрести у фирмы WCH её спектранализатор. На Алиэкспрессе можно купить как одноканальный, так и трехканальный прибор. Работают они под управлением программы WCH_BLEAnalyzer.
Второй путь - воспользоваться услугами нашего старого знакомого, фирмы NORDIC и её донглом на основе nRF52840. Описание как это сделать, можно прочитать здесь. Скачать софт можно на странице производителя. Мне нравится второй путь. Захват пакетов производится в программе Wireshark, которая очень удобна для разбора пакетов.
Анализ обмена данными в эфире
Для полноты картины, я хотел бы привести несколько картинок из файла обмена между контроллером WCH и смартфоном. Сам файл можно скачать здесь. Мне в своё время очень помог тщательный анализ пакетов в Wireshark-е.
Давайте посмотрим на эфирные пакеты. Вначале идут advertising пакеты и их достаточно много. Они имеют наименование ADV_IND. Иногда они перемежаются запросами SCAN_REQ с телефона , на которые устройство отвечает дополнительным пакетом SCAN_RSP с расширенными данными. Когда пользователь в приложении на телефоне нажимает кнопку Connect, телефон посылает пакет CONNECT_IND. Это последний пакет на advertising каналах. В Wireshark-е я остановился на нем и вывел в окно ниже его настройки. В нем мы видим поля, которые я уже описывал в предыдущей статье. Очень полезно разобраться с ними. Далее, исходя из этих настроек, телефон и BLE устройство начнут обмениваться пакетами уже на рабочих каналах с указанным интервалом.
Вначале мы видим интенсивный обмен между устройствами. Во время которого телефон запрашивает у гаджета всё что он умеет делать. К примеру, сначала телефон запускает процедуру обмена функциями LL_FEATURE_REQ, а гаджет на неё отвечает. Я привел её только в качестве примера и не буду досконально описывать все строки обмена. При достаточном усердии всё это вполне можно найти в спецификации. Там дается такая картинка на этот обмен.
Я приведу лишь самые интересные, на мой взгляд строки обмена данными. Например в строке 9724 телефон запрашивает GATT Primary Service Declaration из всего диапазона таблицы атрибутов, от 0х0001 до 0хffff.
А в строке 9729 получает ответ, что Generic Access Profile (0х1800) занимает в таблице атрибутов диапазон строк от 0х0001 до 0х0009. В то же время, Generic Attribute Profile (0х1801) занимает диапазон от 0х000a до 0х000d.
На запрос наличия характеристик из строчки 9730, мы получаем в строке 9733 ответ, что на строчке таблицы атрибутов номер 0х000e заявлена 128-битная характеристика. Длина её пока не определена. При последующих запросах атрибутов из таблицы это станет ясно.
В строке 9734 делается запрос GATT Include Declaration (0x2802), а далее в строке 9737 получаем ответ - Attribute Not Found. Т.е. никакого включения атрибута не имеем.
.
Заключение
Я постарался как мог, описать устройство таблицы атрибутов BLE стека. Тема эта обширная и в одной статье всё не опишешь. Но, на мой взгляд, получилось достаточно подробно. Теперь, опираясь на эту статью, можно переходить к описанию BLE контроллера CH582. Я уже начал её писать и в скором времени предоставлю вашему вниманию.
Печерских Владимир
Сотрудник Группы Компаний «Цезарь Сателлит»
Комментарии (6)
R2SAAF
21.05.2023 21:10Спасибо за статью. Читал с интересом.
Недавно на предприятии перешли с PICа на WCH, но на серию по проще. В целом нормальные контроллеры, хоть и аналоги STM.
Достать WCH не проблема, везут все.
pecherskih Автор
21.05.2023 21:10Я уже на треть написал статью про ch582. В ней я буду разбирать работу стека в режимах central b peripheral. Обобщу, так сказать опыт :-). В целом контроллеры мне то же нравятся. Гораздо лучше чем TeLink. Хотя и в них (точнее в стеке) не всё так гладко как хотелось бы. Но это лучшее, на мой взгляд, что сейчас можно использовать. Отладка за 180 рублей. https://aliexpress.ru/item/1005005496697691.html?sku_id=12000033308775338&spm=a2g2w.productlist.search_results.12.35e64aa6iOYFv9
foxyrus
Подскажите, есть ли в BLE функция сохранения ранее проверенных (discovered) устройств?
Реальный кейс: есть 6 светильников Tuya ble mesh один раз как то на телефоне, в Tuya smart, светильники появились, но не соединились, теперь видны только как BLE устройства в приложениях сканнерах их не видно в tuya/ Не помогло и покупка Tuya BLE хаба.
Сами устройства\телефон где то в кэше сохраняют устройства?
Тоже самое с BLE термометром Xiaomi, один раз добавился в Home Assistant, потом я вынул Bluetooth свисток и теперь чтобы я не делал, устройство больше не видно в HA. В тож время PC через браузер Chrome продолжает показывать температуру и влажность...
pecherskih Автор
Добрый день, Я не работал с mesh устройствами, поэтому на счет светильников не подскажу. Что касается Xiaomi, то дело я думаю вот в чем. В Блютусе есть такая функция как bonding. Она связывает два устройства. Разрешение на эту операцию дает пользователь или приложение. После чего устройства генерят общий секретный ключ (по моему методом диффи хеллмана) и после этого уже близко дружат как голубая пара :-) )) Если сканер видит своё забонденное устройство, тогда уже без всякой команды со стороны человека, он присоединяет к себе этот гаджет, проверяя предварительно секретный ключик Я думаю это и происходит в вашем устройстве. Надо найти способ сбросить эту порочную связь :-)
Indemsys
Если речь про PC с Windows, то там бондинг привязан к BLE адаптеру. Чтобы бондинг сработал адаптер должен быть один и тот же . Через другой адаптер с другим MAC автоматическое соединение не произойдёт. Это может касаться и устройств, они могут просто отказывать в повторной привязке с другого адаптера.
Отвязаться тоже можно только с того адаптера, с которого была сделана привязка (pairing).
andreykorol
Я правильно понимаю, что утеряв адаптер привязаться новым адаптером у тому же устройству не выйдет?