import asyncio import sys from bleak import BleakClient, BleakScanner from bleak.exc import BleakError DEVICE_NAME = "BJ_LED" WRITE_UUID = "0000ee01-0000-1000-8000-00805f9b34fb" HEADER = bytes.fromhex("6996") SCAN_SECONDS = 15 CONNECT_SECONDS = 20 CONNECT_TRIES = 3 EFFECT_MAX = 0x15 def frame(body_hex): return HEADER + bytes.fromhex(body_hex) def command(action, name, speed): if action == "on": return frame("02 01 01") if action == "off": return frame("02 01 00") if action == "color": return frame("05 02 " + name) if action == "effect": mode = int(name) if not 0 <= mode <= EFFECT_MAX: sys.exit("effect must be 0-%d" % EFFECT_MAX) return frame("03 03 %02x %02x" % (mode, int(speed))) if action == "send": return bytes.fromhex(name) sys.exit("unknown action " + action) async def find_strips(): found = await BleakScanner.discover(timeout=SCAN_SECONDS, return_adv=True) return [(d, adv) for d, adv in found.values() if (d.name or "").startswith(DEVICE_NAME)] async def scan(): for device, adv in await find_strips(): print("%s|%s|%s" % (device.name, device.address, adv.rssi)) async def write_once(address, payload, verbose): async with BleakClient(address, timeout=CONNECT_SECONDS) as client: if verbose: for service in client.services: for char in service.characteristics: print("%s %s" % (char.uuid, ",".join(char.properties)), file=sys.stderr) await client.write_gatt_char(WRITE_UUID, payload, response=False) ## Strips at the edge of range often miss a connection attempt or two. async def write(address, payload, verbose): for attempt in range(CONNECT_TRIES): try: return await write_once(address, payload, verbose) except (TimeoutError, BleakError) as e: if verbose: print("attempt %d: %r" % (attempt + 1, e), file=sys.stderr) sys.exit("could not connect to %s" % address) def main(): sys.stdout.reconfigure(newline="\n") action, address, name, speed, verbose = sys.argv[1:6] if action == "scan": asyncio.run(scan()) else: asyncio.run(write(address, command(action, name, speed), verbose)) if __name__ == "__main__": main()