"""Inspect MX-T40 BLE services without reading/writing control values.""" import argparse import asyncio from controller.discovery import is_candidate async def inspect(timeout): from bleak import BleakClient, BleakScanner print(f"Scanning BLE advertisements for {timeout:g} seconds…", flush=True) devices = await BleakScanner.discover(timeout=timeout, return_adv=True) candidates = [(device, adv) for device, adv in devices.values() if is_candidate(device, adv)] print(f"Nearby BLE devices: {len(devices)}; MX-T40 candidates: {len(candidates)}") if not candidates: print("No matching advertisement. This does not rule out BLE support.") print("Named nearby devices:") for device, adv in devices.values(): name = adv.local_name or device.name if name: print(f" {name} [{device.address}]") return 1 failures = False for device, adv in candidates: print(f"\nDevice: {adv.local_name or device.name} [{device.address}]") print("Advertised services:", ", ".join(adv.service_uuids) or "(none)") try: # Use the scanned device object so Bleak retains Windows address # type information; the classic audio MAC may not be the BLE MAC. async with BleakClient(device, timeout=15) as client: for service in client.services: print(f"Service: {service.uuid} {service.description}") for char in service.characteristics: print(f" Characteristic: {char.uuid} [{', '.join(char.properties)}]") except Exception as exc: failures = True print(f"GATT inspection failed: {type(exc).__name__}: {exc}") print("\nNo control values were read or written; no notifications enabled.") return 1 if failures else 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--timeout", type=float, default=15) args = parser.parse_args() try: raise SystemExit(asyncio.run(inspect(args.timeout))) except ImportError: print("Run: uv.exe run --python 3.14 --with bleak -m controller.ble_probe") raise SystemExit(2) except Exception as exc: print(f"BLE scan failed: {type(exc).__name__}: {exc}") raise SystemExit(1)