mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 01:01:12 -07:00
Do not fail a hand-back on the step the counters round to
The post-run pass compared the kernel position counters exactly, so a test that put the head back within a hundredth of a millimeter failed whenever that distance did not round to the same step. motion.lid-cancel-home found it on the bench reference. The test cancels a job with the lid twice and the controller returns the head to the job start each time, landing 0.038 and 0.037 mm out - the same figures as the run before it, which passed. This time the two returns left four steps on X, 0.075 mm at 53.333 steps per mm, and the hand-back called it a leftover. Twenty-three runs of that test, all passing, on returns of the same accuracy: whether the residue rounds to zero is chance, not a property of the machine or of the test. A difference inside POSITION_DEADBAND_MM (0.1 mm, five steps at x8) on X and Y is now the quantization rather than a leftover. The head is still put back, so nothing accumulates over a campaign - only the failure goes. Z stays exact: the return never moves the lens, so a Z difference is still a leftover and still unrestorable. Host-proven: four new unit tests on the boundary (four steps and a moved Z on either side of it, and an unreadable reading), and the forgetest suite.
This commit is contained in:
@@ -133,6 +133,13 @@ GRBL_PORT_S = 30 # the Grbl port after the supervisor reports
|
|||||||
|
|
||||||
XY_STEPS_PER_MM = 53.333 # boards/glowforge.h (x8 microstepping)
|
XY_STEPS_PER_MM = 53.333 # boards/glowforge.h (x8 microstepping)
|
||||||
RETURN_MAX_MM = 100.0 # a displaced head is jogged back at most this far
|
RETURN_MAX_MM = 100.0 # a displaced head is jogged back at most this far
|
||||||
|
# The counters count steps; the controller's own returns land within a
|
||||||
|
# tenth of a millimeter of where they started, not on the step. A
|
||||||
|
# difference under this is that quantization, not a leftover: the head is
|
||||||
|
# still put back, so nothing accumulates over a campaign, but the test
|
||||||
|
# that made it does not fail for it. X and Y only - Z is the lens, which
|
||||||
|
# the return never moves, so any Z difference is still a leftover.
|
||||||
|
POSITION_DEADBAND_MM = 0.1
|
||||||
|
|
||||||
# The XY microstep mode (the xy_microsteps setting: 8, 16 or 32; unset =
|
# The XY microstep mode (the xy_microsteps setting: 8, 16 or 32; unset =
|
||||||
# 8). The GRBL controller reads it at its start and derives its scale,
|
# 8). The GRBL controller reads it at its start and derives its scale,
|
||||||
@@ -198,6 +205,19 @@ def read_position():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def position_quantized(was, now):
|
||||||
|
"""True when two step-counter readings differ by no more than the
|
||||||
|
dead band on X and Y and not at all on Z: the step quantization of a
|
||||||
|
move that landed where it meant to, rather than a leftover. A cancel
|
||||||
|
that returns the head to the job start lands within a few hundredths
|
||||||
|
of a millimeter, which is a step or four, and whether that rounds to
|
||||||
|
the same integer is chance."""
|
||||||
|
if was is None or now is None or now[2] != was[2]:
|
||||||
|
return False
|
||||||
|
return (abs(now[0] - was[0]) / XY_STEPS_PER_MM <= POSITION_DEADBAND_MM and
|
||||||
|
abs(now[1] - was[1]) / XY_STEPS_PER_MM <= POSITION_DEADBAND_MM)
|
||||||
|
|
||||||
|
|
||||||
def read_ring_residue():
|
def read_ring_residue():
|
||||||
"""Unplayed bytes queued in the kernel pulse ring: total written minus
|
"""Unplayed bytes queued in the kernel pulse ring: total written minus
|
||||||
processed (the cnc/position byte counters), or None when unreadable.
|
processed (the cnc/position byte counters), or None when unreadable.
|
||||||
@@ -677,7 +697,12 @@ class Baseline:
|
|||||||
was = captured.get("position")
|
was = captured.get("position")
|
||||||
now = read_position()
|
now = read_position()
|
||||||
if was is not None and now is not None and now != was and not self.cloud_mode():
|
if was is not None and now is not None and now != was and not self.cloud_mode():
|
||||||
left.append(Leftover("position", now, was, self._return_head(was, now)))
|
act = self._return_head(was, now)
|
||||||
|
if position_quantized(was, now):
|
||||||
|
self.log("position: %s (expected %s) -> %s; inside the %.2f mm dead band, "
|
||||||
|
"not a leftover" % (now, was, act, POSITION_DEADBAND_MM))
|
||||||
|
else:
|
||||||
|
left.append(Leftover("position", now, was, act))
|
||||||
was = captured.get("settings")
|
was = captured.get("settings")
|
||||||
if was:
|
if was:
|
||||||
st, body = self.fc_get("/settings")
|
st, body = self.fc_get("/settings")
|
||||||
|
|||||||
@@ -354,6 +354,31 @@ if __name__ == "__main__":
|
|||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
||||||
|
class PositionDeadbandTests(unittest.TestCase):
|
||||||
|
"""The counters count steps and a controller's own return lands within
|
||||||
|
a few hundredths of a millimeter, not on the step: a difference that
|
||||||
|
small is quantization, not a leftover. Found on the bench reference,
|
||||||
|
where motion.lid-cancel-home returned the head twice, each within
|
||||||
|
0.04 mm, and failed the hand-back on the four steps left over."""
|
||||||
|
|
||||||
|
def test_a_few_steps_of_xy_are_the_quantization(self):
|
||||||
|
self.assertTrue(baseline.position_quantized([0, 0, 0], [4, 0, 0]))
|
||||||
|
self.assertTrue(baseline.position_quantized([0, 0, 0], [-4, 4, 0]))
|
||||||
|
self.assertTrue(baseline.position_quantized([10, 20, 30], [10, 20, 30]))
|
||||||
|
|
||||||
|
def test_past_the_dead_band_is_a_leftover(self):
|
||||||
|
over = int(baseline.POSITION_DEADBAND_MM * baseline.XY_STEPS_PER_MM) + 2
|
||||||
|
self.assertFalse(baseline.position_quantized([0, 0, 0], [over, 0, 0]))
|
||||||
|
self.assertFalse(baseline.position_quantized([0, 0, 0], [0, over, 0]))
|
||||||
|
|
||||||
|
def test_z_is_exact_because_the_return_never_moves_it(self):
|
||||||
|
self.assertFalse(baseline.position_quantized([0, 0, 0], [0, 0, 1]))
|
||||||
|
|
||||||
|
def test_an_unreadable_reading_is_never_quantization(self):
|
||||||
|
self.assertFalse(baseline.position_quantized(None, [0, 0, 0]))
|
||||||
|
self.assertFalse(baseline.position_quantized([0, 0, 0], None))
|
||||||
|
|
||||||
|
|
||||||
class BaselineModeTests(BaselineTests):
|
class BaselineModeTests(BaselineTests):
|
||||||
"""The baseline against a fake forgectrl: what the mode in force
|
"""The baseline against a fake forgectrl: what the mode in force
|
||||||
owns. Reuses the fake sysfs tree of BaselineTests; only the new
|
owns. Reuses the fake sysfs tree of BaselineTests; only the new
|
||||||
|
|||||||
Reference in New Issue
Block a user