918  BLE Code Examples and Simulators

918.1 Learning Objectives

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

  • Write Python BLE Scanners: Use the bleak library to discover nearby BLE devices
  • Build ESP32 BLE Beacons: Create Arduino-based GATT servers that advertise sensor data
  • Understand GATT Structure: Implement services, characteristics, and descriptors
  • Use BLE Notifications: Enable real-time data push from peripheral to central
  • Test with Simulators: Experiment with Wokwi-based BLE device simulations

What you’ll learn: This chapter provides working code examples for BLE development - both Python scripts for scanning/connecting and ESP32 Arduino code for creating BLE peripherals.

Prerequisites: - Basic Python or C/Arduino programming - Understanding of BLE concepts (GATT, services, characteristics) - Review Bluetooth Fundamentals first

Key takeaway: BLE programming follows a client-server model where peripherals (servers) advertise services and centrals (clients) discover and connect to read/write data.

918.2 Prerequisites

Before working through these examples:

  • Bluetooth Fundamentals and Architecture: Understanding BLE protocol stack, GATT services, characteristics, and the master/slave model
  • Basic Programming Skills: Familiarity with Python (asyncio) or Arduino C++ for embedded development

918.3 Python BLE Scanner

A minimal BLE scanner using the bleak library:

import asyncio
from bleak import BleakScanner

async def scan_devices():
    """Scan for BLE devices"""
    print("Scanning for BLE devices...")
    devices = await BleakScanner.discover(timeout=10.0)

    print(f"\nFound {len(devices)} devices:\n")
    for device in devices:
        print(f"Name: {device.name or 'Unknown'}")
        print(f"Address: {device.address}")
        print(f"RSSI: {device.rssi} dBm\n")

# Run scanner
asyncio.run(scan_devices())

Install: pip install bleak

918.4 Arduino ESP32 BLE Beacon

Create a BLE GATT server on ESP32 that advertises a temperature sensor service:

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

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHAR_UUID    "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic *pCharacteristic;
bool deviceConnected = false;

class ServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      Serial.println("Device connected");
    }

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("Disconnected");
    }
};

void setup() {
  Serial.begin(115200);

  BLEDevice::init("ESP32-Sensor");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
    CHAR_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_NOTIFY
  );

  pCharacteristic->addDescriptor(new BLE2902());
  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();

  Serial.println("BLE beacon advertising!");
}

void loop() {
  if (deviceConnected) {
    float temp = 20.0 + random(0, 100) / 10.0;
    char tempStr[8];
    dtostrf(temp, 4, 2, tempStr);

    pCharacteristic->setValue(tempStr);
    pCharacteristic->notify();

    Serial.printf("Sent: %s°C\n", tempStr);
  }
  delay(2000);
}
TipInteractive Simulator: BLE Advertiser (GATT Server)

Try it yourself! See how ESP32 broadcasts BLE advertisements and serves data to connected clients.

What This Simulates: An ESP32 acting as a BLE GATT server, advertising a temperature sensor service and notifying connected clients with readings.

How to Use: 1. Click Start Simulation 2. Watch the Serial Monitor show “BLE beacon advertising!” 3. Observe temperature readings being generated 4. See connection status when clients connect 5. Notice GATT service and characteristic UUIDs

NoteLearning Points

Observe: - BLEDevice::init(): Initializes BLE stack with device name - createService(): Creates GATT service with UUID - createCharacteristic(): Defines data attribute (readable, notifiable) - BLE2902: Client Characteristic Configuration Descriptor (enables notifications) - Advertising: Broadcasts service UUID so clients can discover device - notify(): Pushes data to connected clients without polling

GATT (Generic Attribute Profile) Structure:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#ecf0f1', 'fontSize': '14px'}}}%%
graph TB
    DEV[BLE Device<br/>ESP32-Sensor]
    SERV1[Service<br/>UUID: 4fafc201...]
    CHAR1[Characteristic<br/>UUID: beb5483e...]
    DESC1[CCCD Descriptor<br/>Enable/Disable Notifications]

    DEV --> SERV1
    SERV1 --> CHAR1
    CHAR1 --> DESC1

    CHAR1 -.->|Properties| PROP[READ + NOTIFY]
    CHAR1 -.->|Value| VAL[Temperature: 23.5°C]

    style DEV fill:#2C3E50,stroke:#16A085,stroke-width:3px,color:#fff
    style SERV1 fill:#16A085,stroke:#2C3E50,stroke-width:2px
    style CHAR1 fill:#E67E22,stroke:#2C3E50,stroke-width:2px
    style DESC1 fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px
    style PROP fill:#ecf0f1,stroke:#2C3E50,stroke-width:1px
    style VAL fill:#ecf0f1,stroke:#2C3E50,stroke-width:1px

