diff --git a/controller/protocol.py b/controller/protocol.py new file mode 100644 index 0000000..2424f36 --- /dev/null +++ b/controller/protocol.py @@ -0,0 +1,94 @@ +""" +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