Add MX-T40 BLE desktop controller and protocol documentation
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
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
This commit is contained in:
38 files changed
+2344
No files matched your search
@@ -0,0 +1,65 @@
|
||||
"""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)
|
||||
Reference in new issue
Block a user