BLE под микроскопом (ATTы GATTы...) Продолжение

72e69fcfd2403a3c4c667d1b8a3cdf8d.png

BLE под микроскопом. (ATTы GATTы…) Продолжение

В этой статье мы будем разбираться с таблицей атрибутов. С её структурой и наполнением. Таблица размещается в глубинах стека и каждая её строка — это отдельный атрибут. Он может быть различным, в зависимости от того, что он описывает.  Раньше я уже бегло описывал её структуру. Сегодня мы будем более подробно с ней знакомиться. Итак, каждая строка таблицы атрибутов имеет следующую структуру.

1efb20eab70f79d12bf5714cb14cdf03.png

1. Указатель атрибута (Attribute Handle) — это индекс таблицы, соответствующий атрибуту
2. Тип атрибута (Attribute Type) — это UUID, который описывает его тип
3. Значение атрибута (Attribute Value) — это данные, индексируемые указателем атрибута
4. Разрешения атрибутов (Attribute Permissions) — это часть атрибута, разрешения, которые не могут быть прочитаны или записаны с использованием протокола атрибутов

Какие же объекты могут находится в этой таблице? Спецификация их строго регламентирует. Давайте же перейдем к их описанию. Опираться мы будем на самую раннюю спецификацию для BLE Core_V4.0

Определение сервисов (SERVICE DEFINITION)

c5cb68975b04c028c876d016ab954181.png

В начале любого сервиса идет его объявление. Признаком того, что это именно объявление сервиса, является значение 0×2800 или 0×2801 в столбце «Attribute Type». На рисунке мы видим, что сервис может быть как первичный, так и вторичный. На мой взгляд, это некоторое излишество. Мы можем все свои сервисы делать первичными. Разница между ними скорее идеологическая. Первичный сервис инкапсулирует то, что делает устройство. Вторичный сервис помогает основной службе выполнять свои функции. Я лично не встречал ситуации, в которых это имело хоть какое-то значение.

В столбце «Attribute Value» должен находится UUID этого сервиса. Он может быть как 16-ти битный, так и 128-ми битный. Напомню, что 16-ти битные сервисы зарезервированы Bluetooth Special Interest Group (Bluetooth SIG) для наиболее распространенных устройств. Например, сервису сердечного ритма присвоено значение UUID 0×180D. На сайте Bluetooth SIGможно найти много различных документов, таких как спецификации, профили и сервисы. Все зарезервированные 16-ти битные значения можно посмотреть в документе Assigned Numbers.

Но вернемся к объявлению сервиса. Если UUID сервиса 128-ми битный, тогда он может быть любым. Например, вы можете придумать его сами или воспользоваться специальным генератором. Ну и наконец, столбец с разрешениями атрибутов «Attribute Permissions». Для сервисов разрешения атрибута должны быть доступны только для чтения и не должны требовать аутентификации или авторизации.

Определение включений (INCLUDE DEFINITION)

7985762cdf68cd958815ca1c4baeea3c.png

Этим атрибутом производится включение ранее объявленного атрибута. Он должен идти сразу за объявлением сервиса. Главное условие — нельзя включать атрибуты, которые ссылаются друг на друга. В общем мне это напоминает косвенную адресацию. Я в своей практике никогда ранее не встречал атрибута включения. Возможно, он создавался для серверов, имеющих большое количество функций и большую таблицу атрибутов. Мы чаще всего будем использовать устройства с несколькими сервисами, поэтому такие сложные конструкции с включением, вряд ли стоит использовать.

Определение характеристик (CHARACTERISTIC DEFINITION)

Следующей строчкой в таблице атрибутов, после объявления сервиса, чаще всего идет объявление характеристик. Признаком объявления характеристики является значение 0×2803 в столбце «Attribute Type». Характеристик у сервиса может быть несколько. Спецификация указывает на то, что 16-ти битные характеристики должны быть объединены в одну группу, а 128-ми битные в другую. Это скорее всего связано с экономией памяти устройств при выравнивании характеристик в ней. Следом за определением характеристики идет определение значения характеристики. Они тесно связаны и их проще рассматривать совместно. Это на мой взгляд самая запутанная часть таблицы атрибутов.

2fca4b050f50aa0c477378129a262601.png

Дело в том, что каждая строчка в таблице атрибутов имеет всего четыре поля, а втиснуть в них надо много чего. В случае с сервисом, мы умещаемся в эти жесткие рамки. Но у характеристик данных больше.  Вот и приходится как-то выкручиваться.  На практике получается, что почти всё поле «Attribute Value» характеристики мы превращаем в отдельный атрибут. Который следует сразу за объявлением характеристики. Я, как смог, постарался нарисовать общую картинку взаимосвязей, а ниже разместил код реальной программы. Справа вверху показал, как всё это видится в эфире, из приложения nRF Connect.

10455d04da9c2c9645cbc0256a7debc1.png

