From eb9e61d3c66e80dc8a6f75e393981eafae126646 Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Mon, 28 Sep 2026 12:58:34 -0400 Subject: [PATCH] motion.soft-limits: past the envelope the home armed, and stopped before it fails After a home the driver's X/Y envelope reaches to the far edges the bed check measured (envelope_x_mm, envelope_y_mm, held by gfhome_clamp_envelope_mm to 50 mm up to the travel plus 30 mm), and to $130 by $131 only on a machine that was never measured. The test aimed its refusals at $130 + 5 and $131 + 5, which holds only on an unmeasured machine. On the bench reference, measured at X 508 and Y 288.925, image 20260928153805's campaign sent G90 G1 X500 F600, the controller rightly ran it, and the failed check ended the test before its soft reset, so the head drove the whole 476.6 mm while the restores were refused with 409 for 40 s; the jog check after it would have run a 500 mm jog the same way. The test now takes the far edges as the driver does and sends each move and the jog 5 mm past them, and it stops a move that was not refused (the soft reset, now ahead of the checks) and a jog that was not refused (the jog cancel) before it fails. The judgment is unchanged: ALARM:2 with no kernel motion, error 15 for the jog, a move inside the bed runs. Proof: pyflakes clean. On the bench reference, image 20260928153805 with this suite bind-mounted, the test passes: the far edges 508 and 288.925, G90 G1 X513, Y293.9 and X-1 each refused with ALARM:2 and 0.0 mm of kernel motion, the jog refused with error 15, the move inside the bed run, the head handed back where it was found, the baseline clean. Acceptance: the change is motion.soft-limits itself; its own function and description carry it, so no other test's fingerprint moves. --- forgetest/forgetest/suite/motion.py | 43 +++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/forgetest/forgetest/suite/motion.py b/forgetest/forgetest/suite/motion.py index bc752b8..ea3c2b5 100644 --- a/forgetest/forgetest/suite/motion.py +++ b/forgetest/forgetest/suite/motion.py @@ -1209,12 +1209,15 @@ def respawn_gate(ctx): "must refuse, and hands the head back where it found it."], description="The machine has no limit switches, so the core's $20 cannot be turned on and " "the X/Y soft limits are the driver's: off while the position is not trusted, on " - "after a home, when the envelope is the bed ($130 by $131 from the home corner). " + "after a home, when the envelope is the bed from the home corner: the far edges the " + "bed check measured (envelope_x_mm, envelope_y_mm, held to 50 mm up to the travel " + "plus 30 mm), or $130 by $131 on a machine that was never measured. " "With homing_mode = gfcloud and the camera-home offsets unset for the run, $H runs " "the web-service homing session, every motion of it run whole. Homed, a program " - "move 5 mm past X max or Y max, or past the near edge, raises ALARM:2 before any " - "motion (the kernel counters do not move), a jog past the bed is refused with " - "error 15, and a move inside the bed runs. The Z envelope is the lens window, as " + "move 5 mm past the far edge in X or in Y, or past the near edge, raises ALARM:2 " + "before any motion (the kernel counters do not move), a jog past the far edge is " + "refused with error 15, and a move inside the bed runs. A move or jog that is not " + "refused is stopped before the test fails. The Z envelope is the lens window, as " "before. The settings are put back as found, the camera home is dropped, and the " "head is jogged back to where the test found it by the travel the session's " "motions logged.") @@ -1253,6 +1256,22 @@ def soft_limits(ctx): x_travel = float(grbl_setting(g, "$130")) y_travel = float(grbl_setting(g, "$131")) ev["travel"] = {"x": x_travel, "y": y_travel} + + # The far edges the home armed, as the driver takes them + # (gfhome_clamp_envelope_mm): the measured key held to 50 mm up + # to the travel plus 30 mm, or the travel when it is unset. + def far_edge(key, travel): + try: + mm = float(s0.get(key) or 0) + except ValueError: + mm = 0.0 + if not mm > 0: + return travel + return min(max(mm, 50.0), travel + 30.0) + + x_edge = far_edge("envelope_x_mm", x_travel) + y_edge = far_edge("envelope_y_mm", y_travel) + ev["envelope"] = {"x": x_edge, "y": y_edge} k0 = kernel_xy_mm(ctx) def refused(cmd): @@ -1265,24 +1284,30 @@ def soft_limits(ctx): rec = {"cmd": cmd, "reply": lines, "alarm": "ALARM:2" in text, "moved_mm": round(moved, 3), "state": st} ctx.log("%s", rec) - ctx.check(rec["alarm"], "%s was not refused with ALARM:2: %s", cmd, lines) - ctx.check(moved < 0.05, "%s moved the kernel %.3f mm before the alarm", cmd, moved) + # Stopped first: a move that was not refused must not run on + # to its target while the test fails. g.realtime(0x18) ctx.sleep(1.5) g.drain() + ctx.check(rec["alarm"], "%s was not refused with ALARM:2: %s", cmd, lines) + ctx.check(moved < 0.05, "%s moved the kernel %.3f mm before the alarm", cmd, moved) unlock = g.command("$X") ctx.check(unlock and unlock[-1] == "ok", "$X after the soft-limit alarm: %s", unlock) st = g.status_report()["state"] ctx.check(st.startswith("Idle"), "controller is %s after the recovery", st) return rec - ev["refused"] = [refused("G90 G1 X%.1f F600" % (x_travel + 5)), - refused("G90 G1 Y%.1f F600" % (y_travel + 5)), + ev["refused"] = [refused("G90 G1 X%.1f F600" % (x_edge + 5)), + refused("G90 G1 Y%.1f F600" % (y_edge + 5)), refused("G90 G1 X-1 F600")] # Homed still: the soft-limit alarm and its reset keep the reference. ctx.check(fc.status().get("homed"), "the soft-limit alarm and the reset un-homed the machine") - jog = g.command("$J=G91X%.1fF1200" % (x_travel + 5)) + jog = g.command("$J=G91X%.1fF1200" % (x_edge + 5)) ev["jog"] = jog + if not any(l.startswith("error:15") for l in jog): + g.realtime(0x85) # the jog cancel, before the test fails + ctx.sleep(1.5) + g.drain() ctx.check(any(l.startswith("error:15") for l in jog), "a jog past the bed was not refused with error 15: %s", jog) # The core answers the line after an error with that error again