Bluetooth Low Energy (BLE) — IoT Connectivity & Beacon Guide
In this tutorial, you'll learn about Bluetooth Low Energy (BLE). We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Bluetooth Low Energy (BLE) is a wireless personal area network protocol designed for short-range communication with ultra-low power consumption, enabling IoT devices to operate for months or years on a single coin-cell battery.
Why BLE Matters
Classic Bluetooth consumes 1-100 watts and pairs devices for continuous streaming. BLE uses 0.01-0.5 watts and works on a broadcast-and-scan model. A BLE beacon broadcasting every second can run for two years on a CR2032 battery. Smartphones, laptops, and tablets all have BLE hardware built in, making it the most accessible IoT protocol for consumer-facing applications. Apple iBeacon and Google Eddystone use BLE advertising for proximity detection. Medical devices use BLE for continuous glucose monitoring and pulse oximeters. Fitness trackers sync step data over BLE. Doda Browser uses BLE Web API to scan for nearby beacons and deliver location-aware content.
Plain-Language Explanation
Think of BLE like a bulletin board in a hallway. A beacon (like a store) pins a note to the board saying "Store A is 5 meters away with a sale on shoes." Your phone walks past, reads the note, and shows you a notification. No pairing needed — the phone just scans the board periodically. This is BLE advertising mode.
For two-way communication, BLE uses the GATT (Generic Attribute Profile) model. One device acts as a GATT server (has data), the other as a GATT client (reads data). The server organizes data into services (like "Battery Service") containing characteristics (like "Battery Level"). The client reads or subscribes to characteristic updates.
graph TD
subgraph "BLE Communication Modes"
B[Beacon] -->|Advertising
iBeacon / Eddystone| S[Smartphone]
S -->|Scan request| B
B -->|Scan response| S
end
subgraph "GATT Client-Server"
Server[BLE Peripheral
Sensor Tag] -->|Service: Temperature| Char1[Characteristic: Temperature]
Server -->|Service: Battery| Char2[Characteristic: Battery Level]
Client[BLE Central
Smartphone App] -->|Read| Char1
Client -->|Subscribe| Char2
end
style B fill:#3498db,color:#fff
style Server fill:#27ae60,color:#fff
style Client fill:#e67e22,color:#fff
BLE Roles
Broadcaster: Transmits advertising packets. Beacons are broadcasters. They have no connections.
Observer: Scans for advertising packets without connecting. A smartphone scanning for beacons is an Observer.
Peripheral (Server): Advertises its presence and accepts connections. A temperature sensor acting as a GATT server is a peripheral.
Central (Client): Scans for peripherals and initiates connections. A smartphone app is a central.
Advertising Protocol
BLE advertising uses three dedicated channels (37, 38, 39) spread across the 2.4 GHz band to avoid WiFi interference. Each advertising channel carries the same packet to increase reliability.
An advertising packet contains:
- PDU type (connectable, non-connectable, scan request)
- Advertiser address (public or random)
- Advertising data (31 bytes) — manufacturer data, service UUIDs, device name
- Scan response data (31 bytes) — additional data the device can provide on request
GATT Services and Characteristics
Standard services are defined by the Bluetooth SIG. Common ones:
| Service | UUID | Characteristics |
|---|---|---|
| Battery Service | 0x180F | Battery Level |
| Device Information | 0x180A | Manufacturer Name, Model Number, Serial Number |
| Environmental Sensing | 0x181A | Temperature, Humidity, Pressure |
| Heart Rate | 0x180D | Heart Rate Measurement, Body Sensor Location |
Arduino BLE Peripheral Example
#include <BLEPeripheral.h>
BLEPeripheral blePeripheral;
BLEService envService("181A"); // Environmental Sensing Service
BLEFloatCharacteristic tempChar("2A6E", BLERead | BLENotify);
BLEFloatCharacteristic humChar("2A6F", BLERead | BLENotify);
void setup() {
Serial.begin(115200);
blePeripheral.setLocalName("DodaEnvSensor");
blePeripheral.setAdvertisedServiceUuid(envService.uuid());
envService.addCharacteristic(tempChar);
envService.addCharacteristic(humChar);
blePeripheral.addAttribute(envService);
blePeripheral.begin();
Serial.println("BLE Environmental Sensor Ready");
}
void loop() {
blePeripheral.poll();
float temperature = 23.5;
float humidity = 55.2;
tempChar.setValue(temperature);
humChar.setValue(humidity);
delay(10000); // Update every 10 seconds
}
Python BLE Scanner
import asyncio
from bleak import BleakScanner
async def scan_devices():
devices = await BleakScanner.discover(timeout=5.0)
for d in devices:
print(f"Device: {d.name or 'Unnamed'}")
print(f" Address: {d.address}")
print(f" RSSI: {d.rssi} dBm")
print(f" Metadata: {d.metadata}")
print()
devices = asyncio.run(scan_devices())
Expected output:
Device: DodaEnvSensor
Address: C4:5B:BE:01:23:45
RSSI: -67 dBm
Metadata: {'uuids': ['181A'], 'manufacturer_data': {0xFFFF: b'\x01'}}
Device: Unnamed
Address: D3:21:4A:67:89:AB
RSSI: -85 dBm
Metadata: {'uuids': [], 'manufacturer_data': {}}
BLE Beacons
Beacons are non-connectable BLE devices that broadcast a fixed payload. Two major formats:
iBeacon (Apple): Proximity UUID (16 bytes), Major (2 bytes), Minor (2 bytes), TX power. Used for indoor navigation and proximity marketing.
Eddystone (Google): Multiple frame types — UID (unique beacon ID), URL (direct eddystone:// URL), TLM (telemetry — battery, temperature). Eddystone-URL is clever: the beacon broadcasts a compressed URL that any smartphone can resolve.
Python iBeacon Decoder
import asyncio
from bleak import BleakScanner
def beacon_callback(device, advertising_data):
if advertising_data.manufacturer_data:
for manufacturer_id, data in advertising_data.manufacturer_data.items():
# Apple manufacturer ID is 0x004C
if manufacturer_id == 0x004C and len(data) >= 25:
# iBeacon data starts with 0x02 0x15
if data[0] == 0x02 and data[1] == 0x21:
uuid = data[2:18].hex()
major = int.from_bytes(data[18:20], 'big')
minor = int.from_bytes(data[20:22], 'big')
tx_power = data[22] - 256
distance = 10 ** ((-69 - tx_power) / (10 * 2.0))
print(f"iBeacon: {uuid} Major:{major} Minor:{minor} "
f"TX:{tx_power} dBm ~{distance:.1f}m")
async def main():
scanner = BleakScanner(beacon_callback)
await scanner.start()
await asyncio.sleep(10.0)
await scanner.stop()
asyncio.run(main())
Common Mistakes
Not managing connection intervals: BLE devices negotiate connection intervals. A short interval (7.5 ms) drains battery. Set an interval appropriate for your data rate (100 ms for sensors).
Large advertising payloads: Advertising data is limited to 31 bytes. Use scan response for additional data. Keep the primary advertising payload minimal.
Ignoring BLE coexistence: BLE, WiFi, and Zigbee all share 2.4 GHz. In dense deployments, adaptive frequency hopping helps, but congested environments cause packet loss.
No power optimization: Notifying characteristics every 100 ms when data changes once per minute wastes battery. Only send updates when values actually change.
Forgetting security: BLE pairing can use "Just Works" (no security), passkey entry, or numeric comparison. For sensitive data like medical readings, require authenticated pairing with encryption.
Practice Questions
What is the difference between BLE advertising and GATT? Advertising is a connectionless broadcast model (one-to-many). GATT is a connection-oriented client-server model (one-to-one) for bidirectional data exchange.
How does BLE achieve lower power than classic Bluetooth? BLE uses shorter radio bursts, longer sleep intervals, simpler protocol stack, and connectionless advertising modes. Peak current is ~15 mA vs ~30 mA for classic BT.
What is the purpose of BLE connection intervals? Connection intervals define how often the peripheral and central exchange data between connection events. Longer intervals reduce power consumption but increase latency.
Why do beacons use non-connectable advertising? Beacons broadcast to any scanning device without establishing a connection. This allows one-to-many broadcasting with the lowest possible power consumption.
What information does an iBeacon advertising packet contain? iBeacon packets contain a proximity UUID, major number, minor number, and calibrated TX power level. The receiving device calculates distance from RSSI and TX power.
Mini Project
Build a BLE environmental sensor simulator:
import asyncio
import random
from bleak import BleakClient
SENSOR_SERVICE_UUID = "181A"
TEMP_CHAR_UUID = "2A6E"
HUM_CHAR_UUID = "2A6F"
class BLEVirtualSensor:
def __init__(self):
self.connected = False
async def connect_and_read(self, address: str):
async with BleakClient(address) as client:
self.connected = await client.is_connected()
print(f"Connected: {self.connected}")
while True:
temp_bytes = await client.read_gatt_char(TEMP_CHAR_UUID)
hum_bytes = await client.read_gatt_char(HUM_CHAR_UUID)
temperature = float(temp_bytes.decode())
humidity = float(hum_bytes.decode())
print(f"Temperature: {temperature}°C, Humidity: {humidity}%")
await asyncio.sleep(5)
# Run with a real BLE device address
# asyncio.run(BLEVirtualSensor().connect_and_read("C4:5B:BE:01:23:45"))
Cross-References
- ESP32
- IoT Communication Protocols
- MQTT
- IoT Overview
- IoT Security
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro