ESP32:Примеры/Bluetooth Low Energy: уведомления

Материал из Онлайн справочника
Перейти к: навигация, поиск

Перевод: Максим Кузьмин (Cubewriter)
Перевел 33127 статей для сайта.

Контакты:

Проверка/Оформление/Редактирование: Мякишев Е.А.


Pixel Art Mini Meow Animated.gif Черновик


Bluetooth Low Energy: уведомления

Рассмотрим скетч-пример, в котором ESP32-сервер после подключения клиента каждые 2 секунды отправляет ему уведомляющее сообщение. Это скетч-пример из ESP32-аддона для IDE Arduino.

Esp32 BLE notify 1.png

Примечание: ESP32 можно настроить так, чтобы она была и сервером, и клиентом.

Справочная информация

Bluetooth Low Energy (BLE) в ESP32

В этом Разделе мы изучим, что такое BLE (означает «Bluetooth low energy», что можно перевести как «Bluetooth с пониженным энергопотреблением») и как использовать его на практике. Чип ESP32 оснащен не только WiFi-компонентами, но и компонентами для передачи данных по Bluetooth и Bluetooth Low Energy (BLE).

В этом Разделе мы рассмотрим следующее:

  • Основы Bluetooth Low Energy (BLE)
  • Терминология BLE: UUID, сервис, характеристика и свойства
  • Взаимодействие между сервером и клиентом

Что такое Bluetooth?

Bluetooth – это беспроводной стандарт связи для обмена данными на коротком расстоянии. Как и WiFi, Bluetooth работает на частоте 2.4 ГГц.

Bluetooth communication.png

Bluetooth применяется во множестве разных ситуаций, где требуются беспроводные управление и передача данных. Например:

  • Передача аудиоданных в наушники или аудиосистему автомобиля
  • Коммуникация между периферийными устройствами и ПК
  • Передача данных между Bluetooth-устройствами

Другими словами, Bluetooth используется в ситуациях, когда для передачи данных между устройствами требуется непрерывное сквозное («точка-точка») подключение.

Что такое Bluetooth Low Energy (BLE)?

Bluetooth Low Energy (BLE) – это энергосберегающий вариант Bluetooth, и его главная область применения – это передача маленьких порций данных на короткие расстояния. Этот стандарт предназначен для очень маломощных проектов, питаемых от батареек-таблеток.

Ble communication.png

В отличие от Bluetooth, который включен постоянно, BLE-устройство постоянно находится в спящем режиме, кроме ситуаций, когда оно подключено к другим устройствам. Благодаря этому BLE-устройства потребляют очень мало питания. Эта функция крайне полезна для коммуникации типа M2M (англ. «machine-to-machine», т.е. «между машинами»), т.к. позволяет делать проекты из маленьких устройств, питаемых от батареек и работающих очень долгое время.

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

О других отличиях между Bluetooth и BLE можно почитать в этой статье или просто ознакомиться с ними в таблице ниже:

Bluetooth vs ble.png

Сервер и клиент Bluetooth

В стандарте Bluetooth Low Energy предусмотрено два типа устройств: сервер и клиент. В нашем примере ESP32 работает в качестве сервера, а смартфон – в качестве клиента.

Примечание: ESP32 (как и смартфон) может быть и сервером, и клиентом.

Сервер оповещает о своем существовании, чтобы его могли найти другие устройства, а также содержит данные для считывания клиентом. Клиент сканирует близлежащие устройства и, найдя искомый сервер, подключается к нему и начинает прослушивать входящие данные. Этот тип коммуникации называют «сквозным» или «точка-точка».

Ble server-client.png

Стандарт BLE предусматривает и другие типы подключения:

  • Режим вещания. Сервер передает данные нескольким клиентам, подключенным к нему.
  • Ячеистая сеть. Все устройства подключены друг к другу. Это тип соединения, когда в сети несколько устройств, и у каждого из них по несколько подключений.

