diff --git a/forgetest/forgetest/runner.py b/forgetest/forgetest/runner.py index 49b4067..cba12c6 100644 --- a/forgetest/forgetest/runner.py +++ b/forgetest/forgetest/runner.py @@ -424,6 +424,45 @@ class Context: self.log("ACT %s %s: done after %.1f s", channel, state, dt) return dt + def press_now(self, text="The button is lit white. Press it now: the laser fires after your press."): + """The arm press, at the moment the machine says it is waiting. + + A check that runs a job opens a `press` prompt of its own when + it sees the button lit and the tube still dark, which is the + machine's own word that the press it wants is this one. Pressing + on that is exact; pressing on a button LED read from here is + not, because the button is lit through parts of a check that are + not the arm (the lens reference, the program on its way to the + controller), and a press then lands before the job waits for it + and is lost. + + The fixture presses where the bench took this test's presses (or + opted in standing); otherwise the notice goes up for a person, + who is looking at the same prompt. Returns True when the fixture + pressed.""" + fixture = getattr(self.runner, "fixture", None) if self.runner is not None else None + rec = {"channel": "button", "state": "arm", "by": "operator", "ts": now_ts()} + self.evidence.setdefault("actions", []).append(rec) + opted_in = fixture is not None and fixture.covers("button") and \ + (fixture.arm_press or self.run.fixture_takeover) + if not opted_in: + if self.run.fixture_takeover: + rec["fixture_lost"] = True + self.log("ARM: WARNING the bench actuator took this test's presses and is now " + "gone - asking the operator for the arm press") + self.notice(text) + return False + try: + fixture.act("button", "press") + except _fixture.FixtureError as e: + rec["fixture_error"] = str(e) + self.log("ARM press: fixture failed (%s) - asking the operator", e) + self.notice(text) + return False + rec["by"] = "fixture" + self.log("ARM press by the fixture, at the machine's own press prompt") + return True + def arm_press(self, text="The button lights white: press it to arm. The machine fires after your press.", lit_timeout=60): """The arm cue of a live test. A person's press by default: a diff --git a/forgetest/forgetest/suite/commission_dark.py b/forgetest/forgetest/suite/commission_dark.py index 8762ae3..4609500 100644 --- a/forgetest/forgetest/suite/commission_dark.py +++ b/forgetest/forgetest/suite/commission_dark.py @@ -15,6 +15,8 @@ from ..baseline import read_position from .commission import wiz, Restore # noqa: F401 - Restore is re-exported for the sheet POLL_S = 1.0 +# What the machine's own press prompt says, used when it sends no text. +PRESS_TEXT = "The button is lit white. Press it now: the laser fires after your press." DARK_COVERS = [("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"), ("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*")] @@ -76,6 +78,12 @@ def run_check(ctx, wid, on_prompt, timeout_s): if value is not None: st, body = answer(fc, wid, p, value) ctx.check(st == 200, "the answer to %s -> %s %s", p.get("id"), st, body) + elif p.get("id") == "press": + # The arm press, asked for by the machine itself: the + # check opens this prompt when the button is lit and + # the tube is still dark. Nothing answers it - the + # press does, and the discharge closes it. + ctx.press_now(p.get("text") or PRESS_TEXT) if not d.get("running"): last = d break diff --git a/forgetest/forgetest/suite/commission_sheet.py b/forgetest/forgetest/suite/commission_sheet.py index 6be640e..fd66f92 100644 --- a/forgetest/forgetest/suite/commission_sheet.py +++ b/forgetest/forgetest/suite/commission_sheet.py @@ -36,19 +36,18 @@ SHEET_COVERS = [("forgectrl", "src/wizlive.*"), ("forgectrl", "src/wizrun.h"), # The live wizards in the order the sheet burns them: the wizard id, the # answers to its prompts (a middle pick where a person would look at the -# sheet), the keys its result must carry, and how long the bench actuator -# waits for the button to light before the arm press goes to a person. -# The flow-load card settles the coolant loop before it lights the button -# (60 to 240 s); the others light within seconds of the lens reference. +# sheet), and the keys its result must carry. The arm press is not timed +# from here: each card opens a press prompt of its own when the button is +# lit and the tube still dark, and run_check presses on that. CARDS = [ - ("sheet.frame", {"frame-arm": "Continue", "frame-ok": "Yes"}, ["mark_s", "mark_feed"], 120), + ("sheet.frame", {"frame-arm": "Continue", "frame-ok": "Yes"}, ["mark_s", "mark_feed"]), ("laser.focus", {"focus-arm": "Continue", "focus-pick": "11", "thickness": "Keep"}, ["pick", "thickness_mm", "pick_half_steps", "edge_z_mm", "steps_per_mm", "max_height_mm", - "focus_range_mm", "stops"], 120), - ("laser.floor", {"floor-arm": "Continue", "floor-pick": "8"}, ["faintest_density", "floor_density"], 120), - ("laser.dose-curve", {"dose-arm": "Continue"}, ["points", "curve"], 120), - ("laser.corner", {"corner-arm": "Continue", "corner-pick": "1.50"}, ["gamma"], 120), - ("cooling.flow-load", {"load-arm": "Continue"}, ["lit_s", "dose_raw_s", "peak_c", "k_density", "k_cw"], 420), + "focus_range_mm", "stops"]), + ("laser.floor", {"floor-arm": "Continue", "floor-pick": "8"}, ["faintest_density", "floor_density"]), + ("laser.dose-curve", {"dose-arm": "Continue"}, ["points", "curve"]), + ("laser.corner", {"corner-arm": "Continue", "corner-pick": "1.50"}, ["gamma"]), + ("cooling.flow-load", {"load-arm": "Continue"}, ["lit_s", "dose_raw_s", "peak_c", "k_density", "k_cw"]), ] # Every setting a card writes, restored as found when the run ends. CARD_SETTINGS = ["lens_hall_edge_z_mm", "lens_stop_below_steps", "lens_stop_above_steps", @@ -130,12 +129,12 @@ def place(ctx): file_card(ctx, "sheet.place") -def burn(ctx, wid, answers, want, lit_s): +def burn(ctx, wid, answers, want): """One live card: the preview, the burn after the press with the prompts answered from `answers`, the witnesses, the keys of the - result in `want`. The actuator waits `lit_s` for the button to light.""" + result in `want`. run_check makes the arm press when the card asks + for it.""" preview_ok(ctx, wid) - ctx.arm_press(lit_timeout=lit_s) def on_prompt(p): return answers.get(p.get("id")) @@ -161,9 +160,10 @@ def burn(ctx, wid, answers, want, lit_s): "it goes with its top edge at the top of the cut area (the head's home corner); " "lid closed. Eye protection on, exhaust on, extinguisher in reach.", "Start the test and press the machine's button once. That press says you are at the " - "machine, and the bench actuator makes every arm press after it. With no actuator " - "the start is the Ready answer instead, and you press the button each time it lights " - "white: six times, the frame and the five cards.", + "machine, and the bench actuator makes every arm press after it, each one when the " + "card itself asks for it. With no actuator the start is the Ready answer instead, " + "and you press the button each time the page asks: six times, the frame and the " + "five cards.", "Nothing else: the test answers every prompt itself and puts back every setting the " "cards write. It runs about half an hour, mostly the coolant settle and the dark " "tails."], @@ -191,6 +191,6 @@ def sheet(ctx): "each. The bench presses when the button lights.") place(ctx) with Restore(ctx, CARD_SETTINGS): - for wid, answers, want, lit_s in CARDS: - burn(ctx, wid, answers, want, lit_s) + for wid, answers, want in CARDS: + burn(ctx, wid, answers, want) ctx.log("PASS: the sheet's seven wizards ran on one piece; settings restored") diff --git a/forgetest/tests/test_fixture.py b/forgetest/tests/test_fixture.py index e57c154..cfdfa2f 100644 --- a/forgetest/tests/test_fixture.py +++ b/forgetest/tests/test_fixture.py @@ -586,6 +586,41 @@ class RoutingTests(unittest.TestCase): finally: runner_mod.hw.button_lit = saved + def test_the_press_prompt_is_what_the_actuator_presses_on(self): + """The machine's own press prompt, not a button LED read from + here: the button is lit through parts of a live check that are + not the arm, and a press then lands before the job waits for it + and is lost. press_now presses at the moment the check asks, with + no LED reading and no waiting thread.""" + run = Run("test", "r.live", "r.live") + ctx = Context(run, self.runner, self.reg["r.live"]) + self.runner.probe_fixture(force=True) + self.stub.arm_press = False + run.fixture_takeover = True + saved = runner_mod.hw.button_lit + runner_mod.hw.button_lit = lambda: False # never consulted + try: + self.assertTrue(ctx.press_now()) + self.assertIn(("button", "press"), self.stub.acts) + finally: + runner_mod.hw.button_lit = saved + rec = [r for r in run.evidence["actions"] if r["state"] == "arm"][-1] + self.assertEqual(rec["by"], "fixture") + + def test_the_press_prompt_goes_to_the_operator_without_an_actuator(self): + """No actuator, or one that never took this test's presses: the + notice goes up for a person, who is looking at the same prompt.""" + run = Run("test", "r.live", "r.live") + ctx = Context(run, self.runner, self.reg["r.live"]) + self.runner.probe_fixture(force=True) + self.stub.arm_press = False + run.fixture_takeover = False + seen = [] + run.set_notice = lambda text: seen.append(text) + self.assertFalse(ctx.press_now("Press it now.")) + self.assertNotIn(("button", "press"), self.stub.acts) + self.assertIn("Press it now.", seen) + def test_the_arm_press_waits_as_long_as_the_card_says_for_the_light(self): """A card that settles the coolant before it lights the button takes minutes; the caller names the wait, and the actuator presses diff --git a/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc b/meta-forgefirm/recipes-forgefirm/forgectrl/forgectrl-pin.inc index 3d29384..10301bb 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 = "9e5330ffbb27dcf2fb89f414f1e8fc38bfdfb6f7" -PV = "0.1.14" +SRCREV = "6e694798903df6add92ec97876cda4851b739aa0" +PV = "0.1.15"