""" protocol.py - Command builder for common cheap BLE LED controller protocols. These controllers all speak one of a handful of similar protocols. We'll confirm which format the KAIYU uses after sniffing/probing. Supported protocol families: - 0x7E family (most common, used by ELK-BLEDOM, LEDBLE, etc.) - 0x56 family (older MagicLight / JACKYLED style) """ class Protocol7E: """ The 0x7E protocol family. Packet format: 7e 00 ef CMD 0x04 = power on/off CMD 0x05 = set colour (P1=mode, P2=R, P3=G, P4=B) CMD 0x01 = set brightness (P1=0x00–0x64) CMD 0x03 = set effect/speed """ # Colour mode byte for CMD 0x05 MODE_STATIC = 0x03 MODE_EFFECT = 0x02 # LED channel — 0xFF = all channels LED_ALL = 0xFF @staticmethod def set_color(r: int, g: int, b: int) -> bytes: r, g, b = _clamp(r), _clamp(g), _clamp(b) return bytes([0x7e, 0x00, 0x05, Protocol7E.MODE_STATIC, r, g, b, 0x00, 0xef]) @staticmethod def set_brightness(level: int) -> bytes: """level: 0–100""" level = max(0, min(100, level)) return bytes([0x7e, 0x00, 0x01, level, 0x00, 0x00, 0x00, Protocol7E.LED_ALL, 0xef]) @staticmethod def turn_on() -> bytes: return bytes([0x7e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, Protocol7E.LED_ALL, 0xef]) @staticmethod def turn_off() -> bytes: return bytes([0x7e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef]) @staticmethod def set_effect(effect_id: int, speed: int = 0x03) -> bytes: """ effect_id: 0x80–0x8b (varies by controller) speed: 0x01 (fast) – 0x0a (slow) """ return bytes([0x7e, 0x00, 0x03, effect_id, speed, 0x00, 0x00, Protocol7E.LED_ALL, 0xef]) class Protocol56: """ The 0x56 protocol family (older MagicLight style). RGB command : 56 RR GG BB 00 f0 aa White cmd : 56 00 00 00 WW 0f aa Power on : cc 23 33 Power off : cc 24 33 """ @staticmethod def set_color(r: int, g: int, b: int) -> bytes: r, g, b = _clamp(r), _clamp(g), _clamp(b) return bytes([0x56, r, g, b, 0x00, 0xf0, 0xaa]) @staticmethod def set_white(level: int) -> bytes: level = _clamp(level) return bytes([0x56, 0x00, 0x00, 0x00, level, 0x0f, 0xaa]) @staticmethod def turn_on() -> bytes: return bytes([0xcc, 0x23, 0x33]) @staticmethod def turn_off() -> bytes: return bytes([0xcc, 0x24, 0x33]) def _clamp(v: int) -> int: return max(0, min(255, v)) # ── Convenience ─────────────────────────────────────────────────────────────── # Default to 7E family — update after confirming with scanner ActiveProtocol = Protocol7E