Bluetooth Low Energy (BLE) is the second radio built into the ESP32: a short-range, very frugal link designed to exchange small amounts of data between an object and a phone or another device. Where Wi-Fi aims at the network and the Internet, BLE aims at the direct and frugal link: a sensor publishing a measurement, a remote control, a configuration set from a mobile application. Like Wi-Fi, it is a capability the classic Arduino does not have. The concept in general is covered in wireless technologies and BLE. This page gives the ESP32 incarnation of it.

What is it for?

The BLE of the ESP32 earns its place when the link has to be local, frugal and free of infrastructure:

  • Publishing measurements to a phone — temperature, level, state — which a mobile application reads by connecting, with no Wi-Fi network and no server.
  • Receiving commands — an application sends a setpoint, the ESP32 applies it. A remote control, or a field configuration tool.
  • Saving energy — BLE draws far less than Wi-Fi, which suits battery-powered objects that have no need for the Internet.

Warning

BLE is not “classic” Bluetooth. BLE exchanges small pieces of data through characteristics. Classic Bluetooth (audio, SPP serial port) is another protocol. Only the original ESP32 has classic Bluetooth. Every other variant is BLE only. And the ESP32-S2 has no Bluetooth at all, to be checked board by board (see the overview in the ESP32 hub).

The GATT model: services and characteristics

BLE organises data along the GATT model. Two roles and three objects to know:

  • Roles — the server (the ESP32, which holds the data and exposes it, also called the peripheral) and the client (the phone, which connects and reads or writes, also called the central).
  • Service — a logical grouping of data, identified by a UUID (unique identifier).
  • Characteristic — the data itself (a measurement, a state), inside a service. It carries properties: READ (the client can read), WRITE (the client can write), NOTIFY (the server pushes the value with no request).
  • Advertising — to be findable, the server broadcasts its presence. The client discovers it during a scan.

The idea: the ESP32 publishes a service holding a characteristic. A phone scans, connects, reads the characteristic (or receives its notifications).

The two BLE roles: the peripheral ESP32 (server) exposes a service and a characteristic and broadcasts its presence; the central phone (client) scans, connects, then reads or subscribes to the value.|640

Example — Publishing a counter by notification

A minimal GATT server: the ESP32 exposes a characteristic whose value increments every second and notifies it to the connected clients. Testable with the nRF Connect application (free, iOS/Android).

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>     // descriptor required for notifications
 
// UUIDs chosen freely (use an online generator for real projects)
const char* UUID_SERVICE        = "12345678-1234-1234-1234-1234567890ab";
const char* UUID_CARACTERISTIQUE = "abcd1234-5678-90ab-cdef-1234567890ab";
 
BLECharacteristic* caracteristique;
unsigned long dernier = 0;
int compteur = 0;
 
void setup() {
  Serial.begin(115200);
 
  BLEDevice::init("ESP32-Capteur");              // name visible at scan time
  BLEServer* serveur = BLEDevice::createServer();
  BLEService* service = serveur->createService(UUID_SERVICE);
 
  caracteristique = service->createCharacteristic(
      UUID_CARACTERISTIQUE,
      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
  caracteristique->addDescriptor(new BLE2902());  // turns notifications on
 
  service->start();
 
  BLEAdvertising* advertising = BLEDevice::getAdvertising();
  advertising->addServiceUUID(UUID_SERVICE);
  advertising->start();                           // becomes findable
  Serial.println("BLE up, waiting for a connection...");
}
 
void loop() {
  if (millis() - dernier >= 1000) {               // every second
    dernier = millis();
    compteur++;
    caracteristique->setValue(String(compteur).c_str());  // as text, readable on the phone
    caracteristique->notify();                            // pushes to the clients
    Serial.print("Notified: ");
    Serial.println(compteur);
  }
}

Upload, open nRF Connect on the phone, scan: ESP32-Capteur shows up. Connect, unfold the service, turn on notifications for the characteristic: the value increments live. The board pushes the data without the phone having to ask again.

Tip

Text or binary? The value goes out as text here, so that it shows up as it is in nRF Connect. A BLE characteristic really carries raw bytes: setValue(compteur) with an integer would send four bytes, which the application would show in hexadecimal (01-00-00-00). Real projects prefer that binary format, more compact. Text is a comfort for bring-up.

Two nRF Connect screens side by side — the scan where "ESP32-Capteur" shows up with its Connect button boxed, then the connected view where the unknown service is unfolded and the characteristic shows its value in hexadecimal and as text.|480

Pitfalls

Mixing up BLE and classic Bluetooth. Trying to hook an audio headset or an SPP serial port onto BLE does not work: they are two protocols. For a “wireless serial port”, it is classic Bluetooth, on the original ESP32 only.

A variant with no Bluetooth. On an ESP32-S2, this code neither compiles nor runs: no Bluetooth radio at all. Check the variant before aiming at BLE.

Notifications with no descriptor. A NOTIFY characteristic without the BLE2902 descriptor: the client cannot subscribe and receives nothing. Add it as a matter of course for notifications.

Mismatched UUIDs. The client looks for a precise UUID. One wrong character on the server side and it does not find the characteristic. Copy and paste the UUIDs, do not retype them.

A bulky BLE library. The built-in BLE stack takes a lot of Flash. A BLE plus Wi-Fi sketch can overflow the default partition. Pick a suitable partition scheme (Outils → Partition Scheme) or the lighter NimBLE stack (see Going further).

No test tool. With no BLE application on the phone, there is no way to check the server. Install nRF Connect (or an equivalent) before you start.

Exercises

Wiring: sensor on GPIO34 and LED on GPIO16 (see the circuits of configuring the GPIO).

Exercise 1 — Notifying a real measurement

Replace the counter by the reading of an analog sensor on GPIO34 (ADC1), notified every 500 ms. What changes?

Exercise 2 — Receiving a command (a write characteristic)

Add a write characteristic: when the client writes 1 into it, the board lights a LED (GPIO16). With 0, it switches it off. Hint: a write callback.

Special case — The board as a client (scanning)

The ESP32 can also be the central: scanning the BLE peripherals around it. Useful to detect beacons or other sensors.

#include <BLEDevice.h>
#include <BLEScan.h>
 
void setup() {
  Serial.begin(115200);
  BLEDevice::init("");
  BLEScan* scan = BLEDevice::getScan();
  BLEScanResults* res = scan->start(5);     // 5-second scan (core 3.x: a pointer)
  Serial.print(res->getCount());
  Serial.println(" BLE peripherals found.");
}
 
void loop() {}

A board cannot be an elaborate server and an intensive scanner at the same time without precautions. For mixed roles, plan the architecture (see Multitasking with FreeRTOS).

Where it fits in the project

  • Step 4 of the proof-of-concept phase — if the project exposes data to a phone with no network (a wearable sensor, a remote control), validating early a GATT server readable by nRF Connect lifts the connectivity uncertainty. Often enough where Wi-Fi would be oversized.
  • A mobile configuration interface — a write characteristic lets you set parameters from a phone, with no physical HMI.

Choosing between Wi-Fi and BLE from the PoC onwards, on the real need (Internet versus local link, energy available), avoids oversizing the radio: BLE is more frugal when the Internet is not required.

Going further

  • Wireless technologies · BLE — the concept, compared with Wi-Fi / Zigbee / LoRa (cross-cutting).
  • Wi-Fi — the other radio, for the network and the Internet.
  • NimBLE-Arduino — an alternative BLE stack, far lighter in Flash and in RAM than the built-in one, recommended as soon as room runs short.
  • BLE documentation of the Arduino-ESP32 core — server, client, security, pairing.

See also