ESP32 Bluetooth Low Energy (BLE): A Beginner’s Guide to GATT, Advertising & Your First Connection

Have you worked with the ESP32’s Wi-Fi features before? Then Bluetooth Low Energy (BLE) might feel like a different world. It uses different terms. It follows a different connection model. It makes you think about data differently.
But BLE matters for a huge category of IoT projects. Anything that talks to a phone, runs on battery for a long time, or communicates over short range without heavy network overhead needs BLE.
In this guide, we’ll break down how BLE works on the ESP32. We’ll cover the GATT model, advertising, and how to get your first BLE connection running. We’ll keep it practical and code-first.
What Is Bluetooth Low Energy (BLE)?
Bluetooth Low Energy is a wireless protocol for devices that send small amounts of data while using very little power. It shares the same 2.4GHz radio band as Classic Bluetooth. But it uses a different architecture — one built around battery life, not throughput.
BLE vs Classic Bluetooth — Key Differences
Classic Bluetooth handles continuous data streams well. Think audio streaming to headphones. It keeps a constant connection open, which costs more power.
BLE takes the opposite approach. It sends short, infrequent bursts of data. A BLE device can run for months on a coin-cell battery. Why? It spends most of its time in a low-power idle state. The radio only wakes up briefly to send or receive data.
The trade-off is speed. BLE transfers data much slower than Classic Bluetooth. That’s exactly why it suits sensor readings, notifications, and small control commands — not audio or file transfers.
Why BLE Is Ideal for IoT and Battery-Powered Devices
Think about a temperature sensor, a fitness tracker, or a smart lock. None of these need a constant, high-bandwidth connection. They need to send small pieces of data occasionally, then stay near zero power the rest of the time.
BLE’s advertising model (more on this below) fits that need perfectly. It also pairs well with the ESP32’s Deep Sleep mode — a strong combination for projects that need to run for months without recharging.
Understanding the BLE Architecture on ESP32
BLE organizes data in a specific way. This model differs from how you might think about Wi-Fi or serial communication. Once you understand it, everything else about BLE clicks into place.
GATT — Generic Attribute Profile Explained
GATT (Generic Attribute Profile) defines how two connected BLE devices exchange data. Instead of sending raw byte streams, GATT organizes data into a clear hierarchy: Services contain Characteristics, and each characteristic holds one specific piece of data.
Picture GATT as a small, well-organized filing system. One device exposes it. The other device browses and interacts with it.
Services and Characteristics: The Building Blocks
A Service groups related functionality together — for example, a “Battery Service” or an “Environmental Sensing Service.” Each service carries a UUID (Universally Unique Identifier). You can use a standard, predefined UUID for common service types, or generate a custom one for your own proprietary application.
Inside a service, one or more Characteristics hold the actual data points. A “Battery Level” characteristic might live inside a “Battery Service,” for example. Each characteristic includes:
- A UUID that identifies what it represents
- A value — the actual data
- Properties that define how you can use it: Read (a client can request the value), Write (a client can change the value), Notify (the server pushes updates automatically when the value changes), and others.
Server vs Client Roles (Where ESP32 Fits)
Most BLE interactions involve two roles. The GATT Server holds the data and exposes services and characteristics. The GATT Client connects and reads, writes, or subscribes to that data.
In most ESP32 IoT projects, the ESP32 plays the server role. It acts as the sensor node and holds the data. A phone app usually plays the client role — it connects to read sensor values or send commands.
How BLE Advertising Works
Before any connection happens, a BLE device needs to make itself discoverable. We call this advertising.
What Happens When a Device “Advertises”
An advertising device periodically broadcasts small data packets over the air. These packets announce its presence — and sometimes basic information — to any nearby device that’s scanning. A phone running a BLE scanner app sees these advertisements. It can connect based on the device name, advertised services, or signal strength.
Advertising doesn’t require a connection. It’s a one-way broadcast. Many battery-powered BLE beacons work exactly this way: they never connect to anything. They just advertise a small piece of data — like a location identifier — continuously, and nearby devices simply listen.
Advertising Intervals and Power Trade-offs
How often should your device advertise? This is a direct power-vs-discoverability trade-off. A shorter interval, like every 20ms, makes your device easy and fast to find, but it uses more power since the radio turns on more often. A longer interval, like every 1000ms, saves significant power — but a scanning device might take longer to find you.
Most real-world projects land somewhere between 100ms and 500ms. That range balances the two concerns reasonably well.
Setting Up Your ESP32 for BLE Development
Required Libraries and Tools (Arduino IDE)
Using the Arduino IDE with the ESP32 board package? BLE support comes built in through the BLEDevice library. You won’t need to install anything extra. You’ll typically include:
cpp
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>Prefer ESP-IDF over Arduino? BLE support comes through the esp_bt and esp_gatts components, or through NimBLE — a lighter-weight alternative.
Hardware Requirements
Any standard ESP32 development board works. The BLE radio sits built into the chip itself, so you don’t need extra hardware to get started. To test your BLE server, grab a phone and install a generic BLE scanning app, like nRF Connect for Mobile (free on iOS and Android). This lets you see and interact with your ESP32’s advertised services — no custom app required.
Building Your First BLE Server on ESP32
Here’s a complete, minimal example. It creates a BLE server with one service and one readable, notifiable characteristic:
cpp
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Client connected");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Client disconnected");
pServer->getAdvertising()->start(); // resume advertising after disconnect
}
};
void setup() {
Serial.begin(115200);
// Step 1 — Initialize the BLE device
BLEDevice::init("ESP32-Sensor");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Step 2 — Create a service and characteristic
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->addDescriptor(new BLE2902()); // required for Notify to work
pCharacteristic->setValue("Hello from ESP32");
pService->start();
// Step 3 — Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("BLE server started, waiting for a connection...");
}
void loop() {
if (deviceConnected) {
static int counter = 0;
String value = "Reading: " + String(counter++);
pCharacteristic->setValue(value.c_str());
pCharacteristic->notify(); // push the updated value to the connected client
delay(2000);
}
}Step 4 — Connect and Test with a Phone App
- Upload the sketch to your ESP32. Open the Serial Monitor to confirm it’s advertising.
- Open nRF Connect (or a similar BLE scanner app) on your phone.
- Scan for nearby devices. You should see “ESP32-Sensor” in the list.
- Tap to connect. You’ll see the service — identified by the UUID you set — and the characteristic underneath it.
- Tap the characteristic and enable notifications (usually a small icon next to the value). You should see the “Reading: X” value update every 2 seconds.
Do you see the updating values? Your first BLE connection works end to end.
Common BLE Pitfalls (and How to Avoid Them)
Connection Drops and MTU Size Issues
The default BLE MTU (Maximum Transmission Unit) stays quite small — 23 bytes, with only 20 usable for actual data. Sending larger payloads? You might see truncated or dropped data. To fix this, negotiate a larger MTU size on both the server and client side. Most modern phones support MTU negotiation up to 512 bytes, but you must request it explicitly.
Power Consumption Mistakes
It’s easy to assume BLE saves power automatically just because you’re using it. Poor configuration can erase most of those savings. An unnecessarily short advertising interval does this. So does an aggressive connection interval when the device sits idle.
Combining BLE with Deep Sleep? Check our ESP32 Deep Sleep guide first. Remember: an active BLE connection can’t survive Deep Sleep. For ultra-low-power projects, advertising-only designs — without a persistent connection — usually work better.
Debugging Tips
Something not connecting or notifying as expected? Check three things first. Did you add the BLE2902 descriptor to any characteristic you want to notify from? (It’s required, and easy to forget.) Did you actually start the service (pService->start()) before advertising begins? Is your scanning app subscribing to notifications, not just reading the value once? Most “it’s not updating” issues trace back to one of these three.
Real-World ESP32 BLE Project Ideas
- BLE Sensor Beacon — Advertise temperature or humidity readings continuously. No active connection required. Great for always-on environmental monitoring picked up by a central hub.
- BLE-to-Wi-Fi Bridge — Use the ESP32 to collect data from BLE sensors, like fitness trackers or beacons, then forward it to a cloud service over Wi-Fi.
- Custom Mobile App Integration — Pair your ESP32 GATT server with a custom-built mobile app (using frameworks like Flutter or React Native with BLE plugins). Build a fully branded IoT product experience.
Frequently Asked Questions
Can ESP32 do BLE and Wi-Fi at the same time? Yes. The ESP32 supports Wi-Fi and BLE simultaneously — they share the same radio through time-division multiplexing. Heavy simultaneous use of both, though, can reduce performance and raise power consumption compared to using either alone.
What’s the maximum range of ESP32 BLE? In open air, ESP32 BLE typically reaches 30–50 meters. Indoors, that range drops significantly due to walls and interference. Expect a realistic indoor range closer to 10–20 meters, depending on your environment.
Is ESP32 BLE secure enough for production use? BLE supports pairing and encryption, including passkey-based pairing. This works for many consumer applications. But the default examples — like the one above — don’t enable encryption. Building something for production that handles sensitive data? Configure BLE security explicitly — bonding, encryption, and authentication — rather than relying on defaults.
Conclusion
Coming from a Wi-Fi background, BLE might feel unfamiliar at first. But the GATT model — services, characteristics, and advertising — becomes intuitive fast once you’ve built a working example. Start simple. Get a basic server advertising and notifying. Confirm it with a phone scanner app. Then build out the specific services and characteristics your project actually needs.
Does your next project need to run on battery for months instead of days? Pair what you’ve learned here with our ESP32 Deep Sleep guide. BLE’s low power profile and Deep Sleep’s near-zero idle draw make a natural combination for long-running, battery-powered IoT devices.
For more hardware tutorials and modern embedded technology trends, explore our complete IoT Magazine collection.



