Audio failover: press FOH Backup by itself when Studio B dies; Companion button presses as fixes
A new fix kind "companion_press" presses any Companion button over its HTTP API (page, row, col). A fix marked "failover" runs by itself within seconds of its check failing: inside the "failover" windows (Sunday 06:30-13:30), even while ALARMS is muted, once per fault, never back the other way. The hidden STB AUDIO key (inside AUDIO) watches Studio B's ping every second, Pro Tools over SSH every 2 s and its Dante device; each check's fix is the FOH Backup press. The keys say ON FOH BACKUP until STREAM STB on the Restart page switches back. Settings page: the press builder, the failover tick and undo, the Failover calendar, help entry "A fallback button". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+26
-2
@@ -30,6 +30,13 @@ def label(value, what):
|
||||
return value
|
||||
|
||||
|
||||
def whole(value, what):
|
||||
"""A whole number (0 counts; a bool, a blank or 1.5 does not)."""
|
||||
if isinstance(value, bool) or not re.fullmatch(r"-?\d+", str(value).strip()):
|
||||
raise ValueError("%s must be a whole number, not %r" % (what, value))
|
||||
return int(str(value).strip())
|
||||
|
||||
|
||||
def home_dir(path):
|
||||
"""~/x -> $HOME/x inside double quotes; refuses quotes and $ in the rest."""
|
||||
path = str(path)
|
||||
@@ -221,6 +228,17 @@ TEMPLATES = {
|
||||
"spotify": {"words": "Play the service playlist in Spotify", "kind": "spotify", "start_only": True, "params": [], "render": None,
|
||||
"suggest": lambda p, cfg: "play the %s playlist in Spotify on %s (opens Spotify if it is closed)"
|
||||
% ((cfg.get("spotify") or {}).get("name") or "service", (cfg.get("site") or {}).get("ingest_name") or "this Mac")},
|
||||
# a press: "copy" names the params that are also fields of the fix itself (the watchdog reads those)
|
||||
"companion_press": {
|
||||
"words": "Press a Companion button (any page, row and column)", "kind": "companion_press", "start_only": True,
|
||||
"params": [("page", "Page", "The Companion page the button is on (page 4 = Audio).", "int", None),
|
||||
("row", "Row", "Top row is 0.", "int", None),
|
||||
("col", "Column", "Left column is 0.", "int", None),
|
||||
("button", "What the button says", "Only for the words: FOH Backup, say.", "str", "")],
|
||||
"copy": ["page", "row", "col"], "render": None,
|
||||
"suggest": lambda p, cfg: "press the %sbutton in Companion (page %s, row %s, column %s)"
|
||||
% (("%s " % p["button"]) if p.get("button") else "", p["page"], p["row"], p["col"]),
|
||||
},
|
||||
"custom": {"words": "Custom command (Advanced: typed by hand)", "params": [], "render": None, "suggest": lambda p, cfg: ""},
|
||||
}
|
||||
|
||||
@@ -259,6 +277,9 @@ def build(cfg, fix):
|
||||
raise ValueError("%s: %s is needed" % (t["words"], words.lower()))
|
||||
out = {k: v for k, v in fix.items() if k not in ("run", "host", "kind")}
|
||||
out["template"], out["params"] = name, params
|
||||
for n in t.get("copy", []):
|
||||
words = next(w for a, w, _, _, _ in t["params"] if a == n)
|
||||
out[n] = params[n] = whole(params.get(n), "%s: %s" % (t["words"], words.lower()))
|
||||
if t.get("kind"):
|
||||
out["kind"] = t["kind"]
|
||||
return out
|
||||
@@ -362,8 +383,11 @@ def mismatches(cfg):
|
||||
except ValueError as e:
|
||||
out.append("fix %s: %s" % (fid, e))
|
||||
continue
|
||||
for k in ("run", "kind", "host"):
|
||||
if (b.get(k) or None) != (fix.get(k) or None):
|
||||
for k in ("run", "kind", "host") + tuple(TEMPLATES[fix["template"]].get("copy", [])):
|
||||
bv, fv = b.get(k), fix.get(k)
|
||||
if k in ("run", "kind", "host"):
|
||||
bv, fv = bv or None, fv or None # a blank and an absent command are the same
|
||||
if bv != fv:
|
||||
out.append("fix %s: its %s no longer matches its fields - open it on Fixes and press Save" % (fid, "command" if k == "run" else k))
|
||||
break
|
||||
for t in cfg.get("tiles") or []:
|
||||
|
||||
Reference in New Issue
Block a user