CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-bleak

Cross-platform Bluetooth Low Energy GATT client library for asynchronous BLE communication

Overall
score

97%

Overview
Eval results
Files

device-discovery.mddocs/

Device Discovery and Scanning

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.

Capabilities

Scanner Initialization and Configuration

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
        """

Scanner Control Operations

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."""

Advertisement Data Streaming

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)
    """

One-Time Discovery Operations

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
    """

Device Lookup Operations

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
    """

Discovery Results Access

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
    """

Usage Examples

Basic Device Discovery

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())

Discovery with Advertisement Data

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())

Continuous Scanning with Callbacks

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())

Filtered Discovery

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())

Platform-Specific Configuration

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())

Types

# 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-bleak

docs

data-structures.md

device-discovery.md

exception-handling.md

gatt-client.md

index.md

uuid-utilities.md

tile.json