aboutsummaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/udt-ledstrip32
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"]: