ESP32 BLE Basics: Scanning, Advertising, and Custom Services
BLE vs Classic Bluetooth
ESP32 supports both, but BLE (Bluetooth Low Energy) is what you want for nearly all modern projects — much lower power draw, and it's what phones/tablets and most modern BLE peripherals actually speak. Classic Bluetooth is mostly relevant now only for audio streaming (A2DP) or legacy device compatibility.
Scanning for Nearby BLE Devices
#include <BLEDevice.h> #include <BLEScan.h> class ScanCallback: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice device) { Serial.printf("Found: %s (RSSI %d)\n", device.getName().c_str(), device.getRSSI()); } }; void setup() { Serial.begin(115200); BLEDevice::init(""); BLEScan* scan = BLEDevice::getScan(); scan->setAdvertisedDeviceCallbacks(new ScanCallback()); scan->start(5); // scan for 5 seconds }This is the basis for presence detection projects — scanning for a specific known device's MAC address/name to trigger "someone's home" automations, a common and genuinely reliable pattern for Home Assistant presence detection when phone-based GPS presence proves flaky indoors.
Advertising: Making ESP32 Discoverable
#include <BLEDevice.h> #include <BLEServer.h> void setup() { BLEDevice::init("MyESP32Device"); BLEServer* server = BLEDevice::createServer(); BLEAdvertising* advertising = BLEDevice::getAdvertising(); advertising->start(); }Now your ESP32 shows up as "MyESP32Device" when scanning from a phone's Bluetooth settings or a BLE scanner app — the foundation before adding any actual functionality via services/characteristics.
Custom Services and Characteristics
BLE organizes functionality into Services (a logical grouping, identified by a UUID) containing Characteristics (individual readable/writable data points, each with their own UUID):
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" BLEServer* server = BLEDevice::createServer(); BLEService* service = server->createService(SERVICE_UUID); BLECharacteristic* characteristic = service->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); characteristic->setValue("Hello"); service->start();Generate your own UUIDs (any online UUID generator) rather than reusing example ones for a real project — UUID collisions between unrelated devices can cause phone apps to get confused connecting to the wrong device if you're running multiple custom BLE projects nearby.
Testing Without Writing a Phone App
Generic BLE scanner/explorer apps (nRF Connect is the most widely used, free on iOS/Android) let you discover your ESP32's services/characteristics and read/write values manually — genuinely useful for testing your device side before writing any actual phone app code, or if you never need a dedicated app at all and nRF Connect is sufficient for your use case.
Power Note
BLE advertising continuously draws more power than you'd expect from "low energy" branding alone — if this is a battery project, tune the advertising interval (longer interval = lower power, slower discoverability) and combine with deep sleep between active BLE windows for anything genuinely battery-constrained (see the deep sleep guide).
Related Guides
- How to Hack Wi-Fi and Bluetooth with the Flipper Zero and Wi-Fi Marauder
- Choosing the Right ESP32 Variant: ESP32, S2, S3, C3, and C6 Compared
- Build a Bluetooth A2DP Audio Receiver with ESP32
- How to Build a Class-D Audio Amplifier: TPA3116, Power Supply, and Speaker Matching
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols
- How to Use Sensors with Arduino and ESP32: Temperature, Distance, Load, Current, and Hall Effect
- How to Control Motors with Arduino and ESP32: Stepper, DC, and Servo Drivers