Tune key at every location: the key the tracks send must reach each location's FOH (SuperRack) in that location's own Planning Center key
NA works out each location's expected Waves Tune programs (campus_tune) and serves them at /api/tune/<id>; locations fetch their list, read ProPresenter through Companion's connection, and compare with a MIDI witness on the FOH Mac (SC FOH now; WS FOH needs the Command Line Tools first). FOH KEY WRONG / FOH GOT NO KEY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+50
-9
@@ -191,6 +191,8 @@ class Snapshot:
|
||||
self.disk = {} # path -> free GB
|
||||
self.listeners = {} # MIDI source name -> coremidi.Listener
|
||||
self.witness = {} # host ("" here) -> midi_witness.py's file, with "read": when it was read
|
||||
self.tune_expect = None # a location: {song: {"programs", "key", "clips"}} fetched from the hub (/api/tune/<id>)
|
||||
self.tune_expect_time = 0.0
|
||||
self.prop_song = None # {"name", "since", "changes"}: the song ProPresenter shows, for the key checks
|
||||
self.remote = {} # (host, command) -> True / False / None (could not ask)
|
||||
self.dante_map = {} # Dante device name -> {"ip", "mac", "switch", "port", "shared", "poe", "seen"}
|
||||
@@ -579,7 +581,8 @@ def poll_tune(snap, cfg):
|
||||
if data is not None:
|
||||
data["read"] = time.time()
|
||||
snap.witness = dict(snap.witness, **{host: data})
|
||||
url = ((cfg.get("ready") or {}).get("prop_url") or "").rstrip("/")
|
||||
chk = next((c for t in all_tiles(cfg) for c in t.get("checks", []) if c["type"] == "tune_rx"), {})
|
||||
url = ((cfg.get("ready") or {}).get("prop_url") or "").rstrip("/") or tune_location_inputs(snap, chk, (cfg.get("site") or {}).get("id") or "")
|
||||
if not url:
|
||||
return
|
||||
try:
|
||||
@@ -599,6 +602,26 @@ def poll_tune(snap, cfg):
|
||||
snap.prop_song = dict(song, changes=song["changes"] + 1, slide=idx)
|
||||
|
||||
|
||||
def tune_location_inputs(snap, chk, site_id):
|
||||
"""A location's Tune check (2026-10-28): fetch from the hub, once a minute, the programs its songs must arrive
|
||||
in ("expect": "hub"), and return its ProPresenter's address as Companion reaches it ("prop_conn")."""
|
||||
if chk.get("expect") == "hub" and time.time() - snap.tune_expect_time > 60:
|
||||
snap.tune_expect_time = time.time()
|
||||
try:
|
||||
here = os.path.dirname(os.path.abspath(__file__)) # installed beside campuses.json
|
||||
campuses = json.load(open(os.path.join(here, "campuses.json"))) or {}
|
||||
home = (campuses.get("home") or "").rstrip("/")
|
||||
with urllib.request.urlopen("%s/api/tune/%s" % (home, urllib.parse.quote(site_id)), timeout=5) as r:
|
||||
snap.tune_expect = json.loads(r.read()).get("songs")
|
||||
except (OSError, ValueError, AttributeError):
|
||||
pass # the hub not answering: keep the last list
|
||||
if not chk.get("prop_conn"):
|
||||
return ""
|
||||
host = snap.conn_hosts.get(chk["prop_conn"])
|
||||
port = (snap.conn_info.get(chk["prop_conn"]) or {}).get("port")
|
||||
return "http://%s:%s" % (host, port) if host and port else ""
|
||||
|
||||
|
||||
def check_tune_rx(chk, snap, ctx):
|
||||
"""The key the tracks send (the Waves Tune clip's program at the song's start) really arrived on a Mac (Studio
|
||||
B's Pro Tools), judged against the song ProPresenter shows: a program change received during the song must be
|
||||
@@ -613,22 +636,27 @@ def check_tune_rx(chk, snap, ctx):
|
||||
return [("tune", Result(OK))]
|
||||
try:
|
||||
import readiness
|
||||
sent = readiness.LAST.get("tune_sent") or {}
|
||||
exp = next((v for k, v in sent.items() if readiness.same(k, song["name"])), None)
|
||||
if snap.tune_expect is not None: # a location: its own plan's keys, from the hub
|
||||
exp = next((v for k, v in snap.tune_expect.items() if not k.startswith("_") and readiness.same(k, song["name"])), None)
|
||||
else: # the hub: what its own set sends
|
||||
sent = readiness.LAST.get("tune_sent") or {}
|
||||
exp = next((v for k, v in sent.items() if readiness.same(k, song["name"])), None)
|
||||
exp = dict(exp, programs=[exp["program"]]) if exp and exp.get("program") is not None else None
|
||||
except Exception: # noqa: BLE001 - no READY page here
|
||||
exp = None
|
||||
if not exp or exp.get("program") is None:
|
||||
if not exp or not exp.get("programs"):
|
||||
return [("tune", Result(OK))]
|
||||
what = exp.get("clip") or "/".join(exp.get("clips") or []) or exp.get("key", "")
|
||||
pcs = [p for p in w.get("pcs") or [] if p[0] >= song["since"] - 5]
|
||||
if pcs:
|
||||
got = pcs[-1][3]
|
||||
if got != exp["program"]:
|
||||
return [("tune", Result(ERR, "%s KEY WRONG" % name, info="%s got program %d; %s's clip %s sends %d" % (
|
||||
name, got, song["name"], exp.get("clip"), exp["program"]), named=True))]
|
||||
return [("tune", Result(OK, info="%s KEY %s" % (name, (exp.get("clip") or "").upper())))]
|
||||
if got not in exp["programs"]:
|
||||
return [("tune", Result(ERR, "%s KEY WRONG" % name, info="%s got program %d; %s should get %s (program %s)" % (
|
||||
name, got, song["name"], what, "/".join(str(x) for x in exp["programs"])), named=True))]
|
||||
return [("tune", Result(OK, info="%s KEY %s" % (name, (exp.get("clip") or exp.get("key") or "").upper())))]
|
||||
if ctx["now"] - song["since"] >= 45 and song.get("changes", 0) >= 2:
|
||||
return [("tune", Result(ERR, "%s GOT NO KEY" % name, info="%s has run %d s and %s received no key (%s)" % (
|
||||
song["name"], ctx["now"] - song["since"], name, exp.get("clip")), named=True))]
|
||||
song["name"], ctx["now"] - song["since"], name, what), named=True))]
|
||||
return [("tune", Result(OK))]
|
||||
|
||||
|
||||
@@ -4138,6 +4166,18 @@ def start_pollers(cfg, snap, stop, data_dir=None):
|
||||
return threads
|
||||
|
||||
|
||||
def tune_expect_now(snap):
|
||||
"""The hub: each location's expected Tune programs (readiness works them out from that location's plan);
|
||||
a location: its own list, as fetched from the hub."""
|
||||
if snap.tune_expect is not None:
|
||||
return {"here": snap.tune_expect}
|
||||
try:
|
||||
import readiness
|
||||
return readiness.LAST.get("tune_expect")
|
||||
except Exception: # noqa: BLE001 - no READY page here
|
||||
return None
|
||||
|
||||
|
||||
def snapshot_summary(snap, engine, now):
|
||||
"""Everything the watchdog knows right now, in plain JSON (the settings page reads it instead of
|
||||
asking the gear again): what Companion reports, pings, MIDI sessions here and on the other Macs,
|
||||
@@ -4182,6 +4222,7 @@ def snapshot_summary(snap, engine, now):
|
||||
"cleared": engine.burst["cleared"]},
|
||||
"live": list(engine.live_now(now)),
|
||||
"ready": snap.ready,
|
||||
"tune_expect": tune_expect_now(snap),
|
||||
"faults": {},
|
||||
}
|
||||
for key, tr in engine.trackers.items():
|
||||
|
||||
Reference in New Issue
Block a user