ESP32 BLE: a Bluetooth Low Energy server with notifications
The ESP32 core鈥檚 BLE library (BLEDevice.h, BLEServer.h, BLEUtils.h and BLE2902.h) turns the board into a Bluetooth Low Energy peripheral that a phone app such as nRF Connect or LightBlue can find, connect to, read and write. It is the BLE stack Neil Kolban wrote, now part of the core.
Built in on Mokxi: #include <BLEDevice.h> and it builds, with nothing to install. Free, and no account needed.
What BLE (ESP32 core) does
BLE is organized as a tree. The server holds services, each named by a UUID; a service holds characteristics, each a small value with properties saying whether a client may read it, write it, or subscribe to notifications. A temperature sensor is typically one service with one characteristic that the board updates and notifies every second.
The order of setup calls matters: init the device with a name, create the server, create the service, create its characteristics, start the service, then start advertising. Once a phone connects the board stops advertising, so a sketch that wants to be found again after a disconnect has to restart advertising itself, usually from the onDisconnect callback.
| Library Manager name | BLE (ESP32 core) |
|---|---|
| Author | Espressif |
| Include | #include <BLEDevice.h>#include <BLEServer.h>#include <BLEUtils.h>#include <BLE2902.h> |
| Boards | ESP32-C3, ESP32-C6, Seeed XIAO ESP32-C3, ESP32-C3 SuperMini, Seeed XIAO ESP32-C6, ESP32 DevKit, ESP32-DevKitC V4, LOLIN32 Lite, Wemos D1 R32, Cheap Yellow Display, TTGO T-Display, ESP32-S3-DevKitC-1, Seeed XIAO ESP32-S3, Arduino Nano ESP32 |
| In the simulator | Runs, with a gap the note below names. |
Install it
It builds on ESP32-C3, ESP32-C6, Seeed XIAO ESP32-C3, ESP32-C3 SuperMini, Seeed XIAO ESP32-C6, ESP32 DevKit, ESP32-DevKitC V4, LOLIN32 Lite, Wemos D1 R32, Cheap Yellow Display, TTGO T-Display, ESP32-S3-DevKitC-1, Seeed XIAO ESP32-S3, Arduino Nano ESP32.
In Mokxi it is built in: #include <BLEDevice.h> and the sketch compiles, with no download, no zip and no Library Manager.
On a real board it comes with the board package (Espressif), so the Arduino IDE has it as soon as the board is installed: there is nothing to add from the Library Manager.
ESP32-C3 路 ESP32-C6 路 Seeed XIAO ESP32-C3 路 ESP32-C3 SuperMini 路 Seeed XIAO ESP32-C6 路 ESP32 DevKit 路 ESP32-DevKitC V4 路 LOLIN32 Lite 路 Wemos D1 R32 路 Cheap Yellow Display 路 TTGO T-Display 路 ESP32-S3-DevKitC-1 路 Seeed XIAO ESP32-S3 路 Arduino Nano ESP32
The calls you will use
In the order a sketch meets them.
BLEDevice::init("ESP32-Sensor")
Starts the Bluetooth stack with the name phones will see in a scan.
BLEServer *pServer = BLEDevice::createServer()
Makes the board a GATT server. Attach a BLEServerCallbacks object with setCallbacks() to hear connects and disconnects.
BLEService *pService = pServer->createService(SERVICE_UUID)
Adds a service named by a 128-bit UUID string. Generate your own UUIDs rather than reusing a tutorial鈥檚.
pService->createCharacteristic(CHAR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY)
Adds a characteristic and says what a client may do with it: read, write, notify or indicate, combined with |.
pCharacteristic->addDescriptor(new BLE2902())
Adds the client configuration descriptor that lets a phone switch notifications on. Without it many apps cannot subscribe.
pCharacteristic->setValue("21.5")
Sets the characteristic鈥檚 value, from a string, an int, a float or a byte array.
pCharacteristic->notify()
Pushes the current value to every subscribed client. Call setValue() first, and only while a client is connected.
if (deviceConnected) {
pCharacteristic->setValue(String(temp).c_str());
pCharacteristic->notify();
}pCharacteristic->setCallbacks(new MyCallbacks())
Attaches a BLECharacteristicCallbacks subclass whose onWrite() runs when a phone writes the value; getValue() inside it reads what was sent.
BLEDevice::startAdvertising()
Starts or restarts advertising. Add the service UUID first with getAdvertising()->addServiceUUID() so scanners can filter for it.
Example: ESP32 BLE UART lamp
On the ESP32 DevKit V1. It runs in the browser exactly as printed.
// BLE UART lamp: a Bluetooth LE server with the Nordic UART service.
//
// Simulated Bluetooth: a phone in Mokxi, not a real radio. Press Run, open
// the Bluetooth tab and connect to "ESP32 BLE Lamp", like nRF Connect on a
// phone: write on or off to the RX characteristic, and turn on notifications
// on TX to hear back. The same sketch runs on a real ESP32 (Arduino-ESP32 3.x).
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
const int kLed = 4; // an LED and a resistor to ground
BLEServer *server = nullptr;
BLECharacteristic *tx = nullptr;
bool connected = false;
bool wasConnected = false;
void say(const String &text)
{
tx->setValue(text);
tx->notify();
}
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *) override
{
connected = true;
Serial.println("A phone connected.");
}
void onDisconnect(BLEServer *) override
{
connected = false;
Serial.println("The phone disconnected.");
}
};
class RxCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *c) override
{
String value = c->getValue();
value.trim();
value.toLowerCase();
Serial.print("Phone wrote: ");
Serial.println(value);
if (value == "on") {
digitalWrite(kLed, HIGH);
say("lamp on");
} else if (value == "off") {
digitalWrite(kLed, LOW);
say("lamp off");
} else {
say("send on or off");
}
}
};
void setup()
{
Serial.begin(115200);
pinMode(kLed, OUTPUT);
BLEDevice::init("ESP32 BLE Lamp");
server = BLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
BLEService *service = server->createService(SERVICE_UUID);
tx = service->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
tx->addDescriptor(new BLE2902());
BLECharacteristic *rx = service->createCharacteristic(RX_UUID, BLECharacteristic::PROPERTY_WRITE);
rx->setCallbacks(new RxCallbacks());
service->start();
BLEAdvertising *advertising = BLEDevice::getAdvertising();
advertising->addServiceUUID(SERVICE_UUID);
BLEDevice::startAdvertising();
Serial.println("Advertising as ESP32 BLE Lamp: connect from the Bluetooth tab.");
}
void loop()
{
// The board stops advertising when a phone connects. Start again once it
// has gone, or nothing can find the board a second time.
if (!connected && wasConnected) {
delay(500);
server->startAdvertising();
Serial.println("Advertising again.");
}
wasConnected = connected;
delay(20);
}Open it in the editor, wired and ready to run 路 ESP32 BLE UART lamp: the project page
Common errors and how to fix them
Sketch too big; text section exceeds available space
The BLE stack is large, and with WiFi as well it can pass the default 1.2 MB app partition. Pick a bigger partition scheme in the board settings, such as Huge APP or Minimal SPIFFS.
The phone connects once, then never finds the board again
Advertising stops while a client is connected and does not restart on its own. In your BLEServerCallbacks onDisconnect(), call BLEDevice::startAdvertising() (or pServer->startAdvertising()).
The app shows the characteristic but notifications never arrive
Add a BLE2902 descriptor to the characteristic, create it with PROPERTY_NOTIFY, and make sure the app has subscribed (tapped the notify arrows) before the board calls notify().
onWrite() gets a std::string on one core and a String on another
Core 2 returns std::string from getValue() and core 3 returns Arduino String. Write auto value = pCharacteristic->getValue(); and use value.length() and value[i], which work with both.
Keep going
More communication libraries
Try BLE (ESP32 core) without installing anything
Open the example, change the sketch, and keep your version in a free account.