Python Bluetooth Low Energy (BLE) — Core Concepts
What BLE Is
Bluetooth Low Energy (BLE), introduced in Bluetooth 4.0, is a wireless protocol designed for short bursts of data transfer with minimal power consumption. Unlike classic Bluetooth (which handles continuous streaming like audio), BLE is optimized for intermittent communication — sensor readings, button presses, status updates.
BLE devices include fitness trackers (Fitbit, Apple Watch), smart home sensors (temperature, humidity, door contacts), medical devices (glucose monitors, pulse oximeters), beacons (iBeacon, Eddystone), and input devices (wireless keyboards, game controllers).
Roles: Central and Peripheral
BLE defines two roles:
Peripheral — The small device that advertises its presence and provides data. A heart rate sensor, a temperature beacon, or a smart lock. Peripherals broadcast advertisement packets and wait for connections.
Central — The device that scans for peripherals and initiates connections. Your computer, phone, or Raspberry Pi running Python. The central device discovers, connects, reads, and writes data.
In Python BLE programming, you almost always act as the central device.
GATT: The Data Model
Once connected, BLE uses GATT (Generic Attribute Profile) to organize data into a hierarchy:
- Profile — A collection of services (e.g., Heart Rate Profile)
- Service — A group of related data points, identified by a UUID (e.g., Heart Rate Service:
0x180D) - Characteristic — A single data value within a service (e.g., Heart Rate Measurement:
0x2A37) - Descriptor — Metadata about a characteristic (e.g., notification configuration)
Think of it like a filing cabinet: services are drawers, characteristics are folders inside those drawers, and descriptors are labels on the folders.
Standard services have well-known 16-bit UUIDs assigned by the Bluetooth SIG:
0x180D— Heart Rate0x181A— Environmental Sensing0x180F— Battery Service0x1812— Human Interface Device
Custom services use full 128-bit UUIDs.
Python BLE with bleak
bleak is the modern, cross-platform Python library for BLE central operations. It works on Windows, macOS, and Linux with an async API:
pip install bleak
Scanning for Devices
import asyncio
from bleak import BleakScanner
async def scan():
devices = await BleakScanner.discover(timeout=5.0)
for device in devices:
print(f"{device.name or 'Unknown'}: {device.address} (RSSI: {device.rssi})")
asyncio.run(scan())
Connecting and Reading Data
from bleak import BleakClient
DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"
TEMP_CHARACTERISTIC = "00002a6e-0000-1000-8000-00805f9b34fb"
async def read_temperature():
async with BleakClient(DEVICE_ADDRESS) as client:
print(f"Connected: {client.is_connected}")
# Read a characteristic
value = await client.read_gatt_char(TEMP_CHARACTERISTIC)
temperature = int.from_bytes(value, byteorder='little') / 100.0
print(f"Temperature: {temperature}°C")
asyncio.run(read_temperature())
Receiving Notifications
Many sensors push data through notifications instead of requiring polls:
async def monitor_heart_rate():
def notification_handler(sender, data):
# Heart rate is in the second byte for most sensors
heart_rate = data[1]
print(f"Heart rate: {heart_rate} bpm")
async with BleakClient(DEVICE_ADDRESS) as client:
await client.start_notify(
"00002a37-0000-1000-8000-00805f9b34fb",
notification_handler
)
# Keep listening for 60 seconds
await asyncio.sleep(60)
await client.stop_notify(
"00002a37-0000-1000-8000-00805f9b34fb"
)
asyncio.run(monitor_heart_rate())
Discovering Services
async def explore_device():
async with BleakClient(DEVICE_ADDRESS) as client:
for service in client.services:
print(f"\nService: {service.uuid} ({service.description})")
for char in service.characteristics:
print(f" Characteristic: {char.uuid}")
print(f" Properties: {char.properties}")
if "read" in char.properties:
value = await client.read_gatt_char(char.uuid)
print(f" Value: {value.hex()}")
asyncio.run(explore_device())
Advertisement Data
Before connecting, you can extract useful information from advertisements:
async def scan_with_details():
def detection_callback(device, advertisement_data):
if advertisement_data.local_name:
print(f"Device: {advertisement_data.local_name}")
print(f" Address: {device.address}")
print(f" RSSI: {advertisement_data.rssi} dBm")
print(f" Services: {advertisement_data.service_uuids}")
if advertisement_data.manufacturer_data:
for company_id, data in advertisement_data.manufacturer_data.items():
print(f" Manufacturer ({company_id}): {data.hex()}")
scanner = BleakScanner(detection_callback)
await scanner.start()
await asyncio.sleep(10)
await scanner.stop()
asyncio.run(scan_with_details())
Common Misconception
BLE and classic Bluetooth are not the same protocol. A device that supports classic Bluetooth (audio streaming, file transfer) does not necessarily support BLE, and vice versa. Many modern devices support both (called “dual-mode”), but the APIs and data models are completely different. The bleak library only handles BLE, not classic Bluetooth.
One thing to remember: BLE’s GATT hierarchy (services containing characteristics) is the key to understanding any BLE device — once you know the service and characteristic UUIDs, reading sensor data in Python is just a few lines of async code with bleak.
See Also
- Python Behavior Trees Robotics How robots make decisions using a tree-shaped rulebook that keeps them organized, like a flowchart that tells a robot what to do in every situation.
- Python Circuitpython Hardware Why CircuitPython makes wiring up LEDs, sensors, and motors as easy as plugging in a USB drive.
- Python Computer Vision Autonomous How self-driving cars use cameras and Python to see the road, spot pedestrians, read signs, and understand traffic — like giving a car human eyes and a brain.
- Python Home Assistant Automation How Python turns your home into a smart home that reacts to you automatically, like a helpful invisible butler.
- Python Lidar Point Cloud Processing How self-driving cars use millions of laser dots to build a 3D picture of the world around them, and how Python helps make sense of it all.