Теперь давайте рассмотрим несколько важных терминов, касающихся BLE.

GATT

GATT расшифровывается как «generic attributes» («общие атрибуты»). Эта спецификация определяет иерархию данных, которую BLE-устройство демонстрирует другим BLE-устройствам, подключенным к нему. Другими словами, GATT определяет то, как два BLE-устройства отправляют и получают стандартные сообщения.

GATT hierarchy.png

На верхнем уровне этой иерархии – профиль, состоящий из одного или более сервисов. Каждый сервис содержит как минимум одну характеристику, а также может отсылать к другим сервисам.

Характеристики всегда находятся внутри сервисов, и это то место, где, собственно, хранятся данные во всей этой GATT-иерархии. Характеристика всегда содержит два атрибута: объявление характеристики (содержит метаданные о данных) и значение характеристики.

Кроме того, после значения характеристики могут идти дескрипторы, которые дополняют метаданные, содержащиеся в объявлении характеристики.

UUID

Каждый сервис, характеристика и дескриптор имеют собственный UUID (англ. «universally unique identifier», что переводится как «универсальный уникальный идентификатор»). UUID – это уникальное 128-битное (16-байтное) число вроде такого:

55072829-bc9e-4c53-938a-74a6d4c78776

Сокращенные UUID для всех сервисов, характеристик, профилей и т.д. можно найти на сайте Bluetooth SIG. К примеру, в неполной таблице ниже представлены сокращенные UUID для Bluetooth-сервисов. Полностью эту таблицу можно найти по этой ссылке.

Service UUID.png

Но если вашему приложению нужен собственный UUID, его можно сгенерировать при помощи этого UUID-генератора.

Итак, если вкратце, в UUID хранится уникальная идентифицирующая информация. С его помощью, к примеру, можно идентифицировать какой-либо сервис, предлагаемый Bluetooth-устройством.

Сервис «Battery Service»

Для примера давайте рассмотрим сервис «Battery Service». Он используется в большинстве питаемых от батареи BLE-устройств с целью определения текущего уровня заряда батареи (в процентах). Если BLE-устройство было сделано как следует, оно должно использовать стандартизированный сервис под названием «Battery Service».

Bluetooth service characteristics battery level 1.PNG

Этот сервис позволяет приложениям, которые подключены к BLE-устройству, постоянно знать процент заряда батареи независимо от производителя устройства, т.к. они используют для этого уникальный ID сервиса, обозначающий уровень заряда батареи.

Характеристика «Battery Level»

О характеристиках сервиса «Battery Service» можно узнать по этой ссылке. Нам же понадобится характеристика «Battery Level», возвращающая текущий уровень заряда батареи в диапазоне от 0 до 100 процентов.

Итак, если у вас есть BLE-устройство с батареей (то есть сервер с сервисом «Battery Service», рассылающий оповещения с характеристикой «Battery Level»), то клиент (то есть ваш смартфон, подключенный к этому BLE-устройству) может видеть уровень заряда батареи этого BLE-устройства, т.к. это широко используемый сервис со стандартной характеристикой.

Свойства характеристики «Battery Level»

Как мы уже говорили ранее, у одного сервиса может быть одна или несколько характеристик. Характеристика – это данные, передаваемые между клиентом и сервером. Характеристика может иметь разные свойства. К примеру, характеристика «Battery Level» предназначена только для чтения, и это обязательное требование для ее работы.

Вы также можете, если хотите, активировать свойство «Notify» (оно опционально), чтобы BLE-устройства, к примеру, сообщали об уровне заряда своей батареи каждые X секунд.

Bluetooth service characteristics battery level 2.PNG

В разных характеристиках могут быть разные свойства.

Структура Bluetooth-устройства

На рисунке ниже показано Bluetooth-устройство с сервисом «Battery Service», содержащим характеристику «Battery Level».

Bluetooth device overview.png

