ESP32:Примеры/BLE-сервер и BLE-клиент при помощи ESP32

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

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

Контакты:

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


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


Содержание

BLE-сервер и BLE-клиент при помощи ESP32

В этом примере мы научимся настраивать Bluetooth-соединение между двумя платами ESP32. Одна из этих ESP32 будет работать в качестве сервера, а другая – в качестве клиента. В проекте, который мы сделаем ниже, к серверу будет подключен датчик температуры и влажности (DHT11/DHT22), и этот сервер каждые 10 секунд будет отправлять клиенту самые последние данные, считанные с датчика. Кроме того, клиент будет печатать эти данные в подключенном к нему OLED-дисплее.

Итак, на одну ESP32 будет загружен серверный скетч, а на другую – клиентский. Клиент начнет сканировать близлежащие устройства и, найдя другую ESP32, подключится к ней. Спустя несколько секунд ESP32-клиент начнет получать данные о температуре и влажности.

Установка библиотек

Устанавливаем библиотеку «DHT Sensor»

Чтобы считывать данные с датчика DHT11/DHT22 при помощи IDE Arduino, нам понадобится установить библиотеку «DHT Sensor». Для этого проделайте следующее:

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки;
  2. Распакуйте скачанный архив. У вас должна получиться папка под названием «DHT-sensor-library-master»;
  3. Переименуйте ее на «DHT_sensor»;
  4. Переместите папку «DHT_sensor» в папку «libraries», которая находится внутри папки, куда установлена IDE Arduino;
  5. Откройте IDE Arduino;

Устанавливаем библиотеку «Adafruit Sensor»

Нам также нужно установить библиотеку «Adafruit Sensor». Для этого проделайте следующее:

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки;
  2. Распакуйте скачанный архив. У вас должна получиться папка «Adafruit_sensor-master»;
  3. Переименуйте ее на «Adafruit_Sensor»;
  4. Переместите папку «Adafruit_Sensor» в папку «libraries», которая находится внутри папки, где установлена IDE Arduino;
  5. Откройте IDE Arduino;

Устанавливаем библиотеку «Adafruit SSD1306»

Библиотека «Adafruit SSD1306» упрощает печать текста на OLED-дисплее при помощи IDE Arduino. Чтобы установить эту библиотеку в IDE Arduino, проделайте следующее:

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки
  2. Распакуйте этот архив. У вас должна появиться папка «Adafruit_SSD1306-master»
  3. Переименуйте ее на «Adafruit_SSD1306»
  4. Переместите папку «Adafruit_SSD1306» в папку «libraries», которая находится внутри папки, где установлена ваша IDE Arduino
  5. Откройте IDE Arduino

Устанавливаем библиотеку «Adafruit GFX»

Нам также понадобится библиотека «Adafruit GFX». Чтобы установить ее в IDE Arduino, проделайте следующее:

  1. Кликните тут, чтобы скачать ZIP-архив библиотеки
  2. Распакуйте этот архив. У вас должна получиться папка «Adafruit-GFX-library-master»
  3. Переименуйте ее на «Adafruit_GFX_library»
  4. Переместите папку «Adafruit_GFX_library» в папку «libraries», которая находится в папке, где установлена ваша IDE Arduino
  5. Откройте IDE Arduino

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

Серверный код

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

Примечание: Для этого скетча необходимо, чтобы в IDE Arduino были установлены библиотеки «ESP32 BLE Arduino», «DHT sensor» и «Adafruit Sensor».

Импортируем библиотеки

Начинаем с импорта необходимых для этого скетча библиотек:

  1. #include <BLEDevice.h>
  2. #include <BLEServer.h>
  3. #include <BLEUtils.h>
  4. #include <BLE2902.h>
  5. #include "DHT.h"

Выбираем температурную единицу измерения

По умолчанию ESP32 будет отправлять температуру в градусах Цельсия. Чтобы она начала отправлять температуру в градусах Фаренгейта, удалите или закомментируйте строчку ниже:

  1. // закомментируйте строчку ниже, если вам нужны градусы Фаренгейта:
  2. #define temperatureCelsius

В общем, решать вам. Вы можете либо оставить градусы Цельсия, либо закомментировать эту строчку, чтобы скетч начал отправлять температуру в градусах Фаренгейта. Мы в этом примере будем использовать градусы Цельсия.

Задаем название для BLE-сервера

Оставьте здесь прежнее название, иначе название сервера нужно будет также поменять в скетче клиента, т.к. название BLE-сервера должно совпадать и в серверном, и в клиентском скетчах.

#define bleServerName "dhtESP32"

Выбираем тип датчика

Во этом фрагменте мы выбираем тип используемого датчика. В данном случае выбран DHT11:

  1. #define DHTTYPE DHT11 // DHT 11
  2. //#define DHTTYPE DHT21 // DHT 21 (AM2301)
  3. //#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321

