23  BLE Hands-On Labs

In 60 Seconds

Building real BLE applications requires combining GATT service design, notification-based data push, and power-aware connection management. Standard Bluetooth SIG profiles (like Heart Rate Service) ensure cross-platform interoperability, while custom services provide flexibility for unique IoT use cases.

Key Concepts
  • BLE Lab Setup: Minimum requirements: ESP32 development board (with BLE), USB cable, nRF Connect app on smartphone, and IDE (VS Code + ESP-IDF or Arduino IDE)
  • Service Discovery Procedure: Client sends ATT Read By Group Type Request to enumerate all primary services, then ATT Read By Type Request to enumerate characteristics within each service
  • Characteristic Handle: Unique 16-bit identifier assigned by the GATT server to each attribute (service declaration, characteristic declaration, value, descriptor)
  • Notification Enable Sequence: Client writes 0x0001 to the CCCD (at characteristic handle + 1 or + 2) to subscribe; server calls ble_gattc_notify_custom() to push data
  • OTA DFU Lab: Firmware update over BLE using Nordic NRF DFU protocol (Image Info service + Packet characteristic); requires bootloader in flash, DFU trigger, and nRF Connect DFU function
  • BLE Traffic Capture Lab: Using nRF Sniffer USB dongle with Wireshark dissector plugin to capture and decode BLE advertising + connection packets for debugging
  • Power Profiling Lab: Using Nordic PPK2 (Power Profiler Kit 2) or Otii Arc to measure BLE duty cycle power consumption, identifying wakeup overhead and connection event energy
  • BLE Latency Measurement: GPIO toggle at TX and RX points + logic analyzer to measure actual BLE notification latency from sensor event to central receipt
Minimum Viable Understanding

Building real BLE applications requires combining GATT service design, notification-based data push, and power-aware connection management. Standard Bluetooth SIG profiles (like Heart Rate Service) ensure interoperability across platforms, while custom services provide flexibility for unique IoT use cases.

23.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Implement Complete BLE Projects: Construct end-to-end BLE sensor applications using ESP32 and the Arduino BLE library
  • Configure Standard GATT Services: Implement Heart Rate Service (0x180D) and other Bluetooth SIG specifications with correct characteristic properties
  • Develop Python Dashboards: Build real-time monitoring applications using the bleak library with asynchronous notification handling
  • Apply Indoor Positioning: Configure BLE beacons and apply trilateration algorithms to estimate indoor locations from RSSI measurements
  • Analyze Mesh Network Behavior: Evaluate BLE mesh message flooding, TTL propagation, and relay node impact on network traffic

What you’ll build: Four complete projects that demonstrate real-world BLE applications.

Before starting:

Time commitment: Each lab takes 1-3 hours depending on your experience level.

“Four amazing projects await!” announced Max the Microcontroller, spreading out ESP32 boards and sensors on the workbench. “We are building a heart rate monitor, a real-time Python dashboard, an indoor positioning system, and a mesh network simulator. Each project teaches different BLE skills.”

Sammy the Sensor was drawn to the heart rate monitor. “That uses a standard Bluetooth SIG profile, right?” Max nodded. “Exactly! The Heart Rate Service has a UUID of 0x180D, and every BLE-capable phone already knows how to read it. By using standard profiles, your device works with any fitness app without custom code on the phone side.”

“I am excited about indoor positioning,” said Lila the LED. “You place BLE beacons at known locations, measure signal strength from each one, and use trilateration to estimate where a device is. It is like GPS, but indoors! The tricky part is that RSSI varies a lot due to reflections and obstacles, so you need filtering and calibration.”

Bella the Battery reminded everyone about practical constraints. “When building the mesh network simulator, remember that Bluetooth Mesh uses managed flooding – every relay node rebroadcasts every message. With 50 nodes and a TTL of 7, a single message generates hundreds of transmissions. That is great for reliability but terrible for my battery life, so choose your relay nodes carefully.”

