ESP32:Примеры/BLE-сервер и BLE-клиент при помощи ESP32
BLE-сервер и BLE-клиент при помощи ESP32
В этом примере мы научимся настраивать Bluetooth-соединение между двумя платами ESP32. Одна из этих ESP32 будет работать в качестве сервера, а другая – в качестве клиента. В проекте, который мы сделаем ниже, к серверу будет подключен датчик температуры и влажности (DHT11/DHT22), и этот сервер каждые 10 секунд будет отправлять клиенту самые последние данные, считанные с датчика. Кроме того, клиент будет печатать эти данные в подключенном к нему OLED-дисплее.
Итак, на одну ESP32 будет загружен серверный скетч, а на другую – клиентский. Клиент начнет сканировать близлежащие устройства и, найдя другую ESP32, подключится к ней. Спустя несколько секунд ESP32-клиент начнет получать данные о температуре и влажности.
Установка библиотек
Устанавливаем библиотеку «DHT Sensor»
Чтобы считывать данные с датчика DHT11/DHT22 при помощи IDE Arduino, нам понадобится установить библиотеку «DHT Sensor». Для этого проделайте следующее:
- Кликните тут, чтобы скачать ZIP-архив библиотеки;
- Распакуйте скачанный архив. У вас должна получиться папка под названием «DHT-sensor-library-master»;
- Переименуйте ее на «DHT_sensor»;
- Переместите папку «DHT_sensor» в папку «libraries», которая находится внутри папки, куда установлена IDE Arduino;
- Откройте IDE Arduino;
Устанавливаем библиотеку «Adafruit Sensor»
Нам также нужно установить библиотеку «Adafruit Sensor». Для этого проделайте следующее:
- Кликните тут, чтобы скачать ZIP-архив библиотеки;
- Распакуйте скачанный архив. У вас должна получиться папка «Adafruit_sensor-master»;
- Переименуйте ее на «Adafruit_Sensor»;
- Переместите папку «Adafruit_Sensor» в папку «libraries», которая находится внутри папки, где установлена IDE Arduino;
- Откройте IDE Arduino;
Устанавливаем библиотеку «Adafruit SSD1306»
Библиотека «Adafruit SSD1306» упрощает печать текста на OLED-дисплее при помощи IDE Arduino. Чтобы установить эту библиотеку в IDE Arduino, проделайте следующее:
- Кликните тут, чтобы скачать ZIP-архив библиотеки
- Распакуйте этот архив. У вас должна появиться папка «Adafruit_SSD1306-master»
- Переименуйте ее на «Adafruit_SSD1306»
- Переместите папку «Adafruit_SSD1306» в папку «libraries», которая находится внутри папки, где установлена ваша IDE Arduino
- Откройте IDE Arduino
Устанавливаем библиотеку «Adafruit GFX»
Нам также понадобится библиотека «Adafruit GFX». Чтобы установить ее в IDE Arduino, проделайте следующее:
- Кликните тут, чтобы скачать ZIP-архив библиотеки
- Распакуйте этот архив. У вас должна получиться папка «Adafruit-GFX-library-master»
- Переименуйте ее на «Adafruit_GFX_library»
- Переместите папку «Adafruit_GFX_library» в папку «libraries», которая находится в папке, где установлена ваша IDE Arduino
- Откройте IDE Arduino
Как работает код
Серверный код
После того, как мы загрузим этот код, он сразу же начнет работать, сообщая температуру в градусах Цельсия. Ниже мы подробнее рассмотрим, как он работает.
Импортируем библиотеки
Начинаем с импорта необходимых для этого скетча библиотек:
#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-устройства, обменивающихся полезными данными. Смело экспериментируйте с этими скетчами и пробуйте подключить другие датчики.
Необходимое оборудование
- Плата ESP32 - 2шт.;
- Датчик температуры и влажности DHT11/DHT22 - 1шт.;
- OLED-дисплей - 1шт.;
- Резистор 4.7 кОм - 1шт.;
- Макетная плата - 2 шт.
- Провода-перемычки;
Схема
Сервер
Давайте начнем с подключения компонентов друг к другу. Подключите датчик DHT11/DHT22 к ESP32 при помощи резистора на 4.7 кОм (см. схему ниже).
Датчик 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()
}
См.также
Внешние ссылки