ESP32:Примеры/BLE-сервер и BLE-клиент при помощи ESP32: различия между версиями

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску
Нет описания правки
Нет описания правки
 
(не показана 1 промежуточная версия 1 участника)
Строка 1: Строка 1:
{{ESP32 панель перехода}}
{{ESP32 панель перехода}}
{{Перевод от Сubewriter}}
{{Перевод от Сubewriter}}
{{Myagkij-редактор}}
{{Myagkij-редактор}}
{{Черновик}}


=BLE-сервер и BLE-клиент при помощи ESP32=
=BLE-сервер и BLE-клиент при помощи ESP32=
Строка 63: Строка 60:
Начинаем с импорта необходимых для этого скетча библиотек:
Начинаем с импорта необходимых для этого скетча библиотек:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#include <BLEDevice.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEServer.h>
Строка 75: Строка 72:
По умолчанию [[ESP32]] будет отправлять температуру в градусах Цельсия. Чтобы она начала отправлять температуру в градусах Фаренгейта, удалите или закомментируйте строчку ниже:
По умолчанию [[ESP32]] будет отправлять температуру в градусах Цельсия. Чтобы она начала отправлять температуру в градусах Фаренгейта, удалите или закомментируйте строчку ниже:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// закомментируйте строчку ниже, если вам нужны градусы Фаренгейта:
// закомментируйте строчку ниже, если вам нужны градусы Фаренгейта:
#define temperatureCelsius
#define temperatureCelsius
Строка 86: Строка 83:
Оставьте здесь прежнее название, иначе название сервера нужно будет также поменять в скетче клиента, т.к. название [[BLE-сервер]]а должно совпадать и в серверном, и в клиентском скетчах.
Оставьте здесь прежнее название, иначе название сервера нужно будет также поменять в скетче клиента, т.к. название [[BLE-сервер]]а должно совпадать и в серверном, и в клиентском скетчах.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define bleServerName "dhtESP32"
#define bleServerName "dhtESP32"
</syntaxhighlight>
</syntaxhighlight>
Строка 94: Строка 91:
Во этом фрагменте мы выбираем тип используемого датчика. В данном случае выбран [[DHT11]]:
Во этом фрагменте мы выбираем тип используемого датчика. В данном случае выбран [[DHT11]]:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT11 // DHT 11
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
Строка 104: Строка 101:
Я также советую использовать [[UUID]], заданные в скетче по умолчанию. В противном случае их также нужно будет поменять в клиентском скетче, чтобы клиент мог найти сервис и прочесть данные характеристик. [[UUID]] заключены в кавычки во фрагменте ниже:
Я также советую использовать [[UUID]], заданные в скетче по умолчанию. В противном случае их также нужно будет поменять в клиентском скетче, чтобы клиент мог найти сервис и прочесть данные характеристик. [[UUID]] заключены в кавычки во фрагменте ниже:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"
#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"
#ifdef temperatureCelsius
#ifdef temperatureCelsius
Строка 121: Строка 118:
В следующей строчке задаем номер контакта, к которому подключен датчик [[DHT]]. В нашем случае датчик будет подключен к цифровому контакту GPIO14. Если вы подключили его к какому-то другому контакту, не забудьте поменять и эту строчку кода:
В следующей строчке задаем номер контакта, к которому подключен датчик [[DHT]]. В нашем случае датчик будет подключен к цифровому контакту GPIO14. Если вы подключили его к какому-то другому контакту, не забудьте поменять и эту строчку кода:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
const int DHTPin = 14;
const int DHTPin = 14;
</syntaxhighlight>
</syntaxhighlight>
Строка 129: Строка 126:
Блок [[setup()]] начинаем с запуска датчика [[DHT]]:
Блок [[setup()]] начинаем с запуска датчика [[DHT]]:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// запускаем датчик DHT:
// запускаем датчик DHT:
dht.begin();  
dht.begin();  
Строка 136: Строка 133:
Далее запускаем последовательную коммуникацию на скорости [[115200 бод]].
Далее запускаем последовательную коммуникацию на скорости [[115200 бод]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// запускаем последовательную коммуникацию:
// запускаем последовательную коммуникацию:
Serial.begin(115200);
Serial.begin(115200);
Строка 143: Строка 140:
Создаем новое [[BLE-устройство]], которому даем заданное ранее название '''«bleServerName»'''.
Создаем новое [[BLE-устройство]], которому даем заданное ранее название '''«bleServerName»'''.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// создаем BLE-устройство:
// создаем BLE-устройство:
BLEDevice::init(bleServerName);
BLEDevice::init(bleServerName);
Строка 150: Строка 147:
Делаем это [[BLE-устройство]] сервером и привязываем к нему функции обратного вызова.
Делаем это [[BLE-устройство]] сервером и привязываем к нему функции обратного вызова.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// создаем BLE-сервер:
// создаем BLE-сервер:
BLEServer *pServer = BLEDevice::createServer();
BLEServer *pServer = BLEDevice::createServer();
Строка 158: Строка 155:
Функция '''MyServerCallbacks()''' содержит две функции обратного вызова, переключающие значение в булевой переменной '''«deviceConnected»''' между '''«true»''' и '''«false»''' в зависимости от текущего статуса подключения [[BLE-устройства]]. Это значит, что если клиент подключен к серверу, этим значением будет '''«true»''', а если не подключен, то '''«false»'''. Ниже – фрагмент кода, задающий то, как работает функция '''MyServerCallbacks()''':
Функция '''MyServerCallbacks()''' содержит две функции обратного вызова, переключающие значение в булевой переменной '''«deviceConnected»''' между '''«true»''' и '''«false»''' в зависимости от текущего статуса подключения [[BLE-устройства]]. Это значит, что если клиент подключен к серверу, этим значением будет '''«true»''', а если не подключен, то '''«false»'''. Ниже – фрагмент кода, задающий то, как работает функция '''MyServerCallbacks()''':


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
class MyServerCallbacks: public BLEServerCallbacks {
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
void onConnect(BLEServer* pServer) {
Строка 171: Строка 168:
Далее в блоке [[setup()]] запускаем [[BLE-устройство]] с заданным ранее [[UUID]].
Далее в блоке [[setup()]] запускаем [[BLE-устройство]] с заданным ранее [[UUID]].


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// создаем BLE-сервис:
// создаем BLE-сервис:
BLEService *dhtService = pServer->createService(SERVICE_UUID);
BLEService *dhtService = pServer->createService(SERVICE_UUID);
Строка 178: Строка 175:
Затем создаем температурную BLE-характеристику. Если вы выбрали градусы Цельсия, то [[ESP32-сервер]] будет использовать вот эту характеристику:
Затем создаем температурную BLE-характеристику. Если вы выбрали градусы Цельсия, то [[ESP32-сервер]] будет использовать вот эту характеристику:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#ifdef temperatureCelsius
#ifdef temperatureCelsius
     dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
     dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
Строка 188: Строка 185:
В противном случае будет использована характеристика для градусов Фаренгейта:
В противном случае будет использована характеристика для градусов Фаренгейта:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#else
#else
     dhtService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
     dhtService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
Строка 198: Строка 195:
После этого [[ESP32-сервер]] запустит характеристику для данных о влажности:
После этого [[ESP32-сервер]] запустит характеристику для данных о влажности:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
dhtService->addCharacteristic(&dhtHumidityCharacteristics);
dhtService->addCharacteristic(&dhtHumidityCharacteristics);
dhtHumidityDescriptor.setValue("DHT humidity");  
dhtHumidityDescriptor.setValue("DHT humidity");  
Строка 207: Строка 204:
Наконец, запускаем сервис и говорим серверу, чтобы он начал рассылку оповещений – чтобы его могли найти другие устройства.
Наконец, запускаем сервис и говорим серверу, чтобы он начал рассылку оповещений – чтобы его могли найти другие устройства.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// запускаем сервис:
// запускаем сервис:
dhtService->start();
dhtService->start();
Строка 219: Строка 216:
Код в блоке [[loop()]] достаточно прост. Мы просто постоянно проверяем, подключено ли устройство или нет. Если подключено, считываем текущую температуру и влажность:
Код в блоке [[loop()]] достаточно прост. Мы просто постоянно проверяем, подключено ли устройство или нет. Если подключено, считываем текущую температуру и влажность:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
if (deviceConnected) {
if (deviceConnected) {
     // считываем температуру в градусах Цельсия (по умолчанию):
     // считываем температуру в градусах Цельсия (по умолчанию):
Строка 232: Строка 229:
Также задаем условие, проверяющее, корректны ли считанные данные или нет.
Также задаем условие, проверяющее, корректны ли считанные данные или нет.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
if (isnan(h) || isnan(t) || isnan(f)) {
if (isnan(h) || isnan(t) || isnan(f)) {
   Serial.println("Failed to read from DHT sensor!");
   Serial.println("Failed to read from DHT sensor!");
Строка 243: Строка 240:
Если вы используете температуру в Цельсиях, то запустится фрагмент кода ниже. В нем данные о температуре сохраняются во временную переменную '''«temperatureCTemp»'''.
Если вы используете температуру в Цельсиях, то запустится фрагмент кода ниже. В нем данные о температуре сохраняются во временную переменную '''«temperatureCTemp»'''.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#ifdef temperatureCelsius
#ifdef temperatureCelsius
   static char temperatureCTemp[7];
   static char temperatureCTemp[7];
Строка 251: Строка 248:
В двух строчках ниже мы обновляем текущее значение характеристики с помощью '''setValue()''', а также отправляем его подключенному клиенту при помощи '''notify()'''.
В двух строчках ниже мы обновляем текущее значение характеристики с помощью '''setValue()''', а также отправляем его подключенному клиенту при помощи '''notify()'''.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
dhtTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
dhtTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
dhtTemperatureCelsiusCharacteristics.notify();
dhtTemperatureCelsiusCharacteristics.notify();
Строка 258: Строка 255:
Три строчки ниже печатают в мониторе порта данные о температуре (в отладочных целях).
Три строчки ниже печатают в мониторе порта данные о температуре (в отладочных целях).


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
Serial.print("Temperature Celsius: ");
Serial.print("Temperature Celsius: ");
         //  "Температура в градусах Цельсия: "
         //  "Температура в градусах Цельсия: "
Строка 267: Строка 264:
Далее используем аналогичный способ для отправки температуры в градусах Фаренгейта.
Далее используем аналогичный способ для отправки температуры в градусах Фаренгейта.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#else
#else
   static char temperatureFTemp[7];
   static char temperatureFTemp[7];
Строка 284: Строка 281:
И тот же способ используем для отправки данных о влажности.
И тот же способ используем для отправки данных о влажности.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// отправляем уведомление о том,
// отправляем уведомление о том,
// что с датчика DHT считаны данные о влажности:
// что с датчика DHT считаны данные о влажности:
Строка 301: Строка 298:
Функция [[delay()]] задает 10-секундную паузу между считываниями данных с датчика.
Функция [[delay()]] задает 10-секундную паузу между считываниями данных с датчика.


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
delay(10000);
delay(10000);
</syntaxhighlight>
</syntaxhighlight>
Строка 337: Строка 334:
Вначале подключаем необходимые библиотеки:
Вначале подключаем необходимые библиотеки:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
#include "BLEDevice.h"
#include "BLEDevice.h"
#include <Wire.h>
#include <Wire.h>
Строка 348: Строка 345:
По умолчанию клиент будет получать температурные данные в градусах Цельсия. Но если удалить или закомментировать строчку ниже, он начнет получать их в градусах Фаренгейта.
По умолчанию клиент будет получать температурные данные в градусах Цельсия. Но если удалить или закомментировать строчку ниже, он начнет получать их в градусах Фаренгейта.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// по умолчанию температура будет в градусах Цельсия,
// по умолчанию температура будет в градусах Цельсия,
// но если вам нужны градусы Фаренгейта, закомментируйте строчку ниже:
// но если вам нужны градусы Фаренгейта, закомментируйте строчку ниже:
Строка 362: Строка 359:
Таким образом, название [[BLE-сервер]]а должно быть следующим:
Таким образом, название [[BLE-сервер]]а должно быть следующим:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
#define bleServerName "dhtESP32"
#define bleServerName "dhtESP32"
</syntaxhighlight>
</syntaxhighlight>
Строка 368: Строка 365:
[[UUID]] должны быть такими:
[[UUID]] должны быть такими:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// UUID для сервиса:
// UUID для сервиса:
static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");
Строка 388: Строка 385:
Далее объявляем несколько переменных, которые понадобятся нам в дальнейшем:
Далее объявляем несколько переменных, которые понадобятся нам в дальнейшем:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// переменные, используемые для определения того,
// переменные, используемые для определения того,
// нужно ли начинать подключение или завершено ли подключение:
// нужно ли начинать подключение или завершено ли подключение:
Строка 411: Строка 408:
Блок [[setup()]] начинаем, задав правильные настройки для [[OLED]]-дисплея:
Блок [[setup()]] начинаем, задав правильные настройки для [[OLED]]-дисплея:


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
Wire.begin();
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
Строка 422: Строка 419:
Затем печатаем на [[OLED]]-дисплее первое сообщение: '''«DHT READINGS»''' («Данные от DHT-датчика").
Затем печатаем на [[OLED]]-дисплее первое сообщение: '''«DHT READINGS»''' («Данные от DHT-датчика").


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
display.setCursor(30,0);
display.setCursor(30,0);
display.print("DHT READINGS");  //  "Данные от DHT-датчика"
display.print("DHT READINGS");  //  "Данные от DHT-датчика"
Строка 430: Строка 427:
Затем запускаем последовательную передачу данных на скорости [[115200 бод]].
Затем запускаем последовательную передачу данных на скорости [[115200 бод]].


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
Serial.begin(115200);
Serial.begin(115200);
</syntaxhighlight>
</syntaxhighlight>
Строка 436: Строка 433:
Инициализируем BLE-устройство:
Инициализируем BLE-устройство:


<syntaxhighlight lang="c" enclose="div">
<syntaxhighlight lang="c">
BLEDevice::init("");
BLEDevice::init("");
</syntaxhighlight>
</syntaxhighlight>
Строка 444: Строка 441:
Фрагмент кода ниже предназначен для сканирования находящихся рядом устройств.
Фрагмент кода ниже предназначен для сканирования находящихся рядом устройств.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
BLEScan* pBLEScan = BLEDevice::getScan();
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
Строка 455: Строка 452:
Обратите внимание, что функция '''MyAdvertisedDeviceCallbacks()''' не только ищет [[BLE-устройство]], но и проверяет, имеет ли найденное [[BLE-устройство]] правильное название. Если имеет, эта функция завершает сканирование и меняет значение в булевой переменной '''«doConnect»''' на '''«true»'''. Так мы будем знать, что нашли сервер, который искали, и можем начать подключение к нему.
Обратите внимание, что функция '''MyAdvertisedDeviceCallbacks()''' не только ищет [[BLE-устройство]], но и проверяет, имеет ли найденное [[BLE-устройство]] правильное название. Если имеет, эта функция завершает сканирование и меняет значение в булевой переменной '''«doConnect»''' на '''«true»'''. Так мы будем знать, что нашли сервер, который искали, и можем начать подключение к нему.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// функция обратного вызова, которая будет вызвана
// функция обратного вызова, которая будет вызвана
// при получении оповещения от другого устройства:
// при получении оповещения от другого устройства:
Строка 482: Строка 479:
Если значением в переменной '''«doConnect»''' является '''«true»''', [[BLE-клиент]] попробует подключиться к [[BLE-сервер]]у. Функция '''connectToServer()''' управляет всеми подключениями между клиентом и сервером.
Если значением в переменной '''«doConnect»''' является '''«true»''', [[BLE-клиент]] попробует подключиться к [[BLE-сервер]]у. Функция '''connectToServer()''' управляет всеми подключениями между клиентом и сервером.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// функция для подключения к BLE-серверу,
// функция для подключения к BLE-серверу,
// у которого есть название, сервис и характеристики:
// у которого есть название, сервис и характеристики:
Строка 517: Строка 514:
Также присваиваем характеристикам функции обратного вызова, ответственные за то, что будет происходить при получении новых данных.
Также присваиваем характеристикам функции обратного вызова, ответственные за то, что будет происходить при получении новых данных.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
   // присваиваем характеристикам функции обратного вызова:
   // присваиваем характеристикам функции обратного вызова:
   temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
   temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
Строка 526: Строка 523:
После подключения [[BLE-клиент]]а к серверу нам понадобится активировать свойство '''«property»''' для каждой характеристики. Используем для этого методом '''writeValue()'''.
После подключения [[BLE-клиент]]а к серверу нам понадобится активировать свойство '''«property»''' для каждой характеристики. Используем для этого методом '''writeValue()'''.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
Строка 535: Строка 532:
Получив вместе с уведомлением новые данные, клиент запустит функции '''temperatureNotifyCallback()''' и '''humidityNotifyCallback()''', ответственные за получение новых значений, обновление данных на [[OLED-экран]]е и их печать в мониторе порта.
Получив вместе с уведомлением новые данные, клиент запустит функции '''temperatureNotifyCallback()''' и '''humidityNotifyCallback()''', ответственные за получение новых значений, обновление данных на [[OLED-экран]]е и их печать в мониторе порта.


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
// функция обратного вызова, которая будет запущена,
// функция обратного вызова, которая будет запущена,
// если BLE-сервер пришлет вместе с уведомлением
// если BLE-сервер пришлет вместе с уведомлением
Строка 619: Строка 616:
=== Скетч для сервера ===
=== Скетч для сервера ===


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
/*********
/*********
   Руи Сантос
   Руи Сантос
Строка 784: Строка 781:
=== Скетч для клиента===
=== Скетч для клиента===


<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS" enclose="div">
<syntaxhighlight lang="c" line="GESHI_NORMAL_LINE_NUMBERS|GESHI_FANCY_LINE_NUMBERS">
/*********
/*********
   Руи Сантос
   Руи Сантос
Строка 1003: Строка 1000:


=См.также=
=См.также=
{{ads}}


=Внешние ссылки=
=Внешние ссылки=
Строка 1010: Строка 1005:
<references />
<references />


{{Навигационная таблица/Телепорт}}
{{Навигационная таблица/Портал/ESP32}}


[[Категория:ESP32]]
[[Категория:ESP32]]

Текущая версия от 09:24, 18 июня 2023

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


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».

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

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

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include "DHT.h"

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

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

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

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

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

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

#define bleServerName "dhtESP32"

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

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

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

Задаем UUID

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

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

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

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

const int DHTPin = 14;

setup()

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

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

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

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

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

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

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

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

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

class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
 deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
 deviceConnected = false;
}
};

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

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

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

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

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

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

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

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

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

// запускаем сервис:
dhtService->start();

// запускаем рассылку оповещений:
pServer->getAdvertising()->start();

loop()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

delay(10000);

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

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

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

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

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

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

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

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

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

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

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

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

#include "BLEDevice.h"
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

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

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

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

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

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

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

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

#define bleServerName "dhtESP32"

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

// UUID для сервиса:
static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");

#ifdef temperatureCelsius
  // UUID для температурной характеристики (градусы Цельсия):
  static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
#else
  // UUID для температурной характеристики (градусы Фаренгейта):
  static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
#endif

// UUID для влажностной характеристики:
static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

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

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

// переменные, используемые для определения того,
// нужно ли начинать подключение или завершено ли подключение:
static boolean doConnect = false;
static boolean connected = false;

// адрес периферийного устройства;
// (он должен быть найден во время сканирования):
static BLEAddress *pServerAddress;
 
// характеристики, данные которых мы хотим прочесть:
static BLERemoteCharacteristic* temperatureCharacteristic;
static BLERemoteCharacteristic* humidityCharacteristic;

// включение/выключение уведомлений:
const uint8_t notificationOn[] = {0x1, 0x0};
const uint8_t notificationOff[] = {0x0, 0x0};

setup()

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

Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
//display.setBackgroundcolor(BLACK);
display.setTextColor(WHITE,0);

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

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

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

Serial.begin(115200);

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

BLEDevice::init("");

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

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

BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);

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

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

// функция обратного вызова, которая будет вызвана
// при получении оповещения от другого устройства:
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    // проверяем, совпадает ли название 
    // BLE-сервера, рассылающего оповещения:
    if (advertisedDevice.getName() == bleServerName) {
      // мы нашли, что искали, 
      // поэтому сканирование можно завершить:
      advertisedDevice.getScan()->stop(); 
      // сохраняем адрес устройства, рассылающего оповещения: 
      pServerAddress = new BLEAddress(advertisedDevice.getAddress()); 
      // задаем индикатор, дающий понять,
      // что мы готовы подключиться:
      doConnect = true;
      Serial.println("Device found. Connecting!");
                 //  "Устройство найдено. Подключаемся!"
    }
  }
};

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

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

// функция для подключения к BLE-серверу,
// у которого есть название, сервис и характеристики:
bool connectToServer(BLEAddress pAddress) {
   BLEClient* pClient = BLEDevice::createClient();

  // подключаемся к удаленному BLE-серверу:
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");
             //  " – Подключились к серверу"
 
  // считываем UUID искомого сервиса:
  BLERemoteService* pRemoteService = pClient->getService(dhtServiceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
             //  "Не удалось найти UUID нашего сервиса: "
    Serial.println(dhtServiceUUID.toString().c_str());
    return (false);
  }

  // считываем UUID искомых характеристик:
  temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);

  if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID");
             //  "Не удалось найти UUID нашей характеристики"
    return false;
  }
  Serial.println(" - Found our characteristics");
             //  " – Наши характеристики найдены"

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

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

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

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

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

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

// функция обратного вызова, которая будет запущена,
// если BLE-сервер пришлет вместе с уведомлением
// корректные данные о температуре:
static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  display.setCursor(34,10);
  display.print((char*)pData);
  Serial.print("Temperature: ");  //  "Температура: "
  Serial.print((char*)pData);
  #ifdef temperatureCelsius
    // температура в градусах Цельсия:
    display.print(" *C");
    Serial.print(" *C");
  #else
    // температура в градусах Фаренгейта:
    display.print(" *F");
    Serial.print(" *F");
  #endif  
  display.display();
}

// функция обратного вызова, которая будет запущена,
// если BLE-сервер пришлет вместе с уведомлением
// корректные данные о влажности:
static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  display.setCursor(34,20);
  display.print((char*)pData);
  display.print(" %");
  display.display();
  Serial.print(" Humidity: ");  //  " Влажность: "
  Serial.print((char*)pData); 
  Serial.println(" %");
}

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

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

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

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

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

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

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

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

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

Схема

Сервер

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

Примечание

На этой схеме изображена 36-контактная версия ESP32 DEVKIT DOIT V1. Если вы используете другую модель, обязательно сверьтесь с ее распиновкой.

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

Клиент

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

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

Код

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

/*********
  Руи Сантос
  Более подробно о проекте на: http://randomnerdtutorials.com  
*********/

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include "DHT.h"

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

// даем название BLE-серверу:
#define bleServerName "dhtESP32"

// оставьте незакомментированной строчку,
// соответствующую используемому вами типу датчика:
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

// для генерирования UUID можно воспользоваться этим сайтом:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59"

#ifdef temperatureCelsius
  BLECharacteristic dhtTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor dhtTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902));
#else
  BLECharacteristic dhtTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY);
  BLEDescriptor dhtTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2901));
#endif

BLECharacteristic dhtHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor dhtHumidityDescriptor(BLEUUID((uint16_t)0x2903));

// контакт, к которому подключен датчик DHT:
const int DHTPin = 14;

// инициализируем датчик DHT:
DHT dht(DHTPin, DHTTYPE);

bool deviceConnected = false;

// задаем функции обратного вызова onConnect() и onDisconnect():
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

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

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

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

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

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

  // создаем BLE-характеристики и BLE-дескриптор: bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml       

  #ifdef temperatureCelsius
    dhtService->addCharacteristic(&dhtTemperatureCelsiusCharacteristics);
    dhtTemperatureCelsiusDescriptor.setValue("DHT temperature Celsius");
                                         //  "Температура в Цельсиях"
    dhtTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());
  #else
    dhtService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);
    dhtTemperatureFahrenheitDescriptor.setValue("DHT temperature Fahrenheit");
                                         //  "Температура в Фаренгейтах"
    dhtTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902());
  #endif  
  dhtService->addCharacteristic(&dhtHumidityCharacteristics);
  dhtHumidityDescriptor.setValue("DHT humidity"); 
                             //  "Влажность"
  dhtHumidityCharacteristics.addDescriptor(new BLE2902());
  
  // запускаем сервис:
  dhtService->start();

  // запускаем рассылку оповещений:
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
             //  "Ждем подключения клиента, чтобы отправить уведомление..."
}

