Files
soundtower-controller/controller/ble_probe.py
T
lda Verified 038e152bbc
Checks / tests (ubuntu-latest, 3.11) (push) Canceled after 0s
Checks / tests (ubuntu-latest, 3.14) (push) Canceled after 0s
Checks / tests (windows-latest, 3.11) (push) Canceled after 0s
Checks / tests (windows-latest, 3.14) (push) Canceled after 0s
Checks / package (push) Canceled after 0s
Add MX-T40 BLE desktop controller and protocol documentation
2026-10-25 04:43:47 +07:00

66 lines
2.8 KiB
Python

"""Inspect MX-T40 BLE services without reading/writing control values."""
import argparse
import asyncio
# MX-T40 model advertisements from the Android APK's strings.xml. These
# identify discovery records; they are not assumed to be writable services.
MODEL_UUIDS = {
f"0000{short}-0000-1000-8000-00805f9b34fb" for short in ("ff40", "fe40", "ff41", "fe41")
}
def is_candidate(device, advertisement):
names = f"{device.name or ''} {advertisement.local_name or ''}".upper()
return "T40" in names or bool(
MODEL_UUIDS.intersection(value.lower() for value in advertisement.service_uuids)
)
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)