In Bluetooth Mesh, the number of transmissions per message is governed by the Time-To-Live (TTL) and relay density:

\[N_{transmissions} \approx N_{relays} \times (1 + R_{avg})^{TTL}\]

where \(N_{relays}\) is the number of relay nodes, \(R_{avg}\) is the average number of neighbors per node, and \(TTL\) is the message hop limit.

Example: A 50-node mesh network with average connectivity of 3 neighbors per node: - TTL = 7 (maximum hops) - Relay nodes: 40 (80% of nodes) - First hop: 1 originator → 3 relays = 3 transmissions - Each relay reaches ~3 new nodes → exponential growth - Upper bound: \(40 \times 3^{7} = 40 \times 2187 = 87,480\) transmissions (worst case with no loop prevention) - With mesh cache (prevents re-relay): ~120-300 transmissions (observed in practice)

Even with loop prevention, a single message generates 100+ radio events. For battery-powered nodes, limit relay capability to mains-powered devices.

23.2 Prerequisites

Before starting these labs:

23.3 Lab 1: ESP32 BLE Heart Rate Monitor

Objective: Build a BLE heart rate monitor using standard Heart Rate Service (0x180D).

Materials:

  • ESP32 development board
  • Heart rate sensor (MAX30102) or simulate with potentiometer
  • Breadboard and wires
  • nRF Connect app (Android/iOS)

Circuit Diagram:

MAX30102       ESP32
--------       -----
VIN    ------>  3.3V
GND    ------>  GND
SDA    ------>  GPIO 21 (I2C SDA)
SCL    ------>  GPIO 22 (I2C SCL)

Complete Code:

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLE2902.h>

// Standard Heart Rate Service and Measurement UUIDs
#define SERVICE_UUID        "0000180d-0000-1000-8000-00805f9b34fb"
#define CHARACTERISTIC_UUID "00002a37-0000-1000-8000-00805f9b34fb"

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;

class ServerCB : public BLEServerCallbacks {
  void onConnect(BLEServer* s)    { deviceConnected = true; }
  void onDisconnect(BLEServer* s) { deviceConnected = false; }
};

void setup() {
  Serial.begin(115200);
  BLEDevice::init("HR-Monitor-ESP32");
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCB());

  BLEService* pSvc = pServer->createService(SERVICE_UUID);
  pCharacteristic = pSvc->createCharacteristic(CHARACTERISTIC_UUID,
      BLECharacteristic::PROPERTY_READ |
      BLECharacteristic::PROPERTY_NOTIFY);
  pCharacteristic->addDescriptor(new BLE2902());
  pSvc->start();

  BLEDevice::getAdvertising()->addServiceUUID(SERVICE_UUID);
  BLEDevice::getAdvertising()->start();
}

void loop() {
  if (deviceConnected) {
    // HR Measurement: Byte 0 = flags, Byte 1 = BPM
    uint8_t hrData[2] = {0x00, (uint8_t)random(60, 100)};
    pCharacteristic->setValue(hrData, 2);
    pCharacteristic->notify();
    delay(1000);
  } else {
    static bool wasConnected = false;
    if (wasConnected) { pServer->startAdvertising(); wasConnected = false; }
  }
}

Objective: Run the BLE Heart Rate Monitor code on a simulated ESP32 and observe the GATT service lifecycle: initialization, advertising, and simulated heart rate notifications.

Paste the Heart Rate Monitor code above into the simulator. Watch the Serial Monitor for BLE initialization and simulated heart rate values. Note: Wokwi simulates BLE stack output but does not have a virtual BLE client, so you will see advertising start and simulated readings in the serial output.

What to Observe:

  1. BLE initialization creates a GATT server with the standard Heart Rate Service UUID (0x180D)
  2. The characteristic supports both READ and NOTIFY properties
  3. Heart rate values are sent as a 2-byte array (flags + BPM value)
  4. Try modifying random(60, 100) to simulate exercise (e.g., random(120, 180))