void loop() {
  if (deviceConnected) {
    // считываем температуру в градусах Цельсия (по умолчанию):
    float t = dht.readTemperature();
    // считываем температуру в градусах Фаренгейта
    // (isFahrenheit = true):
    float f = dht.readTemperature(true);
    // считываем влажность:
    float h = dht.readHumidity();
 
    // проверяем, удалось ли прочесть данные,
    // и если нет, то выходим из loop(), чтобы попробовать снова:
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
                 //  "Не удалось прочесть данные с датчика DHT!"
      return;
    }
    // отправляем уведомление о том,
    // что с датчика DHT считаны данные о температуре:
    #ifdef temperatureCelsius
      static char temperatureCTemp[7];
      dtostrf(t, 6, 2, temperatureCTemp);
      // задаем значение для температурной характеристики (Цельсий)
      // и отправляем уведомление подключенному клиенту:
      dhtTemperatureCelsiusCharacteristics.setValue(temperatureCTemp);
      dhtTemperatureCelsiusCharacteristics.notify();
      Serial.print("Temperature Celsius: ");
               //  "Температура в градусах Цельсия: "
      Serial.print(t);
      Serial.print(" *C");
    #else
      static char temperatureFTemp[7];
      dtostrf(f, 6, 2, temperatureFTemp);
      // задаем значение для температурной характеристики (Фаренгейт)
      // и отправляем уведомление подключенному клиенту:
   dhtTemperatureFahrenheitCharacteristics.setValue(temperatureFTemp);
      dhtTemperatureFahrenheitCharacteristics.notify();
      Serial.print("Temperature Fahrenheit: ");
               //  "Температура в градусах Фаренгейта: "
      Serial.print(f);
      Serial.print(" *F");
    #endif
    
    // отправляем уведомление о том,
    // что с датчика DHT считаны данные о влажности:
    static char humidityTemp[7];
    dtostrf(h, 6, 2, humidityTemp);
    // задаем значение для влажностной характеристики
    // и отправляем уведомление подключенному клиенту:
    dhtHumidityCharacteristics.setValue(humidityTemp);
    dhtHumidityCharacteristics.notify();   
    Serial.print(" - Humidity: ");
             //  " - Влажность: "
    Serial.print(h);
    Serial.println(" %");
    
    delay(10000);
  }
}

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