Задаем UUID

Я также советую использовать UUID, заданные в скетче по умолчанию. В противном случае их также нужно будет поменять в клиентском скетче, чтобы клиент мог найти сервис и прочесть данные характеристик. UUID заключены в кавычки во фрагменте ниже:

  1. #define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"
  2. #ifdef temperatureCelsius
  3. BLECharacteristic dhtTemperatureCelsiusCharacteristics("cba1d466-344c4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
  4. BLEDescriptor dhtTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
  5. #else
  6. BLECharacteristic dhtTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY);
  7. BLEDescriptor dhtTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2901));
  8. #endif
  9. BLECharacteristic dhtHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
  10. BLEDescriptor dhtHumidityDescriptor(BLEUUID((uint16_t)0x2903));

Задаем номер контакта

В следующей строчке задаем номер контакта, к которому подключен датчик DHT. В нашем случае датчик будет подключен к цифровому контакту GPIO14. Если вы подключили его к какому-то другому контакту, не забудьте поменять и эту строчку кода:

const int DHTPin = 14;

setup()

Блок setup() начинаем с запуска датчика DHT:

  1. // запускаем датчик DHT:
  2. dht.begin();

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

  1. // запускаем последовательную коммуникацию:
  2. Serial.begin(115200);

Создаем новое BLE-устройство, которому даем заданное ранее название «bleServerName».

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

Делаем это BLE-устройство сервером и привязываем к нему функции обратного вызова.

  1. // создаем BLE-сервер:
  2. BLEServer *pServer = BLEDevice::createServer();
  3. pServer->setCallbacks(new MyServerCallbacks());

Функция MyServerCallbacks() содержит две функции обратного вызова, переключающие значение в булевой переменной «deviceConnected» между «true» и «false» в зависимости от текущего статуса подключения BLE-устройства. Это значит, что если клиент подключен к серверу, этим значением будет «true», а если не подключен, то «false». Ниже – фрагмент кода, задающий то, как работает функция MyServerCallbacks():

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

Далее в блоке setup() запускаем BLE-устройство с заданным ранее UUID.

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

Затем создаем температурную BLE-характеристику. Если вы выбрали градусы Цельсия, то ESP32-сервер будет использовать вот эту характеристику:

  1. #ifdef temperatureCelsius
  2.     dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
  3.     dhtTemperatureCelsiusDescriptor.setValue("DHT temperature Celsius");
  4.                                          //  "Температура в Цельсиях"
  5.     dhtTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());

В противном случае будет использована характеристика для градусов Фаренгейта:

  1. #else
  2.     dhtService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
  3.     dhtTemperatureFahrenheitDescriptor.setValue("DHT temperature Fahrenheit");
  4.                                             //  "Температура в Фаренгейтах"
  5.     dhtTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902());

После этого ESP32-сервер запустит характеристику для данных о влажности:

  1. dhtService->addCharacteristic(&dhtHumidityCharacteristics);
  2. dhtHumidityDescriptor.setValue("DHT humidity");
  3.                              //  "Влажность"
  4. dhtHumidityCharacteristics.addDescriptor(new BLE2902());

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

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

loop()

