diff options
Diffstat (limited to 'bin/udt-ledstrip')
| -rwxr-xr-x | bin/udt-ledstrip | 32 |
1 files changed, 25 insertions, 7 deletions
diff --git a/bin/udt-ledstrip b/bin/udt-ledstrip index ca39063..039fe35 100755 --- a/bin/udt-ledstrip +++ b/bin/udt-ledstrip @@ -7,13 +7,16 @@ Usage: udt-ledstrip play the gradient, leaving the strip on or off udt-ledstrip on|off switch it, the gradient with it when on + udt-ledstrip sleep breathe the accent, soft and dim, while asleep udt-ledstrip --selftest The colours are the three udt-accent wrote for the window border, the accent between its two hue neighbours, played as the strip's "flow" scene: a gradient moving along the strip. udt-accent starts this detached on every run, like udt-keyboard. hypr-theme's autostart runs `on` at login and `off` on -Hyprland's shutdown event, so the strip follows the session. +Hyprland's shutdown event, so the strip follows the session. `sleep` is sent +before a suspend and `on` after it, so a sleeping desktop breathes instead of +going dark. The device id and local key come from ~/.config/udt/tuya/devices.json, written once by `python -m tinytuya wizard` against a Tuya IoT cloud project; after @@ -54,6 +57,10 @@ DISCOVERY_TIMEOUT = 20 SATURATION_FLOOR = 100 BRIGHTNESS = 100 SPEED = 50 +# Sleep: the accent alone, desaturated and at half brightness, breathing slowly. +SLEEP_SATURATION = 60 +SLEEP_BRIGHTNESS = 50 +SLEEP_SPEED = 20 SESS_START, SESS_FINISH, CONTROL = 3, 5, 13 @@ -61,17 +68,20 @@ SESS_START, SESS_FINISH, CONTROL = 3, 5, 13 # status pushes; Tuya does not document it): version 1, scene id (0 for a # custom one), mode, speed twice, flags, two zero bytes, brightness, then # per colour a two byte hue and a saturation byte. Mode 0x0a with flags -# 0xe0 is the app's moving gradient. +# 0xe0 is the app's moving gradient; mode 0x03 with flags 0 is its breathing. FLOW, FLOW_FLAGS = 0x0A, 0xE0 +BREATH, BREATH_FLAGS = 0x03, 0x00 -def scene(colours): - """DP 51 bytes playing #rrggbb colours as a flow, base64 for the wire.""" - body = bytearray([1, 0, FLOW, SPEED, SPEED, FLOW_FLAGS, 0, 0, BRIGHTNESS]) +def scene(colours, mode=FLOW, flags=FLOW_FLAGS, speed=SPEED, + brightness=BRIGHTNESS, saturation=None): + """DP 51 bytes playing #rrggbb colours, base64 for the wire. A fixed + saturation replaces each colour's own, floored one.""" + body = bytearray([1, 0, mode, speed, speed, flags, 0, 0, brightness]) for c in colours: h, s, _ = colorsys.rgb_to_hsv(*(int(c[i:i + 2], 16) / 255 for i in (1, 3, 5))) body += (round(h * 360) % 360).to_bytes(2, "big") - body.append(max(round(s * 100), SATURATION_FLOOR)) + body.append(saturation or max(round(s * 100), SATURATION_FLOOR)) return base64.b64encode(body).decode() @@ -149,6 +159,9 @@ def selftest(): raw = base64.b64decode(scene(["#ff0000", "#0000ff"])) assert raw[:9] == bytes([1, 0, FLOW, SPEED, SPEED, FLOW_FLAGS, 0, 0, BRIGHTNESS]) assert raw[9:] == bytes([0, 0, 100, 0, 240, 100]) + # As the app wrote a breathing scene: 01 00 03 32 32 00 00 00 32, hue, sat. + raw = base64.b64decode(scene(["#00ff00"], BREATH, BREATH_FLAGS, 50, 50, 60)) + assert raw == bytes([1, 0, 3, 50, 50, 0, 0, 0, 50, 0, 120, 60]) key = b"0123456789abcdef" assert unpack(key, pack(key, 7, CONTROL, b"payload")) == (CONTROL, b"payload") print("ok") @@ -157,7 +170,7 @@ def selftest(): def main(): if sys.argv[1:] == ["--selftest"]: return selftest() - if sys.argv[1:] not in ([], ["on"], ["off"]): + if sys.argv[1:] not in ([], ["on"], ["off"], ["sleep"]): sys.exit(__doc__.split("\n\n")[1]) try: devices = json.loads(DEVICES.read_text()) @@ -173,6 +186,11 @@ def main(): return if sys.argv[1:] == ["off"]: dps = {"20": False} + elif sys.argv[1:] == ["sleep"] and colours: + # The border's middle colour is the accent itself. + dps = {"20": True, "21": "scene", + "51": scene([colours[len(colours) // 2]], BREATH, BREATH_FLAGS, + SLEEP_SPEED, SLEEP_BRIGHTNESS, SLEEP_SATURATION)} elif colours: dps = {"21": "scene", "51": scene(colours)} if sys.argv[1:] == ["on"]: |