/*********
  Руи Сантос
  Более подробно о проекте на: http://randomnerdtutorials.com  
*********/

#include "BLEDevice.h"
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

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

// задаем название для BLE-сервера 
// (это другая ESP32, на которой запущен серверный скетч):
#define bleServerName "dhtESP32"

// UUID для сервиса:
static BLEUUID dhtServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59");

#ifdef temperatureCelsius
  // UUID для температурной характеристики (градусы Цельсия):
  static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518");
#else
  // UUID для температурной характеристики (градусы Фаренгейта):
  static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408");
#endif

// UUID для влажностной характеристики:
static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");

// переменные, используемые для определения того,
// нужно ли начинать подключение или завершено ли подключение:
static boolean doConnect = false;
static boolean connected = false;

// адрес периферийного устройства;
// (он должен быть найден во время сканирования):
static BLEAddress *pServerAddress;
 
// характеристики, данные которых мы хотим прочесть:
static BLERemoteCharacteristic* temperatureCharacteristic;
static BLERemoteCharacteristic* humidityCharacteristic;

// включение/выключение уведомлений:
const uint8_t notificationOn[] = {0x1, 0x0};
const uint8_t notificationOff[] = {0x0, 0x0};

#define SCREEN_WIDTH 128 // ширина OLED-дисплея (в пикселях)
#define SCREEN_HEIGHT 32 // высота OLED-дисплея (в пикселях)

