BLE против классического Bluetooth
Классический Bluetooth (Serial Port Profile) — это «беспроводной UART»: простой поток байт. BLE (Bluetooth Low Energy) — другой протокол: устройство предоставляет сервисы и характеристики — структурированные поля, которые телефон читает, пишет и на которые подписывается.
ESP32 поддерживает оба. Для телефонов — в первую очередь iOS, где классический SPP недоступен без сертификации — нужен BLE.
Модель: сервисы и характеристики
BLE-устройство (сервер) публикует:
- Service — группа характеристик с UUID (например, «датчик температуры»).
- Characteristic — конкретное значение со свойствами: read / write / notify.
- Descriptor — метаданные характеристики (например, CCCD для подписки на уведомления).
UUID сервисов и характеристик — 128-битные. Стандартные присвоены Bluetooth SIG, свои генерируете уникальными.
BLE-сервер на ESP32
Ставим библиотеку BLEDevice (входит в ядро ESP32):
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#define SERVICE_UUID "12345678-1234-1234-1234-123456789abc"
#define CHAR_UUID "87654321-4321-4321-4321-cba987654321"
BLECharacteristic* pChar;
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32-Sensor");
BLEServer* server = BLEDevice::createServer();
BLEService* service = server->createService(SERVICE_UUID);
pChar = service->createCharacteristic(
CHAR_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
);
pChar->setValue("hello");
service->start();
server->getAdvertising()->start();
Serial.println("BLE сервер запущен");
}
Теперь телефон (nRF Connect, LightBlue) видит устройство «ESP32-Sensor», цепляется и читает характеристику.
Запись характеристики — команды с телефона
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pChar) override {
std::string v = pChar->getValue();
Serial.print("получено: ");
Serial.println(v.c_str());
if (v == "on") digitalWrite(2, HIGH);
if (v == "off") digitalWrite(2, LOW);
}
};
// при создании:
pChar->setCallbacks(new MyCallbacks());
// + добавить PROPERTY_WRITE к свойствам
Телефон пишет «on»/«off» в характеристику — ESP32 выполняет.
Нотификации — телеметрия в реальном времени
Датчик должен сам пушить данные, а не ждать опроса. PROPERTY_NOTIFY + notify():
void loop() {
float t = dht.readTemperature();
char buf[8];
snprintf(buf, sizeof(buf), "%.1f", t);
pChar->setValue(buf);
pChar->notify(); // пуш всем подписанным клиентам
delay(2000);
}
В приложении телефон подписывается на характеристику — и значения приходят сами каждые 2 секунды.
Полный пример: датчик + команда
#include <BLEDevice.h>
#include <DHT.h>
DHT dht(4, DHT22);
BLECharacteristic* pSensorChar;
class CmdCB : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* p) override {
if (p->getValue() == "beep") tone(BUZZER, 1000, 200);
}
};
void setup() {
BLEDevice::init("ESP32-Env");
auto* srv = BLEDevice::createServer();
auto* svc = srv->createService("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// характеристика для чтения+нотификаций
pSensorChar = svc->createCharacteristic(
"beb5483e-36e1-4688-b7f5-ea07361b26a8",
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
// характеристика для команд
auto* cmdChar = svc->createCharacteristic(
"beb5483e-36e1-4688-b7f5-ea07361b26a9",
BLECharacteristic::PROPERTY_WRITE);
cmdChar->setCallbacks(new CmdCB());
svc->start();
srv->getAdvertising()->start();
dht.begin();
}
Классический Bluetooth SPP для Android
Для простого UART-подобного обмена с Android есть BluetoothSerial — проще BLE, но только Android:
#include <BluetoothSerial.h>
BluetoothSerial SerialBT;
void setup() {
SerialBT.begin("ESP32-BT"); // имя для спаривания
}
void loop() {
if (SerialBT.available()) {
char c = SerialBT.read(); // команда с телефона
}
}
Типичные проблемы
- Не видно устройство: забыт
advertising->start(), имя устройства пустое. - Нотификации не приходят: клиент не подписался (нужен CCCD дескриптор) или не вызван
notify(). - iOS не видит: классический SPP — только BLE для iOS.
- Обрывы связи: BLE на ESP32 чувствителен к питанию — стабильные 3.3 В.
- Строки обрезаны:
getValue()— не C-строка, используйте длину илиstd::string.
Итог
BLE на ESP32 — сервер с сервисами и характеристиками: READ для запросов, WRITE для команд, NOTIFY для пушей. Работает с обоими мобильными платформами, в том числе iOS — чего классический SPP не умеет. Для Android-проектов альтернатива — BluetoothSerial, проще но без iOS.
Полный пример: термометр BLE
#include <BLEDevice.h>
#include <DHT.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHAR_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic* pChar;
DHT dht(4, DHT22);
bool connected = false;
class SrvCB : public BLEServerCallbacks {
void onConnect(BLEServer*) override { connected = true; }
void onDisconnect(BLEServer* s) override {
connected = false;
s->getAdvertising()->start(); // возобновить рекламу
}
};
void setup() {
dht.begin();
BLEDevice::init("ESP32-Temp");
auto* srv = BLEDevice::createServer();
srv->setCallbacks(new SrvCB());
auto* svc = srv->createService(SERVICE_UUID);
pChar = svc->createCharacteristic(CHAR_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
svc->start();
srv->getAdvertising()->start();
}
void loop() {
if (connected) {
char buf[8];
snprintf(buf, sizeof(buf), "%.1f", dht.readTemperature());
pChar->setValue(buf);
pChar->notify();
}
delay(2000);
}
Подписка на уведомления (клиент)
В приложении nRF Connect или LightBlue подключаетесь к ESP32-Temp, выбираете характеристику и жмёте «Subscribe» — значения начинают приходить автоматически.
Безопасность BLE
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT) — шифрование трафика; pairing через setMTU и security callbacks — только ваши устройства подключаются. Для открытых датчиков в домашней сети — необязательно, для чего-то публичного — обязательно.
BLE vs классический Bluetooth для ESP32
| | Classic SPP | BLE | |---|---|---| | Поток байт | да | нет (характеристики) | | Потребление | высокое | низкое | | iOS | нет | да | | Простота | проще | сложнее | | Стандарт для IoT | нет | да |
Для «как Serial по воздуху» на Android — BluetoothSerial. Для всего остального и iOS — BLE.
Частые ошибки
- Нет рекламы после отключения —
advertising->start()вonDisconnect. - Notify не приходит — клиент не подписался,
notify()без подписчика ничего не делает. - Строки обрезаны —
getValue()не C-строка, используйте длину. - Малое MTU — большие пакеты режутся;
BLEDevice::setMTU(500). - Устройство засыпает — deep sleep рвёт BLE; используйте light sleep или не спите.