mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 09:11:11 -07:00
XY microstep modes: the baseline, the catalog test and the bench tools
The baseline derives x/y_mode, step_freq, ramp_rate and the configured markers from the xy_microsteps setting; ramp_rate joins the GRBL controller's set (the driver writes it; the cloud client runs at the module's). motion.microstep-modes cycles 8, 16 and 32: the save restarts the idle controller, the kernel reads the mode with its tick and ramp, $100/$101 are the mode's and a typed $100 is overwritten, a 40 mm jog at top speed returns to Idle with the kernel counters over the mode agreeing with the commanded travel and the accelerometer seeing the head move; the setting is put back as found. Bench tools: xy_mode_test.py (the null-sink harness the grblHAL CI runs), raster_dry.py (a top-speed raster per mode), xy_pattern_accel.py (the operator's pattern from home with the machine silent and the head accelerometer listening), arc_tolerance_sweep.py (a $12 ladder on the 9 in circle: the chord rate the protocol loop feeds, about 300 a second, is the ceiling, not the core), and the xymode and xycircle live drills. BRINGUP carries the present state and the facts; CAMPAIGN-LOG the dated record, including the planner-blocks spin (a $398 of 255 or more loops forever at start, a core bug) and its recovery.
This commit is contained in:
@@ -46,10 +46,11 @@ from .log import now_ts
|
||||
# motor_lock/x_mode/y_mode/x_decay/y_decay and the hold currents are the
|
||||
# GRBL controller's init writes (glowforge_io.c): motor_lock 0, every axis
|
||||
# in the pulse path (a job's Z moves the lens; the driver's Z soft limit
|
||||
# guards it), step_freq its default machine tick, ramp_rate the module
|
||||
# default; streaming is only ever 1 inside a live job; the head white LED
|
||||
# is a camera lamp, off at idle; the loop heater and TEC are the
|
||||
# diagnostics' tools, off at idle.
|
||||
# guards it), step_freq its machine tick and ramp_rate the kernel stop
|
||||
# ramp, both derived from the XY microstep mode (the values here are the
|
||||
# x8 default's; fixed_sysfs() gives the mode's); streaming is only ever 1
|
||||
# inside a live job; the head white LED is a camera lamp, off at idle; the
|
||||
# loop heater and TEC are the diagnostics' tools, off at idle.
|
||||
FIXED_SYSFS = [
|
||||
("cnc/motor_lock", "0"),
|
||||
("cnc/x_mode", "8"),
|
||||
@@ -68,11 +69,12 @@ FIXED_SYSFS = [
|
||||
|
||||
# The subset the GRBL controller writes at its start: checked and restored
|
||||
# in GRBL mode only. The cloud client sets its own values for these from
|
||||
# every pulse header (step_freq 10 kHz, the run currents) and hands the
|
||||
# hold currents back at idle; forcing the GRBL values under it would be
|
||||
# the baseline configuring another controller's machine.
|
||||
# every pulse header (step_freq 10 kHz, the run currents), runs at the
|
||||
# service's own x8 with the module's ramp whatever xy_microsteps says, and
|
||||
# hands the hold currents back at idle; forcing the GRBL values under it
|
||||
# would be the baseline configuring another controller's machine.
|
||||
GRBL_CONTROLLER_SYSFS = ("cnc/motor_lock", "cnc/x_mode", "cnc/y_mode", "cnc/x_decay", "cnc/y_decay",
|
||||
"cnc/step_freq", "pic/x_step_current", "pic/y_step_current")
|
||||
"cnc/step_freq", "cnc/ramp_rate", "pic/x_step_current", "pic/y_step_current")
|
||||
|
||||
# Settings the baseline never hands back as bare settings: controller_mode
|
||||
# is the persisted mirror of the live mode (the mode item restores it
|
||||
@@ -117,6 +119,41 @@ GRBL_PORT_S = 30 # the Grbl port after the supervisor reports
|
||||
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
|
||||
|
||||
# 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,
|
||||
# its machine tick and the kernel stop ramp from it (boards/glowforge.h):
|
||||
# the fixed values of x/y_mode, step_freq and ramp_rate are the mode's.
|
||||
XY_MODES = (8, 16, 32)
|
||||
XY_MODE_DEFAULT = 8
|
||||
XY_STEPS_PER_MM_OF = {8: 53.333, 16: 106.667, 32: 213.333} # $100/$101 per mode
|
||||
XY_TICK_X8_HZ = 28160 # the x8 machine tick
|
||||
XY_RAMP_X8_HZ_PER_S = 125000 # the kernel stop ramp at it
|
||||
|
||||
|
||||
def xy_mode_of(settings):
|
||||
"""The mode a /settings body names (unset, unreadable, or not a mode
|
||||
reads as the default)."""
|
||||
try:
|
||||
mode = int((settings or {}).get("xy_microsteps") or XY_MODE_DEFAULT)
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
return XY_MODE_DEFAULT
|
||||
return mode if mode in XY_MODES else XY_MODE_DEFAULT
|
||||
|
||||
|
||||
def xy_steps_per_mm(mode):
|
||||
return XY_STEPS_PER_MM_OF[mode]
|
||||
|
||||
|
||||
def fixed_sysfs(mode=XY_MODE_DEFAULT):
|
||||
"""FIXED_SYSFS with the mode's own x/y_mode, step_freq and ramp_rate:
|
||||
the tick and the ramp scale with the mode (x16 doubles both, x32
|
||||
quadruples them)."""
|
||||
k = mode // XY_MODE_DEFAULT
|
||||
own = {"cnc/x_mode": str(mode), "cnc/y_mode": str(mode),
|
||||
"cnc/step_freq": str(XY_TICK_X8_HZ * k),
|
||||
"cnc/ramp_rate": str(XY_RAMP_X8_HZ_PER_S * k)}
|
||||
return [(attr, own.get(attr, val)) for attr, val in FIXED_SYSFS]
|
||||
|
||||
|
||||
def leds_root():
|
||||
r = os.environ.get("GF_LEDS_ROOT") or "/sys/class/leds/"
|
||||
@@ -228,6 +265,12 @@ class Baseline:
|
||||
except hw.HwError as e:
|
||||
return None, str(e)
|
||||
|
||||
def xy_mode(self):
|
||||
"""The XY microstep mode the settings name now (the default when
|
||||
forgectrl does not answer)."""
|
||||
st, body = self.fc_get("/settings")
|
||||
return xy_mode_of(body if st == 200 and isinstance(body, dict) else None)
|
||||
|
||||
def wait_settled(self, timeout=SETTLE_S, unreachable_s=10):
|
||||
"""Block until forgectrl reports a settled supervisor: motion
|
||||
verified (the probe passed), motion-fault (the ladder exhausted),
|
||||
@@ -510,7 +553,7 @@ class Baseline:
|
||||
if residue:
|
||||
left.append(Leftover("pulse ring", "%d unplayed bytes" % residue, "0 (nothing queued)",
|
||||
"unrestorable: the next run would replay them first"))
|
||||
for attr, want in FIXED_SYSFS:
|
||||
for attr, want in fixed_sysfs(self.xy_mode()):
|
||||
if self.cloud_mode() and attr in GRBL_CONTROLLER_SYSFS:
|
||||
continue
|
||||
got = hw.sysfs_read(attr)
|
||||
@@ -703,7 +746,7 @@ def check_fixed_against(ref, log):
|
||||
machine worth a look, not a leftover."""
|
||||
diffs = []
|
||||
sysfs = ref.get("sysfs") or {}
|
||||
for attr, want in FIXED_SYSFS + IDLE_READBACKS:
|
||||
for attr, want in fixed_sysfs(ref_xy_mode(ref)) + IDLE_READBACKS:
|
||||
got = sysfs.get(attr)
|
||||
if got is not None and got != want:
|
||||
diffs.append("%s: boot=%s constant=%s" % (attr, got, want))
|
||||
@@ -719,34 +762,52 @@ def check_fixed_against(ref, log):
|
||||
# the state /mode already calls "running", because "running" is the spawn,
|
||||
# not the config. motor_lock is no marker: the probe and the controller
|
||||
# both leave it 0.
|
||||
CONFIGURED_MARKERS = [(a, dict(FIXED_SYSFS)[a]) for a in ("cnc/step_freq", "cnc/y_mode")]
|
||||
CONFIGURED_MARKER_ATTRS = ("cnc/step_freq", "cnc/y_mode")
|
||||
CONFIGURED_MARKERS = [(a, dict(FIXED_SYSFS)[a]) for a in CONFIGURED_MARKER_ATTRS]
|
||||
CONFIGURED_TIMEOUT_S = 20
|
||||
CONFIGURED_SETTLE_S = 1.0
|
||||
|
||||
|
||||
def configured_markers(mode=XY_MODE_DEFAULT):
|
||||
"""The markers with the mode's own values (the tick and the mode both
|
||||
follow the setting)."""
|
||||
return [(a, dict(fixed_sysfs(mode))[a]) for a in CONFIGURED_MARKER_ATTRS]
|
||||
|
||||
|
||||
def ref_xy_mode(ref):
|
||||
"""The XY microstep mode a saved reference was taken under: its own
|
||||
/settings dump, else the default."""
|
||||
fc = (ref or {}).get("forgectrl") or {}
|
||||
return xy_mode_of(fc.get("/settings") if isinstance(fc, dict) else None)
|
||||
|
||||
|
||||
def reference_preconfig(ref):
|
||||
"""True when a saved reference shows the pre-controller state: every
|
||||
marker present differs from its fixed value (the probe's step_freq and
|
||||
the module's y_mode together), i.e. it was dumped before the
|
||||
controller's init writes landed."""
|
||||
sysfs = (ref or {}).get("sysfs") or {}
|
||||
seen = [(sysfs.get(a), want) for a, want in CONFIGURED_MARKERS if sysfs.get(a) is not None]
|
||||
markers = configured_markers(ref_xy_mode(ref))
|
||||
seen = [(sysfs.get(a), want) for a, want in markers if sysfs.get(a) is not None]
|
||||
return bool(seen) and all(got != want for got, want in seen)
|
||||
|
||||
|
||||
def wait_controller_configured(log, mode_body, timeout=CONFIGURED_TIMEOUT_S, sleep=time.sleep):
|
||||
def wait_controller_configured(log, mode_body, timeout=CONFIGURED_TIMEOUT_S, sleep=time.sleep,
|
||||
xy_mode=XY_MODE_DEFAULT):
|
||||
"""After the supervisor reports the controller running: block until the
|
||||
GRBL controller's init writes have landed (the CONFIGURED_MARKERS read
|
||||
their fixed values), then a short settle. Only the GRBL controller
|
||||
writes those; in any other mode return at once. Bounded: on timeout
|
||||
the caller proceeds and the reference will say so."""
|
||||
GRBL controller's init writes have landed (the configured markers read
|
||||
the fixed values of the XY microstep mode in force), then a short
|
||||
settle. Only the GRBL controller writes those; in any other mode
|
||||
return at once. Bounded: on timeout the caller proceeds and the
|
||||
reference will say so."""
|
||||
if not (isinstance(mode_body, dict) and mode_body.get("controller") == "running"
|
||||
and (mode_body.get("mode") or "grbl") == "grbl"):
|
||||
return True
|
||||
markers = configured_markers(xy_mode)
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
got = [(a, hw.sysfs_read(a)) for a, _ in CONFIGURED_MARKERS]
|
||||
if all(g == want for (a, g), (_, want) in zip(got, CONFIGURED_MARKERS)):
|
||||
got = [(a, hw.sysfs_read(a)) for a, _ in markers]
|
||||
if all(g == want for (a, g), (_, want) in zip(got, markers)):
|
||||
sleep(CONFIGURED_SETTLE_S)
|
||||
log("baseline: controller configured %.1f s after running" % (time.time() - t0))
|
||||
return True
|
||||
@@ -755,7 +816,7 @@ def wait_controller_configured(log, mode_body, timeout=CONFIGURED_TIMEOUT_S, sle
|
||||
sleep(0.25)
|
||||
log("baseline: WARNING - controller did not apply its config within %d s (%s); "
|
||||
"the reference may show the supervisor's probe values"
|
||||
% (timeout, ", ".join("%s=%s" % (a, hw.sysfs_read(a)) for a, _ in CONFIGURED_MARKERS)))
|
||||
% (timeout, ", ".join("%s=%s" % (a, hw.sysfs_read(a)) for a, _ in markers)))
|
||||
return False
|
||||
|
||||
|
||||
@@ -794,7 +855,7 @@ def boot_reference(log, data_dir):
|
||||
return None
|
||||
bl = Baseline(log)
|
||||
mode = bl.wait_settled()
|
||||
wait_controller_configured(log, mode)
|
||||
wait_controller_configured(log, mode, xy_mode=bl.xy_mode())
|
||||
ref = dump_all(bl)
|
||||
ref.update({"ts": now_ts(), "boot_id": bid, "uptime_s": uptime_s()})
|
||||
try:
|
||||
|
||||
@@ -249,6 +249,38 @@ TOOLS = [
|
||||
"desc": "The Z soft limit belongs to the driver, not to $20: an unreferenced Z is collapsed to where the "
|
||||
"lens stands, and neither a $20 nor a $132 write frees it. A CI harness (the grblHAL repo): needs "
|
||||
"the host-built null-sink controller, not the machine, so it is not a bench-page tool."},
|
||||
{"id": "raster-dry", "title": "Dry top-speed raster per XY microstep mode", "script": "raster_dry.py",
|
||||
"safety": "dry", "where": "board", "ported": True,
|
||||
"args": [_arg("modes", "choice", "8 16 32", "the modes to run, in order", ["8", "16", "32", "8 16 32"])],
|
||||
"desc": "For each mode given: stores xy_microsteps, waits for the restarted controller, streams a 60-pass "
|
||||
"raster of 150 mm at F12000 with the laser off, and reports the peak feed, the controller CPU, the "
|
||||
"kernel counters against the start, underruns and clamped events. Needs the head with 150 mm of "
|
||||
"free +X and 12 mm of free +Y travel, no other Grbl client. Ends at x8."},
|
||||
{"id": "xy-pattern-accel", "title": "XY microstep modes by the head accelerometer, the machine silent",
|
||||
"script": "xy_pattern_accel.py", "safety": "dry", "where": "board", "ported": True,
|
||||
"args": [_arg("modes", "choice", "8 16 32", "the modes to run, in order", ["8", "16", "32", "8 16 32"])],
|
||||
"desc": "From home at F12000: to (18, 9) in, to (9, 9) in, a 9 in circle from its mid-bottom back to "
|
||||
"(9, 9), to (9, 0), home. Per mode: every fan, the coolant pump and the TEC commanded off through "
|
||||
"the engine's quiet hold (the machine silent, the modes audible), the fixed 10 s wait, the head "
|
||||
"accelerometer sampled over the bus at 800 Hz through the pattern; "
|
||||
"cruise-window RMS and peak-to-peak per leg and overall, the kernel counters against home, "
|
||||
"underruns; JSON with the trace to the bench data directory. Needs the machine homed and at home, "
|
||||
"the lid closed, no other Grbl client, the bed clear. Ends at x8."},
|
||||
{"id": "arc-tolerance-sweep", "title": "How fine an arc the controller can plan ($12 ladder)",
|
||||
"script": "arc_tolerance_sweep.py", "safety": "dry", "where": "board", "ported": True,
|
||||
"args": [_arg("mode", "choice", "16", "the XY microstep mode", ["8", "16", "32"], flag="--mode"),
|
||||
_arg("ladder", "str", "0.002 0.001 0.0005 0.00025 0.0001", "the $12 values to try, in order")],
|
||||
"desc": "From home, the machine silent: to (9, 9) in, then the 9 in circle at F12000 once per $12 in the "
|
||||
"ladder. Per rung: the chords and boundaries a second, the circle time against the ideal, the lowest "
|
||||
"feed mid-circle, the fewest free planner blocks, the controller CPU, clamped events, underruns, the "
|
||||
"accelerometer's cruise RMS. $12 goes back to what it was; the head returns home. Needs the machine "
|
||||
"homed and at home, the lid closed, no other Grbl client."},
|
||||
{"id": "xy-mode-test", "title": "XY microstep mode harness", "script": "xy_mode_test.py",
|
||||
"safety": "dry", "where": "host", "ported": False, "args": [],
|
||||
"desc": "The XY scale is the microstep mode's, never typed: xy_microsteps sets $100/$101 and the "
|
||||
"machine tick, a typed $100 is overwritten, and $110/$111 are held under a lowered tick. A CI "
|
||||
"harness (the grblHAL repo): needs the host-built null-sink controller, not the machine, so it "
|
||||
"is not a bench-page tool."},
|
||||
{"id": "puls-profile", "title": "Factory .puls profile decoder", "script": "puls_profile.py",
|
||||
"safety": "dry", "where": "host", "ported": False, "args": [],
|
||||
"desc": "Decodes factory pulse streams into velocity/accel profiles. Runs anywhere; needs a .puls file "
|
||||
|
||||
@@ -23,7 +23,7 @@ job's length for its progress report.
|
||||
import gzip
|
||||
import struct
|
||||
|
||||
STEPS_PER_MM = 53.333 # boards/glowforge.h, x8 microstepping
|
||||
STEPS_PER_MM = 53.333 # x8 microstepping: the service's scale, which cloud mode runs at
|
||||
|
||||
# The header of a factory print for this machine type, as captured.
|
||||
PRINT_HEADER = {
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
import time
|
||||
|
||||
from ..catalog import test
|
||||
from .. import baseline as _baseline
|
||||
from .. import hw
|
||||
from ..runner import Failed
|
||||
|
||||
@@ -289,6 +290,145 @@ def jog_roundtrip(ctx):
|
||||
"motion on %d of %d legs", len(moves), maxrate, drift, max(p2px, p2py), len(moving), len(moves))
|
||||
|
||||
|
||||
# ---------------------------------------------------------- microstep modes
|
||||
|
||||
def grbl_setting(g, key):
|
||||
"""One $-setting's value from the controller, or None."""
|
||||
for line in g.command(key):
|
||||
if line.startswith(key + "="):
|
||||
try:
|
||||
return float(line[len(key) + 1:].split()[0])
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def set_xy_mode(ctx, fc, value):
|
||||
"""Store xy_microsteps (empty = clear it) and wait for the GRBL
|
||||
controller forgectrl restarts for it to come back configured, with
|
||||
the Grbl port answering. Returns the /mode body the machine settled
|
||||
on."""
|
||||
if value:
|
||||
st, body = fc.post("/settings", data={"xy_microsteps": value})
|
||||
else:
|
||||
st, body = fc.post("/settings", params={"xy_microsteps": ""})
|
||||
ctx.check(st == 200, "could not set xy_microsteps=%r (%s: %s)", value or "(clear)", st, body)
|
||||
mode = int(value or _baseline.XY_MODE_DEFAULT)
|
||||
ctx.sleep(2.0) # the controller's stop is under way
|
||||
bl = _baseline.Baseline(ctx.log, abort=ctx.run.aborted.is_set)
|
||||
body = bl.wait_settled()
|
||||
ctx.check(isinstance(body, dict) and body.get("controller") == "running",
|
||||
"the controller did not come back after the xy_microsteps write (/mode: %s)", body)
|
||||
ctx.check(_baseline.wait_controller_configured(ctx.log, body, xy_mode=mode),
|
||||
"the controller came back but did not apply its x%d config", mode)
|
||||
end = time.time() + _baseline.GRBL_PORT_S
|
||||
while True:
|
||||
ctx.checkpoint()
|
||||
try:
|
||||
with hw.Grbl():
|
||||
return body
|
||||
except OSError:
|
||||
if time.time() > end:
|
||||
raise Failed("the Grbl port did not answer within %d s of the restart"
|
||||
% _baseline.GRBL_PORT_S)
|
||||
time.sleep(1.0)
|
||||
|
||||
|
||||
@test("motion.microstep-modes",
|
||||
title="XY microstep modes: the setting drives the drivers, the scale and the tick",
|
||||
subsystem="motion", kind="auto", mode="grbl", est_min=5,
|
||||
covers=_MOTION_COVERS + [("forgectrl", "src/main.c"), ("forgectrl", "src/settings.*")],
|
||||
requires=["motion.jog-roundtrip"],
|
||||
steps=["Bed clear, lid closed; the head needs 40 mm of free +X travel."],
|
||||
description="One setting, xy_microsteps, is the XY scale. For each mode it admits (8, 16, "
|
||||
"32): the save restarts the idle controller; the kernel reads the mode on both "
|
||||
"axes with the mode's machine tick and stop ramp; $100/$101 are the mode's and a "
|
||||
"typed $100 is overwritten on the spot; a 40 mm jog out and back at the top speed "
|
||||
"returns to Idle with the kernel counters, over the mode, agreeing with the "
|
||||
"commanded travel, no drift, and the head accelerometer seeing the head move. "
|
||||
"The setting is put back as found, with the controller restarted for it.")
|
||||
def microstep_modes(ctx):
|
||||
ev = ctx.evidence
|
||||
fc = ctx.forgectrl
|
||||
ctx.check(hw.AccelSampler().available,
|
||||
"no head accelerometer found (i2c %s): the motion witness is missing", hw.HEAD_ACCEL_I2C)
|
||||
was = (fc.settings() or {}).get("xy_microsteps") or ""
|
||||
ev["xy_microsteps_before"] = was or "(unset)"
|
||||
results = []
|
||||
try:
|
||||
for mode in _baseline.XY_MODES:
|
||||
ctx.checkpoint()
|
||||
ctx.log("--- x%d ---", mode)
|
||||
set_xy_mode(ctx, fc, str(mode))
|
||||
r = {"mode": mode, "sysfs": {}}
|
||||
results.append(r) # in the evidence even when a check fails
|
||||
for attr, want in _baseline.fixed_sysfs(mode):
|
||||
if attr not in ("cnc/x_mode", "cnc/y_mode", "cnc/step_freq", "cnc/ramp_rate"):
|
||||
continue
|
||||
got = hw.sysfs_read(attr)
|
||||
r["sysfs"][attr] = got
|
||||
ctx.check(got == want, "x%d: %s reads %s, want %s", mode, attr, got, want)
|
||||
spm = _baseline.xy_steps_per_mm(mode)
|
||||
accel = hw.AccelSampler()
|
||||
with ctx.grbl() as g, accel:
|
||||
clean_slate(ctx, g)
|
||||
for key in ("$100", "$101"):
|
||||
v = grbl_setting(g, key)
|
||||
r[key] = v
|
||||
ctx.check(v is not None and abs(v - spm) < 0.001,
|
||||
"x%d: %s=%s, want %.3f", mode, key, v, spm)
|
||||
g.command("$100=53.333")
|
||||
v = grbl_setting(g, "$100")
|
||||
r["typed_100"] = v
|
||||
ctx.check(v is not None and abs(v - spm) < 0.001,
|
||||
"x%d: a typed $100 stuck at %s (want %.3f: the scale is the mode's)", mode, v, spm)
|
||||
|
||||
start = g.status_report()["MPos"]
|
||||
k0 = kernel_xy_mm(ctx)
|
||||
t0 = time.time()
|
||||
rr = g.command("$J=G91X40F12000")
|
||||
ctx.check(not any(x.startswith("error") for x in rr), "x%d: jog refused: %s", mode, rr)
|
||||
peak, states, _ = wait_idle(ctx, g, 30)
|
||||
ctx.check("TIMEOUT" not in states, "x%d: the jog out did not return to Idle: %s", mode, states)
|
||||
# grblHAL's Idle leads the kernel by the stream depth and
|
||||
# the decel tail: the counters are read once the machine
|
||||
# itself is idle.
|
||||
machine_idle(ctx)
|
||||
out_mm = kernel_xy_mm(ctx)[0] - k0[0]
|
||||
r["kernel_out_mm"] = round(out_mm, 3)
|
||||
r["peak"] = peak
|
||||
ctx.check(abs(out_mm - 40.0) <= 0.2,
|
||||
"x%d: the kernel counters say %.3f mm for a 40 mm jog: the scale and the "
|
||||
"drivers' mode disagree", mode, out_mm)
|
||||
rr = g.command("$J=G91X-40F12000")
|
||||
ctx.check(not any(x.startswith("error") for x in rr), "x%d: jog back refused: %s", mode, rr)
|
||||
peak2, states, _ = wait_idle(ctx, g, 30)
|
||||
ctx.check("TIMEOUT" not in states, "x%d: the jog back did not return to Idle: %s", mode, states)
|
||||
machine_idle(ctx)
|
||||
t1 = time.time()
|
||||
final = g.status_report()["MPos"]
|
||||
drift = abs(final[0] - start[0])
|
||||
p2px, p2py, n = accel.p2p(t0, t1)
|
||||
r.update({"drift_mm": round(drift, 3), "accel_p2p": [p2px, p2py], "accel_samples": n})
|
||||
ctx.log("x%d: out %.3f mm by the kernel, peak %.0f/%.0f mm/min, drift %.3f mm, "
|
||||
"accel p2p x=%d y=%d over %d samples",
|
||||
mode, out_mm, peak, peak2, drift, p2px, p2py, n)
|
||||
ctx.check(drift <= 0.05, "x%d: position drift %.3f mm", mode, drift)
|
||||
ctx.check(max(peak, peak2) >= 6000, "x%d: the jog peaked at only %.0f mm/min", mode,
|
||||
max(peak, peak2))
|
||||
ctx.check(max(p2px, p2py) >= hw.ACCEL_P2P_MOVING,
|
||||
"x%d: the head did not move during the jog (accel p2p x=%d y=%d, below %d): "
|
||||
"the counters ran without the gantry", mode, p2px, p2py, hw.ACCEL_P2P_MOVING)
|
||||
machine_idle(ctx)
|
||||
finally:
|
||||
ev["modes"] = results
|
||||
set_xy_mode(ctx, fc, was)
|
||||
ev["xy_microsteps_after"] = (fc.settings() or {}).get("xy_microsteps") or "(unset)"
|
||||
ctx.log("PASS: %s", "; ".join("x%d: %.3f mm, drift %.3f, accel %d"
|
||||
% (r["mode"], r["kernel_out_mm"], r["drift_mm"], max(r["accel_p2p"]))
|
||||
for r in results))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- liveness
|
||||
|
||||
@test("motion.liveness-probe", title="Supervisor motion-liveness verdict", subsystem="motion",
|
||||
|
||||
Reference in New Issue
Block a user