// создаем дисплей SSD1306, 
// подключенный через I2C (контакты SDA и SCL):
#define OLED_RESET     4 // номер контакта для сброса
                         // (или «-1», если контакт для сброса
                         // такой же, как и у Arduino)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// подключаемся к BLE-серверу,
// у которого есть название, сервис и характеристики:
bool connectToServer(BLEAddress pAddress) {
   BLEClient* pClient = BLEDevice::createClient();
 
  // подключаемся к удаленному BLE-серверу:
  pClient->connect(pAddress);
  Serial.println(" - Connected to server");
             //  " – Подключились к серверу"
 
  // считываем UUID искомого сервиса:
  BLERemoteService* pRemoteService = pClient->getService(dhtServiceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
             //  "Не удалось найти UUID нашего сервиса: "
    Serial.println(dhtServiceUUID.toString().c_str());
    return (false);
  }
 
  // считываем UUID искомых характеристик:
  temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID);
  humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID);

  if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID");
             //  "Не удалось найти UUID нашей характеристики"
    return false;
  }
  Serial.println(" - Found our characteristics");
             //  " – Наши характеристики найдены"
 
  // присваиваем характеристикам функции обратного вызова:
  temperatureCharacteristic->registerForNotify(temperatureNotifyCallback);
  humidityCharacteristic->registerForNotify(humidityNotifyCallback);
}