Figure 918.1: BLE GATT hierarchy: device, service, characteristic, and descriptor

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
gantt
    title BLE Device Power States Over 10 Second Period
    dateFormat X
    axisFormat %Ls

    section Advertising
    ADV TX (1.5mA)     :active, a1, 0, 30
    Sleep (3µA)        :done, s1, 30, 970
    ADV TX (1.5mA)     :active, a2, 1000, 30
    Sleep (3µA)        :done, s2, 1030, 970
    ADV TX (1.5mA)     :active, a3, 2000, 30

    section Connected (CI=100ms)
    TX Event (15mA)    :crit, c1, 0, 5
    Idle (5µA)         :done, i1, 5, 95
    RX Event (12mA)    :crit, c2, 100, 5
    Idle (5µA)         :done, i2, 105, 95
    TX Event (15mA)    :crit, c3, 200, 5

    section Notify Burst
    Notify TX (15mA)   :crit, n1, 0, 10
    ACK RX (12mA)      :crit, n2, 10, 5
    Sleep (3µA)        :done, ns, 15, 2985

Figure 918.2: BLE power consumption timeline comparing advertising mode (low duty cycle, ~1% active) versus connected mode with notifications (connection interval driven). Shorter connection intervals increase responsiveness but reduce battery life.

BLE Communication Flow:

1. ESP32 advertises: "ESP32-Sensor service available!"
2. Client (phone app) scans and discovers ESP32
3. Client connects to ESP32 GATT server
4. Client reads service/characteristic info
5. Client enables notifications (writes to CCCD)
6. ESP32 pushes temperature updates every 2 seconds
7. Client receives notifications without polling

UUID Standards: - 16-bit UUIDs: Standard Bluetooth SIG services (0x180F = Battery Service) - 128-bit UUIDs: Custom services (use UUID generator for unique IDs) - Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Real-World Applications: - Fitness Trackers: Heart rate monitor broadcasts HR data to phone app - Smart Home: Temperature sensors advertise readings to central hub - Asset Tracking: BLE beacons broadcast location info (iBeacon, Eddystone) - Medical Devices: Blood glucose meters send readings to patient apps - Wearables: Smartwatches notify phone of incoming calls/messages

Experiment: - Add battery level characteristic (standard UUID 0x2A19) - Implement multiple characteristics (temp, humidity, pressure) - Add WRITE property for remote control - Implement custom advertising data (manufacturer data, tx power) - Add security: require pairing before connection

918.5 Interactive Simulator: BLE Scanner (Central/Client)

TipBLE Scanner Simulator

What This Simulates: ESP32 scanning for nearby BLE devices and reading their advertised data

BLE Scanning Flow:

Step 1: ESP32 starts BLE scan
   - Sets scan parameters (interval, window, active/passive)

Step 2: Receives advertisements from devices
   - Device Name
   - RSSI (signal strength)
   - Service UUIDs
   - Manufacturer data

Step 3: Filters results
   - By name pattern
   - By RSSI threshold
   - By service UUID

Step 4: Optionally connects to device
   - Read characteristics
   - Subscribe to notifications

How to Use: 1. Click Start Simulation 2. Watch ESP32 discover nearby BLE devices 3. See device names, addresses, and RSSI values 4. Observe scan results filtering 5. Monitor connection attempts to specific devices

NoteLearning Points

What You’ll Observe:

  1. Active vs Passive Scanning - Active sends scan requests for more data
  2. RSSI Measurement - Signal strength can be a rough distance proxy
  3. Advertisement Data - Devices broadcast identity without connection
  4. Scan Parameters - Interval and window affect power vs discovery speed
  5. Service Discovery - Finding devices by UUID without connection

BLE Scan Parameters:

Scan Interval: 100 ms (example; time between scan windows)
Scan Window:   99 ms (example; active scanning time)
Scan Type:     Active (request additional data)

Power Consumption (varies by platform):
- Passive Scan: lower
- Active Scan:  higher
- Continuous:   High power, fast discovery
- Periodic:     Low power, slower discovery

RSSI (Received Signal Strength Indicator):

RSSI Value (dBm) Distance Quality
-30 to -50 < 1m Excellent
-50 to -70 1-5m Good
-70 to -85 5-15m Fair
-85 to -100 > 15m Poor

Note: RSSI-to-distance estimates are highly environment-dependent (multipath, antenna orientation, body attenuation). Treat this as a rough heuristic unless you calibrate for your setting.

Distance Formula (simplified log-distance model): Distance = 10 ^ ((TxPower - RSSI) / (10 * N)) - N = Path loss exponent (2.0 in free space, 2.7-4.3 indoors)