Код в блоке loop() достаточно прост. Мы просто постоянно проверяем, подключено ли устройство или нет. Если подключено, считываем текущую температуру и влажность:

  1. if (deviceConnected) {
  2.     // считываем температуру в градусах Цельсия (по умолчанию):
  3.     float t = dht.readTemperature();
  4.     // считываем температуру в градусах Фаренгейта
  5.     // (isFahrenheit = true):
  6.     float f = dht.readTemperature(true);
  7.     // считываем влажность:
  8.     float h = dht.readHumidity();

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

  1. if (isnan(h) || isnan(t) || isnan(f)) {
  2.   Serial.println("Failed to read from DHT sensor!");
  3.              //  "Не удалось прочесть данные с датчика DHT!"
  4.   return;

Если считанные данные корректны, код продолжит работать.

Если вы используете температуру в Цельсиях, то запустится фрагмент кода ниже. В нем данные о температуре сохраняются во временную переменную «temperatureCTemp».

  1. #ifdef temperatureCelsius
  2.   static char temperatureCTemp[7];
  3.   dtostrf(t, 6, 2, temperatureCTemp);

В двух строчках ниже мы обновляем текущее значение характеристики с помощью setValue(), а также отправляем его подключенному клиенту при помощи notify().

  1. dhtTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
  2. dhtTemperatureCelsiusCharacteristics.notify();

Три строчки ниже печатают в мониторе порта данные о температуре (в отладочных целях).

  1. Serial.print("Temperature Celsius: ");
  2.          //  "Температура в градусах Цельсия: "
  3. Serial.print(t);
  4. Serial.print(" *C");

Далее используем аналогичный способ для отправки температуры в градусах Фаренгейта.

  1. #else
  2.   static char temperatureFTemp[7];
  3.   dtostrf(f, 6, 2, temperatureFTemp);
  4.   // задаем значение для температурной характеристики (Фаренгейт)
  5.   // и отправляем уведомление подключенному клиенту:
  6.   dhtTemperatureFahrenheitCharacteristics.setValue(temperatureFTemp);
  7.   dhtTemperatureFahrenheitCharacteristics.notify();
  8.   Serial.print("Temperature Fahrenheit: ");
  9.            //  "Температура в градусах Фаренгейта: "
  10.   Serial.print(f);
  11.   Serial.print(" *F");
  12. #endif

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

  1. // отправляем уведомление о том,
  2. // что с датчика DHT считаны данные о влажности:
  3. static char humidityTemp[7];
  4. dtostrf(h, 6, 2, humidityTemp);
  5. // задаем значение для влажностной характеристики
  6. // и отправляем уведомление подключенному клиенту:
  7. dhtHumidityCharacteristics.setValue(humidityTemp);
  8. dhtHumidityCharacteristics.notify();  
  9. Serial.print(" - Humidity: ");
  10.          //  " - Влажность: "
  11. Serial.print(h);
  12. Serial.println(" %");

Функция delay() задает 10-секундную паузу между считываниями данных с датчика.

delay(10000);

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

Тестирование BLE-сервера на базе ESP32

Загрузите код на плату. Затем возьмите смартфон и откройте приложение nRF connect от Nordic.

Убедитесь, что ESP32 включена, затем включите Bluetooth на смартфоне и начните сканирование. В результате вы должны найти устройство под названием «dhtESP32» – это название ESP32-сервера, заданное нами ранее.

Esp32 server ble rf connect dhtESP32 1.PNG

Подключитесь к нему и откройте сервис, созданный нами в скетче.

Esp32 server ble rf connect dhtESP32 2.PNG

Активируйте свойства «Notify» для температуры и влажности.

Esp32 server ble rf connect dhtESP32 3.PNG

После этого в приложении каждые 10 секунд должны начать появляться новые данные от датчика.

Esp32 server ble rf connect readings in the app.png

Если все в порядке, это значит, что BLE-сервер на базе ESP32 работает как надо!

На этом пока все. Перейдите к следующему Разделу, чтобы завершить проект. В нем мы научимся создавать ESP32-клиент, получающий данные от сервера и печатающего их в OLED-мониторе.

Клиентский код

Импортируем библиотеки

Вначале подключаем необходимые библиотеки:

  1. #include "BLEDevice.h"
  2. #include <Wire.h>
  3. #include <Adafruit_SSD1306.h>
  4. #include <Adafruit_GFX.h>

Выбираем температурную единицу измерения

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

  1. // по умолчанию температура будет в градусах Цельсия,
  2. // но если вам нужны градусы Фаренгейта, закомментируйте строчку ниже:
  3. #define temperatureCelsius

Важно: Клиент и сервер должны использовать одну и ту же температурную единицу измерения, иначе проект просто не будет работать.

Задаем название и UUID для BLE-сервера

Как уже говорилось ранее, лучше использовать те UUID и название BLE-сервера, что стоят в коде по умолчанию – так они будут совпадать с теми, что заданы в серверном скетче.

Таким образом, название BLE-сервера должно быть следующим:

#define bleServerName "dhtESP32"

UUID должны быть такими:

  1. // UUID для сервиса:
  2. static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
  3.  
  4. #ifdef temperatureCelsius
  5.   // UUID для температурной характеристики (градусы Цельсия):
  6.   static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
  7. #else
  8.   // UUID для температурной характеристики (градусы Фаренгейта):
  9.   static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
  10. #endif
  11.  
  12. // UUID для влажностной характеристики:
  13. static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

Объявляем переменные

Далее объявляем несколько переменных, которые понадобятся нам в дальнейшем:

  1. // переменные, используемые для определения того,
  2. // нужно ли начинать подключение или завершено ли подключение:
  3. static boolean doConnect = false;
  4. static boolean connected = false;
  5.  
  6. // адрес периферийного устройства;
  7. // (он должен быть найден во время сканирования):
  8. static BLEAddress *pServerAddress;
  9.  
  10. // характеристики, данные которых мы хотим прочесть:
  11. static BLERemoteCharacteristic* temperatureCharacteristic;
  12. static BLERemoteCharacteristic* humidityCharacteristic;
  13.  
  14. // включение/выключение уведомлений:
  15. const uint8_t notificationOn[] = {0x1, 0x0};
  16. const uint8_t notificationOff[] = {0x0, 0x0};

setup()

Блок setup() начинаем, задав правильные настройки для OLED-дисплея:

  1. Wire.begin();
  2. display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  3. display.clearDisplay();
  4. display.setTextSize(1);
  5. //display.setBackgroundcolor(BLACK);
  6. display.setTextColor(WHITE,0);

Затем печатаем на OLED-дисплее первое сообщение: «DHT READINGS» («Данные от DHT-датчика").

  1. display.setCursor(30,0);
  2. display.print("DHT READINGS");  //  "Данные от DHT-датчика"
  3. display.display();

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

Serial.begin(115200);

Инициализируем BLE-устройство:

BLEDevice::init("");

Сканируем близлежащие устройства

Фрагмент кода ниже предназначен для сканирования находящихся рядом устройств.

  1. BLEScan* pBLEScan = BLEDevice::getScan();
  2. pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  3. pBLEScan->setActiveScan(true);
  4. pBLEScan->start(30);

Создаем функцию MyAdvertisedDeviceCallbacks()

Обратите внимание, что функция MyAdvertisedDeviceCallbacks() не только ищет BLE-устройство, но и проверяет, имеет ли найденное BLE-устройство правильное название. Если имеет, эта функция завершает сканирование и меняет значение в булевой переменной «doConnect» на «true». Так мы будем знать, что нашли сервер, который искали, и можем начать подключение к нему.

  1. // функция обратного вызова, которая будет вызвана
  2. // при получении оповещения от другого устройства:
  3. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  4.   void onResult(BLEAdvertisedDevice advertisedDevice) {
  5.     // проверяем, совпадает ли название
  6.     // BLE-сервера, рассылающего оповещения:
  7.     if (advertisedDevice.getName() == bleServerName) {
  8.       // мы нашли, что искали,
  9.       // поэтому сканирование можно завершить:
  10.       advertisedDevice.getScan()->stop();
  11.       // сохраняем адрес устройства, рассылающего оповещения:
  12.       pServerAddress = new BLEAddress(advertisedDevice.getAddress());
  13.       // задаем индикатор, дающий понять,
  14.       // что мы готовы подключиться:
  15.       doConnect = true;
  16.       Serial.println("Device found. Connecting!");
  17.                  //  "Устройство найдено. Подключаемся!"
  18.     }
  19.   }
  20. };

Подключаемся к серверу

Если значением в переменной «doConnect» является «true», BLE-клиент попробует подключиться к BLE-серверу. Функция connectToServer() управляет всеми подключениями между клиентом и сервером.

  1. // функция для подключения к BLE-серверу,
  2. // у которого есть название, сервис и характеристики:
  3. bool connectToServer(BLEAddress pAddress) {
  4.    BLEClient* pClient = BLEDevice::createClient();
  5.  
  6.   // подключаемся к удаленному BLE-серверу:
  7.   pClient->connect(pAddress);
  8.   Serial.println(" - Connected to server");
  9.              //  " – Подключились к серверу"
  10.  
  11.   // считываем UUID искомого сервиса:
  12.   BLERemoteService* pRemoteService = pClient->getService(dhtServiceUUID);
  13.   if (pRemoteService == nullptr) {
  14.     Serial.print("Failed to find our service UUID: ");
  15.              //  "Не удалось найти UUID нашего сервиса: "
  16.     Serial.println(dhtServiceUUID.toString().c_str());
  17.     return (false);
  18.   }
  19.  
  20.   // считываем UUID искомых характеристик:
  21.   temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  22.   humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);
  23.  
  24.   if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
  25.     Serial.print("Failed to find our characteristic UUID");
  26.              //  "Не удалось найти UUID нашей характеристики"
  27.     return false;
  28.   }
  29.   Serial.println(" - Found our characteristics");
  30.              //  " – Наши характеристики найдены"

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

  1.   // присваиваем характеристикам функции обратного вызова:
  2.   temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
  3.   humidityCharacteristic->registerForNotify(humidityNotifyCallback);
  4. }

