MIDI hold and auto-fix move a link joined by name to the owning Mac's wired address

- WS 2026-10-21: FOH Mac mini joined ProPresenter's session by name; hold said "no automatic fix". Now the
  address comes from the link's own check, else the Mac that owns that session (wired SSH / ingest address +
  session port); auto-fix does it too, never while a HyperDeck records
- WS: FOH Mac mini <-> ProPresenter watched (FOH PROP); auto-fix moved it to 10.2.1.12:5004 by itself

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:52:42 -04:00
co-authored by Claude Opus 5.5
parent af4bfc868d
commit 51a14140cc
3 changed files with 75 additions and 6 deletions
+8
View File
@@ -537,6 +537,14 @@
"tag": "PROP LIGHTS", "tag": "PROP LIGHTS",
"type": "midi_peer" "type": "midi_peer"
}, },
{
"type": "midi_peer",
"session": "ProPresenter",
"peer": "WS FOH Mac mini",
"address": "10.2.1.12:5004",
"fix": "midi_reconnect",
"tag": "FOH PROP"
},
{ {
"fix": "midi_reconnect", "fix": "midi_reconnect",
"nets": [ "nets": [
+30 -5
View File
@@ -687,6 +687,27 @@ def by_name(address):
return str(address or "").startswith("\t") return str(address or "").startswith("\t")
def owner_wired_address(cfg, snap, session_name, nets):
""""ip:port" of the Mac whose network MIDI session is called `session_name`, at its wired address:
this Mac (its wired ingest address), or another computer the settings know (its SSH address), using
that session's own port. None when no known Mac owns it or none of its addresses is wired."""
def wired(ip):
try:
return any(ipaddress.ip_address(ip) in n for n in nets)
except ValueError:
return False
if snap.midi and session_name in snap.midi:
ip = next((i for i in (cfg.get("site") or {}).get("ingest_ips") or [] if wired(i)), None)
if ip:
return "%s:%s" % (ip, snap.midi[session_name].get("port") or 5004)
for alias, sessions in (snap.remote_midi or {}).items():
if session_name in (sessions or {}):
ip = str((cfg.get("hosts") or {}).get(alias, {}).get("ssh", "")).split("@")[-1]
if wired(ip):
return "%s:%s" % (ip, sessions[session_name].get("port") or 5004)
return None
def wired_nets(cfg): def wired_nets(cfg):
"""The wired network(s) the MIDI key's wired check names (empty: no rule).""" """The wired network(s) the MIDI key's wired check names (empty: no rule)."""
nets = [] nets = []
@@ -1602,16 +1623,20 @@ class Engine:
target = chk["conn"] target = chk["conn"]
if fix_kind == "midi_connect" and chk["type"] == "midi_wired": if fix_kind == "midi_connect" and chk["type"] == "midi_wired":
# A peer off the wired network or joined by name: join it again at the wired # A peer off the wired network or joined by name: join it again at the wired
# address its own MIDI check names. One no check names (ProdDash's own) is left # address its own MIDI check names, else at the wired address of the Mac whose
# to its program: the key says which. # session it is (WS, 2026-10-21: FOH Mac mini joined ProPresenter by name and a hold
if errors_only: # did nothing). Moving a working link drops it for a second: auto-fix does it too,
continue # moving a working link drops it a second: a hold only # but never while a HyperDeck records. A peer no known Mac owns (ProdDash's own) is
# left to its program: the key says which.
if errors_only and self.any_recording:
continue
host, session, peer = (sub.split("|", 2) + ["", ""])[:3] host, session, peer = (sub.split("|", 2) + ["", ""])[:3]
nets = wired_nets(self.cfg) nets = wired_nets(self.cfg)
want = next((c.get("address") for t2 in all_tiles(self.cfg) for c in t2.get("checks", []) want = next((c.get("address") for t2 in all_tiles(self.cfg) for c in t2.get("checks", [])
if c.get("type") == "midi_peer" and c.get("host", "") == host and c.get("session") == session if c.get("type") == "midi_peer" and c.get("host", "") == host and c.get("session") == session
and c.get("peer") == peer and peer_ip(c.get("address")) is not None and c.get("peer") == peer and peer_ip(c.get("address")) is not None
and any(peer_ip(c["address"]) in n for n in nets)), None) and any(peer_ip(c["address"]) in n for n in nets)), None) \
or owner_wired_address(self.cfg, snap, peer, nets)
if not want: if not want:
continue continue
chk = dict({"host": host} if host else {}, type="midi_peer", session=session, peer=peer, address=want, repoint=True) chk = dict({"host": host} if host else {}, type="midi_peer", session=session, peer=peer, address=want, repoint=True)
+37 -1
View File
@@ -1293,7 +1293,10 @@ class TestFixes(unittest.TestCase):
self.assertEqual([(c["host"], c["session"], c["peer"], c["address"]) for c in moves], self.assertEqual([(c["host"], c["session"], c["peer"], c["address"]) for c in moves],
[("tracks", "NA Tracks", "Studio B Mac Studio", "10.1.13.175:5004")]) # ProdDash's own: left to it [("tracks", "NA Tracks", "Studio B Mac Studio", "10.1.13.175:5004")]) # ProdDash's own: left to it
auto = engine.fix_plan("midi", self.snap, SUNDAY + 30, 0, True) auto = engine.fix_plan("midi", self.snap, SUNDAY + 30, 0, True)
self.assertFalse(any(c.get("repoint") for _, _, _, c in auto)) # never by itself: a hold self.assertTrue(any(c.get("repoint") for _, _, _, c in auto)) # auto-fix moves it too (user, 2026-10-21) ...
engine.any_recording = True
auto = engine.fix_plan("midi", self.snap, SUNDAY + 30, 0, True)
self.assertFalse(any(c.get("repoint") for _, _, _, c in auto)) # ... but never while a HyperDeck records
def test_an_off_pc_is_woken(self): def test_an_off_pc_is_woken(self):
from unittest import mock from unittest import mock
@@ -2663,3 +2666,36 @@ class TestMidiNoState(unittest.TestCase):
self.assertEqual(self.check(s, 110.0).msg, "MIDI CONNECTING") # put back: not trusted yet self.assertEqual(self.check(s, 110.0).msg, "MIDI CONNECTING") # put back: not trusted yet
self.assertEqual(self.check(s, 125.0).msg, "MIDI CONNECTING") self.assertEqual(self.check(s, 125.0).msg, "MIDI CONNECTING")
self.assertEqual(self.check(s, 131.0).level, sp.OK) # stayed 20 s: it answered self.assertEqual(self.check(s, 131.0).level, sp.OK) # stayed 20 s: it answered
class TestByNameMove(unittest.TestCase):
"""WS 2026-10-21: FOH Mac mini joined ProPresenter's session by name; a hold said "no automatic fix"."""
def setUp(self):
self.cfg = sp.load_config(os.path.join(ROOT, "sites", "ws", "config.json"))
self.engine = sp.Engine(self.cfg)
self.snap = healthy_snapshot(SUNDAY)
self.snap.midi = {"ProPresenter": {"port": 5004, "peers": [
{"name": "WS FOH Mac mini", "address": "\tWS FOH Mac mini\tlocal.", "state": "running"},
{"name": "Lighting", "address": "10.2.2.2:5004", "state": "running"}]}}
self.snap.midi_time = SUNDAY
foh = next(a for a, h in self.cfg["hosts"].items() if str(h.get("ssh", "")).endswith("@10.2.1.12"))
self.snap.remote_midi = {foh: {"WS FOH Mac mini": {"port": 5004, "peers": []}}}
self.snap.remote_midi_time = {foh: SUNDAY}
self.midi = next(t["id"] for t in self.cfg["tiles"] if t["label"] == "MIDI")
def moves(self, errors_only):
run_steps(self.engine, self.snap, SUNDAY, 3)
return [chk for _, kind, what, chk in self.engine.fix_plan(self.midi, self.snap, now=SUNDAY + 3, errors_only=errors_only)
if chk.get("repoint")]
def test_a_hold_moves_it_to_the_owning_macs_wired_address(self):
moves = self.moves(False)
self.assertEqual([(c["session"], c["peer"], c["address"]) for c in moves],
[("ProPresenter", "WS FOH Mac mini", "10.2.1.12:5004")])
def test_auto_fix_moves_it_too_but_never_while_recording(self):
self.assertEqual(len(self.moves(True)), 1)
self.engine.any_recording = True # a HyperDeck records: left alone
plan = self.engine.fix_plan(self.midi, self.snap, now=SUNDAY + 3, errors_only=True)
self.assertEqual([c for _, k, w, c in plan if c.get("repoint")], [])