Advertisement Packet Structure:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#ecf0f1', 'fontSize': '13px'}}}%%
graph LR
    ADV[Advertisement Packet]

    ADV --> PREAMBLE[Preamble<br/>1 byte]
    ADV --> ACCESS[Access Address<br/>4 bytes]
    ADV --> HEADER[PDU Header<br/>2 bytes]
    ADV --> PAYLOAD[Payload<br/>6-37 bytes]
    ADV --> CRC[CRC<br/>3 bytes]

    PAYLOAD --> FLAGS[Flags]
    PAYLOAD --> NAME[Device Name]
    PAYLOAD --> UUID[Service UUIDs]
    PAYLOAD --> RSSI_TX[TX Power]
    PAYLOAD --> MFG[Manufacturer Data]

    style ADV fill:#2C3E50,stroke:#16A085,stroke-width:3px,color:#fff
    style PREAMBLE fill:#16A085,stroke:#2C3E50,stroke-width:2px
    style ACCESS fill:#16A085,stroke:#2C3E50,stroke-width:2px
    style HEADER fill:#16A085,stroke:#2C3E50,stroke-width:2px
    style PAYLOAD fill:#E67E22,stroke:#2C3E50,stroke-width:2px
    style CRC fill:#16A085,stroke:#2C3E50,stroke-width:2px
    style FLAGS fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px
    style NAME fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px
    style UUID fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px
    style RSSI_TX fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px
    style MFG fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px

Figure 918.3: BLE advertisement packet structure with payload fields

Real-World Applications:

  1. Asset Tracking - Scan for BLE beacons to locate equipment
  2. Healthcare - Discover medical sensors (glucose, heart rate)
  3. Smart Home - Find nearby BLE light bulbs, sensors
  4. Retail - Detect iBeacons for proximity marketing
  5. Industrial - Monitor factory sensors via BLE mesh

Experiments to Try:

  1. Filter by RSSI - Only show devices within 2 meters (> -60 dBm)
  2. Service UUID Filter - Find devices advertising specific services
  3. Connect and Read - Connect to found device and read characteristics
  4. Scan Duration - Compare continuous vs periodic scanning power
  5. Duplicate Filtering - Remove duplicate advertisements

Central vs Peripheral:

Peripheral (Server):         Central (Scanner):
- Advertises presence       - Scans for devices
- Waits for connections     - Initiates connections
- Provides data/services    - Consumes data
- Lower power (sleeps)      - Higher power (active)
- Example: Sensor, beacon   - Example: Phone, gateway

Connection:
Peripheral <-advertising-> Central
Peripheral <-connection<- Central (initiates)
Peripheral ->data-> Central (after connection)

918.6 Video Tutorial

918.7 BLE Development Stack Overview

Understanding the complete development stack helps you choose the right tools and libraries:

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D'}}}%%
graph TB
    subgraph App["Application Layer"]
        A1[Python bleak]
        A2[Mobile SDK<br/>CoreBluetooth/Android BLE]
        A3[Web Bluetooth API]
    end

    subgraph Stack["BLE Protocol Stack"]
        S1[GATT<br/>Services & Characteristics]
        S2[ATT<br/>Attribute Protocol]
        S3[L2CAP<br/>Logical Link Control]
        S4[HCI<br/>Host Controller Interface]
    end

    subgraph Firmware["Firmware SDK"]
        F1[ESP-IDF BLE]
        F2[Zephyr BLE]
        F3[Arduino BLE]
    end

    subgraph Hardware["Hardware"]
        H1[ESP32<br/>Xtensa + BLE 4.2/5.0]
        H2[nRF52840<br/>ARM Cortex-M4 + BLE 5.3]
        H3[CC2640<br/>ARM Cortex-M3 + BLE 5.1]
    end

    App --> Stack --> Firmware --> Hardware

    style App fill:#E67E22,stroke:#2C3E50,stroke-width:2px
    style Stack fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
    style Firmware fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
    style Hardware fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff

Figure 918.4: BLE development stack showing the relationship between application code, protocol layers, firmware SDKs, and hardware platforms.

918.8 Summary

This chapter provided practical BLE code examples:

  • Python Scanner: Using bleak library to discover nearby BLE devices with RSSI readings
  • ESP32 GATT Server: Creating a BLE peripheral that advertises services and notifies connected clients
  • Interactive Simulators: Wokwi-based ESP32 simulations for hands-on experimentation
  • GATT Structure: Understanding services, characteristics, descriptors, and properties
  • Development Stack: Overview of tools from hardware to application layer

918.9 What’s Next

Continue to BLE Python Implementations for production-ready Python code including device filtering, GATT server exploration, beacon management, and proximity detection algorithms.