diff --git a/forgetest/forgetest/baseline.py b/forgetest/forgetest/baseline.py index 930c2a3..0c19c7b 100644 --- a/forgetest/forgetest/baseline.py +++ b/forgetest/forgetest/baseline.py @@ -65,6 +65,21 @@ FIXED_SYSFS = [ ("head/white_led", "0"), ("thermal/heater_pwm", "0"), ("thermal/tec_on", "0"), + # Purge air runs continuously, as on the factory machine: the engine + # turns it on at start and only a listening (the quiet hold) switches + # it off, which puts it back on release. A check that switches it off + # to measure it and forgets leaves the machine one job away from an + # airflow hold mid-cut, judged against the floor that same check just + # wrote, and nothing notices until the daemon restarts: the engine's + # idle phase never re-applies its own duties. The airflow check did + # exactly that, and it reached an operator's first fire. + ("head/purge_air", "1"), + # The lens motor at rest: hold current, half step. The lens checks and + # the sheet cards take it to the run current in full or half step and + # must hand it back, or the motor sits hot and the next reference + # starts from a state nobody chose. + ("head/z_current", "1"), + ("head/z_mode", "1"), ] # The subset the GRBL controller writes at its start: checked and restored diff --git a/forgetest/forgetest/runner.py b/forgetest/forgetest/runner.py index b8e546a..49b4067 100644 --- a/forgetest/forgetest/runner.py +++ b/forgetest/forgetest/runner.py @@ -994,7 +994,21 @@ class Runner: run.log(traceback.format_exc().rstrip()) self.fixture_release(run) try: - self._baseline_post(run, captured) + left = self._baseline_post(run, captured) + # A test hands the machine back as it found it. Anything left + # behind was recorded here and nothing more, so a check that + # measured correctly and walked away with the machine in a + # state nobody chose still passed: that is how the airflow + # check came to leave the purge fan off, and an operator met + # it at their first fire instead of the bench meeting it here. + # The pass is now conditional on the machine being whole. A + # leftover the baseline put back still fails: the restore is + # the bench cleaning up after a defect, not the defect's + # absence. + if left and result == _campaign.PASS: + result = _campaign.FAIL + message = ("the machine was not handed back as found: %s" + % "; ".join(str(x) for x in left)) except Exception as e: # noqa: BLE001 - never lose the result over the cleanup run.log("baseline: post pass errored: %s: %s" % (type(e).__name__, e)) duration = int(time.time() - run.started) diff --git a/forgetest/forgetest/suite/commission_dark.py b/forgetest/forgetest/suite/commission_dark.py index d9ed910..9c22d05 100644 --- a/forgetest/forgetest/suite/commission_dark.py +++ b/forgetest/forgetest/suite/commission_dark.py @@ -167,7 +167,8 @@ def check_sensors(ctx): "for 35 s, the purge is measured on and off, and five settings are written: the " "four floors at 55 percent of steady and the grace from the slowest spin-up. Each " "fan must read at least 1000 rpm. The five settings are put back as found at the " - "end and the controller must be running again.") + "end, the purge fan is on again and drawing over the floor the check just wrote, " + "and the controller must be running again.") def check_airflow(ctx): keys = ["cool_tach_exhaust_min_rpm", "cool_tach_intake_min_rpm", "cool_tach_air_assist_min_rpm", "cool_purge_min_current", "cool_fan_grace_s"] @@ -186,6 +187,20 @@ def check_airflow(ctx): for k in keys: ctx.check(s.get(k) == floors.get(k), "%s reads %r, the check wrote %r", k, s.get(k), floors.get(k)) ctx.log("floors: %s", json.dumps(floors)) + # The check switches purge air off to read its off current. Leaving + # it off costs the next job an airflow hold mid-cut, judged against + # the very floor written above, and nothing puts it back until the + # daemon restarts: the engine's idle phase never re-applies its own + # duties. Prove the machine is whole, not just measured. + purge_on = ctx.sysfs("head/purge_air") + ctx.check(purge_on == "1", "purge air reads %r after the check, expected 1 (on)", purge_on) + gate = ((ctx.forgectrl.get("/cool/status")[1] or {}).get("fan_gates") or {}).get("purge") or {} + ctx.evidence["purge_after"] = {"purge_air": purge_on, "gate": gate} + ctx.log("purge after the check: commanded %s, drawing %s against the %s floor", + purge_on, gate.get("reading"), gate.get("floor")) + ctx.check((gate.get("reading") or 0) >= (gate.get("floor") or 0), + "purge draws %s, under the %s floor the check just wrote: the next job would be held", + gate.get("reading"), gate.get("floor")) ok = ctx.wait_for(lambda: (ctx.forgectrl.get("/mode")[1] or {}).get("controller") == "running", 60) ctx.check(ok is not None, "the controller did not come back after the check") diff --git a/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc b/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc index f499d56..fe0cb92 100644 --- a/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc +++ b/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc @@ -2,5 +2,5 @@ # only SRCREV and PV here - the image manifest leaves *-pin.inc out of the # layer content hash because the component entry already identifies the # pinned source (forgefirm-image-manifest.bbclass). -SRCREV = "468ee215dd1148b92c979decacecc41aa6a75ccf" -PV = "0.1.12" +SRCREV = "a2d73efeb9af376e122e0877a4988d92d021cfa7" +PV = "0.1.13"