BLE Integration Guide¶
Learn how to integrate bluetooth-sig with your preferred BLE connection library.
Philosophy¶
The bluetooth-sig library follows a clean separation of concerns:
BLE Library → Device connection, I/O operations, provides UUIDs
bluetooth-sig → Automatic UUID identification, standards interpretation, data parsing
You don’t need to know what the UUIDs mean! Your BLE library gives you UUIDs, and bluetooth-sig automatically identifies them and parses the data correctly.
This design lets you choose the best BLE library for your platform while using bluetooth-sig for consistent data parsing.
Integration with bleak¶
bleak is a cross-platform async BLE library (recommended).
bleak Installation¶
pip install bluetooth-sig bleak
Basic Example¶
import asyncio
from bluetooth_sig import BluetoothSIGTranslator
# ============================================
# SIMULATED DATA - Replace with actual BLE read
# ============================================
SIMULATED_BATTERY_DATA = bytearray([85]) # Simulates 85% battery level
async def main():
translator = BluetoothSIGTranslator()
# Example: Your BLE library gives you UUIDs - you don't need to know what they mean!
battery_uuid = "2A19" # From your BLE library
# bluetooth-sig automatically identifies it and parses correctly
result = translator.parse_characteristic(
battery_uuid, SIMULATED_BATTERY_DATA
)
print(f"Discovered: {result.info.name}") # "Battery Level"
print(f"Battery: {result.value}%") # 85%
# Alternative: If you know the characteristic, convert enum to UUID first
from bluetooth_sig.types.gatt_enums import CharacteristicName
battery_uuid = translator.get_characteristic_uuid_by_name(
CharacteristicName.BATTERY_LEVEL
)
if battery_uuid:
result2 = translator.parse_characteristic(
str(battery_uuid), SIMULATED_BATTERY_DATA
)
print(f"Using enum: {result2.info.name} = {result2.value}%")
asyncio.run(main())
Reading Multiple Characteristics¶
import asyncio
from bluetooth_sig import BluetoothSIGTranslator
# ============================================
# SIMULATED DATA - Replace with actual BLE reads
# ============================================
SIMULATED_BATTERY_DATA = bytearray([85]) # Simulates 85% battery
SIMULATED_TEMP_DATA = bytearray([0x64, 0x09]) # Simulates 24.04°C
SIMULATED_HUMIDITY_DATA = bytearray([0x3A, 0x13]) # Simulates 49.22%
async def read_sensor_data():
translator = BluetoothSIGTranslator()
# Example data from BLE reads - use UUIDs from your BLE library
characteristics = {
"Battery": ("2A19", SIMULATED_BATTERY_DATA),
"Temperature": ("2A6E", SIMULATED_TEMP_DATA),
"Humidity": ("2A6F", SIMULATED_HUMIDITY_DATA),
}
# Parse each characteristic
for name, (uuid, raw_data) in characteristics.items():
result = translator.parse_characteristic(uuid, raw_data)
if result.parse_success:
print(f"{name}: {result.value}{result.info.unit or ''}")
asyncio.run(read_sensor_data())
Handling Notifications¶
# SKIP: Notification handler pattern - not standalone executable
import asyncio
from bluetooth_sig import BluetoothSIGTranslator
translator = BluetoothSIGTranslator()
# SKIP: Callback function pattern
def notification_handler(sender, data):
"""Handle BLE notifications."""
# Parse the notification data
uuid = "2A37" # Heart rate measurement
result = translator.parse_characteristic(uuid, data)
if result.parse_success:
print(f"Heart Rate: {result.value.heart_rate} bpm")
# SKIP: Example wrapper
# SKIP: Example function
async def example():
# Simulate notification
notification_handler(None, bytearray([0x00, 0x55]))
asyncio.run(example())
# Unsubscribe
await client.stop_notify("2A37")
Integration with bleak-retry-connector¶
bleak-retry-connector adds robust retry logic (recommended for production).
bleak-retry-connector Installation¶
pip install bluetooth-sig bleak-retry-connector
Example (bleak-retry-connector)¶
# SKIP: Example pattern only
import asyncio
from bluetooth_sig import BluetoothSIGTranslator
async def read_with_retry():
translator = BluetoothSIGTranslator()
# Example: reading battery level
raw_data = bytearray([85])
result = translator.parse_characteristic("2A19", raw_data)
print(f"Battery: {result.value}%")
asyncio.run(read_with_retry())
Integration with simplepyble¶
simplepyble is a cross-platform sync BLE library.
simplepyble Installation¶
pip install bluetooth-sig simplepyble
Example (simplepyble)¶
from simplepyble import Adapter, Peripheral
from bluetooth_sig import BluetoothSIGTranslator
def main():
translator = BluetoothSIGTranslator()
# Get adapter
adapters = Adapter.get_adapters()
if not adapters:
print("No adapters found")
return
adapter = adapters[0]
# Scan for devices
adapter.scan_for(5000) # 5 seconds
peripherals = adapter.scan_get_results()
if not peripherals:
print("No devices found")
return
# Connect to first device
peripheral = peripherals[0]
peripheral.connect()
try:
# Find battery service
services = peripheral.services()
battery_service = next(
(s for s in services if s.uuid() == "180F"), None
)
if battery_service:
# Find battery level characteristic
battery_char = next(
(
c
for c in battery_service.characteristics()
if c.uuid() == "2A19"
),
None,
)
if battery_char:
# Read and parse
raw_data = peripheral.read(
battery_service.uuid(), battery_char.uuid()
)
result = translator.parse_characteristic(
"2A19", bytearray(raw_data)
)
print(f"Battery: {result.value}%")
finally:
peripheral.disconnect()
Best Practices¶
1. Error Handling¶
Always handle exceptions from both BLE operations and parsing:
from bluetooth_sig.gatt.exceptions import (
InsufficientDataError,
ValueRangeError,
)
try:
# BLE operation
raw_data = await client.read_gatt_char(uuid)
# Parse
result = translator.parse_characteristic(uuid, raw_data)
except BleakError as e:
print(f"BLE error: {e}")
except InsufficientDataError as e:
print(f"Data too short: {e}")
except ValueRangeError as e:
print(f"Invalid value: {e}")
2. Connection Management¶
Use context managers for automatic cleanup:
# ✅ Good - automatic cleanup
async with BleakClient(address) as client:
result = translator.parse_characteristic(uuid, data)
# ❌ Bad - manual cleanup required
client = BleakClient(address)
await client.connect()
# ...
await client.disconnect()
3. Timeouts¶
Always specify timeouts:
# ✅ Good - with timeout
raw_data = await asyncio.wait_for(client.read_gatt_char(uuid), timeout=10.0)
# ❌ Bad - no timeout (could hang forever)
raw_data = await client.read_gatt_char(uuid)
4. Reuse Translator¶
Create one translator instance and reuse it:
from bluetooth_sig import BluetoothSIGTranslator
# ============================================
# SIMULATED DATA - Replace with actual BLE reads
# ============================================
sensor_data = {"2A19": bytearray([85]), "2A6E": bytearray([0x64, 0x09])}
# ✅ Good - reuse translator
translator = BluetoothSIGTranslator()
for uuid, data in sensor_data.items():
result = translator.parse_characteristic(uuid, data)
# ❌ Bad - creating multiple instances
for uuid, data in sensor_data.items():
translator = BluetoothSIGTranslator() # Wasteful
result = translator.parse_characteristic(uuid, data)
Complete Example¶
Here’s a complete production-ready example:
# SKIP: Requires actual BLE device connection
import asyncio
from bleak import BleakClient
from bluetooth_sig import BluetoothSIGTranslator
from bluetooth_sig.gatt.exceptions import BluetoothSIGError
class SensorReader:
"""Read and parse BLE sensor data."""
def __init__(self, address: str):
self.address = address
self.translator = BluetoothSIGTranslator()
async def read_battery(self) -> int:
"""Read battery level."""
async with BleakClient(self.address) as client:
raw_data = await client.read_gatt_char("2A19")
result = self.translator.parse_characteristic("2A19", raw_data)
return result.value
async def read_temperature(self) -> float:
"""Read temperature in °C."""
async with BleakClient(self.address) as client:
raw_data = await client.read_gatt_char("2A6E")
result = self.translator.parse_characteristic("2A6E", raw_data)
return result.value
async def read_all(self) -> dict:
"""Read all sensor data."""
results = {}
async with BleakClient(self.address) as client:
sensors = {
"battery": "2A19",
"temperature": "2A6E",
"humidity": "2A6F",
}
for name, uuid in sensors.items():
try:
raw_data = await asyncio.wait_for(
client.read_gatt_char(uuid), timeout=5.0
)
result = self.translator.parse_characteristic(
uuid, raw_data
)
results[name] = result.value
except BluetoothSIGError as e:
print(f"Parse error for {name}: {e}")
except Exception as e:
print(f"BLE error for {name}: {e}")
return results
# ============================================
# SIMULATED DATA - Replace with actual device
# ============================================
SIMULATED_DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF" # Example MAC address
async def main():
reader = SensorReader(SIMULATED_DEVICE_ADDRESS)
# Read battery
battery = await reader.read_battery()
print(f"Battery: {battery}%")
# Read all sensors
data = await reader.read_all()
for name, value in data.items():
print(f"{name}: {value}")
if __name__ == "__main__":
asyncio.run(main())
See Also¶
Quick Start - Basic usage
API Reference - Full API documentation
Examples - More examples and patterns