Понимать эту иерархию очень важно, т.к. это упростит понимание того, как использовать BLE и создавать собственные проекты, использующие эту функцию.

Более подробно о стандартных сервисах можно почитать на этой странице. Вы также можете создавать новые сервисы, которых в этом списке нет.

Это лишь базовая теория о BLE-устройствах, взаимодействии между клиентом и сервером, а также о некоторых стандартах, о которых нужно знать, чтобы работать с Bluetooth Low Energy. Стоит отметить, что мы затронули тему BLE поверхностно, и впереди нас ждет еще много интересного. Мы лишь рассмотрели несколько функций, имеющих отношение к нашим проектам.

Настоятельно рекомендуем почитать эту статью о BLE на «Википедии» – там о BLE рассказано более подробно.

Также советуем добавить в закладки сайт bluetooth.com, т.к. он содержит информацию о стандартных сервисах и UUID, которые вы будете использовать в BLE-устройствах и приложениях.

Как работает этот код

Теперь давайте рассмотрим этот код более подробно.

Он начинается с подключения библиотек, необходимых для работы с BLE.

  1. #include <BLEDevice.h>
  2. #include <BLEUtils.h>
  3. #include <BLEScan.h>
  4. #include <BLEAdvertisedDevice.h>

Следующая строчка создает указатель на «BLECharacteristic».

BLECharacteristic *pCharacteristic;

Далее создаем булеву переменную для хранения информации о том, подключен ли клиент к серверу или нет.

bool deviceConnected = false;

Переменная «value» будет использоваться для хранения значения характеристики.

  1. uint8_t value = 0;

Затем нам нужно задать UUID для новых сервиса и характеристики.

  1. #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
  2. #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

Вы можете оставить здесь старые UUID или воспользоваться UUID-генератором uuidgenerator.net, который позволяет создавать случайные UUID для своих сервисов и характеристик.

setup()

Пролистайте вниз до блока setup().

Здесь мы начинаем последовательную коммуникацию на скорости 115200 бод.

  1. Serial.begin(115200);

Затем создаем BLE-устройство под названием «MyESP32». Вы, впрочем, если хотите, можете задать ему любое другое название.

  1. // создаем BLE-устройство:
  2. BLEDevice::init("MyESP32");

В следующей строчке делаем это BLE-устройство сервером.

BLEServer *pServer = BLEDevice::createServer();

Привязываем к этому серверу две функции обратного вызова.

pServer->setCallbacks(new MyServerCallbacks());

Первая функция при успешном подключении клиента будет менять значение переменной «deviceConnected» на «true», а вторая – при отключении клиента менять его на «false».

  1. class MyServerCallbacks: public BLEServerCallbacks {
  2. void onConnect(BLEServer* pServer) {
  3.  deviceConnected = true;
  4. };
  5. void onDisconnect(BLEServer* pServer) {
  6.  deviceConnected = false;
  7. }
  8. };

После этого создаем BLE-сервис для сервера с заданным выше UUID.

  1. // создаем BLE-сервис:
  2. BLEService *pService = pServer->createService(SERVICE_UUID);

Затем создаем характеристику для этого сервиса. Как видите, мы используем для этого заданный выше UUID, а также указываем в параметрах свойства, которыми обладает этот сервис. В данном случае это: READ, WRITE, NOTIFY и INDICATE.

  1. // создаем BLE-характеристику:
  2. pCharacteristic = pService->createCharacteristic(
  3.  CHARACTERISTIC_UUID,
  4.  BLECharacteristic::PROPERTY_READ |
  5.  BLECharacteristic::PROPERTY_WRITE |
  6.  BLECharacteristic::PROPERTY_NOTIFY |
  7.  BLECharacteristic::PROPERTY_INDICATE
  8.  );

Также создаем BLE-дескриптор для этой характеристики:

  1. // создаем BLE-дескриптор:
  2. pCharacteristic->addDescriptor(new BLE2902());