// функция обратного вызова, которая будет вызвана
// при получении оповещения от другого устройства:
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    // проверяем, совпадает ли название 
    // BLE-сервера, рассылающего оповещения:
    if (advertisedDevice.getName() == bleServerName) {
      // мы нашли, что искали, 
      // поэтому сканирование можно завершить:
      advertisedDevice.getScan()->stop(); 
      // сохраняем адрес устройства, рассылающего оповещения: 
      pServerAddress = new BLEAddress(advertisedDevice.getAddress()); 
      // задаем индикатор, дающий понять,
      // что мы готовы подключиться:
      doConnect = true;
      Serial.println("Device found. Connecting!");
                 //  "Устройство найдено. Подключаемся!"
    }
  }
};
 
// функция обратного вызова, которая будет запущена,
// если BLE-сервер пришлет вместе с уведомлением
// корректные данные о температуре:
static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                        uint8_t* pData, size_t length, bool isNotify) {
  display.setCursor(34,10);
  display.print((char*)pData);
  Serial.print("Temperature: ");  //  "Температура: "
  Serial.print((char*)pData);
  #ifdef temperatureCelsius
    // температура в градусах Цельсия:
    display.print(" *C");
    Serial.print(" *C");
  #else
    // температура в градусах Фаренгейта:
    display.print(" *F");
    Serial.print(" *F");
  #endif  
  display.display();
}