Expected Output (Serial Monitor):

BLE Heart Rate Monitor Starting...
BLE Heart Rate Monitor ready!
Open nRF Connect app to connect
Client connected
Heart Rate: 72 BPM
Heart Rate: 78 BPM
Heart Rate: 65 BPM
Client disconnected
Advertising restarted

Testing with nRF Connect:

  1. Open nRF Connect app
  2. Scan for devices - Find “HR-Monitor-ESP32”
  3. Connect to device
  4. Find Heart Rate Service (0x180D)
  5. Enable notifications on Heart Rate Measurement characteristic
  6. Watch real-time heart rate updates

Learning Outcomes:

  • Implement standard BLE GATT services
  • Handle BLE server callbacks (connect/disconnect)
  • Use BLE notifications for real-time data
  • Work with standard Bluetooth SIG services
  • Test BLE devices with professional tools

Challenges:

  1. Add Battery Service (0x180F) with battery level notifications
  2. Implement actual MAX30102 heart rate sensor reading
  3. Add energy expended calculation (per BLE HRS specification)
  4. Implement RR-Interval measurements for heart rate variability

23.4 Lab 2: Python BLE Environmental Monitor Dashboard

Objective: Create Python dashboard that connects to BLE environmental sensors and displays data in real-time.

Materials:

  • Python 3.7+
  • ESP32 with BLE (from Lab 1 or separate sensor)
  • bleak library (pip install bleak)

Expected Output:

============================================================
  BLE Environmental Monitor Dashboard
============================================================

Scanning for devices matching 'HR-Monitor'...
Found: HR-Monitor-ESP32 (A4:CF:12:34:56:78)

Connecting to HR-Monitor-ESP32...
Connected to HR-Monitor-ESP32

Subscribed to Heart Rate notifications
Warning: Battery service not available

Monitoring for 30 seconds...

[14:23:10] Heart Rate: 72 BPM
[14:23:11] Heart Rate: 78 BPM
[14:23:12] Heart Rate: 65 BPM
[14:23:13] Heart Rate: 82 BPM
...
Disconnected

Learning Outcomes:

  • Use Python bleak library for BLE communication
  • Connect to BLE GATT servers
  • Subscribe to BLE notifications
  • Parse standard BLE data formats
  • Handle asynchronous BLE operations

23.5 Lab 3: BLE Beacon-Based Indoor Positioning

Objective: Use multiple BLE beacons to estimate indoor position using RSSI trilateration.

Materials:

  • 3+ ESP32 boards (as iBeacon transmitters)
  • 1 ESP32 or Python device (as scanner/receiver)
  • Known beacon positions