Наконец, запускаем сервис и делаем так, чтобы он начал рассылать оповещения, чтобы другие BLE-устройства могли его просканировать и найти.

  1. // запускаем сервис:
  2. pService->start();
  3. // начинаем рассылку оповещений:
  4. pServer->getAdvertising()->start();

loop()

В блоке loop() проверяем, подключено ли устройство или нет. Если устройство подключено:

  • Оно напечатает в мониторе порта текущее значение в переменной «value»
  • Оно задаст новое значение характеристики
  • Оно отправит уведомление подключенному клиенту
  • Наконец, оно увеличит значение в переменной «value» на «1»
  1. if (deviceConnected) {
  2.  Serial.printf("*** NOTIFY: %d ***\n", value);
  3.  pCharacteristic->setValue(&value, 1);
  4.  pCharacteristic->notify();
  5.  //pCharacteristic->indicate();
  6.  value++;
  7. }

Этот процесс будет повторяться каждые 2 секунды. То есть при первом прохождении цикла loop() в переменной «value» будет задано значение «1», во втором – «2», в третьем – «3» и т.д.

delay(2000);

Тестируем скетч-пример «BLE_notify»

Убедитесь, что в IDE Arduino выбраны правильные плата и COM-порт, а потом загрузите код на ESP32. После этого откройте монитор порта на скорости 115200 бод.

Оставляем окно монитора порта открытым...

Подготавливаем смартфон

BLE-функционал есть у большинства современных смартфонов. Для нашего проекта должен подойти любой современный смартфон. О том, есть ли в вашем смартфоне BLE-функционал, можно почитать в его характеристиках.

Примечание: Смартфон может служить и клиентом, и сервером. В нашем случае он будет клиентом, который будет подключаться к BLE-серверу в лице ESP32.

Итак, возьмите свой смартфон...

Для нашего теста мы воспользуемся бесплатным приложением nRF Connect for Mobile от Nordic – оно есть и для Android (Google Play Store), и для iOS (App Store). Зайдите в один из этих магазинов приложений и вбейте в поиске «nRF Connect for Mobile». Установите приложение и откройте его.

NRf connect for mobile 1.jpg

Не забудьте зайти в настройки Bluetooth в своем смартфоне и включить Bluetooth. Заодно можно включить видимость для других устройств, что пригодится при тестировании других скетчей.

Настроив BLE в смартфоне и запустив на ESP32 скетч «BLE_notify», откройте приложение nRF Connect for Mobile и нажмите на кнопку сканирования ближайших устройств. Поиск должен показать ESP32 с названием «MyESP32».

NRf connect for mobile 2.jpg

Кликните по кнопке «Connect». Как видно на скриншоте ниже, у ESP32 есть сервис с UUID, который мы задали ранее. Если вы нажмете на этот сервис, раскроется его меню, в котором будет информация о характеристике, чей UUID мы тоже задали ранее.

NRf connect for mobile 3.jpg

У этой характеристики должно быть 4 свойства: INDICATE, NOTIFY, READ и WRITE. Четыре кнопки, обведенные красной рамкой на скриншоте ниже, позволяют:

  1. считывать текущее значение характеристики;
  2. записывать новое значение характеристики;
  3. включать/выключать свойство «Notify»;
  4. включать/выключать свойство «Indicate».
NRf connect for mobile 4.jpg

В этом примере мы оставим свойство «Notify» включенным, в результате чего смартфон будет получать новое значение характеристики каждые 2 секунды.

Кроме того, в приложении будет показано сообщение, которое ESP32 печатает в мониторе порта. Это значит, что все работает как надо.

Примечание: Значение, получаемое приложением, имеет шестнадцатеричный формат.

Ble esp32 hexadecimal values.png

Теперь можно выключить Bluetooth в смартфоне и закрыть смартфонное приложение.

Необходимое оборудование

  • Плата ESP32 - 1шт.;

Схема

Для данного примера нужна только плата.

Esp32 board exm 1.png

