diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-29 16:56:08 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-29 16:56:08 +0100 |
| commit | 8657d6c72ee10380f9af61e224050b7d1623e686 (patch) | |
| tree | 327d771e6d5990579f83e170f2575aadb8648fe5 | |
| parent | 66f8c77b853bb67166aac8ca9c3b31abe6468426 (diff) | |
| download | unified-desktop-theme-8657d6c72ee10380f9af61e224050b7d1623e686.tar.gz unified-desktop-theme-8657d6c72ee10380f9af61e224050b7d1623e686.zip | |
bulb: `on` waits up to 30 s for the gateway
The bulb stayed off after a resume while the LED strip came back.
hypridle's after_sleep_cmd runs `udt-bulb on` the moment the resume
finishes, and the wired link only came up 4 s later (16:52:22 resume,
16:52:26 eth0 carrier in the log). A request sent before that fails at
once, and udt-bulb gave up after one try; the strip survived because a
failed cached address makes it listen for its broadcast for up to 20 s.
`on` now polls the first bulb every 2 s until the gateway answers, for
up to 30 s, which also covers the same race at login. `off` still makes
a single attempt, since it runs as the session ends and cannot wait.
The cause is read from timing, not observed: nothing logs what the
after_sleep_cmd did. The next resume confirms it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rwxr-xr-x | bin/udt-bulb | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/bin/udt-bulb b/bin/udt-bulb index b2eddf5..eee17bc 100755 --- a/bin/udt-bulb +++ b/bin/udt-bulb @@ -30,6 +30,7 @@ import secrets import shutil import subprocess import sys +import time from pathlib import Path CONFIG = Path.home() / ".config/udt/tradfri.json" @@ -65,6 +66,22 @@ def coap(method, url, ident, key, payload=None): return out.strip() +def wait_for_gateway(url, auth, seconds, now=time.monotonic, nap=time.sleep): + """True once a get on url answers, False if it has not by `seconds`. + + For `on`, which runs straight after a resume and at login: the wired link + came up 4 s after the resume finished, and a request sent before that + fails at once, with nothing to try again. `off` never waits, since it runs + as the session ends. + """ + end = now() + seconds + while coap("get", url, *auth) is None: + if now() >= end: + return False + nap(2) + return True + + def light_states(colour): """3311 states for #rrggbb, sent in order.""" h, s, _ = colorsys.rgb_to_hsv(*(int(colour[i:i + 2], 16) / 255 for i in (1, 3, 5))) @@ -102,6 +119,20 @@ def selftest(): # A pastel keeps its hue and loses its paleness to the floor. colour = light_states("#8080ff")[0] assert (colour["5707"], colour["5708"]) == (round(HUE_MAX * 2 / 3), HUE_MAX) + + # The wait for the gateway retries until it answers, and gives up at the + # deadline. coap and the clock are swapped for fakes. + global coap + real, answers, naps = coap, [None, None, "{}"], [] + coap = lambda *a: answers.pop(0) + clock = iter(range(100)) + try: + assert wait_for_gateway("u", ("i", "k"), 30, lambda: next(clock), naps.append) + assert len(naps) == 2, naps + coap = lambda *a: None + assert not wait_for_gateway("u", ("i", "k"), 3, lambda: next(clock), naps.append) + finally: + coap = real print("ok") @@ -132,6 +163,10 @@ def main(): else: return auth = cfg["identity"], cfg["key"] + first = f"coaps://{cfg['host']}:5684/{DEVICES}/{cfg['bulbs'][0]}" + if args == ["on"] and not wait_for_gateway(first, auth, 30): + print("udt-bulb: the gateway did not answer for 30 s", file=sys.stderr) + return for dev in cfg["bulbs"]: url = f"coaps://{cfg['host']}:5684/{DEVICES}/{dev}" if not args: |