Давайте разбираться. Начнем с кода программы. В таблице ble_ProfileAttrTbl[] мы видим три блока данных из которых получатся три записи в таблице атрибутов. Каждый блок содержит поле типов, разрешений, значений, а также поле handle.  Чтобы не загромождать этот демонстрационный пример, я не стал приводить объявления некоторых массивов и структур. Необходимые для понимания данные я привел в качестве комментариев каждой строчки.

Итак приступаем. В самом верху таблицы задается сервис (primaryServiceUUID == 0×2800), имеющий разрешение на чтение (GATT_PERMIT_READ), и 128-ми битный UUID. По спецификации прочесть определение сервиса мы можем безо всякой идентификации и авторизации. Поля handle всех атрибутов всегда остаются пустыми. По мере того, как стек формирует внутри себя таблицу атрибутов, он самостоятельно заполняет эти поля. По значениям, лежащим в них, мы будем обращаться к сервису и характеристикам.

Далее задается характеристика (characterUUID == 0×2803). У неё так же разрешение только на чтение (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 (0×2A37). В поле Attribute Permissions как обычно лежит разрешение характеристики. У нас это разрешение записи.

Дескрипторы характеристики (Characteristic Descriptor)

В некоторых, довольно частых случаях, характеристики дополняют ещё одним или несколькими элементами — дескрипторами. Они используются для того, чтобы расширить характеристику дополнительным качеством. Дескрипторы помещаются сразу после объявление значений характеристик. Их может быть несколько. Давайте посмотрим на них.

Расширенные свойства характеристики (Characteristic Extended Properties)

1f70ba742047babd113aa309cb62f6e9.png

Объявление расширенных свойств характеристики — это дескриптор, который определяет дополнительные свойства характеристики. Битовое поле расширенных свойств характеристики описывает дополнительные свойства, касающиеся того, как можно использовать значение характеристики или как можно получить доступ к дескрипторам характеристики. Я в своей практике не встречал этот дескриптор, поэтому описание его я делаю исключительно по спецификации. У него следующие битовые поля.

c778eca27c26ba4337e81f305679bff9.png

.

Пользовательское описание характеристики (Characteristic User Description)

3f2b8d6fa81b16cd6bbd1db820dbf289.png

Этот тип дескриптора очень полезен и часто встречается в практике. Он определяет строку UTF-8 переменного размера, представляющую собой пользовательское текстовое описание значения характеристики. Довольно часто мы не знаем что за характеристики содержит сервис, особенно если они 128-ми битные. Но если есть такой дескриптор, он помогает нам прояснить ситуацию. Например у всем известной iTag антипотеряшки практически каждая характеристики снабжена этим дескриптором. Я выделил на картинке данный дескриптор и дескриптор CCCD из раздела ниже. Выглядит это так.

a5043cdde35bbc7f3a41e8ddb4d9ad19.png

Клиентская конфигурация характеристики (Client Characteristic Configuration)

7c1b97dfb2afa061a927f0ad91967378.png

Этот дескриптор, наверное самый часто используемый, его ещё называют CCCD (Client Characteristic Configuration Descriptor). Он позволяет клиенту настраивать поведение сервера, а именно — включать нотификацию или индикацию. Разницу между ними я уже описывал в предыдущей статье. Иногда проблема системы бывает в том, что сервер, (например датчик сердечного ритма), работает только по запросам со стороны клиента (чаще всего смартфона). Что бы изменить такое поведение, клиент может включить на сервере нотификацию и просто ожидать от него сообщений об изменении состояния. Это гораздо удобнее, чем каждый раз дергать датчик на наличие у него новых данных.

52655e87d777f1025b9b2fc11c155362.png

Что бы включить нотификацию, надо записать в дескриптор значение 0×0001 или 0×0002 для индикации. Для отключения записываем 0×0000 и всё будет отключено. Разумеется характеристика должна поддерживать нотификацию. Однако тут есть важная деталь. Каждый клиент имеет свой собственный экземпляр конфигурации. Т.е., используя этот дескриптор, клиент включает/отключает нотификацию только для себя. Если у сервера имеются другие клиенты, для них поведение сервера никак не изменится. Получается, что сервер, в общем случае, должен хранить в своей памяти всех клиентов и их настройки.

Серверная конфигурация характеристики (Server Characteristic Configuration)

6ae230ad6763b31002aa53c71e961acf.png

В отличии от предыдущего дескриптора, данный позволяет изменить работу сервера сразу для всех клиентов. Существует единый экземпляр конфигурации характеристик сервера для всех. Вот его поля конфигурации.

0f7d89864b44327f130023c09e268e9a.png

Формат представления характеристики (Characteristic Presentation Format)

fabd2bf062364eee96ec48d871a29591.png

Его назначение понятно из описания. Он задает некоторую форму представления, того как будет видится характеристика для клиента. Я никогда ранее не встречал её, поэтому долго останавливаться на ней не буду. В спецификации подробно разбирается каждое её поле, но я ограничусь только их описанием.

0b7bea48dcef7e1280bf9e0451bdee2e.png

Сводный формат характеристики (Characteristic Aggregate Format)

c4b78a6712b90b2cb7d2a99fc6bcaae7.png

Этот дескриптор является расширение предыдущего, хотя может быть использован и самостоятельно. Если в определении характеристики существует более одного объявления формата представления характеристик, то также должно быть одно объявление сводного формата характеристик. Он должен включать в себя каждое объявление формата представления. В своей практике я вряд ли буду когда то его использовать.

Краткое описание типов атрибутов профиля GATT

72116d5201960bb610b3e6607fa55c02.png

Снифферы

Давайте теперь поговорим о средствах контроля. Я в своей первой статье упоминал о сниффере на основе донгла nRF51822. Приобрести его было сложно, а без таких приборов делать разработку BLE устройств весьма проблематично. На данный момент, ситуация значительно улучшилась. Можно пойти двумя путями. Первый — это приобрести у фирмы WCH её спектранализатор. На Алиэкспрессе можно купить как одноканальный, так и трехканальный прибор. Работают они под управлением программы WCH_BLEAnalyzer.

a151573d82788b9f89377780762b781d.png

Второй путь — воспользоваться услугами нашего старого знакомого, фирмы NORDIC и её донглом на основе nRF52840. Описание как это сделать, можно прочитать здесь. Скачать софт можно на странице производителя. Мне нравится второй путь. Захват пакетов производится в программе Wireshark, которая очень удобна для разбора пакетов.

ef5c1dbaac6a03d7a3c7f02d6d7d8a6c.png

Анализ обмена данными в эфире

Для полноты картины, я хотел бы привести несколько картинок из файла обмена между контроллером WCH и смартфоном. Сам файл можно скачать здесь. Мне в своё время очень помог тщательный анализ пакетов в Wireshark-е.

09fcccb20c587ee614c263f66999d150.png

Давайте посмотрим на эфирные пакеты. Вначале идут advertising пакеты и их достаточно много. Они имеют наименование ADV_IND. Иногда они перемежаются запросами SCAN_REQ с телефона , на которые устройство отвечает дополнительным пакетом SCAN_RSP с расширенными данными. Когда пользователь в приложении на телефоне нажимает кнопку Connect, телефон посылает пакет CONNECT_IND. Это последний пакет на advertising каналах. В Wireshark-е я остановился на нем и вывел в окно ниже его настройки. В нем мы видим поля, которые я уже описывал в предыдущей статье. Очень полезно разобраться с ними. Далее, исходя из этих настроек, телефон и BLE устройство начнут обмениваться пакетами уже на рабочих каналах с указанным интервалом.

14e5e2f4ac07bd9e2f1e9365759598b0.png

Вначале мы видим интенсивный обмен между устройствами. Во время которого телефон запрашивает у гаджета всё что он умеет делать. К примеру, сначала телефон запускает процедуру обмена функциями LL_FEATURE_REQ, а гаджет на неё отвечает. Я привел её только в качестве примера и не буду досконально описывать все строки обмена. При достаточном усердии всё это вполне можно найти в спецификации. Там дается такая картинка на этот обмен.

706eabad9cc77b352125161945482ce8.png

Я приведу лишь самые интересные, на мой взгляд строки обмена данными. Например в строке 9724 телефон запрашивает GATT Primary Service Declaration из всего диапазона таблицы атрибутов, от 0×0001 до 0хffff.

ada412b3e7f569c2d68369e160c83606.png

А в строке 9729 получает ответ, что Generic Access Profile (0×1800) занимает в таблице атрибутов диапазон строк от 0×0001 до 0×0009. В то же время, Generic Attribute Profile (0×1801) занимает диапазон от 0×000a до 0×000d.

488ad3cb525d1e120e5d196bab4e04e3.png

А на запрос наличия характеристик из строчки 9730, мы получаем в строке 9733 ответ, что на строчке таблицы атрибутов номер 0×000e заявлена 128-битная характеристика. Длина её пока не определена. При последующих запросах атрибутов из таблицы это станет ясно.

e21d826025c79e7be67a91660f69f029.png

В строке 9734 делается запрос GATT Include Declaration (0×2802), а далее в строке 9737 получаем ответ — Attribute Not Found. Т.е. никакого включения атрибута не имеем.

38bc070a39f6d766e04a5155d970eb49.png

.

Заключение

Я постарался как мог, описать устройство таблицы атрибутов BLE стека. Тема эта обширная и в одной статье всё не опишешь. Но, на мой взгляд, получилось достаточно подробно. Теперь, опираясь на эту статью, можно переходить к описанию BLE контроллера CH582. Я уже начал её писать и в скором времени предоставлю вашему вниманию.

Печерских Владимир

Сотрудник Группы Компаний «Цезарь Сателлит»

© Habrahabr.ru