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

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

Перевод: Максим Кузьмин (Cubewriter) Перевел 364226 статей для сайта.</br>Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


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 }

См.также

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