The LTC write happens by itself: READY fixes ticked for auto-fix act on the checklist, any hour for ready_ltc
Auto-fix now looks at the READY page's keys too, for the READY fixes ticked under Settings > Auto-fix, and acts on a checklist gap while it is still amber. A fix marked anytime (only a READY fix may be) runs outside the auto-fix hours as well, never while ALARMS is muted; the window's own fixes keep their hours and start fresh each window. ready_ltc is ticked and marked anytime at North Attleboro: once the weekend's set is on Tracks, every song's timecode goes into all four campuses' plans. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,7 @@ During that window, with ALARMS on, the panel fixes a problem by itself once it
|
|||||||
- Never automatic: restarting a running ProPresenter, Bible app, ProdCom (or DVS on NA Ingest, which restarts ProdCom), captions or the bridge, power-cycling a switch port, or turning on a connection someone switched off. `load_config` refuses an auto fix that closes a program.
|
- Never automatic: restarting a running ProPresenter, Bible app, ProdCom (or DVS on NA Ingest, which restarts ProdCom), captions or the bridge, power-cycling a switch port, or turning on a connection someone switched off. `load_config` refuses an auto fix that closes a program.
|
||||||
- **Daily log:** everything auto-fix does is written as it happens to `~/Desktop/Auto-Fix Logs/<date> <weekday> Auto-Fix.log` on NA Ingest (e.g. `2026-10-11 Sunday Auto-Fix.log`): the problem, what it did, whether it worked, when the fault cleared, when it gave up or held back. Double-click it to open it in Console, which shows new lines live. One file per day; each is deleted a calendar month after its date (the 11 Oct file on 11 Nov).
|
- **Daily log:** everything auto-fix does is written as it happens to `~/Desktop/Auto-Fix Logs/<date> <weekday> Auto-Fix.log` on NA Ingest (e.g. `2026-10-11 Sunday Auto-Fix.log`): the problem, what it did, whether it worked, when the fault cleared, when it gave up or held back. Double-click it to open it in Console, which shows new lines live. One file per day; each is deleted a calendar month after its date (the 11 Oct file on 11 Nov).
|
||||||
- Press ALARMS to mute the panel for maintenance: that also stops auto-fix. The window and the list are `"auto_fix"` in `config.json`.
|
- Press ALARMS to mute the panel for maintenance: that also stops auto-fix. The window and the list are `"auto_fix"` in `config.json`.
|
||||||
|
- **READY page fixes** ticked under Settings › Auto-fix act on the checklist's gaps too, amber or red. A fix marked `"anytime": true` (only a READY fix may be; today the LTC write, `ready_ltc`) runs at any hour, not only in the auto-fix hours: once the weekend's set is on Tracks, every song's timecode is written into Planning Center for all four campuses by itself. ALARMS muted still stops it.
|
||||||
|
|
||||||
## Audio failover (Studio B -> FOH Backup)
|
## Audio failover (Studio B -> FOH Backup)
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -26,7 +26,8 @@
|
|||||||
"atem_audio",
|
"atem_audio",
|
||||||
"atem_camera",
|
"atem_camera",
|
||||||
"atem_midi",
|
"atem_midi",
|
||||||
"spotify_play"
|
"spotify_play",
|
||||||
|
"ready_ltc"
|
||||||
],
|
],
|
||||||
"max_tries": 2,
|
"max_tries": 2,
|
||||||
"pause_after_network_seconds": 60,
|
"pause_after_network_seconds": 60,
|
||||||
@@ -355,6 +356,7 @@
|
|||||||
},
|
},
|
||||||
"ready_ltc": {
|
"ready_ltc": {
|
||||||
"action": "ltc_to_pco",
|
"action": "ltc_to_pco",
|
||||||
|
"anytime": true,
|
||||||
"kind": "ready",
|
"kind": "ready",
|
||||||
"label": "write each song's timecode from the tracks into its LTC note in Planning Center",
|
"label": "write each song's timecode from the tracks into its LTC note in Planning Center",
|
||||||
"short": "LTC"
|
"short": "LTC"
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ field("fixes.*.reconnect", "bool", False, "Reconnect the Companion connection af
|
|||||||
field("fixes.*.conn", "str", None, "The Companion connection it reconnects.", "Fixes")
|
field("fixes.*.conn", "str", None, "The Companion connection it reconnects.", "Fixes")
|
||||||
field("fixes.*.restart_ok", "bool", False, "May close a running program (and still run by itself).", "Fixes")
|
field("fixes.*.restart_ok", "bool", False, "May close a running program (and still run by itself).", "Fixes")
|
||||||
field("fixes.*.never_auto", "bool", False, "Never runs by itself.", "Fixes")
|
field("fixes.*.never_auto", "bool", False, "Never runs by itself.", "Fixes")
|
||||||
|
field("fixes.*.anytime", "bool", False, "Ticked for auto-fix, runs by itself at any hour, not only in the auto-fix hours (a READY fix that only writes to Planning Center).", "Fixes")
|
||||||
field("fixes.*.part", "enum", None, "Which part of the switcher.", "Fixes", options=["audio", "camera"])
|
field("fixes.*.part", "enum", None, "Which part of the switcher.", "Fixes", options=["audio", "camera"])
|
||||||
field("fixes.*.verify_seconds", "int", 20, "Wait this long before judging a reconnect.", "Fixes", advanced=True,
|
field("fixes.*.verify_seconds", "int", 20, "Wait this long before judging a reconnect.", "Fixes", advanced=True,
|
||||||
lo=5, hi=120)
|
lo=5, hi=120)
|
||||||
@@ -669,10 +670,14 @@ def validate(cfg, live=None):
|
|||||||
errors.append("%s: a Companion button needs its page, row and column" % where)
|
errors.append("%s: a Companion button needs its page, row and column" % where)
|
||||||
if fix.get("failover") and fix.get("never_auto"):
|
if fix.get("failover") and fix.get("never_auto"):
|
||||||
errors.append("%s: is a failover (runs by itself) and marked never to run by itself - one or the other" % where)
|
errors.append("%s: is a failover (runs by itself) and marked never to run by itself - one or the other" % where)
|
||||||
|
if fix.get("anytime") and kind != "ready":
|
||||||
|
errors.append("%s: only a READY page fix may run at any hour (anytime)" % where)
|
||||||
|
if fix.get("anytime") and fix.get("never_auto"):
|
||||||
|
errors.append("%s: runs at any hour (anytime) and never by itself - one or the other" % where)
|
||||||
if fix.get("undo") and fix["undo"] not in fixes:
|
if fix.get("undo") and fix["undo"] not in fixes:
|
||||||
errors.append("%s: the fix that switches back (undo) is unknown: %s" % (where, fix["undo"]))
|
errors.append("%s: the fix that switches back (undo) is unknown: %s" % (where, fix["undo"]))
|
||||||
for k in fix:
|
for k in fix:
|
||||||
if k not in FIX_KIND_FIELDS.get(kind, []) and k not in ("kind", "short", "label", "restart_ok", "never_auto", "disabled",
|
if k not in FIX_KIND_FIELDS.get(kind, []) and k not in ("kind", "short", "label", "restart_ok", "never_auto", "anytime", "disabled",
|
||||||
"template", "params", "timeout", "failover", "undo"):
|
"template", "params", "timeout", "failover", "undo"):
|
||||||
warnings.append("%s: %s is not a field of a %s fix" % (where, k, kind))
|
warnings.append("%s: %s is not a field of a %s fix" % (where, k, kind))
|
||||||
named = set()
|
named = set()
|
||||||
|
|||||||
+1
-1
@@ -939,7 +939,7 @@
|
|||||||
<div class="field"><span class="fl">name${hint("As the keys say it")}</span><input id="spname" value="${esc(s.name || "")}" ${editing() ? "" : "disabled"}></div><p id="spinfo" class="help"></p></div>` + fieldsHtml("Spotify");
|
<div class="field"><span class="fl">name${hint("As the keys say it")}</span><input id="spname" value="${esc(s.name || "")}" ${editing() ? "" : "disabled"}></div><p id="spinfo" class="help"></p></div>` + fieldsHtml("Spotify");
|
||||||
}
|
}
|
||||||
function autofixHtml(c) {
|
function autofixHtml(c) {
|
||||||
const fixes = Object.entries(c.fixes).map(([id, f]) => { const cl = closes(f); const on = (c.auto_fix.fixes || []).includes(id); return `<label class="inline" data-hint="${esc(f.label || id)}${cl ? " · closes a program: ticking it allows that by itself" : f.never_auto ? " · marked never by itself: ticking it lifts that" : ""}"><input type="checkbox" data-autofix="${esc(id)}" ${on ? "checked" : ""} ${editing() ? "" : "disabled"}> ${esc(f.short || id)}${cl || f.restart_ok ? " ⚠" : ""}</label>`; }).join(" ");
|
const fixes = Object.entries(c.fixes).map(([id, f]) => { const cl = closes(f); const on = (c.auto_fix.fixes || []).includes(id); return `<label class="inline" data-hint="${esc(f.label || id)}${cl ? " · closes a program: ticking it allows that by itself" : f.never_auto ? " · marked never by itself: ticking it lifts that" : f.anytime ? " · any hour, not only these" : ""}"><input type="checkbox" data-autofix="${esc(id)}" ${on ? "checked" : ""} ${editing() ? "" : "disabled"}> ${esc(f.short || id)}${cl || f.restart_ok ? " ⚠" : ""}</label>`; }).join(" ");
|
||||||
return `<div class="card"><p style="margin-top:0">${onOff("auto_fix", c.auto_fix, "auto-fix")} <b>Auto-fix</b> ${c.auto_fix.disabled ? "is off: nothing is fixed by itself (holds still work)." : "fixes the ticked fixes by itself in the painted hours."}</p>${calendarHtml("auto_fix.windows", c.auto_fix.windows)}</div>
|
return `<div class="card"><p style="margin-top:0">${onOff("auto_fix", c.auto_fix, "auto-fix")} <b>Auto-fix</b> ${c.auto_fix.disabled ? "is off: nothing is fixed by itself (holds still work)." : "fixes the ticked fixes by itself in the painted hours."}</p>${calendarHtml("auto_fix.windows", c.auto_fix.windows)}</div>
|
||||||
<div class="card"><h3 style="margin-top:0">Fixes it may run${hint("Ticked fixes run by themselves inside the painted hours. ⚠ = it closes a running program to do its job; ticking it allows that without a hold.")}</h3><div class="chips">${fixes}</div></div><p class="help">Pressing a Companion button by itself when something dies is under Settings › <a href="#settings/failover">Auto switch</a>.</p>` + fieldsHtml("Schedules");
|
<div class="card"><h3 style="margin-top:0">Fixes it may run${hint("Ticked fixes run by themselves inside the painted hours. ⚠ = it closes a running program to do its job; ticking it allows that without a hold.")}</h3><div class="chips">${fixes}</div></div><p class="help">Pressing a Companion button by itself when something dies is under Settings › <a href="#settings/failover">Auto switch</a>.</p>` + fieldsHtml("Schedules");
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-19
@@ -645,7 +645,8 @@ def effective_config(cfg):
|
|||||||
|
|
||||||
def all_tiles(cfg):
|
def all_tiles(cfg):
|
||||||
"""Every key the watchdog judges: the Status page's tiles and the READY page's keys. The READY
|
"""Every key the watchdog judges: the Status page's tiles and the READY page's keys. The READY
|
||||||
page's keys are not in ALL (nor in auto-fix): a weekday gap in the checklist is not an alarm."""
|
page's keys are not in ALL: a weekday gap in the checklist is not an alarm. Auto-fix does look
|
||||||
|
at them, for the READY fixes ticked under Settings > Auto-fix."""
|
||||||
return cfg["tiles"] + ready_keys(cfg)
|
return cfg["tiles"] + ready_keys(cfg)
|
||||||
|
|
||||||
# Checks whose connection a long press may restart (Dante is shared, so not that one).
|
# Checks whose connection a long press may restart (Dante is shared, so not that one).
|
||||||
@@ -1233,12 +1234,13 @@ class Engine:
|
|||||||
tile = self.tiles.get(tile_id, {})
|
tile = self.tiles.get(tile_id, {})
|
||||||
return [c["conn"] for c in tile.get("checks", []) if c["type"] in RESTARTABLE and c.get("conn")]
|
return [c["conn"] for c in tile.get("checks", []) if c["type"] in RESTARTABLE and c.get("conn")]
|
||||||
|
|
||||||
def fix_plan(self, tile_id, snap, now=None, min_age=0, errors_only=False):
|
def fix_plan(self, tile_id, snap, now=None, min_age=0, errors_only=False, levels=(ERR,)):
|
||||||
"""What a hold on this button would do: fix only what is failing right now.
|
"""What a hold on this button would do: fix only what is failing right now.
|
||||||
|
|
||||||
[(identity, "fix", fix id, check) or (identity, "restart", connection label, check)]
|
[(identity, "fix", fix id, check) or (identity, "restart", connection label, check)]
|
||||||
A check's own "fix" wins; otherwise a failing Companion connection is restarted.
|
A check's own "fix" wins; otherwise a failing Companion connection is restarted.
|
||||||
Nothing is done for a healthy button, and a recording HyperDeck is never touched.
|
Nothing is done for a healthy button, and a recording HyperDeck is never touched.
|
||||||
|
With errors_only, only faults of these `levels` that have lasted min_age count (auto-fix).
|
||||||
"""
|
"""
|
||||||
tile = self.tiles.get(tile_id, {})
|
tile = self.tiles.get(tile_id, {})
|
||||||
ids = self.ready_ids if tile.get("kind") == "ready" else (tile.get("members") or [tile_id])
|
ids = self.ready_ids if tile.get("kind") == "ready" else (tile.get("members") or [tile_id])
|
||||||
@@ -1248,7 +1250,7 @@ class Engine:
|
|||||||
tr = self.trackers.get(key)
|
tr = self.trackers.get(key)
|
||||||
if tid not in ids or not tr or not tr.confirmed or tr.result.level not in (WARN, ERR):
|
if tid not in ids or not tr or not tr.confirmed or tr.result.level not in (WARN, ERR):
|
||||||
continue
|
continue
|
||||||
if errors_only and (tr.result.level != ERR or now - (tr.since or now) < min_age):
|
if errors_only and (tr.result.level not in levels or now - (tr.since or now) < min_age):
|
||||||
continue # auto-fix: only real faults that have lasted
|
continue # auto-fix: only real faults that have lasted
|
||||||
# A connection switched off in Companion is fixed by switching it on - never by
|
# A connection switched off in Companion is fixed by switching it on - never by
|
||||||
# acting on the device (2026-10-08: a disabled HyperDeck connection got a deck reboot).
|
# acting on the device (2026-10-08: a disabled HyperDeck connection got a deck reboot).
|
||||||
@@ -1500,19 +1502,26 @@ class Engine:
|
|||||||
# (playing the Spotify playlist): those keep running inside the window.
|
# (playing the Spotify playlist): those keep running inside the window.
|
||||||
muted = not self.service.on(now)
|
muted = not self.service.on(now)
|
||||||
keep = set(a.get("while_muted", [])) if muted else None
|
keep = set(a.get("while_muted", [])) if muted else None
|
||||||
if not in_windows(a.get("windows", []), now) or (muted and not keep):
|
hours = in_windows(a.get("windows", []), now)
|
||||||
|
# A fix marked "anytime" (writing the timecode into Planning Center) runs by itself at any
|
||||||
|
# hour, not only in the auto-fix hours - still never while ALARMS is muted.
|
||||||
|
anytime = {f for f in a.get("fixes", []) if self.cfg["fixes"].get(f, {}).get("anytime")
|
||||||
|
and not self.cfg["fixes"].get(f, {}).get("never_auto") and not self.cfg["fixes"].get(f, {}).get("disabled")}
|
||||||
|
if not hours or (muted and not keep):
|
||||||
if self.auto_on and self.autolog:
|
if self.auto_on and self.autolog:
|
||||||
self.autolog.write(now, "PANEL", "auto-fix off (%s)" % ("ALARMS muted" if in_windows(
|
self.autolog.write(now, "PANEL", "auto-fix off (%s)" % ("ALARMS muted" if hours else "end of the window"))
|
||||||
a.get("windows", []), now) else "end of the window"))
|
|
||||||
self.auto_on = False
|
self.auto_on = False
|
||||||
self.auto_tries.clear()
|
for ident in [i for i in self.auto_tries if not (i[0] == "fix" and i[1] in anytime)]:
|
||||||
self.auto_seen.clear()
|
del self.auto_tries[ident] # the window's fixes start fresh next time
|
||||||
return []
|
self.auto_seen.pop(ident, None)
|
||||||
if (not self.auto_on or self.auto_muted != muted) and self.autolog: # opens today's file, so it can be watched from the start
|
if muted or not anytime:
|
||||||
ends = [w["end"] for w in a.get("windows", []) if in_windows([w], now)]
|
return []
|
||||||
self.autolog.write(now, "PANEL", "auto-fix on%s%s" % (" until " + ends[0] if ends else "",
|
else:
|
||||||
" (ALARMS muted: only %s)" % ", ".join(sorted(keep)) if muted else ""))
|
if (not self.auto_on or self.auto_muted != muted) and self.autolog: # opens today's file, so it can be watched from the start
|
||||||
self.auto_on, self.auto_muted = True, muted
|
ends = [w["end"] for w in a.get("windows", []) if in_windows([w], now)]
|
||||||
|
self.autolog.write(now, "PANEL", "auto-fix on%s%s" % (" until " + ends[0] if ends else "",
|
||||||
|
" (ALARMS muted: only %s)" % ", ".join(sorted(keep)) if muted else ""))
|
||||||
|
self.auto_on, self.auto_muted = True, muted
|
||||||
net_down = any(self.trackers.get("net:" + k) and self.trackers["net:" + k].confirmed
|
net_down = any(self.trackers.get("net:" + k) and self.trackers["net:" + k].confirmed
|
||||||
and self.trackers["net:" + k].result.level == ERR for k in ("link", "gateway"))
|
and self.trackers["net:" + k].result.level == ERR for k in ("link", "gateway"))
|
||||||
if net_down or (self.burst and (self.burst["cleared"] is None
|
if net_down or (self.burst and (self.burst["cleared"] is None
|
||||||
@@ -1525,9 +1534,11 @@ class Engine:
|
|||||||
self.autolog.write(now, "NET", "network settled: auto-fix is back on")
|
self.autolog.write(now, "NET", "network settled: auto-fix is back on")
|
||||||
self.auto_paused = False
|
self.auto_paused = False
|
||||||
gentle = {f for f in a.get("fixes", []) if not self.cfg["fixes"].get(f, {}).get("never_auto")
|
gentle = {f for f in a.get("fixes", []) if not self.cfg["fixes"].get(f, {}).get("never_auto")
|
||||||
and not self.cfg["fixes"].get(f, {}).get("disabled")}
|
and not self.cfg["fixes"].get(f, {}).get("disabled")} if hours else anytime
|
||||||
|
restarts = bool(hours and a.get("restart_connections"))
|
||||||
|
ready_ids = {k["id"] for k in ready_keys(self.cfg)}
|
||||||
done = []
|
done = []
|
||||||
for t in self.cfg["tiles"]:
|
for t in all_tiles(self.cfg):
|
||||||
if "checks" not in t:
|
if "checks" not in t:
|
||||||
continue # groups repeat their members
|
continue # groups repeat their members
|
||||||
gone = [k for k in self.keys_for(t["id"]) if self.meta.get(k) and self.meta[k][1]["type"] == "dante"
|
gone = [k for k in self.keys_for(t["id"]) if self.meta.get(k) and self.meta[k][1]["type"] == "dante"
|
||||||
@@ -1544,8 +1555,10 @@ class Engine:
|
|||||||
self.auto_seen[("dante-wide", t["id"])] = now
|
self.auto_seen[("dante-wide", t["id"])] = now
|
||||||
continue
|
continue
|
||||||
plan = []
|
plan = []
|
||||||
for ident, kind, what, chk in self.fix_plan(t["id"], snap, now, a.get("after_seconds", 20), True):
|
# A READY key's gap is amber until the checklist is due: a ticked READY fix acts on it anyway.
|
||||||
if not ((kind == "fix" and what in gentle) or (kind == "restart" and a.get("restart_connections"))):
|
levels = (WARN, ERR) if t["id"] in ready_ids else (ERR,)
|
||||||
|
for ident, kind, what, chk in self.fix_plan(t["id"], snap, now, a.get("after_seconds", 20), True, levels):
|
||||||
|
if not ((kind == "fix" and what in gentle) or (kind == "restart" and restarts)):
|
||||||
continue # not gentle: stays a hold
|
continue # not gentle: stays a hold
|
||||||
if keep is not None and not (kind == "fix" and what in keep):
|
if keep is not None and not (kind == "fix" and what in keep):
|
||||||
continue # ALARMS muted: only the "while_muted" fixes
|
continue # ALARMS muted: only the "while_muted" fixes
|
||||||
@@ -1567,7 +1580,7 @@ class Engine:
|
|||||||
try_no = max(self.auto_tries[i][0] for i, _, _, _ in plan)
|
try_no = max(self.auto_tries[i][0] for i, _, _, _ in plan)
|
||||||
log.info("AUTO %s: fixing %s (try %d)", t["id"], ", ".join("%s %s" % (k, w) for _, k, w, _ in plan), try_no)
|
log.info("AUTO %s: fixing %s (try %d)", t["id"], ", ".join("%s %s" % (k, w) for _, k, w, _ in plan), try_no)
|
||||||
faults = [r.msg for k in self.keys_for(t["id"]) for r in [self.trackers[k].result]
|
faults = [r.msg for k in self.keys_for(t["id"]) for r in [self.trackers[k].result]
|
||||||
if self.trackers[k].confirmed and r.level == ERR]
|
if self.trackers[k].confirmed and r.level in levels]
|
||||||
for f in faults:
|
for f in faults:
|
||||||
self.auto_watch["%s %s" % (t["label"], f)] = now
|
self.auto_watch["%s %s" % (t["label"], f)] = now
|
||||||
label = t["label"]
|
label = t["label"]
|
||||||
|
|||||||
@@ -975,6 +975,45 @@ class TestFixes(unittest.TestCase):
|
|||||||
self.assertEqual(self.engine.auto_fix(self.snap, comp, weekday + 30), [])
|
self.assertEqual(self.engine.auto_fix(self.snap, comp, weekday + 30), [])
|
||||||
self.assertEqual(ran, [])
|
self.assertEqual(ran, [])
|
||||||
|
|
||||||
|
def test_a_ready_fix_marked_anytime_runs_on_a_weekday(self):
|
||||||
|
"""The LTC write, ticked for auto-fix and marked anytime, fills an amber TIMECODE gap on a
|
||||||
|
Thursday morning; the window's fixes (Ableton closed) still wait for the window."""
|
||||||
|
cfg = json.loads(json.dumps(CONFIG))
|
||||||
|
cfg["auto_fix"]["fixes"] = cfg["auto_fix"]["fixes"] + ["ready_ltc"]
|
||||||
|
cfg["fixes"]["ready_ltc"]["anytime"] = True
|
||||||
|
thursday = SUNDAY - 3 * 86400
|
||||||
|
self.engine = sp.Engine(cfg)
|
||||||
|
self.engine.autolog = sp.AutoLog(tempfile.mkdtemp())
|
||||||
|
self.engine.service.set(True, thursday) # ALARMS on all week, as at NA
|
||||||
|
ran, comp = self.auto_setup()
|
||||||
|
self.snap.ready = {"items": {"timecode": {"faults": [[sp.ERR, "NO LTC JEHOVAH"]], "info": ""}}}
|
||||||
|
self.snap.remote["tracks\0pgrep -xq Live"] = False
|
||||||
|
run_steps(self.engine, self.snap, thursday, 10)
|
||||||
|
self.assertEqual(self.engine.auto_fix(self.snap, comp, thursday + 10), []) # not 20 s yet
|
||||||
|
done = self.engine.auto_fix(self.snap, comp, thursday + 30)
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.assertEqual((done, ran), (["rd_timecode"], ["ready_ltc"])) # never "ableton"
|
||||||
|
self.assertEqual(self.engine.auto_fix(self.snap, comp, thursday + 60), []) # 90 s between tries
|
||||||
|
self.engine.service.set(False, thursday + 61) # ALARMS muted: nothing
|
||||||
|
self.assertEqual(self.engine.auto_fix(self.snap, comp, thursday + 200), [])
|
||||||
|
self.engine.service.set(True, thursday + 201)
|
||||||
|
del cfg["fixes"]["ready_ltc"]["anytime"] # not marked: the window only
|
||||||
|
self.engine = sp.Engine(cfg)
|
||||||
|
self.engine.autolog = sp.AutoLog(tempfile.mkdtemp())
|
||||||
|
self.engine.service.set(True, thursday)
|
||||||
|
ran, comp = self.auto_setup()
|
||||||
|
run_steps(self.engine, self.snap, thursday, 25)
|
||||||
|
self.assertEqual(self.engine.auto_fix(self.snap, comp, thursday + 30), [])
|
||||||
|
run_steps(self.engine, self.snap, SUNDAY, 25)
|
||||||
|
done = self.engine.auto_fix(self.snap, comp, SUNDAY + 30)
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.assertIn("rd_timecode", done) # in the window it acts
|
||||||
|
self.assertIn("ready_ltc", ran)
|
||||||
|
import schema
|
||||||
|
bad = json.loads(json.dumps(CONFIG))
|
||||||
|
bad["fixes"]["ableton"]["anytime"] = True
|
||||||
|
self.assertTrue(any("anytime" in e for e in schema.validate(bad)[0])) # only a READY fix may
|
||||||
|
|
||||||
def test_restart_keys_flash_when_their_thing_is_down(self):
|
def test_restart_keys_flash_when_their_thing_is_down(self):
|
||||||
devices = json.loads(self.snap.vars[("audinate-dantecontroller", "devices")])
|
devices = json.loads(self.snap.vars[("audinate-dantecontroller", "devices")])
|
||||||
devices.remove("NA-Tracks") # DVS on the Tracks Mac gone
|
devices.remove("NA-Tracks") # DVS on the Tracks Mac gone
|
||||||
|
|||||||
Reference in New Issue
Block a user