После подключения BLE-клиента к серверу нам понадобится активировать свойство «property» для каждой характеристики. Используем для этого методом writeValue().

  1. temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
  2. humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);

Обрабатываем получение новых данных

Получив вместе с уведомлением новые данные, клиент запустит функции temperatureNotifyCallback() и humidityNotifyCallback(), ответственные за получение новых значений, обновление данных на OLED-экране и их печать в мониторе порта.

  1. // функция обратного вызова, которая будет запущена,
  2. // если BLE-сервер пришлет вместе с уведомлением
  3. // корректные данные о температуре:
  4. static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  5.   display.setCursor(34,10);
  6.   display.print((char*)pData);
  7.   Serial.print("Temperature: ");  //  "Температура: "
  8.   Serial.print((char*)pData);
  9.   #ifdef temperatureCelsius
  10.     // температура в градусах Цельсия:
  11.     display.print(" *C");
  12.     Serial.print(" *C");
  13.   #else
  14.     // температура в градусах Фаренгейта:
  15.     display.print(" *F");
  16.     Serial.print(" *F");
  17.   #endif  
  18.   display.display();
  19. }
  20.  
  21. // функция обратного вызова, которая будет запущена,
  22. // если BLE-сервер пришлет вместе с уведомлением
  23. // корректные данные о влажности:
  24. static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  25.   display.setCursor(34,20);
  26.   display.print((char*)pData);
  27.   display.print(" %");
  28.   display.display();
  29.   Serial.print(" Humidity: ");  //  " Влажность: "
  30.   Serial.print((char*)pData);
  31.   Serial.println(" %");
  32. }

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

