Добавить в избранное

ESP32 и Bluetooth BLE: свои сервисы, уведомления и связь с телефоном

Чтение ≈ 4 мин

BLE на ESP32 с нуля: что такое сервисы и характеристики, создание своего сервера, чтение и запись характеристик, нотификации для телеметрии и связь с iPhone/Android без классического Bluetooth.

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 или не спите.
Поделиться
Понравился материал?

Комментарии

Добавить комментарий