Cross-platform Bluetooth Low Energy GATT client library for asynchronous BLE communication
Overall
score
97%
Bleak provides comprehensive data structures representing BLE devices, advertisement data, and the complete GATT hierarchy including services, characteristics, and descriptors. These models provide a consistent interface across all supported platforms.
Core representation of discovered BLE devices with address, name, and platform-specific details.
class BLEDevice:
"""A simple wrapper class representing a BLE server detected during scanning."""
__slots__ = ("address", "name", "details")
def __init__(self, address: str, name: Optional[str], details: Any, **kwargs: Any):
"""
Initialize BLE device representation.
Args:
address: Bluetooth address of the device on this machine (UUID on macOS)
name: Operating system name of the device (not necessarily the local name
from advertising data), suitable for display to the user
details: OS native details required for connecting to the device
**kwargs: Deprecated additional arguments (no effect)
"""
address: str # The Bluetooth address of the device on this machine (UUID on macOS)
name: Optional[str] # The operating system name of the device, suitable for display
details: Any # The OS native details required for connecting to the device
def __str__(self) -> str:
"""String representation showing address and name."""
def __repr__(self) -> str:
"""Detailed string representation for debugging."""Structured container for BLE advertisement data received during device discovery.
class AdvertisementData(NamedTuple):
"""Container for BLE advertisement data from device discovery."""
local_name: Optional[str]
"""Device local name or None if not included in advertising data."""
manufacturer_data: dict[int, bytes]
"""
Dictionary of manufacturer data from advertising.
Keys are Bluetooth SIG Company Identifiers, values are data bytes.
"""
service_data: dict[str, bytes]
"""
Dictionary of service data from advertising.
Keys are service UUIDs, values are associated data bytes.
"""
service_uuids: list[str]
"""List of service UUIDs advertised by the device."""
tx_power: Optional[int]
"""TX Power Level in dBm from advertising data, or None if not present."""
rssi: int
"""Radio Receive Signal Strength Indicator (RSSI) in dBm."""
platform_data: tuple[Any, ...]
"""
Tuple of platform-specific data.
This is not a stable API and may change between releases.
"""
def __repr__(self) -> str:
"""Detailed string representation showing all available data."""Container managing the complete GATT service hierarchy for a connected device.
class BleakGATTServiceCollection:
"""Simple data container for storing the peripheral's service complement."""
def __init__(self) -> None:
"""Initialize empty service collection."""
def __getitem__(
self, item: Union[str, int, UUID]
) -> Optional[Union[BleakGATTService, BleakGATTCharacteristic, BleakGATTDescriptor]]:
"""Get service, characteristic, or descriptor by UUID or handle."""
def __iter__(self) -> Iterator[BleakGATTService]:
"""Returns an iterator over all BleakGATTService objects."""
@property
def services(self) -> dict[int, BleakGATTService]:
"""Returns dictionary of handles mapping to BleakGATTService."""
@property
def characteristics(self) -> dict[int, BleakGATTCharacteristic]:
"""Returns dictionary of handles mapping to BleakGATTCharacteristic."""
@property
def descriptors(self) -> dict[int, BleakGATTDescriptor]:
"""Returns a dictionary of integer handles mapping to BleakGATTDescriptor."""
def get_service(
self, specifier: Union[int, str, UUID]
) -> Optional[BleakGATTService]:
"""
Get service by handle (int) or UUID (str or uuid.UUID).
Args:
specifier: Service handle (int) or UUID (str/UUID)
Returns:
BleakGATTService if found, None otherwise
Raises:
BleakError: If multiple services have the same UUID
"""
def get_characteristic(
self, specifier: Union[int, str, UUID]
) -> Optional[BleakGATTCharacteristic]:
"""
Get characteristic by handle (int) or UUID (str or uuid.UUID).
Args:
specifier: Characteristic handle (int) or UUID (str/UUID)
Returns:
BleakGATTCharacteristic if found, None otherwise
Raises:
BleakError: If multiple characteristics have the same UUID
"""
def get_descriptor(self, handle: int) -> Optional[BleakGATTDescriptor]:
"""
Get descriptor by handle.
Args:
handle: Descriptor handle
Returns:
BleakGATTDescriptor if found, None otherwise
"""Representation of a GATT service containing characteristics and metadata.
class BleakGATTService:
"""Representation of a GATT service."""
def __init__(self, obj: Any, handle: int, uuid: str) -> None:
"""
Initialize GATT service.
Args:
obj: Platform-specific service object
handle: Service handle
uuid: Service UUID
"""
def __str__(self) -> str:
"""String representation showing UUID, handle, and description."""
@property
def handle(self) -> int:
"""Handle of this service."""
@property
def uuid(self) -> str:
"""UUID of this service."""
@property
def description(self) -> str:
"""Human-readable description of this service."""
@property
def characteristics(self) -> list[BleakGATTCharacteristic]:
"""List of characteristics belonging to this service."""
def get_characteristic(
self, uuid: Union[str, UUID]
) -> Union[BleakGATTCharacteristic, None]:
"""
Get characteristic by UUID.
Args:
uuid: Characteristic UUID to match
Returns:
First matching BleakGATTCharacteristic or None
"""Representation of a GATT characteristic with properties, descriptors, and metadata.
class BleakGATTCharacteristic:
"""Representation of a GATT characteristic."""
def __init__(
self,
obj: Any,
handle: int,
uuid: str,
properties: list[CharacteristicPropertyName],
max_write_without_response_size: Callable[[], int],
service: BleakGATTService,
):
"""
Initialize GATT characteristic.
Args:
obj: Platform-specific characteristic object
handle: Characteristic handle
uuid: Characteristic UUID
properties: List of characteristic properties
max_write_without_response_size: Function returning max write size
service: Parent service
"""
def __str__(self) -> str:
"""String representation showing UUID, handle, and description."""
@property
def service_uuid(self) -> str:
"""UUID of the service containing this characteristic."""
@property
def service_handle(self) -> int:
"""Handle of the service containing this characteristic."""
@property
def handle(self) -> int:
"""Handle of this characteristic."""
@property
def uuid(self) -> str:
"""UUID of this characteristic."""
@property
def description(self) -> str:
"""Human-readable description of this characteristic."""
@property
def properties(self) -> list[CharacteristicPropertyName]:
"""List of properties supported by this characteristic."""
@property
def max_write_without_response_size(self) -> int:
"""
Maximum size for write-without-response operations.
Note: May return default value (20) initially and update later.
BlueZ versions < 5.62 always return 20.
"""
@property
def descriptors(self) -> list[BleakGATTDescriptor]:
"""List of descriptors belonging to this characteristic."""
def get_descriptor(
self, specifier: Union[int, str, UUID]
) -> Union[BleakGATTDescriptor, None]:
"""
Get descriptor by handle or UUID.
Args:
specifier: Descriptor handle (int) or UUID (str/UUID)
Returns:
BleakGATTDescriptor if found, None otherwise
"""Representation of a GATT descriptor with metadata and parent characteristic reference.
class BleakGATTDescriptor:
"""Representation of a GATT descriptor."""
def __init__(
self, obj: Any, handle: int, uuid: str, characteristic: BleakGATTCharacteristic
):
"""
Initialize GATT descriptor.
Args:
obj: Platform-specific descriptor object
handle: Descriptor handle
uuid: Descriptor UUID
characteristic: Parent characteristic
"""
def __str__(self) -> str:
"""String representation showing UUID, handle, and description."""
@property
def characteristic_uuid(self) -> str:
"""UUID of the characteristic this descriptor belongs to."""
@property
def characteristic_handle(self) -> int:
"""Handle of the characteristic this descriptor belongs to."""
@property
def uuid(self) -> str:
"""UUID of this descriptor."""
@property
def handle(self) -> int:
"""Handle of this descriptor."""
@property
def description(self) -> str:
"""Human-readable description of this descriptor."""Enumeration and utilities for GATT characteristic properties.
class GattCharacteristicsFlags(enum.Enum):
"""Enumeration of GATT characteristic property flags."""
broadcast = 0x0001
read = 0x0002
write_without_response = 0x0004
write = 0x0008
notify = 0x0010
indicate = 0x0020
authenticated_signed_writes = 0x0040
extended_properties = 0x0080
reliable_write = 0x0100
writable_auxiliaries = 0x0200
def gatt_char_props_to_strs(
props: int,
) -> frozenset[CharacteristicPropertyName]:
"""
Convert characteristic properties bitmask to set of property names.
Args:
props: Properties bitmask
Returns:
Frozenset of property name strings
"""import asyncio
from bleak import BleakScanner
async def analyze_discovered_devices():
# Discover devices with advertisement data
discovered = await BleakScanner.discover(timeout=10.0, return_adv=True)
for address, (device, adv_data) in discovered.items():
print(f"Device: {device}")
print(f" Address: {device.address}")
print(f" Name: {device.name}")
print(f" Advertisement Data:")
print(f" Local Name: {adv_data.local_name}")
print(f" RSSI: {adv_data.rssi} dBm")
print(f" TX Power: {adv_data.tx_power}")
print(f" Service UUIDs: {adv_data.service_uuids}")
# Process manufacturer data
for company_id, data in adv_data.manufacturer_data.items():
print(f" Manufacturer {company_id:04x}: {data.hex()}")
# Process service data
for service_uuid, data in adv_data.service_data.items():
print(f" Service {service_uuid}: {data.hex()}")
asyncio.run(analyze_discovered_devices())import asyncio
from bleak import BleakClient
async def explore_gatt_structure():
address = "00:11:22:33:44:55" # Replace with actual device address
async with BleakClient(address) as client:
services = client.services
print(f"Device has {len(services.services)} services")
print(f"Total characteristics: {len(services.characteristics)}")
print(f"Total descriptors: {len(services.descriptors)}")
# Iterate through service collection
for service in services:
print(f"\nService: {service}")
print(f" Handle: {service.handle}")
print(f" UUID: {service.uuid}")
print(f" Description: {service.description}")
for char in service.characteristics:
print(f" Characteristic: {char}")
print(f" Handle: {char.handle}")
print(f" UUID: {char.uuid}")
print(f" Properties: {char.properties}")
print(f" Max write size: {char.max_write_without_response_size}")
for desc in char.descriptors:
print(f" Descriptor: {desc}")
print(f" Handle: {desc.handle}")
print(f" UUID: {desc.uuid}")
asyncio.run(explore_gatt_structure())import asyncio
from bleak import BleakClient
async def find_specific_elements():
address = "00:11:22:33:44:55" # Replace with actual device address
async with BleakClient(address) as client:
services = client.services
# Find Generic Access service
gap_service = services.get_service("00001800-0000-1000-8000-00805f9b34fb")
if gap_service:
print(f"Found GAP service: {gap_service.description}")
# Find device name characteristic within the service
device_name_char = gap_service.get_characteristic(
"00002a00-0000-1000-8000-00805f9b34fb"
)
if device_name_char:
print(f"Found device name characteristic: {device_name_char.description}")
# Find characteristic by UUID across all services
battery_char = services.get_characteristic("00002a19-0000-1000-8000-00805f9b34fb")
if battery_char:
print(f"Found battery level characteristic: {battery_char.description}")
print(f" Service UUID: {battery_char.service_uuid}")
print(f" Properties: {battery_char.properties}")
# Find descriptor by handle
desc = services.get_descriptor(42) # Replace with actual handle
if desc:
print(f"Found descriptor: {desc.description}")
print(f" Characteristic UUID: {desc.characteristic_uuid}")
asyncio.run(find_specific_elements())from bleak.assigned_numbers import gatt_char_props_to_strs
def analyze_characteristic_properties():
# Example properties bitmask
props_mask = 0x12 # read (0x02) + notify (0x10)
# Convert to property names
properties = gatt_char_props_to_strs(props_mask)
print(f"Properties: {properties}")
# Check for specific properties
if "read" in properties:
print("Characteristic supports reading")
if "notify" in properties:
print("Characteristic supports notifications")
if "write" in properties:
print("Characteristic supports write-with-response")
if "write-without-response" in properties:
print("Characteristic supports write-without-response")
analyze_characteristic_properties()# Characteristic property names
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",
]
# Advertisement data type enumeration
class AdvertisementDataType(IntEnum):
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 = 0xFF
# Property mapping for internal use
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",
}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