Cross-platform Bluetooth Low Energy GATT client library for asynchronous BLE communication
Overall
score
97%
Bleak provides comprehensive BLE device discovery capabilities through the BleakScanner class. It supports both active and passive scanning modes with platform-specific optimizations and flexible filtering options.
Initialize scanners with optional detection callbacks, service UUID filtering, and platform-specific arguments for optimal discovery performance.
class BleakScanner:
def __init__(
self,
detection_callback: Optional[AdvertisementDataCallback] = None,
service_uuids: Optional[list[str]] = None,
scanning_mode: Literal["active", "passive"] = "active",
*,
bluez: BlueZScannerArgs = {},
cb: CBScannerArgs = {},
backend: Optional[type[BaseBleakScanner]] = None,
**kwargs: Any,
) -> None:
"""
Initialize BLE scanner.
Args:
detection_callback: Optional function called when devices are discovered
service_uuids: Optional list of service UUIDs to filter on
scanning_mode: "active" or "passive" scanning mode
bluez: BlueZ-specific scanner arguments
cb: CoreBluetooth-specific scanner arguments
backend: Custom backend implementation
"""Start and stop scanning operations with async context manager support for automatic lifecycle management.
async def start(self) -> None:
"""Start scanning for devices."""
async def stop(self) -> None:
"""Stop scanning for devices."""
async def __aenter__(self) -> Self:
"""Async context manager entry - starts scanning."""
async def __aexit__(
self,
exc_type: type[BaseException],
exc_val: BaseException,
exc_tb: TracebackType,
) -> None:
"""Async context manager exit - stops scanning."""Stream advertisement data as devices are discovered using async generators for real-time processing.
async def advertisement_data(
self,
) -> AsyncGenerator[tuple[BLEDevice, AdvertisementData], None]:
"""
Yields devices and advertisement data as they are discovered.
Note: Ensure scanning is started before calling this method.
Returns:
Async iterator yielding tuples of (BLEDevice, AdvertisementData)
"""Convenient class methods for simple discovery operations without manual scanner lifecycle management.
@classmethod
async def discover(
cls,
timeout: float = 5.0,
*,
return_adv: bool = False,
**kwargs: Unpack[ExtraArgs],
):
"""
Scan for devices for specified timeout duration.
Args:
timeout: Time in seconds to scan
return_adv: If True, return advertisement data with devices
**kwargs: Additional scanner arguments
Returns:
List of BLEDevice objects or dict mapping addresses to (device, adv_data) tuples
"""Find specific devices by address, name, or custom filter criteria with configurable timeout.
@classmethod
async def find_device_by_address(
cls, device_identifier: str, timeout: float = 10.0, **kwargs: Unpack[ExtraArgs]
) -> Optional[BLEDevice]:
"""
Find device by Bluetooth address or UUID.
Args:
device_identifier: Bluetooth address or UUID to search for
timeout: Maximum time to search before giving up
**kwargs: Additional scanner arguments
Returns:
BLEDevice if found, None otherwise
"""
@classmethod
async def find_device_by_name(
cls, name: str, timeout: float = 10.0, **kwargs: Unpack[ExtraArgs]
) -> Optional[BLEDevice]:
"""
Find device by local name in advertisement data.
Args:
name: Device name to search for
timeout: Maximum time to search before giving up
**kwargs: Additional scanner arguments
Returns:
BLEDevice if found, None otherwise
"""
@classmethod
async def find_device_by_filter(
cls,
filterfunc: AdvertisementDataFilter,
timeout: float = 10.0,
**kwargs: Unpack[ExtraArgs],
) -> Optional[BLEDevice]:
"""
Find device using custom filter function.
Args:
filterfunc: Function that returns True for desired device
timeout: Maximum time to search before giving up
**kwargs: Additional scanner arguments
Returns:
BLEDevice if found, None otherwise
"""Access discovered devices and their advertisement data through scanner properties.
@property
def discovered_devices(self) -> list[BLEDevice]:
"""List of discovered devices during scanning."""
@property
def discovered_devices_and_advertisement_data(
self,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
"""
Map of device addresses to (device, advertisement_data) tuples.
Returns:
Dict with device addresses as keys and (BLEDevice, AdvertisementData) as values
"""import asyncio
from bleak import BleakScanner
async def discover_devices():
# Simple discovery with timeout
devices = await BleakScanner.discover(timeout=10.0)
for device in devices:
print(f"Found: {device.name} ({device.address})")
asyncio.run(discover_devices())import asyncio
from bleak import BleakScanner
async def discover_with_data():
# Get both devices and advertisement data
discovered = await BleakScanner.discover(timeout=10.0, return_adv=True)
for address, (device, adv_data) in discovered.items():
print(f"Device: {device.name} ({address})")
print(f" RSSI: {adv_data.rssi} dBm")
print(f" Services: {adv_data.service_uuids}")
print(f" Manufacturer: {adv_data.manufacturer_data}")
asyncio.run(discover_with_data())import asyncio
from bleak import BleakScanner, BLEDevice, AdvertisementData
def detection_callback(device: BLEDevice, advertisement_data: AdvertisementData):
print(f"Detected: {device.name} ({device.address}) RSSI: {advertisement_data.rssi}")
async def continuous_scan():
scanner = BleakScanner(detection_callback=detection_callback)
async with scanner: # Auto start/stop
await asyncio.sleep(30) # Scan for 30 seconds
asyncio.run(continuous_scan())import asyncio
from bleak import BleakScanner
async def find_heart_rate_monitors():
# Find devices advertising Heart Rate service
heart_rate_uuid = "0000180d-0000-1000-8000-00805f9b34fb"
devices = await BleakScanner.discover(
timeout=10.0,
service_uuids=[heart_rate_uuid]
)
for device in devices:
print(f"Heart Rate Monitor: {device.name} ({device.address})")
asyncio.run(find_heart_rate_monitors())import asyncio
from bleak import BleakScanner
from bleak.args.bluez import BlueZScannerArgs
async def linux_specific_scan():
# BlueZ-specific configuration
bluez_args = BlueZScannerArgs(
filters={"RSSI": -50} # Only devices with RSSI > -50 dBm
)
devices = await BleakScanner.discover(
timeout=10.0,
bluez=bluez_args
)
for device in devices:
print(f"Strong signal device: {device.name} ({device.address})")
# Only run on Linux
if platform.system() == "Linux":
asyncio.run(linux_specific_scan())# Scanner arguments by platform
class BlueZScannerArgs(TypedDict, total=False):
filters: BlueZDiscoveryFilters
or_patterns: list[OrPatternLike]
class CBScannerArgs(TypedDict, total=False):
use_bdaddr: bool
# Discovery filter configuration for BlueZ
class BlueZDiscoveryFilters(TypedDict, total=False):
UUIDs: list[str]
RSSI: int
Pathloss: int
Transport: str
DuplicateData: bool
Discoverable: bool
Pattern: str
# Advertisement data callback type
AdvertisementDataCallback = Callable[
[BLEDevice, AdvertisementData],
Optional[Coroutine[Any, Any, None]],
]
# Advertisement data filter type
AdvertisementDataFilter = Callable[
[BLEDevice, AdvertisementData],
bool,
]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