#!/usr/bin/env python3 """ LED Panel BLE Slot Switcher Connects to Amusing LED panel via BLE and switches between saved program slots. Protocol reverse-engineered from /home/seb/glove/lib/Device/led/Led.cpp """ import asyncio import sys from bleak import BleakScanner, BleakClient # BLE UUIDs (Microchip transparent UART) SERVICE_UUID = "49535343-fe7d-4ae5-8fa9-9fafd205e455" WRITE_UUID = "49535343-8841-43f4-a8d4-ecbe34729bb3" # Base command for slot switching BASE_CMD = bytearray([ 0xAA, 0x55, 0xFF, 0xFF, 0x0E, 0x00, 0x01, 0x00, 0xC1, 0x02, 0x18, 0x06, 0x02, 0x3C, 0x00, 0x01, 0xFF, 0x7F, 0xAA, 0x05 ]) def make_slot_cmd(slot_id): """Build slot switch command. slot_id starts at 61 (base).""" cmd = bytearray(BASE_CMD) offset = slot_id - 61 cmd[13] = 0x3C + offset # byte 13 cmd[18] = 0xAA + offset # byte 18 return cmd async def scan(): """Scan for BLE devices, look for LED panels.""" print("Scanning for BLE devices (10s)...") devices = await BleakScanner.discover(timeout=10) candidates = [] for d in devices: name = d.name or "" # Show everything with a name, highlight likely LED panels if name: marker = " <<<" if any(k in name.lower() for k in ["led", "amus", "lux", "panel", "pixel"]) else "" rssi = getattr(d, 'rssi', None) or (d.details.get('props', {}).get('RSSI', '?') if hasattr(d, 'details') and isinstance(d.details, dict) else '?') print(f" {d.address} RSSI:{rssi} {name}{marker}") candidates.append(d) if not candidates: print("No named devices found. Panel might need to be in pairing mode.") return candidates async def connect_and_switch(address, slot_id): """Connect to panel and switch to given slot.""" print(f"Connecting to {address}...") async with BleakClient(address, timeout=15) as client: print(f"Connected: {client.is_connected}") # List services for debugging for svc in client.services: if svc.uuid == SERVICE_UUID: print(f" Found LED service: {svc.uuid}") for char in svc.characteristics: props = ",".join(char.properties) print(f" Char {char.uuid} [{props}]") cmd = make_slot_cmd(slot_id) print(f"Switching to slot {slot_id}: {cmd.hex(' ')}") await client.write_gatt_char(WRITE_UUID, cmd, response=False) print("Done!") async def cycle_slots(address, start=61, end=65, delay_sec=3): """Cycle through slots to identify which animation is where.""" print(f"Connecting to {address} for slot cycling...") async with BleakClient(address, timeout=15) as client: print(f"Connected: {client.is_connected}") for slot in range(start, end + 1): cmd = make_slot_cmd(slot) print(f" Slot {slot}: {cmd.hex(' ')}") await client.write_gatt_char(WRITE_UUID, cmd, response=False) if slot < end: print(f" Waiting {delay_sec}s...") await asyncio.sleep(delay_sec) print("Cycle complete!") async def main(): if len(sys.argv) < 2: print("Usage:") print(" python3 led-panel-ble.py scan") print(" python3 led-panel-ble.py slot