forgetest: the air-assist offset calibration runs before the heater tools

A queue runs the catalog in registration order among tests with the
same prerequisites, and cooling.aa-offset-calibrate followed
cooling.flow-verify. A flow-verify trial heats the tube water (a no-flow
trial by 17 C on the bench) and the warm slug circulates past the
coolant sensors for minutes afterward; the calibration's stationary gate
passed 44 s after the trial on image 20260902144848 and the edges read
the wave as disagreement.

The calibration is registered first now, with the reason beside it, and
tests/test_cooling_order.py holds the order.

Catalog consequence: the cooling.* implementation hashes move (the suite
file changed).
This commit is contained in:
ScottW514
2026-09-02 16:21:13 -04:00
parent d5369630d9
commit 1fef9c6f51
2 changed files with 77 additions and 55 deletions
+58 -55
View File
@@ -20,61 +20,9 @@ _COOL_COVERS = [("forgectrl", "src/cool.*"), ("forgectrl", "src/coolfmt.*"), ("f
("kernel-module-glowforge", "src/pic*")] ("kernel-module-glowforge", "src/pic*")]
@test("cooling.flow-verify", title="Coolant flow check separates flow from no-flow", # Registered before the heater tools: a queue runs the catalog in this
subsystem="cooling", kind="auto", est_min=4, # order, and a heater trial leaves a warm slug circulating past the
covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"], # coolant sensors for minutes, which the tool's edges read as disagreement.
steps=["Coolant loop normal (pump on); the machine idle. The controller is suspended by "
"forgectrl for the duration (about 3 minutes)."],
description="forgectrl's flow-verify diagnostic: one heater window with the pump on and "
"one with it commanded off, judged against the configured threshold. PASS = "
"the threshold separates the two readings with the margins forgectrl reports; "
"a thin margin is recorded as a warning.")
def flow_verify(ctx):
fc = ctx.forgectrl
ev = ctx.evidence
st, body = fc.get("/diag/status")
ctx.check(st == 200 and isinstance(body, dict), "GET /diag/status -> %s", st)
ctx.check(not body.get("running"), "a diagnostic is already running (%s)", body.get("tool"))
st, body = fc.post("/diag/flow-verify")
ctx.log("POST /diag/flow-verify -> %s %s", st, body if isinstance(body, dict) else "")
ctx.check(st == 202 and isinstance(body, dict) and body.get("started") is True,
"could not start flow-verify (%s %s)", st, body)
last_phase = None
result = None
t0 = time.time()
try:
while time.time() - t0 < 900:
ctx.checkpoint()
st, d = fc.get("/diag/status")
if st == 200 and isinstance(d, dict):
if d.get("phase") != last_phase:
last_phase = d.get("phase")
ctx.log("phase: %s (down %.1f C, up %.1f C)", last_phase, d.get("down_c", 0), d.get("up_c", 0))
if not d.get("running") and d.get("result") is not None:
result = d.get("result")
for line in d.get("log", [])[-12:]:
ctx.log(" diag: %s", line)
break
time.sleep(2)
except BaseException:
fc.post("/diag/abort")
raise
ctx.check(result is not None, "flow-verify did not finish within 15 minutes")
ev["result"] = result
ctx.check("error" not in result, "flow-verify error: %s", result.get("error"))
ctx.log("verdict: pass=%s threshold=%s flow_rise=%s noflow_rise=%s margins %s/%s thin=%s",
result.get("pass"), result.get("threshold"), result.get("flow_rise"),
result.get("noflow_rise"), result.get("margin_flow"), result.get("margin_noflow"),
result.get("thin_margin"))
ctx.check(result.get("pass") is True, "the threshold does not separate flow from no-flow: %s", result)
if result.get("thin_margin"):
ctx.log("WARNING: thin margin - run flow-calibrate")
ctx.check(fc.wait_idle(120, abort=ctx.aborted), "machine did not return to idle after the diagnostic")
AA_CAL_TIMEOUT_S = 540 # the tool's 420 s settle limit plus its six edges
@test("cooling.aa-offset-calibrate", title="The air-assist ground shift on the coolant readings measures cleanly", @test("cooling.aa-offset-calibrate", title="The air-assist ground shift on the coolant readings measures cleanly",
subsystem="cooling", kind="auto", est_min=6, subsystem="cooling", kind="auto", est_min=6,
covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"], covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"],
@@ -137,6 +85,61 @@ IDLE_REF_TIMEOUT_S = 180 # a previous test's cooldown + spin-down
COOLDOWN_TIMEOUT_S = 240 # the engine's smoke phase + idle drop + spin-down COOLDOWN_TIMEOUT_S = 240 # the engine's smoke phase + idle drop + spin-down
@test("cooling.flow-verify", title="Coolant flow check separates flow from no-flow",
subsystem="cooling", kind="auto", est_min=4,
covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"],
steps=["Coolant loop normal (pump on); the machine idle. The controller is suspended by "
"forgectrl for the duration (about 3 minutes)."],
description="forgectrl's flow-verify diagnostic: one heater window with the pump on and "
"one with it commanded off, judged against the configured threshold. PASS = "
"the threshold separates the two readings with the margins forgectrl reports; "
"a thin margin is recorded as a warning.")
def flow_verify(ctx):
fc = ctx.forgectrl
ev = ctx.evidence
st, body = fc.get("/diag/status")
ctx.check(st == 200 and isinstance(body, dict), "GET /diag/status -> %s", st)
ctx.check(not body.get("running"), "a diagnostic is already running (%s)", body.get("tool"))
st, body = fc.post("/diag/flow-verify")
ctx.log("POST /diag/flow-verify -> %s %s", st, body if isinstance(body, dict) else "")
ctx.check(st == 202 and isinstance(body, dict) and body.get("started") is True,
"could not start flow-verify (%s %s)", st, body)
last_phase = None
result = None
t0 = time.time()
try:
while time.time() - t0 < 900:
ctx.checkpoint()
st, d = fc.get("/diag/status")
if st == 200 and isinstance(d, dict):
if d.get("phase") != last_phase:
last_phase = d.get("phase")
ctx.log("phase: %s (down %.1f C, up %.1f C)", last_phase, d.get("down_c", 0), d.get("up_c", 0))
if not d.get("running") and d.get("result") is not None:
result = d.get("result")
for line in d.get("log", [])[-12:]:
ctx.log(" diag: %s", line)
break
time.sleep(2)
except BaseException:
fc.post("/diag/abort")
raise
ctx.check(result is not None, "flow-verify did not finish within 15 minutes")
ev["result"] = result
ctx.check("error" not in result, "flow-verify error: %s", result.get("error"))
ctx.log("verdict: pass=%s threshold=%s flow_rise=%s noflow_rise=%s margins %s/%s thin=%s",
result.get("pass"), result.get("threshold"), result.get("flow_rise"),
result.get("noflow_rise"), result.get("margin_flow"), result.get("margin_noflow"),
result.get("thin_margin"))
ctx.check(result.get("pass") is True, "the threshold does not separate flow from no-flow: %s", result)
if result.get("thin_margin"):
ctx.log("WARNING: thin margin - run flow-calibrate")
ctx.check(fc.wait_idle(120, abort=ctx.aborted), "machine did not return to idle after the diagnostic")
AA_CAL_TIMEOUT_S = 540 # the tool's 420 s settle limit plus its six edges
def _duties(): def _duties():
return {k: hw.sysfs_int(k) for k in IDLE_DUTY} return {k: hw.sysfs_int(k) for k in IDLE_DUTY}
+19
View File
@@ -0,0 +1,19 @@
"""cooling.aa-offset-calibrate is registered before the heater tools, so
a queue runs it first: a heater trial leaves a warm slug circulating past
the coolant sensors for minutes, and the tool's edges read it as
disagreement."""
import unittest
from forgetest import catalog
class CoolingOrderTests(unittest.TestCase):
def test_aa_offset_precedes_flow_verify(self):
reg = catalog.load_suite()
tests = catalog.all_tests(reg)
order = catalog.order_by_requires(tests, [t.id for t in tests])
self.assertLess(order.index("cooling.aa-offset-calibrate"), order.index("cooling.flow-verify"))
if __name__ == "__main__":
unittest.main()