Hold Woonsocket's keys from NA; MIDI links moved and fixed on Macs without python; one check per link
- NA's CAMPUS page (99): tap = acknowledge, hold = fix, at the campus shown. NA's settings page turns the cell into that campus's key and writes the command into that campus's Companion (sp_cmd), exactly as a press on its own deck does. Logged on both sides. - coremidi.JXA_CONNECT: connect_peer in JavaScript for Automation, used when a Mac has no python (no Command Line Tools): a MIDI hold now reconnects Woonsocket's Tracks, FOH and Lights. - Woonsocket's MIDI links are on wired addresses now (Tracks to Lighting 10.2.2.2, Tracks to FOH 10.2.1.12; the other ends list Tracks at 10.2.1.219), and the wired-network rule is back on there. - Auto-setup keeps one check per MIDI link (the end that reports the link's state): two pulled links read 4 DOWN before. Tried live: a hold sent from NA's page 99 reconnected the FOH link at Woonsocket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+49
-4
@@ -18,12 +18,14 @@ where nothing a person made sits (push_page.safe_target): an empty page, else on
|
||||
end. State: campuses.json in the data folder.
|
||||
"""
|
||||
import gzip
|
||||
import logging
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from settings import PARENT # noqa: F401 (puts the project folder on sys.path)
|
||||
@@ -33,6 +35,7 @@ import push_page
|
||||
import status_panel as sp
|
||||
|
||||
FILE = "campuses.json"
|
||||
log = logging.getLogger("status-settings") # the settings page's own log (status-settings.log)
|
||||
POLL, REFRESH = 2.0, 60.0
|
||||
SHORT = {"na": "NA", "ab": "AB", "sc": "SC", "ws": "WS"}
|
||||
MIRROR_MARK = "STATUS tile" # copied keys keep the tile marker, so a later rebuild may overwrite them
|
||||
@@ -90,6 +93,7 @@ def http_json(url, body=None, timeout=3):
|
||||
|
||||
VIEW = {"state": "sp_state_view", "heartbeat": "sp_hb_view"}
|
||||
SELECT = "sp_view"
|
||||
VIEW_CMD = "sp_view_cmd" # a tap or hold on the CAMPUS page, passed on to that campus's own watchdog
|
||||
CELLS = [(r, c) for r in range(4) for c in range(1, 8)]
|
||||
|
||||
|
||||
@@ -108,16 +112,22 @@ def view_state(state, keys):
|
||||
|
||||
|
||||
def build_view(local_cfg, page, overview_page):
|
||||
"""The CAMPUS page: a key per cell, drawn from sp_state_view; the ALARMS corner goes back."""
|
||||
"""The CAMPUS page: a key per cell, drawn from sp_state_view; tap = acknowledge, hold = fix, at
|
||||
that campus (2026-10-20: the user wants to test and fix Woonsocket from NA). The ALARMS corner goes back."""
|
||||
opts = make_page.settings(local_cfg)
|
||||
controls = make_page.nav_controls(local_cfg)
|
||||
hold_ms = opts["hold_ms"]
|
||||
for r, c in CELLS:
|
||||
layers = make_page.layers(cell_id(r, c), {}, VIEW, "''", opts)
|
||||
tap = make_page.command_action("view_%d_%d" % (r, c), "press", VIEW_CMD, "tap %d %d $(internal:time_unix)" % (r, c))
|
||||
hold = make_page.command_action("view_%d_%d" % (r, c), "hold", VIEW_CMD, "hold %d %d $(internal:time_unix)" % (r, c))
|
||||
make_page.place(controls, (r, c), {
|
||||
"type": "button-layered", "style": {"layers": layers},
|
||||
"options": {"stepProgression": "auto", "stepExpression": "", "rotaryActions": False, "canModifyStyleInApis": True,
|
||||
"notes": "STATUS tile 'campus view %d/%d' - the tapped campus's key here, view only (hold it on that campus's own deck)." % (r, c)},
|
||||
"feedbacks": [], "steps": {"0": {"action_sets": {"down": [], "up": []}, "options": {"runWhileHeld": []}}}, "localVariables": []})
|
||||
"notes": "STATUS tile 'campus view %d/%d' - the tapped campus's key here. Press: acknowledge it there. "
|
||||
"Hold %g s: fix it there, like a hold on that campus's own deck." % (r, c, hold_ms / 1000.0)},
|
||||
"feedbacks": [], "steps": {"0": {"action_sets": {"down": [], "up": [tap], str(hold_ms): [hold]},
|
||||
"options": {"runWhileHeld": [hold_ms]}}}, "localVariables": []})
|
||||
back = make_page.static_button("campus_back", "ALL\\nSITES", 0x37474F, 0xFFFFFF, overview_page,
|
||||
"Tap: back to the CAMPUSES page (page %d)." % overview_page, "STATUS jump key")
|
||||
for layer in back["style"]["layers"]:
|
||||
@@ -169,7 +179,7 @@ def ensure_variables(cfg, ids):
|
||||
from settings import campus
|
||||
have, _ = campus.companion_state(cfg)
|
||||
want = [n for cid in ids for n in ("sp_state_" + cid, "sp_hb_" + cid) if n not in have]
|
||||
want += [n for n in (VIEW["state"], VIEW["heartbeat"], SELECT) if n not in have]
|
||||
want += [n for n in (VIEW["state"], VIEW["heartbeat"], SELECT, VIEW_CMD) if n not in have]
|
||||
if not want:
|
||||
return
|
||||
comp = cfg.get("companion") or {}
|
||||
@@ -303,6 +313,24 @@ class Mirror:
|
||||
save(self.site.data_dir, data)
|
||||
return data
|
||||
|
||||
def pass_on(self, pick, others):
|
||||
"""A tap or hold on the CAMPUS page goes to the shown campus's watchdog, through its Companion."""
|
||||
text = (self.site.comp.get_custom(VIEW_CMD) or "").strip()
|
||||
if not text:
|
||||
return
|
||||
self.site.comp.set_custom(VIEW_CMD, "")
|
||||
cmds = campus_command(self.keys.get(pick), text, time.time()) if pick else []
|
||||
if not cmds:
|
||||
return
|
||||
host = urllib.parse.urlparse(others[pick]["url"]).hostname
|
||||
try:
|
||||
remote = sp.Companion(host, 8000, timeout=3)
|
||||
before = (remote.get_custom("sp_cmd") or "").strip()
|
||||
remote.set_custom("sp_cmd", ";".join([before] + cmds if before else cmds))
|
||||
log.info("CAMPUS %s: %s (from %s's CAMPUS page)", pick, "; ".join(cmds), (self.site.cfg.get("site") or {}).get("name", "here"))
|
||||
except OSError as e:
|
||||
log.warning("CAMPUS %s: could not pass on %s: %s", pick, cmds, e)
|
||||
|
||||
def poll(self):
|
||||
"""The hub: copy each other campus's key states into this Companion."""
|
||||
cfg = self.site.load()
|
||||
@@ -334,6 +362,7 @@ class Mirror:
|
||||
# Nothing tapped yet (or a campus that left): show the first one rather than a purple page.
|
||||
pick = sorted(others)[0]
|
||||
self.site.comp.set_custom(SELECT, pick.upper())
|
||||
self.pass_on(pick if pick in others else None, others)
|
||||
st = self.remote.get(pick)
|
||||
if st and pick in self.keys and (data.get("pages") or {}).get("view"):
|
||||
try:
|
||||
@@ -344,6 +373,22 @@ class Mirror:
|
||||
self.site.comp.set_custom(VIEW["heartbeat"], str(st.get("hb") or "0"))
|
||||
|
||||
|
||||
def campus_command(keys, text, now):
|
||||
"""The CAMPUS page's taps and holds ("tap 1 3 <time>;hold 0 2 <time>") as that campus's own
|
||||
commands ("ack hd1", "restart hd1 <time>"), for the keys at those cells. Stale ones are dropped."""
|
||||
at = {tuple(k["pos"]): k["id"] for k in keys or []}
|
||||
out = []
|
||||
for part in (text or "").split(";"):
|
||||
w = part.split()
|
||||
if len(w) != 4 or w[0] not in ("tap", "hold") or not (w[1].isdigit() and w[2].isdigit() and w[3].isdigit()):
|
||||
continue
|
||||
tid = at.get((int(w[1]), int(w[2])))
|
||||
if not tid or now - int(w[3]) > 15:
|
||||
continue
|
||||
out.append("ack %s" % tid if w[0] == "tap" else "restart %s %s" % (tid, w[3]))
|
||||
return out
|
||||
|
||||
|
||||
def loop(site, stop):
|
||||
m = Mirror(site)
|
||||
site.mirror = m
|
||||
|
||||
Reference in New Issue
Block a user