// функция обратного вызова, которая будет запущена,
// если BLE-сервер пришлет вместе с уведомлением
// корректные данные о влажности:
static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, 
                                    uint8_t* pData, size_t length, bool isNotify) {
  display.setCursor(34,20);
  display.print((char*)pData);
  display.print(" %");
  display.display();
  Serial.print(" Humidity: ");  //  " Влажность: "
  Serial.print((char*)pData); 
  Serial.println(" %");
}

void setup() {
  // настраиваем OLED-дисплей;
  // параметр «SSD1306_SWITCHCAPVCC» в функции begin() задает,
  // что напряжение для дисплея будет генерироваться
  // от внутренней 3.3-вольтовой цепи,
  // а параметр «0x3C» означает «128x32»:
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
                 //  "Не удалось настроить SSD1306"
    for(;;); // дальше не продолжаем,
             // навечно оставшись в блоке loop()
  }
  
  display.clearDisplay();
  display.setTextSize(1);
  //display.setBackgroundcolor(BLACK);
  display.setTextColor(WHITE,0);
  display.setCursor(30,0);
  display.print("DHT READINGS");  //  "Данные от DHT-датчика"
  display.display();
  
  // запускаем последовательную коммуникацию:
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
             //  "Запуск клиентского BLE-приложения... "

  // инициализируем BLE-устройство:
  BLEDevice::init("");
 
  // создаем экземпляр класса «BLEScan» для сканирования
  // и задаем для этого объекта функцию обратного вызова,
  // которая будет информировать о том, найдено ли новое устройство;
  // дополнительно указываем, что нам нужно активное сканирование,
  // а потом запускаем 30-секундное сканирование:
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
}

void loop() {
  // если в переменной «doConnect» значение «true»,
  // то это значит, что сканирование завершено,
  // и мы нашли нужный BLE-сервер, к которому хотим подключиться; 
  // теперь пора, собственно, подключиться к нему;
  // подключившись, мы записываем в «connected» значение «true»:
  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("We are now connected to the BLE Server.");
                 //  "Подключение к BLE-серверу прошло успешно."
      // активируем свойство «notify» у каждой характеристики:
      temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
      humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);
      connected = true;
    } else {
      Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again.");
                 //  "Подключиться к серверу не получилось.
                 //   Перезапустите устройство, чтобы снова
                 //   просканировать ближайший BLE-сервер."
    }
    doConnect = false;
  }
  delay(1000); // делаем секундную задержку между циклами loop()
}

См.также

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