6.9 Practical Implementation with ESP32
This chapter provides a hands-on project to demonstrate the core concepts of a BLE peripheral device using an ESP32. We will move beyond a simple serial example and create a simulated BLE Heart Rate Sensor. This is a standard profile that teaches the essential concepts of services, characteristics, and notifications.
Project: Create a BLE Heart Rate Sensor
Goal: Configure the ESP32 to act as a BLE peripheral that advertises the standard Heart Rate service. When a central device (like a smartphone) connects and enables notifications, the ESP32 will periodically send a simulated heart rate measurement.
You Will Need:
-
An ESP32 development board.
-
The Arduino IDE with the ESP32 board package installed.
-
A smartphone with a BLE scanner app (e.g., "nRF Connect for Mobile" or "LightBlue").
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// Standard Bluetooth Service and Characteristic UUIDs for Heart Rate
#define SERVICE_UUID "0000180d-0000-1000-8000-00805f9b34fb" // Heart Rate Service
#define CHARACTERISTIC_UUID "00002a37-0000-1000-8000-00805f9b34fb" // Heart Rate Measurement
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
// This class handles server events like client connect/disconnect
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Client Connected");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Client Disconnected");
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Heart Rate Sensor...");
// 1. Initialize the BLE device and set its name
BLEDevice::init("ESP32 Heart Rate Sensor");
// 2. Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks()); // Set the event handler
// 3. Create the BLE Service using the standard Heart Rate UUID
BLEService *pService = pServer->createService(SERVICE_UUID);
// 4. Create a BLE Characteristic for the Heart Rate Measurement
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
// 5. Add a 2902 descriptor to the characteristic. This is ESSENTIAL
// for the client to be able to enable notifications.
pCharacteristic->addDescriptor(new BLE2902());
// 6. Start the service
pService->start();
// 7. Start advertising, so other BLE devices can find this one
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); // Advertise our service
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can scan for 'ESP32 Heart Rate Sensor' on your phone.");
}
void loop() {
// Check if a client is connected
if (deviceConnected) {
// Generate a simulated heart rate value
// The first byte is a flag (0), the second is the 8-bit heart rate value
static uint8_t heartRate = 60;
heartRate++;
if (heartRate > 100) {
heartRate = 60; // Reset after 100
}
uint8_t heartRateData[2] = {0, heartRate};
// Set the characteristic's new value
pCharacteristic->setValue(heartRateData, 2);
// Send a notification to the connected client
pCharacteristic->notify();
Serial.print("Heart Rate Notification Sent: ");
Serial.println(heartRate);
}
delay(1000);
}
Code Walkthrough
-
Initialization: We initialize the BLE stack using
BLEDevice::init()
and give our device a public name. -
Server and Service: We create a
BLEServer
to manage connections and aBLEService
to hold our data. We use the official UUID for the "Heart Rate Service." -
Characteristic: Inside the service, we create a
BLECharacteristic
for the "Heart Rate Measurement." We set its properties to allow a client to both READ the value and subscribe to NOTIFY (notifications). -
Descriptor (BLE2902): This is a critical step. The BLE2902 descriptor is the Client Characteristic Configuration Descriptor (CCCD). A client (your phone) writes to this descriptor to tell the server (the ESP32) that it wants to receive notifications. Without this, notifications will not work.
-
Advertising: We start advertising and include the Service UUID. This tells scanning devices what services we offer before they even connect.
-
The Loop: In the main loop, we check if a client is connected. If so, we generate a new simulated heart rate value, update the characteristic with
setValue()
, and then send it to the client usingnotify()
.
How to Test It
-
Upload the code to your ESP32.
-
Open the Arduino Serial Monitor to see the status messages.
-
On your smartphone, open a BLE scanner app (like nRF Connect for Mobile).
-
Scan for devices. You should see "ESP32 Heart Rate Sensor" in the list.
-
Connect to the device. In the Serial Monitor, you should see "Client Connected."
-
Find the Heart Rate Service and expand it to see the Heart Rate Measurement characteristic.
-
Tap the "subscribe" or "enable notifications" icon (often a single or triple downward arrow).
-
You should now see the value updating in your app every second, and the Serial Monitor will show the "Notification Sent" logs.
No comments to display
No comments to display