![image](https://habrastorage.org/files/408/ea7/79a/408ea779ab2a443c93e61c1166a27615.png)
BLE под микроскопом (модемы)
Так получилось, что мне пришлось заниматься темой BLE, используя модемы MC60E от Quectel и SIM868E от SIMCom. Изначально они не могли работать с протоколом BlueTooth 4.0, но их доработали и вот. В модемы добавили модули BLE, а в конце названия букву 'E'. Сегодня мне хотелось бы поговорить об этих устройствах в свете протокола BLE, поэтому я буду рассматривать только те команды, которые к нему относятся. Скачать фирменное описание на команды MC60E можно здесь, а на команды SIM868E здесь.
Инициализация
Итак, начнем. Модемы которые мы сегодня рассмотрим выпускаются разными производителями. В связи с этим у них разная система команд и разные возможности. Т.к. модули BLE находятся в составе модемов, то и управляются они AT командами. В описании мы имеем довольно обширный набор этих команд. Все их можно разделить на три части: общие команды, команды серверной части и команды для клиентов. Общие команды относятся к функционированию самого блока BLE. Это такие команды как включение питания, видимости или мощности устройства. Далее мы выбираем в какой роли будет выступать модем — в роли сервера или клиента. Чаще всего модемы конечно же работают как сервера с которых, при помощи телефонов, можно получать данные. Поэтому мы будем рассматривать серверные команды. У каждого из модемов имеется множество особенностей (это не баг, а фитча :-) Я освещу те, с которыми столкнулся. Думаю есть и те, которые я не увидел.
Инициализация BLE в модеме SIM868E
AT
OK
AT+BTPOWER?
+BTPWR: 0
OK
AT+BTLPWR=6
OK
AT+BTPOWER=1
OK
AT+BTVIS=0
OK
AT+BTHOST=«SIMCom»
OK
AT+BLEADDR?
+BLEADDR: 0,ce:cb:e0:5d:81:a7
OK
AT+BLESREG
+BLESREG: 1,ABCDEFF0
OK
AT+BLESREG
+BLESREG: 2,ABCDEFF1
OK
AT+BLESLSTART=1
+BLESLSTART: 1,ABCDEFF0
OK
OK
AT+BTPOWER?
+BTPWR: 0
OK
AT+BTLPWR=6
OK
AT+BTPOWER=1
OK
AT+BTVIS=0
OK
AT+BTHOST=«SIMCom»
OK
AT+BLEADDR?
+BLEADDR: 0,ce:cb:e0:5d:81:a7
OK
AT+BLESREG
+BLESREG: 1,ABCDEFF0
OK
AT+BLESREG
+BLESREG: 2,ABCDEFF1
OK
AT+BLESLSTART=1
+BLESLSTART: 1,ABCDEFF0
OK
Здесь есть сразу несколько особенностей инициализации. Мощность устройства (AT+BTLPWR=6) настраивается перед командой AT+BTPOWER=1, иначе изменить мощность не получится. Команда AT+BTVIS=0 отвечает за включение видимости модема в эфире. Она включена «по умолчанию» и сразу после включения питания (командой AT+BTPOWER=1) в эфире появляется advertising. В нем содержится только поле флагов и имя устройства, записанные в модем в предыдущей сессии. Поэтому мы сразу же отключаем рекламу. Далее записываем имя устройства. Если после этого включить видимость (AT+BTVIS=1), то имя устройства в эфире уже будет другим. Думаю что механизм включения видимости «по умолчанию», связан с надежностью в работе, при отключении питания. После записи имени, мы запрашиваем модем о его МАС адресе. В последствии, при работе с клиентами, он нам понадобится. Ну и две подряд команды AT+BLESREG инициализируют два GATT сервера. В данном модеме это важно. Как я покажу позже, на одном сервере не получается полноценно развернуть два разных сервиса. Ну и последняя команда — это старт advertising-а. Если попытаться отправить её модему до инициализации сервера, то мы получим ошибку. Как видите, есть разные способы включить advertising.
Инициализация BLE в модеме MC60E
AT
OK
AT+QBTPWR?
+QBTPWR: 0
OK
AT+QBTPWR=1
OK
AT+QBTVISB=0
OK
AT+QBTNAME=«Quectel»
OK
AT+QBTLETXPWR=7
OK
AT+QBTLEADDR?
+QBTLEADDR: C2D7E554A334
OK
AT+QBTGATSREG=1,«A001»
+QBTGATSREG: 1,«A001»,0
OK
AT+QBTGATSL=«A001»,1
+QBTGATSL: «A001»,0
OK
OK
AT+QBTPWR?
+QBTPWR: 0
OK
AT+QBTPWR=1
OK
AT+QBTVISB=0
OK
AT+QBTNAME=«Quectel»
OK
AT+QBTLETXPWR=7
OK
AT+QBTLEADDR?
+QBTLEADDR: C2D7E554A334
OK
AT+QBTGATSREG=1,«A001»
+QBTGATSREG: 1,«A001»,0
OK
AT+QBTGATSL=«A001»,1
+QBTGATSL: «A001»,0
OK
Здесь, так же как и у предыдущего модема, advertising включается «по умолчанию». Поэтому мы отключаем видимость командой AT+QBTVISB=0. Так же, поменяв имя устройства и включив видимость, мы увидим новое имя. Менять мощность командой QBTLETXPWR можно уже после команды включения питания AT+QBTPWR=1. И при запросе мощности, будет выставляться последнее значение. Однако оно будет применено только после перезагрузки модема. Поэтому наверное будет правильней, как и в модеме SIM868E, сначала выставлять мощность устройства, а затем уже подавать на него питание. Отличием является так же то, что у этого модема запускается только один GATT сервер, но он работает корректно и позволяет инициализировать несколько сервисов. Последней командой запускаем advertising. У модема MC60E есть ещё одна возможность включить advertising — это задать частоту пакетов рекламы командой QBTGATADV.
Параметры advertising-а SIM868E
Чтобы не уходить далеко от темы advertising-а давайте разберем как мы можем повлиять на то, что клиент увидит в эфире. В модеме для этого есть всего одна команда AT+BLEADV. Она имеет довольно много параметров. Чуть ниже мы рассмотрим их. Но сначала посмотрим как выглядит advertising после подачи питания на модуль. Я уже упоминал, что в эфир будут выставлены поля флагов и имя устройства, записанное в предыдущую сессию.
![](https://habrastorage.org/webt/q7/io/bl/q7ioblxreqijr8ludjuvl9ez36o.png)
После того, как мы задаем команду AT+BLEADV, видимость устройства в эфире меняется и зависит теперь только от настроек этой команды. Давайте подробнее разберем ее параметры и посмотрим как они влияют на видимость устройства в эфире.
AT+BLEADV =<server_index>,<scan_rsp>,<include_name>,<include_txpower>,<appearancе>, <manufacturer_data>,<service_data>,<service_uuid>
Первый параметр <server_index>, всегда равен 1, т.к. у нас на модеме развертывается один сервер. Если все остальные параметры будут равны нулю, то мы получим следующее.
AT+BLEADV=1,0,0,0,0,"","",""
<server_index>
![](https://habrastorage.org/r/w1560/webt/lv/f3/mm/lvf3mmd-qr3dds8xztfpq5n1u_w.png)
![](https://habrastorage.org/webt/lv/f3/mm/lvf3mmd-qr3dds8xztfpq5n1u_w.png)
Второй параметр <scan_rsp> отвечает за видимость поля флагов. Здесь я бы поспорил с аббревиатурой. RSP это поле ответной посылки в которую мы можем положить дополнительные данные. В данном модеме этого увы нет и мы должны использовать только основную посылку advertising-а. Выставляя это поле в единицу, мы получим следующее.
AT+BLEADV=1,1,0,0,0,"","",""
<scan_rsp>
![](https://habrastorage.org/r/w1560/webt/xx/4n/sx/xx4nsx9st6zskffwjxdpbtkl2hk.png)
![](https://habrastorage.org/webt/xx/4n/sx/xx4nsx9st6zskffwjxdpbtkl2hk.png)
Третий параметр <include_name> отвечает за видимость имени в advertising посылке.
AT+BLEADV=1,1,1,0,0,"","",""
<include_name>
![](https://habrastorage.org/r/w1560/webt/kr/ti/he/krtihewshj9oqphjvrs7c1vjmfk.png)
![](https://habrastorage.org/webt/kr/ti/he/krtihewshj9oqphjvrs7c1vjmfk.png)
Четвертый параметр <include_txpower> включает поле мощности устройства.
AT+BLEADV=1,1,1,1,0,"","",""
<include_txpower>
![](https://habrastorage.org/r/w1560/webt/ic/kz/o8/ickzo8tlwzor7euh1k7ybewyyt0.png)
![](https://habrastorage.org/webt/ic/kz/o8/ickzo8tlwzor7euh1k7ybewyyt0.png)
Пятый параметр «appearance» отвечает за внешний вид, значок нашего устройства на клиентских машинах. Длина этого параметра — 2 байта. Значения, которые нужно подставлять на это место, мы можем посмотреть здесь или здесь. Надо только учитывать, что в документации данные приводятся в hex формате, а в команде их надо подставлять в десятичном виде.
AT+BLEADV=1,1,1,1,64,"","",""
Phone - 0x0040
![](https://habrastorage.org/r/w1560/webt/bn/25/st/bn25st_hfyn66ygcviiggbpnew4.png)
![](https://habrastorage.org/webt/bn/25/st/bn25st_hfyn66ygcviiggbpnew4.png)
AT+BLEADV=1,1,1,1,128,"","",""
Computer - 0х0080
![](https://habrastorage.org/r/w1560/webt/iv/sj/z5/ivsjz5l0db0jb-ox6xsw7jgj798.png)
![](https://habrastorage.org/webt/iv/sj/z5/ivsjz5l0db0jb-ox6xsw7jgj798.png)
AT+BLEADV=1,1,1,1,192,"","",""
Watch - 0x00C0
![](https://habrastorage.org/r/w1560/webt/dd/8d/jc/dd8djc7echob89jqyzlv-qckfr4.png)
![](https://habrastorage.org/webt/dd/8d/jc/dd8djc7echob89jqyzlv-qckfr4.png)
Седьмой <service_data> и восьмой <service_uuid> параметры позволяют выставлять в advertising посылку UUID сервиса и его данные. Здесь надо указать следующее. Данные поля <service_data> передаются в эфире так же как они записываются в команде. А вот данные поля <service_uuid> имеют другой порядок следования байтов. UUID сервисов рассматриваются как 16-ти битные и выставляются старшим байтом вперед. Здесь я сразу хочу предупредить, что все кавычки в команде должны быть верхними. Редактор сайта меняет кавычки, между которыми располагается текст. Я не смог понять как это победить.
AT+BLEADV=1,1,1,1,128,"",«1234»,«5678»
<service_data>,<service_uuid>
![](https://habrastorage.org/r/w1560/webt/de/xf/98/dexf98sdrkjhq-mwl71benbochq.png)
![](https://habrastorage.org/webt/de/xf/98/dexf98sdrkjhq-mwl71benbochq.png)
При работе с полем <service_uuid> есть одна фитча, которая может попортить кровь. В описании на это поле написано: «The length of it should be 0 or 4~32». Т.е. UUID может иметь как 2, так и 16 байт. И по идее, мы можем загружать как 16-ти, так и 128-ми битные UUID сервисов. Однако это не так. Для разных по длине UUID, нужно выставлять разное значение поле в заголовке. Я выделил это в картинке ниже. Однако модем SIM868E выставляет его всегда равным 0х03. Это приводит к тому, что на приемной стороне 128-ми битный UUID разбивается на 8 независимых 16-ти битных сервиса.
![](https://habrastorage.org/webt/_0/t_/ji/_0t_ji_gveffz0hcko15gnjouu8.png)
В конце разбора команды AT+BLEADV, я бы хотел рассмотреть шестое <manufacturer_data> поле. Это самое интересное поле, т.к. мы можем укладывать сюда любые наши данные. Если откинуть все поля, за исключением флагов, мы можем передавать в одной посылке 26 байт данных. Если выкинуть и флаги, то можем передавать ещё на 2 байта больше, но это будет не правильно с точки зрения формата BLE посылок.
AT+BLEADV=1,1,0,0,0,«1111111111222222222233333333334444444444555555555566»,"",""
Phone - 0x0040
![](https://habrastorage.org/r/w1560/webt/3s/m7/l6/3sm7l69kkfluys-iisiiot5c9p8.png)
![](https://habrastorage.org/webt/3s/m7/l6/3sm7l69kkfluys-iisiiot5c9p8.png)
Ну и напоследок. После того как клиент присоединится к модему и отсоединиться, в эфире пропадут данные, задаваемые командой AT+BLEADV. Поэтому алгоритм сервера надо сделать так, что бы после отключения клиента, повторно задавать данные advertising-а командой AT+BLEADV.
Параметры advertising-а MC60E
В модеме MC60E есть несколько команд, которые влияют на advertising. Рассмотрим их. Как я уже писал выше, имя устройства задается командой QBTNAME. Это имя будет присутствовать в посылках сразу после перезагрузки, но после команды QGATADVDATA с данными advertising-а, оно пропадает из посылки. Если мы хотим, что бы оно присутствовало и далее, необходимо включить его либо в основную advertising посылку, либо в ответную RSP посылку. Здесь мы сталкиваемся с тем, что у данного модема, есть возможность настраивать данные в RSP пакете, а следовательно передавать больше данных в режиме advertising.
Производители модема MC60E не стали сильно заморачиваться и передали разработчикам возможность самим наполнять advertising данными. Это сделало модем более гибким, но теперь нам приходиться самим контролировать корректность данных. В связи с чем, хочу напомнить как формируется advertising посылка. В ней можно записывать данные блоками. Каждый блок объявляет какой либо сервис. Он начинается с байта длины блока. Затем следует идентификатор сервиса, а следом идут сами данные. На картинке внизу показаны три сервиса из спецификации BlueTooth Core_V4.0.
![](https://habrastorage.org/webt/pr/4z/kz/pr4zkzf2timvl8avwk99wskuf2g.png)
Давайте рассмотрим следующие команды:
AT+QBTPWR=1
AT+QBTGATSREG=1,«A001»
AT+QGATADVDATA=«A001»,«020118»
AT+QGATSCANRSP=«A001»,«020a0708095175656374656C»
Первая команда включает питание на модуль BlueTooth. Вторая запускает GATT сервер. Без неё следующие команды не будут приняты. Третья команда задает данные, которые будут находиться в основной advertising посылке. Она содержит только флаги. Четвертая команда задает данные RSP ответа. В ней байтами 020a07 задается: 02-длина поля, 0а-сервис мощности устройства, 07-мощность в dBm. Следующий сервис 08095175656374656C имеет 08-байт длины, 09-полное локальное имя, а следом идет само имя «Quectel». В эфире мы увидим следующее:
![](https://habrastorage.org/webt/w1/n7/si/w1n7siesdk875tirjdr2ot_1ege.png)
Здесь, так же как и у модема SIM868E, данные advertising-а, задаваемые командой AT+QGATADVDATA, пропадают из эфира, после отсоединения клиента. Лекарство то же — повторно запускать команду AT+QGATADVDATA.
Ну и напоследок, следует рассмотреть особенности включения advertising-а. У модема MC60 есть целых три способа его включить и два способа выключить. Эти команды делятся на две независимые группы. Как я уже писал, при включении питания на модуль командой AT+QBTPWR=1, у него включается видимость. Отключить её можно только командой AT+QBTVISB=0. Включает же видимость обратная команда AT+QBTVISB=1, либо команда задания параметров advertising-а QBTGATADV. Она задает интервалы максимального и минимального времени, например так AT+QBTGATADV=800,2000. Похожая команда AT+QBTGATCPU которая так же выставляет временные интервалы к включению видимости не приводит.
Вторая группа команд управления advertising-ом состоит из одной команды AT+QBTGATSL с параметрами включения (например так: AT+QBTGATSL=«A001»,1) и отключения (например так: AT+QBTGATSL=«A001»,0) advertising-а. Если модем находится в состоянии отключенной видимости, мы можем включить advertising первой командой и отключить второй. Однако, если была включена видимость (AT+QBTVISB=1), отключить advertising командой AT+QBTGATSL=«A001»,0 мы не сможем. Аналогично и в обратную сторону. Если включить advertising командой AT+QBTGATSL=«A001»,1 мы не сможем отключить его командой AT+QBTVISB=0. Зачем так сделано, мне не понятно. На этом закончим разбор advertising-а и приступим к сервисам и характеристикам.
Сервисы и характеристики
Сначала несколько общих слов о сервисах и характеристиках. В одной из своих предыдущих статей я уже писал о них. Сейчас я бы хотел дать такой взгляд на них. Он будет понятен программистам микроконтроллеров. Представьте, нам надо написать протокол обмена данными по UART-у между GUI приложением и джойстиком с кнопками. Мы обычно в таких случаях используем следующий подход. Каждая посылка имеет заголовок, данные и CRC. Заголовок чаще всего имеет 1 или 2 байта. Первый байт обозначает тип команды, а второй его уточняет. Допустим, первый байт описывает изменение положения джойстика «J»-joystick, а второй — в каком именно направлении он отклонен: «L»-left,«R»-right,«U»-up,«D»-down. В другой посылке будет содержаться информация о нажатии кнопок на джойстике. Первый байт будет «B»-buttons, а второй «L»-left,«R»-right. При таком подходе, мы сначала узнаем что сработал джойстик или кнопки, а затем уточняем какие именно. Например заголовок JR говорит о том, что джойстик отклонили вправо, а BL — о том, что нажата левая кнопка.
Именно этот подход реализован в BlueTooth 4.0 при задании собственных сервисов и характеристик. Правда одним или двумя байтами здесь не обойтись. Как мы знаем, UUID сервиса может быть как 2-х байтным, так и 16-ти байтным. Для собственных сервисов и характеристик применяют 16-ти байтные значения. Поэтому получается, что для обращения к какой либо характеристике мы сначала указываем 16 байт UUID сервиса, затем 16 байт UUID характеристики и только затем идут данные.
Кроме того, при задании характеристик мы указываем их свойства, т.е. что мы можем делать с данными, которые они содержат. Чаще всего используются свойства записи, чтения и нотификации. Запись — это когда мы отправляем серверу какие то данные со стороны телефона. Чтение — когда мы читаем какие то данные (к примеру температуру датчика). Однако чтение всегда подразумевает запрос со стороны клиента. Это не всегда удобно. Иногда хотелось бы, что бы сервер (датчик температуры) сам сообщал об изменении своих данных. В этом случае мы должны подписаться на эти изменения. Задать такой режим работы сервера, что бы он сам выступал инициатором взаимодействия. Это называется подпись на нотификацию. Она возможна в том случае, если в свойствах характеристик она указана. Наглядно свойства характеристик в BlueTooth Core_V4.0 можно посмотреть в Table 2: Format Map of Properties and Permission для MC60E. Приведу её здесь.
![](https://habrastorage.org/webt/ji/uv/nr/jiuvnravqsbkkei7v_42bwgawk8.png)
Эта таблица описывает формат характеристик протокола BLE, а следовательно относится к обоим рассматриваемым модемам. В байте каждая строчка — это отдельное битовое поле. Поэтому мы сразу можем указать, что какая то характеристика может только записывать данные, читать их или делать и то и другое. Как я покажу ниже, свойства характеристик видны при запросе со стороны клиента. Но кроме свойств характеристик есть ещё разрешения. Чем они отличаются? Дело в том, что как чтение, так и запись бывают нескольких видов — без защиты, с шифрованием (encrypted protection) и защитой от прослушивания (MITM protection). Если защиты нет, то при запросе данных, сервер вам их без проблем отдаст. А вот если включена защита, так просто данные не получить. Мы в этой статье будем рассматривать самый простой вариант взаимодействия, без защиты. Но при инициализации характеристик, нам надо будет задавать как свойства, так и разрешения. Поэтому привожу ниже параметры разрешений.
![](https://habrastorage.org/webt/fe/78/fe/fe78fe7_fbwq8h1syo_fcbz8xpe.png)
После всех этих предварительных объяснений перейдем к особенностям работы модемов. Мы будем задавать два сервиса — сервис обмена ключами (I) и сервис чтения данных (II). Мы их будем активировать оба сразу, что бы в приложении видеть их одновременно. Но в реальных устройствах, возможно имеет смысл запускать их последовательно. В этот раз я хотел бы начать с модема от Quectel-а MC60E. В нем меньше багов и фитч.
Сервисы и характеристики MC60E
Выше я уже приводил инициализацию advertising-а модема MC60E. Сейчас мы её продолжим и добавим инициализацию сервисов и характеристик.
Инициализация сервисов и характеристик в модеме MC60E
// подаем питание на модуль BLE
AT+QBTPWR=1
OK
// отключаем видимость
AT+QBTVISB=0
OK
// задаем имя устройства
AT+QBTNAME=«Quectel»
OK
// задаем выходную мощность
AT+QBTLETXPWR=7
OK
// запрос MAC адреса модема
AT+QBTLEADDR?
+QBTLEADDR: C2D7E554A334
OK
// запускаем GATT сервер
AT+QBTGATSREG=1,«A001»
+QBTGATSREG: 1,«A001»,0
OK
// инициализируем сервис обмена ключами I
AT+QBTGATSS=1,«A001»,«11111111111111111111111111111111»,15,1,4
+QBTGATSS: 1,«A001»,«11111111111111111111111111111111»,1,0,4,256
OK
// инициализируем характеристику записи сервиса I
AT+QBTGATSC=1,«A001»,256,«22222222222222222222222222222222»,5,8,16
+QBTGATSC: 1,«A001»,256,«22222222222222222222222222222222»,5,0,258
OK
// инициализируем характеристику чтения сервиса I
AT+QBTGATSC=1,«A001»,256,«33333333333333333333333333333333»,6,2,1
+QBTGATSC: 1,«A001»,256,«33333333333333333333333333333333»,6,0,260
OK
// инициализируем сервис чтения данных II
AT+QBTGATSS=1,«A001»,«44444444444444444444444444444444»,15,1,5
+QBTGATSS: 1,«A001»,«44444444444444444444444444444444»,1,0,5,271
OK
// инициализируем характеристику чтения и нотификации сервиса II
AT+QBTGATSC=1,«A001»,271,«55555555555555555555555555555555»,7,18,1
+QBTGATSC: 1,«A001»,271,«55555555555555555555555555555555»,7,0,273
OK
// инициализируем дескриптор 0229
AT+QBTGATSD=1,«A001»,271,«0229»,5,17
+QBTGATSD: 1,«A001»,271,«0229»,5,0,274
OK
// запускаем сервис I
AT+QBTGATSST=1,«A001»,256,0
+QBTGATSST: 1,«A001»,0,256
OK
// запускаем сервис II
AT+QBTGATSST=1,«A001»,271,0
+QBTGATSST: 1,«A001»,0,271
OK
// инициализируем ответную RSP посылку
AT+QGATSCANRSP=«A001»,«020a0708095175656374656C»
OK
// инициализируем основную advertising посылку
AT+QGATADVDATA=«A001»,«020118»
OK
// запускаем advertising
AT+QBTGATSL=«A001»,1
+QBTGATSL: «A001»,0
OK
AT+QBTPWR=1
OK
// отключаем видимость
AT+QBTVISB=0
OK
// задаем имя устройства
AT+QBTNAME=«Quectel»
OK
// задаем выходную мощность
AT+QBTLETXPWR=7
OK
// запрос MAC адреса модема
AT+QBTLEADDR?
+QBTLEADDR: C2D7E554A334
OK
// запускаем GATT сервер
AT+QBTGATSREG=1,«A001»
+QBTGATSREG: 1,«A001»,0
OK
// инициализируем сервис обмена ключами I
AT+QBTGATSS=1,«A001»,«11111111111111111111111111111111»,15,1,4
+QBTGATSS: 1,«A001»,«11111111111111111111111111111111»,1,0,4,256
OK
// инициализируем характеристику записи сервиса I
AT+QBTGATSC=1,«A001»,256,«22222222222222222222222222222222»,5,8,16
+QBTGATSC: 1,«A001»,256,«22222222222222222222222222222222»,5,0,258
OK
// инициализируем характеристику чтения сервиса I
AT+QBTGATSC=1,«A001»,256,«33333333333333333333333333333333»,6,2,1
+QBTGATSC: 1,«A001»,256,«33333333333333333333333333333333»,6,0,260
OK
// инициализируем сервис чтения данных II
AT+QBTGATSS=1,«A001»,«44444444444444444444444444444444»,15,1,5
+QBTGATSS: 1,«A001»,«44444444444444444444444444444444»,1,0,5,271
OK
// инициализируем характеристику чтения и нотификации сервиса II
AT+QBTGATSC=1,«A001»,271,«55555555555555555555555555555555»,7,18,1
+QBTGATSC: 1,«A001»,271,«55555555555555555555555555555555»,7,0,273
OK
// инициализируем дескриптор 0229
AT+QBTGATSD=1,«A001»,271,«0229»,5,17
+QBTGATSD: 1,«A001»,271,«0229»,5,0,274
OK
// запускаем сервис I
AT+QBTGATSST=1,«A001»,256,0
+QBTGATSST: 1,«A001»,0,256
OK
// запускаем сервис II
AT+QBTGATSST=1,«A001»,271,0
+QBTGATSST: 1,«A001»,0,271
OK
// инициализируем ответную RSP посылку
AT+QGATSCANRSP=«A001»,«020a0708095175656374656C»
OK
// инициализируем основную advertising посылку
AT+QGATADVDATA=«A001»,«020118»
OK
// запускаем advertising
AT+QBTGATSL=«A001»,1
+QBTGATSL: «A001»,0
OK
Давайте разберем что здесь и как. Описывать все параметры я не буду, кто захочет, тот прочтет описание. Пройдемся по самым важным. UUID нашего первого сервиса, для примера, зададим из сплошных единиц. Как я уже писал в предыдущей статье, для генерации собственного UUID можно воспользоваться специальным генератором. При инициализации сервиса, модем выдает нам его <service_handle>. Я его подчеркнул красным цветом. Это значение надо будет указывать при задании характеристик данного сервиса. Так же нужно будет указывать свойства и разрешения. Свойства подчеркнуты зеленым, а разрешения — синим цветом.
![](https://habrastorage.org/webt/j1/gb/rp/j1gbrpi-ghnomcmwwctsokr98do.png)
Давайте их разберем. Значения указываются в десятичном формате. У первой характеристики UUID состоит из одних двоек, свойство равно 8 = 0x0000 1000, а разрешение 16 = 0x0001 0000. Из приведенной выше таблицы свойств видно, что эта характеристика отвечает за запись данных. При этом из таблицы разрешений видим, что запись разрешена без всякой защиты. У второй характеристики, состоящей из троек, свойство равно 2 = 0x0000 0010, а разрешение равно 1 = 0x0000 0001. Это означает что разрешено чтение и так же без всякой защиты.
Зададим второй сервис. Он будет отвечать за передачу данных с сервера на телефон. Здесь так же, как и в предыдущем случае, <service_handle> отмечен красным, свойства зеленым, а разрешения синим цветом. Как видно, свойство равно 18 = 0x0001 0010 что означает разрешено чтение и нотификация. В разрешениях стоит простое чтение без защиты. Кроме того, в документации на модем указано следующее: «If property “notify” or “indicate” of characteristic is added, please add a descriptor with UUID “0229” after it». Т.е. нам необходимо при инициализации этой характеристики добавить ещё и дескриптор с UUID «0229», что мы и сделаем.
![](https://habrastorage.org/webt/eb/3n/ke/eb3nkegqexok_mn3lwc1zt5oggq.png)
Теперь если мы, с помощью программы nRF Connect, присоединимся к нашему модему, то увидим следующие картинки. Слева — это все сервисы, развернутые на нашем модеме, два из которых (0x1800 and 0x1801) системные сервисы. Два другие — это два сервиса заданные нами. Если мы их развернем (см. вторую картинку), то увидим у первого сервиса две заданные характеристики с параметрами записи и чтения, а у второго только одну характеристику с возможностью нотификации и чтения. А так же дескриптор 0x2902 (всё выделено сиреневым цветом).
![](https://habrastorage.org/webt/td/px/jx/tdpxjxkuamalmkdbr74u4stmf9g.png)
Таким образом видим, что наши сервисы и характеристики проинициализированы. Теперь, если послать запрос с клиента на чтение или запись, с заданными характеристиками, мы получим возможность необходимой транзакции. Приложение nRF Connect позволяет сделать и это. Внимательно присмотритесь к стрелочкам вверх и вниз на правом рисунке. Мы обсудим их ниже. Пожалуй на этом закончим. В логе более подробно прокомментированы события, происходящие на стороне модема MC60E.
Сервисы и характеристики SIM868E
Давайте посмотрим инициализацию сервисов и характеристик для модема SIM868E. Здесь мы так же зададим два сервиса — обмена ключами (I) и чтения данных (II).
Инициализация сервисов и характеристик в модеме SIM868E
// подаем питание на модуль BLE
AT+BTPOWER=1
OK
// отключаем видимость
AT+BTVIS=0
OK
// задаем имя устройства
AT+BTHOST=«SIMCom»
OK
// запрос MAC адреса модема
AT+BLEADDR?
+BLEADDR: 0,ce:cb:e0:5d:81:a7
OK
// запускаем 1-й GATT сервер
AT+BLESREG
+BLESREG: 1,ABCDEFF0
OK
// запускаем 2-й GATT сервер
AT+BLESREG
+BLESREG: 2,ABCDEFF1
OK
// инициализируем сервис чтения данных II
AT+BLESSAD=1,«44444444444444444444444444444444»,15,1,1
+BLESAD: 1,ABCDEFF0,44444444444444444444444444444444,1,1,256
OK
// инициализируем характеристику чтения и нотификации сервиса II
AT+BLESSC=001,«55555555555555555555555555555555»,1,18,1
+BLESSC: 1,ABCDEFF0,256,55555555555555555555555555555555,1,258
OK
// инициализируем дескриптор 0229
AT+BLESSD=001,«0229»,1,0
+BLESSD: 1,ABCDEFF0,256,0229,1,259
OK
// инициализируем сервис обмена ключами I
AT+BLESSAD=2,«11111111111111111111111111111111»,15,1,1
+BLESAD: 2,ABCDEFF1,11111111111111111111111111111111,1,1,271
OK
// инициализируем характеристику записи сервиса I
AT+BLESSC=002,«22222222222222222222222222222222»,1,8,16
+BLESSC: 2,ABCDEFF1,271,22222222222222222222222222222222,1,273
OK
// инициализируем характеристику чтения сервиса I
AT+BLESSC=002,«33333333333333333333333333333333»,1,2,1
+BLESSC: 3,ABCDEFF1,271,33333333333333333333333333333333,1,275
OK
// запускаем сервис I
AT+BLESSSTART=2,0
+BLESSSTART: 2,ABCDEFF1,271
OK
// запускаем сервис II
AT+BLESSSTART=1,0
+BLESSSTART: 1,ABCDEFF0,256
OK
// инициализируем advertising посылку
AT+BLEADV=1,1,1,1,64,"","",""
+BLEADV: ABCDEFF0
OK
// запускаем advertising
AT+BLESLSTART=1
+BLESLSTART: 1,ABCDEFF0
OK
AT+BTPOWER=1
OK
// отключаем видимость
AT+BTVIS=0
OK
// задаем имя устройства
AT+BTHOST=«SIMCom»
OK
// запрос MAC адреса модема
AT+BLEADDR?
+BLEADDR: 0,ce:cb:e0:5d:81:a7
OK
// запускаем 1-й GATT сервер
AT+BLESREG
+BLESREG: 1,ABCDEFF0
OK
// запускаем 2-й GATT сервер
AT+BLESREG
+BLESREG: 2,ABCDEFF1
OK
// инициализируем сервис чтения данных II
AT+BLESSAD=1,«44444444444444444444444444444444»,15,1,1
+BLESAD: 1,ABCDEFF0,44444444444444444444444444444444,1,1,256
OK
// инициализируем характеристику чтения и нотификации сервиса II
AT+BLESSC=001,«55555555555555555555555555555555»,1,18,1
+BLESSC: 1,ABCDEFF0,256,55555555555555555555555555555555,1,258
OK
// инициализируем дескриптор 0229
AT+BLESSD=001,«0229»,1,0
+BLESSD: 1,ABCDEFF0,256,0229,1,259
OK
// инициализируем сервис обмена ключами I
AT+BLESSAD=2,«11111111111111111111111111111111»,15,1,1
+BLESAD: 2,ABCDEFF1,11111111111111111111111111111111,1,1,271
OK
// инициализируем характеристику записи сервиса I
AT+BLESSC=002,«22222222222222222222222222222222»,1,8,16
+BLESSC: 2,ABCDEFF1,271,22222222222222222222222222222222,1,273
OK
// инициализируем характеристику чтения сервиса I
AT+BLESSC=002,«33333333333333333333333333333333»,1,2,1
+BLESSC: 3,ABCDEFF1,271,33333333333333333333333333333333,1,275
OK
// запускаем сервис I
AT+BLESSSTART=2,0
+BLESSSTART: 2,ABCDEFF1,271
OK
// запускаем сервис II
AT+BLESSSTART=1,0
+BLESSSTART: 1,ABCDEFF0,256
OK
// инициализируем advertising посылку
AT+BLEADV=1,1,1,1,64,"","",""
+BLEADV: ABCDEFF0
OK
// запускаем advertising
AT+BLESLSTART=1
+BLESLSTART: 1,ABCDEFF0
OK
Здесь мы сразу видим некоторые особенности инициализации. Во-первых, у нас запускаются два GATT сервера. На этом модеме это возможно. Зачем мы это делаем? Дело в том, что на одном GATT сервере не удается корректно развернуть два сервиса. Это один из багов. Характеристики из второго сервиса сползают в первый или же второй сервис вообще не развертывается.
Выглядит это так
![](https://habrastorage.org/r/w1560/webt/eu/wq/ri/euwqrikq8hjhr4zecpd2rsznxcq.png)
![](https://habrastorage.org/webt/eu/wq/ri/euwqrikq8hjhr4zecpd2rsznxcq.png)
Во-вторых, мы сначала разворачиваем сервис у которого есть нотификация. На последующем разворачиваемом сервисе нотификация работать не будет. Это так же один из багов данного модема. Но если проинициализировать сервисы так как указано выше, в эфире мы увидим следующее. Здесь так же свойства характеристик и дескриптор выделены сиреневым цветом.
![](https://habrastorage.org/webt/4j/ak/li/4jaklit5atk--nup1imxka0i0go.png)
Мы видим, что всё что мы хотели сделать, у нас вполне получилось. Рассмотрим как в данном модеме происходит инициализация сервисов и характеристик. Сначала, как я уже писал, мы запускаем два GATT сервера. Модем возвращает нам их <server_index> и имя. На рисунке они подчеркнуты серым цветом. Затем инициализируем сервис чтения данных (II), состоящий из одних четверок. Нам возвращается его <service_index>. Он подчеркнут красным. Ну а затем уже, используя <service_index>, мы инициализируем характеристику и дескриптор 0x2902. Здесь, так же как и в предыдущем модеме, зеленым подчеркнуты свойства, а разрешения синим цветом.
![](https://habrastorage.org/webt/dq/_k/8o/dq_k8omya4tobibkf5ujbhk7-as.png)
Далее инициализируем сервис обмена ключами (I), заданный сплошными единицами.
![](https://habrastorage.org/webt/rc/j-/m3/rcj-m3oia6k2emtzb9oerwnwkre.png)
Значения свойств и разрешений аналогичны предыдущему модему. Вот пожалуй и всё что касается инициализаций сервисов и характеристик модема SIM868E. Возможно у него есть и другие баги не найденные мной. Мне он показался достаточно сырым. В следующем разделе мы рассмотрим как эти модемы отвечают на запросы чтения и записи.
Режимы чтения и записи характеристик
Сейчас мы более подробно поговорим о программе nRF Connect. Скажу честно, мне она очень нравится. С помощью неё мы можем заставить работать телефон в режиме клиента и сервера. Для нас сейчас нужен режим клиента. Мы сможем посмотреть что наши устройства передают в режиме advertising-а, а так же, после присоединения, поработать с сервисами и характеристиками. Есть два варианта этой программы, для платформы андроида и iOs и они немного отличаются. К примеру, в версии iOs не показываются MAC адрес устройств. Есть и другие отличия. Давайте посмотрим как выглядит эта программа на андроиде, в режиме обмена данными с Quectel-ом и SIMCom-ом.
![](https://habrastorage.org/webt/45/gs/0a/45gs0awywed4kxhbycggswsy0nk.png)
На рисунке мы видим результат взаимодействия программы nRF Connect в режиме чтения и записи данных. Транзакции приема/передачи со стороны модемов можно посмотреть ниже.
Обмен данными модема MC60E
// клиент присоединился к модему
+QBTGATSCON: 1,«A001»,0,6B9080DD6D5D,1
// запрос клиента на чтение данных
+QBTGATRREQ: «A001»,1,22,6B9080DD6D5D,260,0,0
// сервер отправляет клиенту свои данные
AT+QBTGATSRSP=«A001»,0,1,0538,258,«12345678»
+QBTGATSRSP: 0,«A001»,1,258
OK
// клиент отправляет серверу данные для записи
+QBTGATWREQ: «A001»,1,23,6B9080DD6D5D,258,AABBCCDD,1,0,0
// сервер подтверждает прием данных для записи (любое число)
AT+QBTGATSRSP=«A001»,0,1,0538,258,«5678»
+QBTGATSRSP: 0,«A001»,1,258
OK
//--------------------------------------------------
// передача данных клиенту в режиме нотификации
AT+QBTGATSIND=«A001»,1,273,1,«AAAABBBBBCCCCCDDDDDEEEEEFFFFF»
+QBTGATSIND: 0,«A001»,1,55189
OK
+QBTGATSCON: 1,«A001»,0,6B9080DD6D5D,1
// запрос клиента на чтение данных
+QBTGATRREQ: «A001»,1,22,6B9080DD6D5D,260,0,0
// сервер отправляет клиенту свои данные
AT+QBTGATSRSP=«A001»,0,1,0538,258,«12345678»
+QBTGATSRSP: 0,«A001»,1,258
OK
// клиент отправляет серверу данные для записи
+QBTGATWREQ: «A001»,1,23,6B9080DD6D5D,258,AABBCCDD,1,0,0
// сервер подтверждает прием данных для записи (любое число)
AT+QBTGATSRSP=«A001»,0,1,0538,258,«5678»
+QBTGATSRSP: 0,«A001»,1,258
OK
//--------------------------------------------------
// передача данных клиенту в режиме нотификации
AT+QBTGATSIND=«A001»,1,273,1,«AAAABBBBBCCCCCDDDDDEEEEEFFFFF»
+QBTGATSIND: 0,«A001»,1,55189
OK
Обмен данными модема SIM868E
// клиент присоединился к модему
+BLESCON: 1,ABCDEFF0,66:ca:14:55:4a:c6,1
+BLESCON: 1,ABCDEFF1,66:ca:14:55:4a:c6,2
// запрос клиента на чтение данных
+BLESRREQ: ABCDEFF1,2,22,66:ca:14:55:4a:c6,275,0,0
// сервер отправляет клиенту свои данные
AT+BLESRSP=0,87654321
+BLESRSP: 0,ABCDEFF1,2,275
OK
// клиент отправляет серверу данные для записи
+BLESWREQ: ABCDEFF1,2,23,66:ca:14:55:4a:c6,273,DDCCBBAA,1,0,0
// сервер подтверждает прием данных для записи
AT+BLESRSP=1
+BLESRSP: 0,ABCDEFF1,2,273
OK
//------------------------------------------
// передача данных клиенту в режиме нотификации
AT+BLESIND=1,«FFFFFEEEEEDDDDDCCCCCBBBBBAAAAA»
+BLESIND: 0,ABCDEFF0,1,258
OK
+BLESCON: 1,ABCDEFF0,66:ca:14:55:4a:c6,1
+BLESCON: 1,ABCDEFF1,66:ca:14:55:4a:c6,2
// запрос клиента на чтение данных
+BLESRREQ: ABCDEFF1,2,22,66:ca:14:55:4a:c6,275,0,0
// сервер отправляет клиенту свои данные
AT+BLESRSP=0,87654321
+BLESRSP: 0,ABCDEFF1,2,275
OK
// клиент отправляет серверу данные для записи
+BLESWREQ: ABCDEFF1,2,23,66:ca:14:55:4a:c6,273,DDCCBBAA,1,0,0
// сервер подтверждает прием данных для записи
AT+BLESRSP=1
+BLESRSP: 0,ABCDEFF1,2,273
OK
//------------------------------------------
// передача данных клиенту в режиме нотификации
AT+BLESIND=1,«FFFFFEEEEEDDDDDCCCCCBBBBBAAAAA»
+BLESIND: 0,ABCDEFF0,1,258
OK
С правой стороны экрана мы можем видеть серые стрелки. Если у характеристики есть свойство READ, то мы увидим стрелку вниз. Нажав её, мы получим запрос к серверу. При этом клиент ждет ответа и не может совершать другие транзакции. Если у характеристики есть свойство WRITE, то стрелочка направлена вверх. Нажав её, нам предлагается форма, где мы можем сформировать пакет для передачи. В приложении nRF Connect пакет WRITE отправляется с параметром подтверждения. Т.е. сервер должен подтвердить получение пакета на запись. Однако это не обязательное правило. Могут существовать WRITE пакеты без подтверждения. Обои модемы, в режиме клиента, могут отправлять пакеты как с подтверждением, так и без. Если у характеристики есть свойство NOTIFY, мы увидим три стрелочки вниз. Давайте разберемся с ней более подробно, т.к. у неё есть много особенностей.
Нотификация и индикация
Как я уже писал, в протоколе BlueTooth 4.0 описан режим, когда инициатором передачи данных становится не клиент (например телефон), а сервер. Это очень удобно для экономии батареи клиента. Нет необходимости постоянно запрашивать сервер об изменении данных. Когда они изменятся, он сам пришлет новые значения. Этот режим называется «подпись на нотификацию». Название не совсем корректное, т.к. этой же процедурой мы можем подписаться и на индикацию данных. Вся разница заключается в том, что при передаче индикации, клиент подтверждает получение данных, а при нотификации передача данных идет без подтверждения. Но, т.к. подтверждение отправляет сам стек, то в обоих случаях приложению клиента делать ничего не приходится. Обычно NORDIC рекомендует использовать нотификацию.
Следующий момент, на который стоит обратить внимание – это сама процедура нотификации. Она двухступенчатая. Сначала клиент подписывается на нотификацию, а затем записывает дескриптор СССD значением 0100. Разберем что это значит. Начнем с дескриптора. У каждого сервиса может быть несколько характеристик, а уже у характеристики может быть дескриптор. Дескрипторы расширяют возможности характеристик. В таблице атрибутов последние пять – это дескрипторы.
![](https://habrastorage.org/webt/qy/gs/cc/qygscckxjwtxob2l_lrmbz-lcgu.png)
Например, атрибут 0х2901 будет содержать текстовое значение в формате UTF-8, допустим такое: «Это значение описывает температуру салона автомобиля». Но нас больше интересует дескриптор 0х2902 – Client Characteristic Configuration Descriptor или сокращенно CCCD. Он позволяет запускать нотификацию или индикацию (передачу данных от сервера к клиенту), конечно, если сама характеристика имеет такие свойства. Т.е. CCCD похож на рубильник на стороне сервера, который клиент должен включить. Включение осуществляется записью в дескриптор 16-ти битного числа. У этого числа важны только два последние бита. Вот такие рисунки приводятся в спецификации bluetooth Core_V4.0
![](https://habrastorage.org/webt/gg/lk/xl/gglkxlvyr51zjsty6-zrwlvsrnk.png)
Т.е. если мы запишем в CCCD значение 0х0001 — мы получим нотификацию, если 0х0002 – индикацию. Ещё раз повторю, что это значения отправляет клиент серверу в пакете с заголовком «Запись дескриптора» к соответствующей характеристике. Тут есть два нюанса. Во-первых, в протоколе блютуса значения характеристик идут младшим битом вперед. Поэтому, например, для активации нотификации надо отправлять число 0x0100 и для индикации – 0x0200. Во-вторых, значение CCCD дескриптора сохраняется в настройках сервера. Т.е. если в результате помехи у нас разорвалась, а потом восстановилась связь, нам не надо повторно активировать CCCD дескриптор. В принципе, после пере-подключения клиент может считать значение CCCD, однако не все модемы поддерживают такой запрос. К примеру, Quectel его поддерживает, а SIMCom нет. Если записать в CCCD значение 0x0000, тогда передаваться данные клиенту не будут ни в каком виде.
Теперь разберемся с подписью на нотификацию. У обоих модемов есть соответствующая команда (AT+QBTGATCRN и AT+BLECRN). И здесь надо быть очень внимательным. Это команда клиента для своего стека. Этой командой активируются дополнительные возможности GATT стека клиента. (!!!) Выглядит это так. Мы сначала активируем на самом клиенте дополнительные ресурсы для приема пакетов нотификации (это и называется подписью на нотификацию), а затем, при помощи активации CCCD, запускаем поток данных с сервера на клиента. Более подробно об этом можно почитать здесь и здесь.
В андроиде за нотификацию отвечает функция setCharacteristicNotification, а в iOs — метод setNotify(). Поэтому телефоны сначала настраивают свои GATT стеки, говоря им, что любое полученное уведомление необходимо отправлять нашему приложению. Затем уже даем отмашку серверу отправлять данные.
Однако на практике, разные приложения, реализуют различные алгоритмы при подписи на нотификацию. Например, программа nRFConnect для андроида и iOs ведет себя по-разному. Если у модемов в свойствах характеристики есть индикация или нотификация, она на nRFConnect на андроиде начинает работать сразу, по умолчанию. Не надо посылать дополнительных пакетов. Просто передаем данные с соответствующим заголовком (AT+QBTGATSIND для MC60E и AT+BLESIND для SIM868E), и они будут приняты. Если мы работаем с программой nRFConnect на iOs, нам, для передачи данных, необходимо обязательно подписаться на нотификацию, либо отправить число 0х0100 в CCCD.
По-видимому, активируя одно действие, программа nRF Connect на iOs выполняет сразу два — подпись на нотификацию и отправка 0х0100 в CCCD. А у андроида вообще выполняет эти действия автоматически, не спрашивая у пользователя. В связи с этим, при написании приложений, надо понимать весь механизм нотификации и учитывать различное поведение модемов на одни и те же команды. Вот пожалуй и всё что я хотел бы вам рассказать о работе этих модемов.
Сотрудник Группы Компаний «Цезарь Сателлит»
Печерских Владимир