Тестируем проект

С кодом все. Можете загрузить его на ESP32.

Загрузив код, запитайте ESP32, настроенную ранее (ESP32-сервер), а затем запитайте ESP32, настроенную в этом Разделе (ESP32-клиент). Клиент начнет сканировать близлежащие устройства и, найдя другую ESP32, установит с ней Bluetooth-соединение. Если все настроено правильно, ESP32-клиент будет каждые 10 секунд показывать на OLED-дисплее самые последние данные, считанные с датчика.

Возможные проблемы и их решение

Если на OLED-дисплее не печатаются никакие данные, то причины у этого, как правило, две:

  • Датчик DHT не смог прочесть данные о температуре и влажности. Откройте монитор порта для BLE-сервера, чтобы посмотреть, печатаются ли в нем какие-нибудь данные
  • У клиента не получается подключиться к серверу. Перезагрузите ESP32, на которой работает клиентский скетч

Проект готов! Теперь вы знаете, как создать два BLE-устройства, обменивающихся полезными данными. Смело экспериментируйте с этими скетчами и пробуйте подключить другие датчики.

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

Схема

Сервер

Давайте начнем с подключения компонентов друг к другу. Подключите датчик DHT11/DHT22 к ESP32 при помощи резистора на 4.7 кОм (см. схему ниже).

Esp32 ble server dht1122 1.PNG
Pixel Art Mini Meow Animated.gif На этой схеме изображена 36-контактная версия ESP32 DEVKIT DOIT V1. Если вы используете другую модель, обязательно сверьтесь с ее распиновкой.


Датчик DHT11/DHT22 можно подключить к любому цифровому контакту ESP32. Потом вам нужно будет лишь поменять в скетче номер контакта, к которому подключен датчик.

Клиент

ESP32-клиент будет подключен к OLED-дисплею, благодаря чему мы будем видеть данные, отправляемые сервером клиенту по Bluetooth. Подключите OLED-дисплей к ESP32-клиенту согласно схеме ниже.

OLED-дисплею нужно 3.3-вольтовое питание, контакт SCL нужно подключить к GPIO22, а контакт SDA – к GPIO21.

Esp32 ble client oled 1.PNG

Код