Beacon Setup (ESP32 #1, #2, #3):

#include <BLEDevice.h>
#include <BLEBeacon.h>

#define BEACON_UUID  "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"
#define BEACON_MAJOR 1
#define BEACON_MINOR 101  // Change to 102, 103 for other beacons

void setup() {
  Serial.begin(115200);
  BLEDevice::init("iBeacon");
  BLEDevice::createServer();

  BLEBeacon beacon;
  beacon.setManufacturerId(0x4C00);  // Apple's company ID
  beacon.setProximityUUID(BLEUUID(BEACON_UUID));
  beacon.setMajor(BEACON_MAJOR);
  beacon.setMinor(BEACON_MINOR);
  beacon.setSignalPower(-59);        // Calibrated RSSI at 1 m

  BLEAdvertisementData advData;
  advData.setFlags(0x04);            // BR_EDR_NOT_SUPPORTED
  advData.setManufacturerData(beacon.getData());

  BLEAdvertising* pAdv = BLEDevice::getAdvertising();
  pAdv->setAdvertisementData(advData);
  pAdv->start();
}

void loop() { delay(1000); }

Expected Output (Python Positioning System):

Indoor Positioning System
============================================================

Known Beacon Positions:
  Beacon 101: (0.0m, 0.0m)
  Beacon 102: (5.0m, 0.0m)
  Beacon 103: (0.0m, 5.0m)

Scanning for beacons...

Scan #1:
  Beacon 101: RSSI -52 dBm -> 1.78m
  Beacon 102: RSSI -65 dBm -> 4.47m
  Beacon 103: RSSI -58 dBm -> 2.82m
Estimated Position: (1.2m, 1.5m)

Scan #2:
  Beacon 101: RSSI -55 dBm -> 2.24m
  Beacon 102: RSSI -62 dBm -> 3.55m
  Beacon 103: RSSI -60 dBm -> 3.16m
Estimated Position: (1.4m, 1.7m)
...

Learning Outcomes:

  • Implement iBeacon protocol on ESP32
  • Parse BLE beacon advertisement packets
  • Convert RSSI to distance using path loss model
  • Implement 2D trilateration algorithm
  • Build indoor positioning systems
  • Understand RSSI limitations and filtering

Challenges:

  1. Add Kalman filtering for smoother position estimates
  2. Calibrate path loss exponent (n) for your environment
  3. Add 4th beacon for 3D positioning
  4. Implement zone-based proximity instead of exact coordinates
  5. Create visual map showing beacons and estimated position

23.6 Lab 4: BLE Mesh Network Simulation

Objective: Simulate a BLE mesh network with multiple nodes relaying messages.

Materials:

  • 3+ ESP32 boards
  • ESP-IDF with BLE Mesh support (or use simulation library)

Example Output (Mesh Simulator):

BLE Mesh Network Simulator
============================================================

Network Topology:
  Node1 (switch): Neighbors -> Node2
  Node2 (relay): Neighbors -> Node1, Node4
  Node3 (light): Neighbors -> Node2, Node5
  Node4 (sensor): Neighbors -> Node2, Node5
  Node5 (light): Neighbors -> Node3, Node4

============================================================
Test 1: Switch broadcasts LIGHT_SET command
============================================================
  Node3 (Light): ON
  Node5 (Light): ON

Message reached via 4 paths:
  Node1 -> Node2 -> Node3
  Node1 -> Node2 -> Node4 -> Node5
  Node1 -> Node2 -> Node4
  Node1 -> Node2

============================================================
Test 2: Sensor sends data to relay
============================================================
  Node2 received sensor data: {'temperature': 22.5, 'humidity': 45}

Message reached via 1 path(s):
  Node4 -> Node2

============================================================
Final Node States:
============================================================
  Node3 (light): {'on': True}
  Node5 (light): {'on': True}

Learning Outcomes:

  • Understand BLE mesh network topology
  • Implement message flooding with TTL
  • Prevent routing loops with message caching
  • Model multi-hop communication
  • Simulate real-world mesh scenarios

Challenges:

  1. Implement managed flooding (more efficient than flooding)
  2. Add friend/low-power node relationships
  3. Implement publish/subscribe model
  4. Add network provisioning and key distribution
  5. Simulate node failures and self-healing

23.7 Worked Examples

Worked Example: Optimizing BLE Throughput with MTU Negotiation for Firmware OTA Update

Scenario: A smart lock manufacturer needs to implement over-the-air (OTA) firmware updates via BLE. The firmware image is 256 KB and must transfer in under 10 minutes to avoid user frustration. The target device uses Nordic nRF52840 with BLE 5.0 support.

Given:

  • Firmware size: 256 KB (262,144 bytes)
  • Target transfer time: < 10 minutes (600 seconds)
  • Default ATT MTU: 23 bytes (20 bytes usable payload)
  • Maximum supported MTU: 247 bytes (244 bytes usable payload)
  • Connection interval: 15 ms (configurable)
  • Data Length Extension (DLE): supported (251 byte PDU)
  • BLE 5.0 2M PHY: supported

Steps:

  1. Calculate minimum required throughput:

    • Data to transfer: 262,144 bytes
    • Time available: 600 seconds
    • Minimum throughput: 262,144 / 600 = 437 bytes/second (approximately 3.5 kbps)
  2. Calculate throughput with default settings (no optimization):

    • MTU: 23 bytes - 20 bytes payload per ATT packet
    • Connection interval: 15 ms - 66 connection events/second
    • Assuming 1 packet per connection event: 20 x 66 = 1,320 bytes/second
    • Transfer time: 262,144 / 1,320 = 199 seconds (approximately 3.3 minutes)
    • This meets the requirement, but let’s optimize further for better UX
  3. Optimize with MTU exchange:

    // After connection established, request MTU exchange
    void on_connected(uint16_t conn_handle) {
        // Request 247-byte MTU (maximum for BLE 4.2+)
        sd_ble_gattc_exchange_mtu_request(conn_handle, 247);
    }
    
    void on_mtu_exchanged(uint16_t conn_handle, uint16_t mtu) {
        // Negotiated MTU (minimum of both sides)
        // Usable payload = MTU - 3 (ATT header)
        g_max_payload = mtu - 3;  // 244 bytes with 247 MTU
    }
  4. Calculate optimized throughput:

    • MTU: 247 bytes - 244 bytes payload
    • With DLE enabled: can send 244 bytes in single LL packet
    • Packets per connection event: up to 6 (with 15ms CI)
    • Conservative estimate (4 packets/event): 244 x 4 x 66 = 64,416 bytes/second
    • Transfer time: 262,144 / 64,416 = 4.1 seconds
  5. Enable 2M PHY for additional speed:

    // Request PHY update to 2M after connection
    ble_gap_phys_t phys = {
        .tx_phys = BLE_GAP_PHY_2MBPS,
        .rx_phys = BLE_GAP_PHY_2MBPS
    };
    sd_ble_gap_phy_update(conn_handle, &phys);
    • 2M PHY doubles bit rate: ~128 kB/second theoretical
    • Transfer time: 262,144 / 128,000 = 2 seconds (theoretical maximum)

Result: With MTU 247, DLE, 2M PHY, and Write Without Response, the 256 KB firmware transfers in approximately 5-8 seconds in practice (accounting for protocol overhead and flow control). This represents a 25-40x improvement over default settings.

Key Insight: BLE throughput optimization requires enabling multiple features together: MTU exchange (12x payload increase), Data Length Extension (fewer LL packets), 2M PHY (2x bit rate), and Write Without Response (eliminates ACK latency). Each feature independently provides improvement, but they multiply when combined.

Worked Example: Power Budget Analysis for Battery-Powered BLE Sensor Node

Scenario: An agricultural sensor node monitors soil moisture, temperature, and light levels every 15 minutes and transmits data to a gateway. The device must operate for 2 years on 2x AA batteries without maintenance. The node uses an ESP32-C3 module.

Given:

  • Battery capacity: 2x AA (2,800 mAh @ 1.5V = 4,200 mWh at 3V effective after boost converter, 85% efficiency)
  • ESP32-C3 deep sleep current: 5 microA
  • ESP32-C3 active current: 80 mA (Wi-Fi/BLE radio)
  • Soil moisture sensor: 15 mA for 50ms per reading
  • Temperature sensor (I2C): 0.5 mA for 10ms
  • Light sensor: 0.2 mA for 5ms
  • BLE advertising TX: 12 mA for 3ms (connectable advertising)
  • BLE connection event: 15 mA for 5ms (TX + RX)
  • Report interval: 15 minutes

Steps:

  1. Calculate energy per measurement cycle:

    Sensor readings:

    • Wake from deep sleep: 80 mA x 2ms = 0.16 mAms
    • Soil moisture: 15 mA x 50ms = 0.75 mAms
    • Temperature: 0.5 mA x 10ms = 0.005 mAms
    • Light: 0.2 mA x 5ms = 0.001 mAms
    • Sensor subtotal: 0.92 mAms

    BLE transmission (advertising + connection):

    • Start advertising: 12 mA x 3ms x 10 events = 0.36 mAms
    • Connection event (data TX): 15 mA x 5ms x 3 events = 0.225 mAms
    • BLE subtotal: 0.59 mAms

    MCU processing:

    • Data processing: 30 mA x 5ms = 0.15 mAms
    • Total per cycle: 0.92 + 0.59 + 0.15 = 1.66 mAms = 0.00166 mAh
  2. Calculate daily energy consumption:

    • Cycles per day: 24h x 4/hour = 96 cycles
    • Active energy: 96 x 0.00166 mAh = 0.159 mAh/day
    • Deep sleep energy: 5 microA x 24h = 0.12 mAh/day
    • Total daily: 0.159 + 0.12 = 0.28 mAh/day
  3. Calculate battery life:

    • Usable capacity (80% of rated): 2,800 x 0.8 = 2,240 mAh
    • Battery life: 2,240 / 0.28 = 8,000 days = 21.9 years
  4. Reality check - identify hidden consumers:

    • Voltage regulator quiescent: ~10 microA - adds 0.24 mAh/day
    • RTC crystal oscillator: ~1 microA - adds 0.024 mAh/day
    • Leakage currents: ~2 microA - adds 0.048 mAh/day
    • Self-discharge (2%/year for alkaline): ~4.7 mAh/month = 0.15 mAh/day
    • Revised daily: 0.28 + 0.24 + 0.024 + 0.048 + 0.15 = 0.74 mAh/day
  5. Revised battery life calculation:

    • Battery life: 2,240 / 0.74 = 3,027 days = 8.3 years
    • Still exceeds 2-year requirement with large margin
  6. Add margin for real-world degradation:

    • Temperature effects (cold reduces capacity 20%): 0.8x factor
    • Battery aging (10% capacity loss/year): ~0.85x over 2 years
    • Connection failures (retry overhead): 1.2x energy estimate
    • Worst-case daily: 0.74 x 1.2 = 0.89 mAh/day
    • Worst-case capacity: 2,240 x 0.8 x 0.85 = 1,523 mAh
    • Conservative estimate: 1,523 / 0.89 = 1,711 days = 4.7 years

Result: The design achieves 4.7+ year battery life under worst-case assumptions, comfortably exceeding the 2-year requirement. Key design decisions: 15-minute report interval (not continuous), deep sleep between readings, short BLE connection using bonding (no re-pairing), and efficient sensor duty-cycling.

Key Insight: Sleep current dominates long-term battery life for infrequent reporting sensors. Always measure actual deep sleep current with a microA-capable meter - leakage from GPIO configuration, pull-ups, and connected sensors often exceeds datasheet MCU values.

Scenario: A smart thermostat sends temperature updates to a smartphone app. The design team debates whether to use a 7.5ms connection interval (fast response) or 100ms interval (low power). Calculate the trade-offs.

Given:

  • BLE connection event duration: 3ms (includes TX, RX, and turnaround time)
  • Peripheral sleep current: 2 microA
  • Peripheral active current (during connection event): 15mA @ 3V
  • Battery: 1000mAh coin cell
  • Update rate: Temperature change every 30 seconds

Steps:

1. Calculate average latency for user-initiated read:

7.5ms interval:  Average latency = 7.5ms / 2 = 3.75ms
100ms interval:  Average latency = 100ms / 2 = 50ms
(User taps "refresh" - must wait for next connection event on average)

2. Calculate energy per connection event:

Active energy = 15mA * 3V * 3ms = 135 microjoules
Sleep energy = 2 microA * 3V * 97ms (between events) = 0.6 microjoules
Total per event = 135.6 microjoules

3. Calculate daily energy consumption:

7.5ms interval:  (1000ms / 7.5) * 24h * 3600s = 11,520,000 events/day
                 11,520,000 * 135.6 microjoules = 1,562 joules/day

100ms interval:  (1000ms / 100) * 24h * 3600s = 864,000 events/day
                 864,000 * 135.6 microjoules = 117 joules/day

4. Calculate battery life:

Battery capacity = 1000mAh * 3V * 3600s = 10,800 joules

7.5ms interval:  10,800 / 1,562 = 6.9 days
100ms interval:  10,800 / 117 = 92.3 days (about 3 months)

Result: The 7.5ms interval provides 13x faster response but drains battery 13x faster. For a thermostat (infrequent user interaction), the 100ms interval is far superior.

Key Insight: Connection interval is the dominant factor in BLE power consumption. For applications with infrequent updates, using the longest acceptable interval (typically 100-500ms) extends battery life by 10-50x compared to aggressive intervals. Only use fast intervals (<20ms) for latency-critical applications like wireless audio, gaming controllers, or fitness trackers with real-time step counting.

23.8 Summary

This chapter provided four complete BLE project implementations:

  • Lab 1 - Heart Rate Monitor: Standard GATT service implementation with ESP32 and nRF Connect testing
  • Lab 2 - Environmental Dashboard: Python bleak client for real-time monitoring with notifications
  • Lab 3 - Indoor Positioning: iBeacon deployment and trilateration-based location estimation
  • Lab 4 - Mesh Network: BLE mesh simulation with message flooding and multi-hop routing
  • Worked Examples: MTU optimization for OTA updates and power budget analysis for battery devices

23.8.1 Knowledge Check: GATT Service Standards

23.8.2 Knowledge Check: BLE Indoor Positioning

23.8.3 Knowledge Check: BLE Power Budget

23.9 Knowledge Check

Common Pitfalls

ESP32 OTA firmware update labs require the OTA bootloader partition table (CONFIG_PARTITION_TABLE_TWO_OTA) and a partition large enough for two firmware images. Flashing a standard single-partition firmware and attempting OTA results in a partition write error. Always use ota_2.csv partition table and verify partition layout with esptool.py read_flash before starting OTA labs.

Arduino BLE libraries (ESP32 BLE Arduino, ArduinoBLE) use different internal architectures than native ESP-IDF NimBLE/Bluedroid. Attempting to use Arduino BLE functions alongside ESP-IDF BLE calls causes duplicate stack initialization and crashes. Choose one framework for the entire project and do not mix API calls across the Arduino and ESP-IDF BLE layers.

If a GATT characteristic requires authenticated write permission for its CCCD, writing 0x0001 without prior pairing and bonding returns ATT Error 0x05 (Insufficient Authentication). The lab must include pairing steps (bonding via nRF Connect: Pair button) before subscribing. Log the ATT error response in nRF Connect to identify authentication failures rather than assuming the notification subscription failed for other reasons.

BLE power optimization labs with a battery-powered device that has low charge will show artificially high power consumption as the voltage regulator drops below its efficient operating range. Always start power profiling labs with a fully charged or bench-power-supplied device at the nominal supply voltage, and document the supply voltage used to ensure reproducible results.

23.10 What’s Next

Chapter Focus Why Read It
Zigbee Fundamentals Zigbee network architecture, coordinator/router/end-device roles Compare BLE mesh with a protocol purpose-built for large-scale IoT mesh deployments
Thread and Matter IP-based mesh networking and the Matter interoperability standard Understand how IP connectivity over mesh differs from BLE GATT-based communication
Wi-Fi for IoT IEEE 802.11 variants, power management, and IoT integration patterns Evaluate when Wi-Fi’s higher throughput justifies its greater power cost versus BLE
BLE Security Pairing modes, key distribution, and attack vectors specific to BLE Secure the GATT services and beacon deployments built in these labs
BLE Performance Optimization Connection parameter tuning, throughput benchmarking, and PHY selection Apply systematic measurement to the power and throughput trade-offs explored in the worked examples
Indoor Positioning Systems System-level architecture for production IPS deployments Extend the Lab 3 trilateration prototype to a production-grade positioning platform