Source code for phomemo.discovery

# This file is part of phomemo.
# Copyright (C) 2026 Paul Bütof, Passionate Bytes Solutions
#
# phomemo is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# phomemo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with phomemo.  If not, see <https://www.gnu.org/licenses/>.

"""BLE device discovery for Phomemo printers.

Wraps ``bleak.BleakScanner`` to find nearby BLE devices, with optional
regex filtering on advertised names.
"""

import logging
import re

from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bleak.exc import BleakError

logger = logging.getLogger(__name__)


[docs] async def discover( name_pattern: str | None = None, timeout: float = 5.0, ) -> list[BLEDevice]: """Discover nearby BLE devices, optionally filtered by name pattern. Args: name_pattern: If provided, only return devices whose advertised name matches this regex pattern (searched, not full-match). timeout: Scan duration in seconds. Returns: List of discovered ``BLEDevice`` objects, sorted by name. Raises: ConnectionError: If the Bluetooth adapter is unavailable or the scan fails at the OS/adapter level. """ logger.debug("Starting BLE scan (timeout=%.1fs, pattern=%s)", timeout, name_pattern) try: discovered = await BleakScanner.discover(timeout=timeout, return_adv=True) except BleakError as exc: raise ConnectionError( f"Bluetooth scan failed, is the adapter powered on? {exc}" ) from exc devices = [dev for dev, _ in discovered.values()] if name_pattern is not None: compiled = re.compile(name_pattern) all_devices = devices devices = [d for d in all_devices if d.name and compiled.search(d.name)] logger.debug( "Pattern %r matched %d of %d device(s)", name_pattern, len(devices), len(all_devices), ) logger.debug("Scan complete: %d device(s) found", len(devices)) return sorted(devices, key=lambda d: d.name or "")