Скетч для сервера

  1. /*********
  2.   Руи Сантос
  3.   Более подробно о проекте на: http://randomnerdtutorials.com  
  4. *********/
  5.  
  6. #include <BLEDevice.h>
  7. #include <BLEServer.h>
  8. #include <BLEUtils.h>
  9. #include <BLE2902.h>
  10. #include "DHT.h"
  11.  
  12. // по умолчанию температура будет в градусах Цельсия;
  13. // закомментируйте строчку ниже, если вам нужны градусы Фаренгейта:
  14. #define temperatureCelsius
  15.  
  16. // даем название BLE-серверу:
  17. #define bleServerName "dhtESP32"
  18.  
  19. // оставьте незакомментированной строчку,
  20. // соответствующую используемому вами типу датчика:
  21. #define DHTTYPE DHT11   // DHT 11
  22. //#define DHTTYPE DHT21   // DHT 21 (AM2301)
  23. //#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
  24.  
  25. // для генерирования UUID можно воспользоваться этим сайтом:
  26. // https://www.uuidgenerator.net/
  27. #define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"
  28.  
  29. #ifdef temperatureCelsius
  30.   BLECharacteristic dhtTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
  31.   BLEDescriptor dhtTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
  32. #else
  33.   BLECharacteristic dhtTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY);
  34.   BLEDescriptor dhtTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2901));
  35. #endif
  36.  
  37. BLECharacteristic dhtHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
  38. BLEDescriptor dhtHumidityDescriptor(BLEUUID((uint16_t)0x2903));
  39.  
  40. // контакт, к которому подключен датчик DHT:
  41. const int DHTPin = 14;
  42.  
  43. // инициализируем датчик DHT:
  44. DHT dht(DHTPin, DHTTYPE);
  45.  
  46. bool deviceConnected = false;
  47.  
  48. // задаем функции обратного вызова onConnect() и onDisconnect():
  49. class MyServerCallbacks: public BLEServerCallbacks {
  50.   void onConnect(BLEServer* pServer) {
  51.     deviceConnected = true;
  52.   };
  53.   void onDisconnect(BLEServer* pServer) {
  54.     deviceConnected = false;
  55.   }
  56. };
  57.  
  58. void setup() {
  59.   // запускаем датчик DHT:
  60.   dht.begin();
  61.  
  62.   // запускаем последовательную коммуникацию:
  63.   Serial.begin(115200);
  64.  
  65.   // создаем BLE-устройство:
  66.   BLEDevice::init(bleServerName);
  67.  
  68.   // создаем BLE-сервер:
  69.   BLEServer *pServer = BLEDevice::createServer();
  70.   pServer->setCallbacks(new MyServerCallbacks());
  71.  
  72.   // создаем BLE-сервис:
  73.   BLEService *dhtService = pServer->createService(SERVICE_UUID);
  74.  
  75.   // создаем BLE-характеристики и BLE-дескриптор: bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml      
  76.  
  77.   #ifdef temperatureCelsius
  78.     dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
  79.     dhtTemperatureCelsiusDescriptor.setValue("DHT temperature Celsius");
  80.                                          //  "Температура в Цельсиях"
  81.     dhtTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());
  82.   #else
  83.     dhtService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
  84.     dhtTemperatureFahrenheitDescriptor.setValue("DHT temperature Fahrenheit");
  85.                                          //  "Температура в Фаренгейтах"
  86.     dhtTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902());
  87.   #endif  
  88.   dhtService->addCharacteristic(&dhtHumidityCharacteristics);
  89.   dhtHumidityDescriptor.setValue("DHT humidity");
  90.                              //  "Влажность"
  91.   dhtHumidityCharacteristics.addDescriptor(new BLE2902());
  92.  
  93.   // запускаем сервис:
  94.   dhtService->start();
  95.  
  96.   // запускаем рассылку оповещений:
  97.   pServer->getAdvertising()->start();
  98.   Serial.println("Waiting a client connection to notify...");
  99.              //  "Ждем подключения клиента, чтобы отправить уведомление..."
  100. }
  101.  
  102. void loop() {
  103.   if (deviceConnected) {
  104.     // считываем температуру в градусах Цельсия (по умолчанию):
  105.     float t = dht.readTemperature();
  106.     // считываем температуру в градусах Фаренгейта
  107.     // (isFahrenheit = true):
  108.     float f = dht.readTemperature(true);
  109.     // считываем влажность:
  110.     float h = dht.readHumidity();
  111.  
  112.     // проверяем, удалось ли прочесть данные,
  113.     // и если нет, то выходим из loop(), чтобы попробовать снова:
  114.     if (isnan(h) || isnan(t) || isnan(f)) {
  115.       Serial.println("Failed to read from DHT sensor!");
  116.                  //  "Не удалось прочесть данные с датчика DHT!"
  117.       return;
  118.     }
  119.     // отправляем уведомление о том,
  120.     // что с датчика DHT считаны данные о температуре:
  121.     #ifdef temperatureCelsius
  122.       static char temperatureCTemp[7];
  123.       dtostrf(t, 6, 2, temperatureCTemp);
  124.       // задаем значение для температурной характеристики (Цельсий)
  125.       // и отправляем уведомление подключенному клиенту:
  126.       dhtTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
  127.       dhtTemperatureCelsiusCharacteristics.notify();
  128.       Serial.print("Temperature Celsius: ");
  129.                //  "Температура в градусах Цельсия: "
  130.       Serial.print(t);
  131.       Serial.print(" *C");
  132.     #else
  133.       static char temperatureFTemp[7];
  134.       dtostrf(f, 6, 2, temperatureFTemp);
  135.       // задаем значение для температурной характеристики (Фаренгейт)
  136.       // и отправляем уведомление подключенному клиенту:
  137.    dhtTemperatureFahrenheitCharacteristics.setValue(temperatureFTemp);
  138.       dhtTemperatureFahrenheitCharacteristics.notify();
  139.       Serial.print("Temperature Fahrenheit: ");
  140.                //  "Температура в градусах Фаренгейта: "
  141.       Serial.print(f);
  142.       Serial.print(" *F");
  143.     #endif
  144.    
  145.     // отправляем уведомление о том,
  146.     // что с датчика DHT считаны данные о влажности:
  147.     static char humidityTemp[7];
  148.     dtostrf(h, 6, 2, humidityTemp);
  149.     // задаем значение для влажностной характеристики
  150.     // и отправляем уведомление подключенному клиенту:
  151.     dhtHumidityCharacteristics.setValue(humidityTemp);
  152.     dhtHumidityCharacteristics.notify();  
  153.     Serial.print(" - Humidity: ");
  154.              //  " - Влажность: "
  155.     Serial.print(h);
  156.     Serial.println(" %");
  157.    
  158.     delay(10000);
  159.   }
  160. }

