mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
forgetest: take the kernel-counter reference only when the machine is at rest
Bench (image 20260817124714): motion.lid-cancel-home FAILED on a machine that did the right thing twice. Both phases logged the cancel, the reset banner and "returned to the job start" with grbl drift 0.000, and the counters ended at the true rest position - but the hold phase's REFERENCE was 2.87 mm off, so the comparison measured a transient. The reference was sampled straight after the jogs that close the running phase, and those wait on grblHAL's Idle. grblHAL is Idle when its planner is empty; the kernel is still playing the stream depth and the decel tail behind that. A counter read in that window records a position the head is only passing through - and the resulting drift reads exactly like the failure this check exists to catch, a move counted by grbl but never played by the machine. So the check stays as strict as it is (it is the one that caught the stranded ring on 2026-08-17): what changes is where the reference comes from. kernel_start() waits for the MACHINE to be idle - the helper the motion tests already end on - before reading the counters, and every reference capture in the lid, interlock and live cancel tests goes through it.
This commit is contained in:
@@ -17,8 +17,8 @@ import time
|
||||
from ..catalog import test
|
||||
from .. import hw
|
||||
from ..runner import Failed
|
||||
from .motion import (kernel_xy_mm, check_kernel_returned, wait_state, wait_state_text,
|
||||
wait_left_state, wait_idle, drain_text)
|
||||
from .motion import (kernel_xy_mm, kernel_start, check_kernel_returned, wait_state,
|
||||
wait_state_text, wait_left_state, wait_idle, drain_text)
|
||||
|
||||
_LASER_COVERS = [("grblhal-glowforge", "src/**"), ("kernel-module-glowforge", "**"),
|
||||
("forgectrl", "src/super.c"), ("forgectrl", "src/cool.c"),
|
||||
@@ -536,8 +536,8 @@ def pause_resume_lid_cancel(ctx):
|
||||
ev = ctx.evidence
|
||||
with ctx.grbl() as g, LiveJob(ctx, g):
|
||||
prepare(ctx, g)
|
||||
k0 = kernel_start(ctx)
|
||||
start = g.status_report()["MPos"]
|
||||
k0 = kernel_xy_mm(ctx)
|
||||
ev["start"] = start
|
||||
ev["kernel_start"] = k0
|
||||
smp = arm_and_fire(ctx, g, job=MARK_JOB_M3)
|
||||
|
||||
@@ -614,6 +614,19 @@ def kernel_xy_mm(ctx):
|
||||
return float(pos.get("x", 0.0)), float(pos.get("y", 0.0))
|
||||
|
||||
|
||||
def kernel_start(ctx, timeout=15.0):
|
||||
"""The kernel counters AT REST, for use as a reference point.
|
||||
|
||||
grblHAL reports Idle when its planner is empty; the kernel is still
|
||||
playing the stream depth and the decel tail behind that. Sampling the
|
||||
counters in that window records a position the head is only passing
|
||||
through, and every later comparison is then measured against a
|
||||
transient - which reads exactly like the failure this reference exists
|
||||
to catch (a move counted but never played)."""
|
||||
machine_idle(ctx, timeout)
|
||||
return kernel_xy_mm(ctx)
|
||||
|
||||
|
||||
def check_kernel_returned(ctx, ev, k0, tol_mm=0.1, tag=""):
|
||||
"""After a return-to-start: the kernel counters must be back where the
|
||||
job started too. grbl's own drift can read 0.000 while the head never
|
||||
@@ -752,8 +765,8 @@ def lid_cancel_home(ctx):
|
||||
ctx.check(policy == "cancel", "lid_policy is %r; this test needs cancel", policy)
|
||||
with ctx.grbl() as g:
|
||||
clean_slate(ctx, g)
|
||||
k0 = kernel_start(ctx)
|
||||
start = g.status_report()["MPos"]
|
||||
k0 = kernel_xy_mm(ctx)
|
||||
ev["kernel_start"] = k0
|
||||
ev["start"] = start
|
||||
g.command("M5")
|
||||
@@ -779,8 +792,11 @@ def lid_cancel_home(ctx):
|
||||
# A job paused on the button must not be resumable past a lid open:
|
||||
# the armed window and the hardware button latch have to agree, so
|
||||
# the lid ends the job here exactly as it does from Run.
|
||||
# The reference for this phase is taken only once the MACHINE is at
|
||||
# rest: the jogs above waited for grblHAL's Idle, which arrives while
|
||||
# the kernel is still playing their tail.
|
||||
k1 = kernel_start(ctx)
|
||||
start2 = g.status_report()["MPos"]
|
||||
k1 = kernel_xy_mm(ctx)
|
||||
ev["hold_start"] = start2
|
||||
g.command("G91") # the reset restored G90
|
||||
g.command("G1X40F300", timeout=0.5)
|
||||
@@ -834,8 +850,8 @@ def interlock_cancel_home(ctx):
|
||||
ctx.check(sw.get("interlock_ok"), "the interlock loop already reads open - close it before this test")
|
||||
with ctx.grbl() as g:
|
||||
clean_slate(ctx, g)
|
||||
k0 = kernel_start(ctx)
|
||||
start = g.status_report()["MPos"]
|
||||
k0 = kernel_xy_mm(ctx)
|
||||
ev["start"] = start
|
||||
ev["kernel_start"] = k0
|
||||
g.command("M5")
|
||||
|
||||
Reference in New Issue
Block a user