%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#E67E22','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D'}}}%%
flowchart TD
subgraph BEACONS["Known Beacon Positions"]
B1[Beacon 1<br/>0,0]
B2[Beacon 2<br/>5,0]
B3[Beacon 3<br/>0,5]
end
PHONE[Smartphone<br/>Scanner]
B1 -->|"RSSI: -52 dBm<br/>d1 = 1.8m"| PHONE
B2 -->|"RSSI: -65 dBm<br/>d2 = 4.5m"| PHONE
B3 -->|"RSSI: -58 dBm<br/>d3 = 2.8m"| PHONE
PHONE --> CALC[Trilateration<br/>Algorithm]
CALC --> POS[Estimated Position<br/>1.2m, 1.5m]
style B1 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style B2 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style B3 fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style PHONE fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style CALC fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
style POS fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
904 Bluetooth Implementation and Labs
Practical BLE Development with ESP32
ble, esp32, arduino, wokwi, gatt, beacon, implementation, lab
904.1 Learning Objectives
By the end of this chapter, you will be able to:
- Implement BLE scanning and advertising on ESP32
- Create GATT services and characteristics
- Build BLE beacon applications (iBeacon format)
- Develop indoor positioning using RSSI trilateration
- Debug BLE applications using Serial Monitor and tools
904.2 Introduction
This chapter provides practical, hands-on experience with Bluetooth Low Energy development. Using the ESP32 microcontroller and Wokwi simulator, you will learn to build real BLE applications from scanning nearby devices to creating beacon-based indoor positioning systems.
Before diving into code, make sure you understand: - BLE roles: Central (scanner) vs Peripheral (advertiser) - GATT structure: Services contain Characteristics contain Values - Advertising: How devices broadcast their presence
The labs below start simple and build complexity gradually.
904.3 Lab 1: BLE Beacon Scanner
This lab implements a BLE scanner that discovers nearby devices and estimates their proximity.
904.3.1 Learning Objectives
By completing this lab, you will be able to:
- Set up ESP32 BLE scanning: Configure the ESP32 as a BLE Central device
- Discover nearby BLE devices: Understand how BLE advertising and scanning work
- Parse BLE advertisement data: Extract device names, addresses, and RSSI values
- Analyze signal strength: Use RSSI to estimate device proximity
904.3.2 Embedded Wokwi Simulator
- Click inside the code editor in the simulator below
- Replace the default code with the BLE Scanner code provided
- Click the green “Play” button to compile and run
- Open Serial Monitor (click the terminal icon) to see scan results
904.3.3 BLE Scanner Code
/*
* ESP32 BLE Beacon Scanner
* Scans for nearby BLE devices and displays:
* - Device name (if available)
* - MAC address
* - RSSI (signal strength)
* - Estimated distance category
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
// Scan configuration
const int SCAN_TIME_SECONDS = 5;
const int PAUSE_BETWEEN_SCANS = 2;
BLEScan* pBLEScan;
int scanCount = 0;
// Callback class for handling discovered devices
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
String deviceName = advertisedDevice.getName().c_str();
String deviceAddress = advertisedDevice.getAddress().toString().c_str();
int rssi = advertisedDevice.getRSSI();
// Determine proximity category based on RSSI
String proximity;
if (rssi >= -50) {
proximity = "IMMEDIATE (<1m)";
} else if (rssi >= -70) {
proximity = "NEAR (1-3m)";
} else if (rssi >= -90) {
proximity = "FAR (3-10m)";
} else {
proximity = "VERY FAR (>10m)";
}
// Print device information
Serial.println("----------------------------------------");
Serial.print("Device Found: ");
if (deviceName.length() > 0) {
Serial.println(deviceName);
} else {
Serial.println("[Unknown Name]");
}
Serial.print(" Address: ");
Serial.println(deviceAddress);
Serial.print(" RSSI: ");
Serial.print(rssi);
Serial.println(" dBm");
Serial.print(" Proximity: ");
Serial.println(proximity);
if (advertisedDevice.haveServiceUUID()) {
Serial.print(" Service UUID: ");
Serial.println(advertisedDevice.getServiceUUID().toString().c_str());
}
}
};
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("========================================");
Serial.println(" ESP32 BLE Beacon Scanner Lab");
Serial.println("========================================");
Serial.println();
Serial.println("Initializing BLE...");
BLEDevice::init("ESP32-Scanner");
pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->setInterval(100);
pBLEScan->setWindow(99);
Serial.println("BLE Scanner Ready!");
Serial.println();
}
void loop() {
scanCount++;
Serial.println("========================================");
Serial.print("Starting Scan #");
Serial.println(scanCount);
Serial.println("========================================");
BLEScanResults foundDevices = pBLEScan->start(SCAN_TIME_SECONDS, false);
Serial.println();
Serial.print("Scan Complete! Found ");
Serial.print(foundDevices.getCount());
Serial.println(" device(s)");
pBLEScan->clearResults();
Serial.print("Next scan in ");
Serial.print(PAUSE_BETWEEN_SCANS);
Serial.println(" seconds...");
Serial.println();
delay(PAUSE_BETWEEN_SCANS * 1000);
}904.3.4 Understanding the Code
1. BLE Initialization
BLEDevice::init("ESP32-Scanner");
pBLEScan = BLEDevice::getScan();Initializes the BLE stack and creates a scanner object.
2. Scan Configuration
pBLEScan->setActiveScan(true); // Request scan response data
pBLEScan->setInterval(100); // Time between scan windows
pBLEScan->setWindow(99); // Duration of each listening window3. RSSI to Distance Estimation
if (rssi >= -50) proximity = "IMMEDIATE (<1m)";
else if (rssi >= -70) proximity = "NEAR (1-3m)";
else if (rssi >= -90) proximity = "FAR (3-10m)";
else proximity = "VERY FAR (>10m)";RSSI-based distance estimation is approximate. Signal strength is affected by: - Obstacles (walls, furniture, people) - Device orientation and antenna design - Interference from other 2.4 GHz devices - Environmental factors (humidity, reflective surfaces)
904.4 Lab 2: BLE GATT Server (Temperature Sensor)
Create a BLE peripheral that exposes temperature data via GATT.
904.4.1 Code: ESP32 Temperature Service
/*
* ESP32 BLE Temperature Sensor
* Exposes Environmental Sensing Service (0x181A)
* with Temperature characteristic (0x2A6E)
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// UUIDs
#define SERVICE_UUID "181A" // Environmental Sensing
#define TEMP_CHAR_UUID "2A6E" // Temperature
BLEServer* pServer = NULL;
BLECharacteristic* pTempCharacteristic = NULL;
bool deviceConnected = false;
float temperature = 22.5; // Simulated temperature
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Client connected!");
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Client disconnected");
// Restart advertising
pServer->getAdvertising()->start();
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE Temperature Sensor...");
// Initialize BLE
BLEDevice::init("ESP32-TempSensor");
// Create server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create temperature characteristic
pTempCharacteristic = pService->createCharacteristic(
TEMP_CHAR_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
// Add CCCD descriptor for notifications
pTempCharacteristic->addDescriptor(new BLE2902());
// Start service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->start();
Serial.println("Temperature sensor ready!");
Serial.println("Advertising as 'ESP32-TempSensor'");
}
void loop() {
if (deviceConnected) {
// Simulate temperature variation
temperature += random(-10, 11) / 100.0;
// Convert to BLE format (0.01 degree resolution)
int16_t tempValue = (int16_t)(temperature * 100);
// Update characteristic
pTempCharacteristic->setValue((uint8_t*)&tempValue, 2);
pTempCharacteristic->notify();
Serial.print("Temperature: ");
Serial.print(temperature, 2);
Serial.println(" C (notified)");
}
delay(1000);
}904.4.2 Key Concepts
- Service Creation: Use standard UUID 0x181A for Environmental Sensing
- Characteristic Properties: READ for polling, NOTIFY for real-time updates
- CCCD (BLE2902): Required descriptor for notifications
- Data Format: Temperature in 0.01°C units as int16_t
904.5 Lab 3: iBeacon Transmitter
Create an iBeacon that broadcasts location information.
904.5.1 Code: ESP32 iBeacon
/*
* ESP32 iBeacon Transmitter
* Broadcasts iBeacon format for indoor positioning
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEBeacon.h>
// iBeacon configuration
#define BEACON_UUID "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
#define BEACON_MAJOR 1 // Store/building number
#define BEACON_MINOR 101 // Specific beacon ID
BLEAdvertising *pAdvertising;
void setup() {
Serial.begin(115200);
Serial.println("Starting iBeacon...");
BLEDevice::init("iBeacon");
BLEServer *pServer = BLEDevice::createServer();
// Configure iBeacon
BLEBeacon beacon;
beacon.setManufacturerId(0x4C00); // Apple's ID
beacon.setProximityUUID(BLEUUID(BEACON_UUID));
beacon.setMajor(BEACON_MAJOR);
beacon.setMinor(BEACON_MINOR);
beacon.setSignalPower(-59); // Calibrated RSSI at 1m
// Get advertising object
pAdvertising = BLEDevice::getAdvertising();
// Set beacon data
BLEAdvertisementData advData;
advData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED
advData.setManufacturerData(beacon.getData());
pAdvertising->setAdvertisementData(advData);
pAdvertising->setScanResponseData(advData);
pAdvertising->start();
Serial.printf("iBeacon broadcasting (Major: %d, Minor: %d)\n",
BEACON_MAJOR, BEACON_MINOR);
}
void loop() {
delay(1000);
}904.5.2 iBeacon Distance Calculation
Use the path loss formula to estimate distance from RSSI:
\[d = 10^{\frac{TxPower - RSSI}{10 \times n}}\]
Where: - TxPower = RSSI at 1 meter (typically -59 to -65 dBm) - n = Path loss exponent (2.0 for free space, 2.5-4.0 for indoor) - RSSI = Measured signal strength
float calculateDistance(int rssi, int txPower = -59, float n = 2.5) {
if (rssi == 0) return -1.0;
float ratio = (txPower - rssi) / (10.0 * n);
return pow(10, ratio);
}904.6 Lab 4: Indoor Positioning System
Combine multiple beacons for trilateration-based positioning.
904.6.1 System Architecture
904.6.2 Trilateration Algorithm
import numpy as np
from scipy.optimize import least_squares
def trilaterate(beacons, distances):
"""
Calculate position from beacon positions and distances.
Args:
beacons: List of (x, y) beacon positions
distances: List of distances to each beacon
Returns:
(x, y) estimated position
"""
def residuals(point, beacons, distances):
return [
np.sqrt((point[0] - b[0])**2 + (point[1] - b[1])**2) - d
for b, d in zip(beacons, distances)
]
# Initial guess: centroid of beacons
x0 = np.mean([b[0] for b in beacons])
y0 = np.mean([b[1] for b in beacons])
result = least_squares(
residuals,
[x0, y0],
args=(beacons, distances)
)
return tuple(result.x)
# Example usage
beacons = [(0, 0), (5, 0), (0, 5)]
distances = [1.78, 4.47, 2.82] # From RSSI
position = trilaterate(beacons, distances)
print(f"Estimated position: {position}")904.7 Common Development Mistakes
Problem: Code connects to BLE device but doesn’t receive updates.
Cause: Notifications require writing to CCCD descriptor.
Wrong:
// Only reads once, no updates
value = characteristic.read();Correct:
// Enable notifications via CCCD
characteristic.getDescriptor(BLEUUID((uint16_t)0x2902))
->writeValue((uint8_t*)"\x01\x00", 2, true);
// Now notifications will arrive in callbackProblem: Battery drains quickly or response is too slow.
Cause: Using default connection interval without optimization.
| Application | Recommended Interval |
|---|---|
| Game controller | 7.5-15ms |
| Fitness tracker | 100-200ms |
| Temperature sensor | 1000-4000ms |
Fix: Request appropriate connection parameters after connecting.
Problem: Large data packets get truncated.
Cause: Default MTU is only 23 bytes (20 payload).
Fix: Negotiate larger MTU after connection:
// Request MTU exchange
BLEDevice::setMTU(247); // Request 247 bytes
// Actual MTU may be less based on negotiation904.8 Challenge Exercises
Modify the scanner to only display devices with RSSI stronger than -80 dBm.
Hint: Add an if statement in onResult():
if (rssi < -80) return; // Skip weak signalsTrack how many devices are in each proximity zone and display a summary after each scan.
Hint: Add counter variables and increment them in onResult().
Create an array of “known” device addresses and highlight them differently.
Hint:
String knownDevices[] = {"a4:c1:38:12:34:56", "b8:27:eb:aa:bb:cc"};
// Check if address matches known devicesModify the scanner to specifically detect and parse iBeacon packets.
Hint: Check manufacturer data for Apple’s company ID (0x004C).
904.9 Inline Knowledge Check
Question 1: How does BLE achieve low power consumption?
BLE achieves ultra-low power through multiple techniques: connection intervals (device sleeps between transmissions), fast connection (6ms vs 6 seconds), simpler modulation (GFSK), fewer channels (40 vs 79), optimized protocol stack, and role separation (Broadcaster, Observer, Peripheral, Central).
Question 2: Your BLE temperature sensor advertises every 1 second. After deployment, you notice smartphone battery drains faster than expected. What’s the optimization?
BLE advertising is for discovery, not data transfer. Continuously scanning for advertisements drains phone battery. The solution is to establish a GATT connection: phone scans briefly, discovers sensor, establishes connection, then receives data via GATT Notify - much more efficient than continuous scanning.
904.10 Summary
This chapter provided hands-on BLE implementation experience:
- Lab 1: BLE Scanner - discovering devices and estimating proximity from RSSI
- Lab 2: GATT Server - creating temperature service with notifications
- Lab 3: iBeacon - broadcasting location information for indoor positioning
- Lab 4: Trilateration - calculating position from multiple beacon distances
- Common Mistakes: CCCD notifications, connection intervals, MTU negotiation
904.11 What’s Next
Continue to Bluetooth Mesh and Advanced Topics for mesh networking, security features, and troubleshooting guidance.