Cross-platform Bluetooth Low Energy GATT client library for asynchronous BLE communication
Overall
score
97%
Bleak provides comprehensive utilities for UUID normalization, conversion, and lookup of Bluetooth assigned numbers. These utilities handle the conversion between different UUID formats and provide human-readable descriptions for standard Bluetooth services and characteristics.
Convert UUIDs between different formats and normalize them to Bleak's standard 128-bit lowercase format.
def normalize_uuid_str(uuid: str) -> str:
"""
Normalize a UUID string to Bleak's standard format.
Converts UUID to lowercase and expands 16-bit and 32-bit UUIDs to 128-bit format.
Args:
uuid: UUID string in 16-bit, 32-bit, or 128-bit format
Returns:
128-bit UUID string in lowercase format
Examples:
normalize_uuid_str("1234") -> "00001234-0000-1000-8000-00805f9b34fb"
normalize_uuid_str("12345678") -> "12345678-0000-1000-8000-00805f9b34fb"
normalize_uuid_str("12345678-0000-1234-1234-1234567890ABC") ->
"12345678-0000-1234-1234-1234567890abc"
Note: Function normalizes according to Bluetooth Core Specification Version 5.4 | Vol 3, Part B - Section 2.5.1
"""
def normalize_uuid_16(uuid: int) -> str:
"""
Normalizes a 16-bit integer UUID to Bleak's standard format.
Args:
uuid: 16-bit integer UUID
Returns:
128-bit UUID string with format "0000xxxx-0000-1000-8000-00805f9b34fb"
Example:
normalize_uuid_16(0x1234) -> "00001234-0000-1000-8000-00805f9b34fb"
"""
def normalize_uuid_32(uuid: int) -> str:
"""
Normalizes a 32-bit integer UUID to Bleak's standard format.
Args:
uuid: 32-bit integer UUID
Returns:
128-bit UUID string with format "xxxxxxxx-0000-1000-8000-00805f9b34fb"
Example:
normalize_uuid_32(0x12345678) -> "12345678-0000-1000-8000-00805f9b34fb"
"""Convert UUIDs to human-readable descriptions using Bluetooth assigned numbers.
def uuidstr_to_str(uuid_: str) -> str:
"""
Convert UUID string to human-readable description.
Looks up the UUID in the assigned numbers database and returns
a descriptive name if found.
Args:
uuid_: UUID string to look up
Returns:
Human-readable description or "Unknown" if not found
Examples:
uuidstr_to_str("0000180f-0000-1000-8000-00805f9b34fb") -> "Battery Service"
uuidstr_to_str("00002a19-0000-1000-8000-00805f9b34fb") -> "Battery Level"
"""
def register_uuids(uuids_to_descriptions: dict[str, str]) -> None:
"""
Register custom UUID to description mappings.
Adds or modifies the mapping of 128-bit UUIDs to descriptions
for application-specific or vendor-specific UUIDs.
Args:
uuids_to_descriptions: Dictionary mapping UUID strings to descriptions
Example:
register_uuids({
"12345678-1234-5678-9012-123456789abc": "My Custom Service",
"87654321-4321-8765-2109-cba987654321": "My Custom Characteristic"
})
"""Comprehensive dictionaries containing Bluetooth SIG assigned numbers for services, characteristics, and descriptors.
# 16-bit UUID assignments
uuid16_dict: dict[int, str] = {
0x1800: "Generic Access Profile",
0x1801: "Generic Attribute Profile",
0x1802: "Immediate Alert",
0x180A: "Device Information",
0x180F: "Battery Service",
0x1812: "Human Interface Device",
# ... (complete mapping with hundreds of entries)
}
# 128-bit UUID assignments (vendor-specific and custom services)
uuid128_dict: dict[str, str] = {
"6e400001-b5a3-f393-e0a9-e50e24dcca9e": "Nordic UART Service",
"6e400002-b5a3-f393-e0a9-e50e24dcca9e": "Nordic UART RX",
"6e400003-b5a3-f393-e0a9-e50e24dcca9e": "Nordic UART TX",
# ... (complete mapping with custom and vendor UUIDs)
}Enumeration of Generic Access Profile advertisement data types.
class AdvertisementDataType(IntEnum):
"""Generic Access Profile advertisement data types."""
FLAGS = 0x01
INCOMPLETE_LIST_SERVICE_UUID16 = 0x02
COMPLETE_LIST_SERVICE_UUID16 = 0x03
INCOMPLETE_LIST_SERVICE_UUID32 = 0x04
COMPLETE_LIST_SERVICE_UUID32 = 0x05
INCOMPLETE_LIST_SERVICE_UUID128 = 0x06
COMPLETE_LIST_SERVICE_UUID128 = 0x07
SHORTENED_LOCAL_NAME = 0x08
COMPLETE_LOCAL_NAME = 0x09
TX_POWER_LEVEL = 0x0A
CLASS_OF_DEVICE = 0x0D
SERVICE_DATA_UUID16 = 0x16
SERVICE_DATA_UUID32 = 0x20
SERVICE_DATA_UUID128 = 0x21
MANUFACTURER_SPECIFIC_DATA = 0xFFConstants and utilities for GATT characteristic properties.
CharacteristicPropertyName = Literal[
"broadcast",
"read",
"write-without-response",
"write",
"notify",
"indicate",
"authenticated-signed-writes",
"extended-properties",
"reliable-write",
"writable-auxiliaries",
"encrypt-read",
"encrypt-write",
"encrypt-authenticated-read",
"encrypt-authenticated-write",
"authorize",
]
CHARACTERISTIC_PROPERTIES: dict[int, CharacteristicPropertyName] = {
0x1: "broadcast",
0x2: "read",
0x4: "write-without-response",
0x8: "write",
0x10: "notify",
0x20: "indicate",
0x40: "authenticated-signed-writes",
0x80: "extended-properties",
0x100: "reliable-write",
0x200: "writable-auxiliaries",
}
def gatt_char_props_to_strs(props: int) -> frozenset[CharacteristicPropertyName]:
"""
Convert GATT characteristic properties bitmask to set of property names.
Args:
props: Characteristic properties bitmask
Returns:
Frozenset of property name strings
"""from bleak.uuids import normalize_uuid_str, normalize_uuid_16, normalize_uuid_32
def demonstrate_uuid_normalization():
# Normalize different UUID formats
uuid_16bit = normalize_uuid_str("180F") # Battery Service
print(f"16-bit UUID: {uuid_16bit}")
# Output: 0000180f-0000-1000-8000-00805f9b34fb
uuid_32bit = normalize_uuid_str("12345678")
print(f"32-bit UUID: {uuid_32bit}")
# Output: 12345678-0000-1000-8000-00805f9b34fb
uuid_128bit = normalize_uuid_str("12345678-1234-5678-9012-123456789ABC")
print(f"128-bit UUID: {uuid_128bit}")
# Output: 12345678-1234-5678-9012-123456789abc
# Normalize from integers
from_int_16 = normalize_uuid_16(0x180F)
print(f"From 16-bit int: {from_int_16}")
from_int_32 = normalize_uuid_32(0x12345678)
print(f"From 32-bit int: {from_int_32}")
demonstrate_uuid_normalization()from bleak.uuids import uuidstr_to_str
def demonstrate_uuid_lookup():
# Look up standard Bluetooth services
gap_service = uuidstr_to_str("00001800-0000-1000-8000-00805f9b34fb")
print(f"GAP Service: {gap_service}")
# Output: Generic Access Profile
battery_service = uuidstr_to_str("0000180f-0000-1000-8000-00805f9b34fb")
print(f"Battery Service: {battery_service}")
# Output: Battery Service
# Look up standard characteristics
device_name = uuidstr_to_str("00002a00-0000-1000-8000-00805f9b34fb")
print(f"Device Name: {device_name}")
# Output: Device Name
battery_level = uuidstr_to_str("00002a19-0000-1000-8000-00805f9b34fb")
print(f"Battery Level: {battery_level}")
# Output: Battery Level
# Look up vendor-specific UUID
nordic_uart = uuidstr_to_str("6e400001-b5a3-f393-e0a9-e50e24dcca9e")
print(f"Nordic UART: {nordic_uart}")
# Output: Nordic UART Service
# Unknown UUID
unknown = uuidstr_to_str("12345678-1234-5678-9012-123456789abc")
print(f"Unknown UUID: {unknown}")
# Output: Unknown
demonstrate_uuid_lookup()from bleak.uuids import register_uuids, uuidstr_to_str
def demonstrate_custom_uuids():
# Register custom UUID mappings
custom_uuids = {
"12345678-1234-5678-9012-123456789abc": "My IoT Device Service",
"87654321-4321-8765-2109-cba987654321": "Temperature Sensor Characteristic",
"11111111-2222-3333-4444-555555555555": "Custom Control Point"
}
register_uuids(custom_uuids)
# Now lookup returns custom descriptions
service_desc = uuidstr_to_str("12345678-1234-5678-9012-123456789abc")
print(f"Custom Service: {service_desc}")
# Output: My IoT Device Service
char_desc = uuidstr_to_str("87654321-4321-8765-2109-cba987654321")
print(f"Custom Characteristic: {char_desc}")
# Output: Temperature Sensor Characteristic
demonstrate_custom_uuids()from bleak.assigned_numbers import gatt_char_props_to_strs, CHARACTERISTIC_PROPERTIES
def demonstrate_characteristic_properties():
# Example properties bitmask (read + write + notify)
props_mask = 0x1A # 0x02 (read) + 0x08 (write) + 0x10 (notify)
# Convert to property names
properties = gatt_char_props_to_strs(props_mask)
print(f"Properties: {properties}")
# Output: frozenset({'read', 'write', 'notify'})
# Check individual properties
if "read" in properties:
print("Characteristic supports reading")
if "write" in properties:
print("Characteristic supports write-with-response")
if "notify" in properties:
print("Characteristic supports notifications")
if "write-without-response" not in properties:
print("Characteristic does not support write-without-response")
# Show all available property mappings
print("\nAll property mappings:")
for bit, name in CHARACTERISTIC_PROPERTIES.items():
print(f" 0x{bit:02X}: {name}")
demonstrate_characteristic_properties()import asyncio
from bleak import BleakClient
from bleak.uuids import normalize_uuid_str, uuidstr_to_str
async def uuid_practical_usage():
address = "00:11:22:33:44:55" # Replace with actual device address
async with BleakClient(address) as client:
print(f"Connected to {client.name}")
# Use UUID utilities with service discovery
for service in client.services:
service_desc = uuidstr_to_str(service.uuid)
print(f"\nService: {service.uuid}")
print(f" Description: {service_desc}")
for char in service.characteristics:
char_desc = uuidstr_to_str(char.uuid)
print(f" Characteristic: {char.uuid}")
print(f" Description: {char_desc}")
print(f" Properties: {char.properties}")
# Demonstrate reading standard characteristics
if char.uuid == normalize_uuid_str("2A00"): # Device Name
try:
data = await client.read_gatt_char(char.uuid)
name = data.decode('utf-8')
print(f" Device Name: {name}")
except Exception as e:
print(f" Could not read device name: {e}")
elif char.uuid == normalize_uuid_str("2A19"): # Battery Level
try:
data = await client.read_gatt_char(char.uuid)
level = int.from_bytes(data, byteorder='little')
print(f" Battery Level: {level}%")
except Exception as e:
print(f" Could not read battery level: {e}")
# asyncio.run(uuid_practical_usage()) # Uncomment with real devicefrom bleak.assigned_numbers import AdvertisementDataType
def process_advertisement_data(adv_data_raw):
"""Example of processing raw advertisement data using constants."""
# Parse advertisement data (simplified example)
pos = 0
while pos < len(adv_data_raw):
length = adv_data_raw[pos]
if length == 0:
break
ad_type = adv_data_raw[pos + 1]
data = adv_data_raw[pos + 2:pos + 1 + length]
# Use constants for readable code
if ad_type == AdvertisementDataType.COMPLETE_LOCAL_NAME:
device_name = data.decode('utf-8')
print(f"Device Name: {device_name}")
elif ad_type == AdvertisementDataType.COMPLETE_LIST_SERVICE_UUID16:
# Parse 16-bit service UUIDs
for i in range(0, len(data), 2):
uuid_16 = int.from_bytes(data[i:i+2], byteorder='little')
print(f"Service UUID: {uuid_16:04X}")
elif ad_type == AdvertisementDataType.TX_POWER_LEVEL:
tx_power = int.from_bytes(data, byteorder='little', signed=True)
print(f"TX Power: {tx_power} dBm")
elif ad_type == AdvertisementDataType.MANUFACTURER_SPECIFIC_DATA:
company_id = int.from_bytes(data[:2], byteorder='little')
mfg_data = data[2:]
print(f"Manufacturer {company_id:04X}: {mfg_data.hex()}")
pos += 1 + length
# Example usage with mock data
mock_adv_data = bytes([
0x05, 0x09, 0x54, 0x65, 0x73, 0x74, # Complete local name: "Test"
0x03, 0x03, 0x0F, 0x18, # Complete 16-bit service UUID: 0x180F
0x02, 0x0A, 0x00, # TX Power: 0 dBm
])
process_advertisement_data(mock_adv_data)# UUID dictionaries
uuid16_dict: dict[int, str]
uuid128_dict: dict[str, str]
# Characteristic property type
CharacteristicPropertyName = Literal[
"broadcast", "read", "write-without-response", "write",
"notify", "indicate", "authenticated-signed-writes",
"extended-properties", "reliable-write", "writable-auxiliaries",
"encrypt-read", "encrypt-write", "encrypt-authenticated-read",
"encrypt-authenticated-write", "authorize"
]
# Property mapping
CHARACTERISTIC_PROPERTIES: dict[int, CharacteristicPropertyName]
# Advertisement data types
class AdvertisementDataType(IntEnum): ...Install with Tessl CLI
npx tessl i tessl/pypi-bleakdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9