Скетч для клиента

  1. /*********
  2.   Руи Сантос
  3.   Более подробно о проекте на: http://randomnerdtutorials.com  
  4. *********/
  5.  
  6. #include "BLEDevice.h"
  7. #include <Wire.h>
  8. #include <Adafruit_SSD1306.h>
  9. #include <Adafruit_GFX.h>
  10.  
  11. // по умолчанию температура будет в градусах Цельсия,
  12. // но если вам нужны градусы Фаренгейта, закомментируйте строчку ниже:
  13. #define temperatureCelsius
  14.  
  15. // задаем название для BLE-сервера
  16. // (это другая ESP32, на которой запущен серверный скетч):
  17. #define bleServerName "dhtESP32"
  18.  
  19. // UUID для сервиса:
  20. static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
  21.  
  22. #ifdef temperatureCelsius
  23.   // UUID для температурной характеристики (градусы Цельсия):
  24.   static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
  25. #else
  26.   // UUID для температурной характеристики (градусы Фаренгейта):
  27.   static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
  28. #endif
  29.  
  30. // UUID для влажностной характеристики:
  31. static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");
  32.  
  33. // переменные, используемые для определения того,
  34. // нужно ли начинать подключение или завершено ли подключение:
  35. static boolean doConnect = false;
  36. static boolean connected = false;
  37.  
  38. // адрес периферийного устройства;
  39. // (он должен быть найден во время сканирования):
  40. static BLEAddress *pServerAddress;
  41.  
  42. // характеристики, данные которых мы хотим прочесть:
  43. static BLERemoteCharacteristic* temperatureCharacteristic;
  44. static BLERemoteCharacteristic* humidityCharacteristic;
  45.  
  46. // включение/выключение уведомлений:
  47. const uint8_t notificationOn[] = {0x1, 0x0};
  48. const uint8_t notificationOff[] = {0x0, 0x0};
  49.  
  50. #define SCREEN_WIDTH 128 // ширина OLED-дисплея (в пикселях)
  51. #define SCREEN_HEIGHT 32 // высота OLED-дисплея (в пикселях)
  52.  
  53. // создаем дисплей SSD1306,
  54. // подключенный через I2C (контакты SDA и SCL):
  55. #define OLED_RESET     4 // номер контакта для сброса
  56.                          // (или «-1», если контакт для сброса
  57.                          // такой же, как и у Arduino)
  58. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  59.  
  60. // подключаемся к BLE-серверу,
  61. // у которого есть название, сервис и характеристики:
  62. bool connectToServer(BLEAddress pAddress) {
  63.    BLEClient* pClient = BLEDevice::createClient();
  64.  
  65.   // подключаемся к удаленному BLE-серверу:
  66.   pClient->connect(pAddress);
  67.   Serial.println(" - Connected to server");
  68.              //  " – Подключились к серверу"
  69.  
  70.   // считываем UUID искомого сервиса:
  71.   BLERemoteService* pRemoteService = pClient->getService(dhtServiceUUID);
  72.   if (pRemoteService == nullptr) {
  73.     Serial.print("Failed to find our service UUID: ");
  74.              //  "Не удалось найти UUID нашего сервиса: "
  75.     Serial.println(dhtServiceUUID.toString().c_str());
  76.     return (false);
  77.   }
  78.  
  79.   // считываем UUID искомых характеристик:
  80.   temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  81.   humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);
  82.  
  83.   if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
  84.     Serial.print("Failed to find our characteristic UUID");
  85.              //  "Не удалось найти UUID нашей характеристики"
  86.     return false;
  87.   }
  88.   Serial.println(" - Found our characteristics");
  89.              //  " – Наши характеристики найдены"
  90.  
  91.   // присваиваем характеристикам функции обратного вызова:
  92.   temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
  93.   humidityCharacteristic->registerForNotify(humidityNotifyCallback);
  94. }
  95.  
  96. // функция обратного вызова, которая будет вызвана
  97. // при получении оповещения от другого устройства:
  98. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  99.   void onResult(BLEAdvertisedDevice advertisedDevice) {
  100.     // проверяем, совпадает ли название
  101.     // BLE-сервера, рассылающего оповещения:
  102.     if (advertisedDevice.getName() == bleServerName) {
  103.       // мы нашли, что искали,
  104.       // поэтому сканирование можно завершить:
  105.       advertisedDevice.getScan()->stop();
  106.       // сохраняем адрес устройства, рассылающего оповещения:
  107.       pServerAddress = new BLEAddress(advertisedDevice.getAddress());
  108.       // задаем индикатор, дающий понять,
  109.       // что мы готовы подключиться:
  110.       doConnect = true;
  111.       Serial.println("Device found. Connecting!");
  112.                  //  "Устройство найдено. Подключаемся!"
  113.     }
  114.   }
  115. };
  116.  
  117. // функция обратного вызова, которая будет запущена,
  118. // если BLE-сервер пришлет вместе с уведомлением
  119. // корректные данные о температуре:
  120. static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
  121.                                         uint8_t* pData, size_t length, bool isNotify) {
  122.   display.setCursor(34,10);
  123.   display.print((char*)pData);
  124.   Serial.print("Temperature: ");  //  "Температура: "
  125.   Serial.print((char*)pData);
  126.   #ifdef temperatureCelsius
  127.     // температура в градусах Цельсия:
  128.     display.print(" *C");
  129.     Serial.print(" *C");
  130.   #else
  131.     // температура в градусах Фаренгейта:
  132.     display.print(" *F");
  133.     Serial.print(" *F");
  134.   #endif  
  135.   display.display();
  136. }
  137.  
  138. // функция обратного вызова, которая будет запущена,
  139. // если BLE-сервер пришлет вместе с уведомлением
  140. // корректные данные о влажности:
  141. static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
  142.                                     uint8_t* pData, size_t length, bool isNotify) {
  143.   display.setCursor(34,20);
  144.   display.print((char*)pData);
  145.   display.print(" %");
  146.   display.display();
  147.   Serial.print(" Humidity: ");  //  " Влажность: "
  148.   Serial.print((char*)pData);
  149.   Serial.println(" %");
  150. }
  151.  
  152. void setup() {
  153.   // настраиваем OLED-дисплей;
  154.   // параметр «SSD1306_SWITCHCAPVCC» в функции begin() задает,
  155.   // что напряжение для дисплея будет генерироваться
  156.   // от внутренней 3.3-вольтовой цепи,
  157.   // а параметр «0x3C» означает «128x32»:
  158.   if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
  159.     Serial.println(F("SSD1306 allocation failed"));
  160.                  //  "Не удалось настроить SSD1306"
  161.     for(;;); // дальше не продолжаем,
  162.              // навечно оставшись в блоке loop()
  163.   }
  164.  
  165.   display.clearDisplay();
  166.   display.setTextSize(1);
  167.   //display.setBackgroundcolor(BLACK);
  168.   display.setTextColor(WHITE,0);
  169.   display.setCursor(30,0);
  170.   display.print("DHT READINGS");  //  "Данные от DHT-датчика"
  171.   display.display();
  172.  
  173.   // запускаем последовательную коммуникацию:
  174.   Serial.begin(115200);
  175.   Serial.println("Starting Arduino BLE Client application...");
  176.              //  "Запуск клиентского BLE-приложения... "
  177.  
  178.   // инициализируем BLE-устройство:
  179.   BLEDevice::init("");
  180.  
  181.   // создаем экземпляр класса «BLEScan» для сканирования
  182.   // и задаем для этого объекта функцию обратного вызова,
  183.   // которая будет информировать о том, найдено ли новое устройство;
  184.   // дополнительно указываем, что нам нужно активное сканирование,
  185.   // а потом запускаем 30-секундное сканирование:
  186.   BLEScan* pBLEScan = BLEDevice::getScan();
  187.   pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  188.   pBLEScan->setActiveScan(true);
  189.   pBLEScan->start(30);
  190. }
  191.  
  192. void loop() {
  193.   // если в переменной «doConnect» значение «true»,
  194.   // то это значит, что сканирование завершено,
  195.   // и мы нашли нужный BLE-сервер, к которому хотим подключиться;
  196.   // теперь пора, собственно, подключиться к нему;
  197.   // подключившись, мы записываем в «connected» значение «true»:
  198.   if (doConnect == true) {
  199.     if (connectToServer(*pServerAddress)) {
  200.       Serial.println("We are now connected to the BLE Server.");
  201.                  //  "Подключение к BLE-серверу прошло успешно."
  202.       // активируем свойство «notify» у каждой характеристики:
  203.       temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
  204.       humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
  205.       connected = true;
  206.     } else {
  207.       Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again.");
  208.                  //  "Подключиться к серверу не получилось.
  209.                  //   Перезапустите устройство, чтобы снова
  210.                  //   просканировать ближайший BLE-сервер."
  211.     }
  212.     doConnect = false;
  213.   }
  214.   delay(1000); // делаем секундную задержку между циклами loop()
  215. }

См.также

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