Smart outlets: the panel can power-cycle a device through a SmartLife (Tuya) plug

settings/outlet.py talks to Tuya's cloud with the campus's own project keys (kept in outlets.json
beside the config, 0600, never in the repo): list, status, on/off, and 'cycle <outlet> --ping
<address>' which leaves a unit that answers alone, cuts power 8 s, and waits for the address to
answer; refused while a HyperDeck is recording. A builder 'Power-cycle a smart outlet' makes the
fix from fields; the TASCAM key's ping check now holds to it (verified live: the hung DA-6400
answered again 26 s after the cycle and Companion reconnected). Settings > Outlets: keys, the
outlet list with live state and on/off, + Outlet from the account's devices. Calendar in 12-hour time.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 19:14:53 -04:00
co-authored by Claude Fable 5.1
parent 095f20b7a6
commit f61761f9bc
6 changed files with 297 additions and 3 deletions
+11
View File
@@ -195,6 +195,17 @@ TEMPLATES = {
"[ \"$(defaults read \"com.blackmagic-design.ATEM Software Control\" midiEnabled)\" = 0 ]"),
"suggest": lambda p, cfg: "turn off MIDI control in ATEM Software Control (quits and reopens the app; the switcher is not affected)",
},
"outlet_cycle": {
"words": "Power-cycle a smart outlet when its device does not answer",
"os": "self", "closes": True,
"params": [("outlet", "Outlet", "As named under Settings > Outlets.", "str", None),
("address", "Address to watch", "The device's address: no power cycle while it answers; afterwards wait for it to answer.", "str", ""),
("wait_seconds", "Off for", "Seconds the outlet stays off.", "int", 8)],
"render": lambda p, cfg: "/usr/bin/python3 \"$HOME/Library/Application Support/status-panel/settings/outlet.py\" cycle %s%s --wait %d"
% (q(p["outlet"]), (" --ping " + q(p["address"])) if p.get("address") else "", int(p.get("wait_seconds", 8))),
"suggest": lambda p, cfg: "power-cycle the %s outlet when %s does not answer, then wait for it to come back (never while a HyperDeck is recording)"
% (p["outlet"], p.get("address") or "its device"),
},
# kinds without a command: the template is the kind, the params are its fields
"reconnect": {"words": "Reconnect a Companion connection", "kind": "reconnect", "start_only": True, "params": [], "render": None,
"suggest": lambda p, cfg: "reconnect Companion to it"},