Код

  1. /*
  2.     Видео: https://www.youtube.com/watch?v=oCMOYS71NIU
  3.     Основан на скетче-примере Нила Колбана для IDF:
  4.     https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
  5.     Порт в ESP32-аддон для IDE Arduino - Эвандро Коперчини
  6.    
  7.     Создаем BLE-сервер, который после подключения клиента
  8.     начнет отправлять ему периодические уведомляющие сообщения.
  9.  
  10.     Сервис оповещает о себе при помощи следующего UUID:
  11.     4fafc201-1fb5-459e-8fcc-c5c9c331914b
  12.     UUID характеристики: beb5483e-36e1-4688-b7f5-ea07361b26a8
  13.  
  14.     Шаги создания BLE-сервера таковы:
  15.      1. Создание BLE-сервера
  16.      2. Создание BLE-сервиса
  17.      3. Создание BLE-характеристики в BLE-сервисе
  18.      4. Создание BLE-дескриптора в BLE-характеристике
  19.      5. Запуск сервера
  20.      6. Запуск рассылки оповещений (advertising)
  21.    
  22.     В результате устройство, выступающее сервером,
  23.     запустит фоновую задачу по рассылке уведомляющих сообщений
  24.     каждые несколько секунд.
  25. */
  26.  
  27. #include <BLEDevice.h>
  28. #include <BLEServer.h>
  29. #include <BLEUtils.h>
  30. #include <BLE2902.h>
  31.  
  32. BLECharacteristic *pCharacteristic;
  33. bool deviceConnected = false;
  34. uint8_t value = 0;
  35.  
  36. // Сайт для генерирования UUID:
  37. // https://www.uuidgenerator.net/
  38.  
  39. #define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
  40. #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
  41.  
  42.  
  43. class MyServerCallbacks: public BLEServerCallbacks {
  44.     void onConnect(BLEServer* pServer) {
  45.       deviceConnected = true;
  46.     };
  47.  
  48.     void onDisconnect(BLEServer* pServer) {
  49.       deviceConnected = false;
  50.     }
  51. };
  52.  
  53.  
  54.  
  55. void setup() {
  56.   Serial.begin(115200);
  57.  
  58.   // создаем BLE-устройство:
  59.   BLEDevice::init("MyESP32");
  60.  
  61.   // Создаем BLE-сервер:
  62.   BLEServer *pServer = BLEDevice::createServer();
  63.   pServer->setCallbacks(new MyServerCallbacks());
  64.  
  65.   // Создаем BLE-сервис:
  66.   BLEService *pService = pServer->createService(SERVICE_UUID);
  67.  
  68.   // Создаем BLE-характеристику:
  69.   pCharacteristic = pService->createCharacteristic(
  70.                       CHARACTERISTIC_UUID,
  71.                       BLECharacteristic::PROPERTY_READ   |
  72.                       BLECharacteristic::PROPERTY_WRITE  |
  73.                       BLECharacteristic::PROPERTY_NOTIFY |
  74.                       BLECharacteristic::PROPERTY_INDICATE
  75.                     );
  76.  
  77.   // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  78.   // создаем BLE-дескриптор:
  79.   pCharacteristic->addDescriptor(new BLE2902());
  80.  
  81.   // запускаем сервис:
  82.   pService->start();
  83.  
  84.   // запускаем оповещения (advertising):
  85.   pServer->getAdvertising()->start();
  86.   Serial.println("Waiting a client connection to notify...");  //  "Ждем подключения клиента, чтобы отправить ему уведомление..."
  87. }
  88.  
  89. void loop() {
  90.  
  91.   if (deviceConnected) {
  92.     Serial.printf("*** NOTIFY: %d ***\n", value);
  93.     pCharacteristic->setValue(&value, 1);
  94.     pCharacteristic->notify();
  95.     //pCharacteristic->indicate();
  96.     value++;
  97.   }
  98.   delay(2000);
  99. }

См.также

Внешние ссылки