From 5f19ae01cd9c105862e5a1b9bff2c5e92f90149a Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Wed, 9 Sep 2026 08:06:04 -0400 Subject: [PATCH] Follow the lens reference into the harness and the catalog The lens now takes its hall-edge reference before any controller starts, so Z is referenced on every start and M103 is gone. The laser-stream harness opened its Z session by referencing the lens the way a commissioning card did; it no longer has to, because Z is already open by the time the session runs. forgectrl.panel-serves gains the assertions for the per-axis reference: homed_axes is an axis mask, homed agrees with it, and with a controller running Z is referenced and reads inside the lens reach the same document reports. That last check is the one that catches a panel showing nothing for a Z the controller holds. commission.check-motion already exercised the new path, because the motion wizard's probe runs the same sequence the supervisor does, so its covers map gains lenshome.c and its description names the lens reference and the hard fault behind it. --- forgetest/forgetest/suite/commission_dark.py | 5 +++- forgetest/forgetest/suite/forgectrl.py | 30 +++++++++++++++++++- scripts/bench/laser_stream_test.py | 9 +++--- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/forgetest/forgetest/suite/commission_dark.py b/forgetest/forgetest/suite/commission_dark.py index ea972df..d9ed910 100644 --- a/forgetest/forgetest/suite/commission_dark.py +++ b/forgetest/forgetest/suite/commission_dark.py @@ -228,9 +228,12 @@ def check_cameras(ctx): @test("commission.check-motion", title="The motion check proves the rail, the lens reference, and the jogs", subsystem="commission", kind="auto", hardware="takeover", est_min=5, covers=DARK_COVERS + [("forgectrl", "src/super.c"), ("forgectrl", "src/liveness.c"), + ("forgectrl", "src/lenshome.c"), ("forgectrl", "src/accel.c"), ("forgectrl", "src/cool.c")], requires=["forgectrl.auth", "motion.pacing"], - description="POST /wiz/motion/start: the controller stops, the liveness probe runs, the lens " + description="POST /wiz/motion/start: the controller stops, the liveness probe runs and the " + "lens takes its hall-edge reference behind it (a lens that cannot reach its " + "edge is a motion fault, and no controller starts), the lens " "finds the hall reference in five agreeing passes, the controller comes back in " "loopback posture, and the head jogs 50 mm each way on X and Y with the " "accelerometer as the witness. The one prompt (the jogs are about to move) is " diff --git a/forgetest/forgetest/suite/forgectrl.py b/forgetest/forgetest/suite/forgectrl.py index 1d638af..970d7da 100644 --- a/forgetest/forgetest/suite/forgectrl.py +++ b/forgetest/forgetest/suite/forgectrl.py @@ -281,7 +281,9 @@ def settings_bounds(ctx): ("forgectrl", "src/curverec.*")], description="The panel page is served, /status carries the machine telemetry the panel and " "the acceptance tool read (including the sys block: CPU busy percent over the " - "interval since the previous read, memory used percent), and /cam/status " + "interval since the previous read, memory used percent; and homed_axes, " + "the axes that carry a reference, Z alone once the lens has taken its " + "own at the controller's start), and /cam/status " "answers. In GRBL mode with a live controller, /status also echoes the " "controller's published state file as the grbl block (fresh age, machine " "state, sender session, laser window and dose model, modal report), " @@ -328,6 +330,32 @@ def panel_serves(ctx): ctx.check(isinstance(sys_.get("mem_pct"), (int, float)) and 0.0 < sys_["mem_pct"] < 100.0, "/status sys.mem_pct is not a percent: %s", sys_) + # The lens takes its own reference on the hall edge at every + # controller start, so with a controller running Z is referenced on + # its own while X and Y wait for a home: homed_axes names the axes + # that carry one, and Z reads inside the lens reach rather than the + # zero an unreferenced axis would show. + axes = s.get("homed_axes") + ev["homed_axes"] = axes + ctx.check(isinstance(axes, int) and 0 <= axes <= 7, + "/status homed_axes is not an axis mask: %s", axes) + ctx.check(bool(s.get("homed")) == (axes == 7), + "/status homed (%s) disagrees with homed_axes (%s)", + s.get("homed"), axes) + st, mode0 = fc.get("/mode") + if isinstance(mode0, dict) and mode0.get("controller") == "running": + lens = s.get("lens") or {} + pos = s.get("pos") or {} + ctx.log("/status homed_axes=%s pos.z=%s reach=%s..%s", axes, + pos.get("z"), lens.get("reach_min"), lens.get("reach_max")) + ctx.check(axes is not None and axes & 4, + "Z is not referenced with a controller running: %s", axes) + ctx.check(isinstance(pos.get("z"), (int, float)) + and lens.get("reach_min") is not None + and lens["reach_min"] <= pos["z"] <= lens["reach_max"], + "Z %s is outside the lens reach %s..%s", pos.get("z"), + lens.get("reach_min"), lens.get("reach_max")) + st, cam = fc.get("/cam/status") ev["cam_status"] = st ctx.log("GET /cam/status -> %s %s", st, cam) diff --git a/scripts/bench/laser_stream_test.py b/scripts/bench/laser_stream_test.py index c696438..be23a58 100644 --- a/scripts/bench/laser_stream_test.py +++ b/scripts/bench/laser_stream_test.py @@ -146,12 +146,11 @@ JOB_M4 = [ "M5", ] -# Session Z: the lens is in the stream. The sender references the lens -# (M103, as a commissioning card does), then a 1 mm move up and back at -# the screw's 2.922 half-steps per millimeter: three Z steps with the -# direction bit set, three with it clear. +# Session Z: the lens is in the stream. The lens is referenced on its hall +# edge at the controller's start, so Z is already open here: a 1 mm move up +# and back at the screw's 2.922 half-steps per millimeter, three Z steps +# with the direction bit set, three with it clear. JOB_Z = [ - "M103 Z3 P14 Q20", "G0 Z4", "G0 Z3", ]