mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 01:01:12 -07:00
Finish the x32 xy_microsteps default in the baseline test and the stream harness
The x32 default landed in forgetest/baseline.py and the driver, but two callers still judged the machine at x8 and both failed on the host. forgetest/tests/test_baseline.py: setUp seeded the fake machine from the x8 FIXED_SYSFS literals while enforce() compares against fixed_sysfs() of the resolved mode, so x_mode, y_mode, step_freq and ramp_rate read as deviations on a clean machine - 23 failures across BaselineTests and TransientNotLeftoverTests. It seeds from fixed_sysfs() now, and the tick expectations come from it (DEFAULT_TICK) rather than a typed 28160. The xy_mode_of and ref_xy_mode unset/invalid cases expect 32, with an explicit "8" case added that had no coverage. Two reference_preconfig dumps taken on an x8 machine carry xy_microsteps = 8, because the markers are read at the reference's own mode. wait_configured wrote the static CONFIGURED_MARKERS where the function watches configured_markers() of the mode in force, and the held-controller jog typed 221 steps for "4.144 mm", which is 1.036 mm at x32; both derive from the mode now. 69 tests, all pass. scripts/bench/laser_stream_test.py: STEPS_PER_MM was the x8 53.333, so the X-peak check failed at 2133 steps against an expected 533. The whole harness now derives from XY_MICROSTEPS_BASE/DEFAULT the way glowforge.h and baseline.py do, which uncovered five more x8-only expectations behind the first: the machine tick, the fire-gap limit (it grows as sqrt(k), not k - a finer mode shortens the accel interval by sqrt(k) while speeding the tick by k), the rung split in fire_spans, the density period and minimum burst (laser_pulse_ticks is in x8 ticks and the stream scales it, so the config keeps the x8 numbers and the measured lengths scale), and the decel/hold budgets. Run against the null-sink build: all stream emission rules hold. xy_mode_test.py's docstring still described the no-key case as x8 while its own assertions had moved to x32. No behavior change and no acceptance-catalog consequence: these are test expectations and a bench harness, not image component sources. The coverage lint is unchanged at 0 uncovered paths.
This commit is contained in:
@@ -15,6 +15,11 @@ import unittest
|
|||||||
|
|
||||||
from forgetest import baseline
|
from forgetest import baseline
|
||||||
|
|
||||||
|
# The fixed machine tick at the mode an unset xy_microsteps reads as.
|
||||||
|
# Taken from baseline rather than typed, so a change to the default mode
|
||||||
|
# moves the expectations with it.
|
||||||
|
DEFAULT_TICK = dict(baseline.fixed_sysfs())["cnc/step_freq"]
|
||||||
|
|
||||||
|
|
||||||
class BaselineTests(unittest.TestCase):
|
class BaselineTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -26,8 +31,11 @@ class BaselineTests(unittest.TestCase):
|
|||||||
for name in baseline.BUTTON_LEDS + ("lid_led",):
|
for name in baseline.BUTTON_LEDS + ("lid_led",):
|
||||||
os.makedirs(self.leds + name)
|
os.makedirs(self.leds + name)
|
||||||
self._led(name, "0")
|
self._led(name, "0")
|
||||||
# a clean machine
|
# A clean machine at the mode an unset xy_microsteps reads as:
|
||||||
for attr, val in baseline.FIXED_SYSFS + baseline.IDLE_READBACKS:
|
# x/y_mode, step_freq and ramp_rate are the mode's, not the x8
|
||||||
|
# literals in FIXED_SYSFS, and that is what enforce() compares
|
||||||
|
# against.
|
||||||
|
for attr, val in baseline.fixed_sysfs() + baseline.IDLE_READBACKS:
|
||||||
self._attr(attr, val)
|
self._attr(attr, val)
|
||||||
self._attr("cnc/interlock_circuit", "45")
|
self._attr("cnc/interlock_circuit", "45")
|
||||||
self._attr("pic/lid_led", "0")
|
self._attr("pic/lid_led", "0")
|
||||||
@@ -95,7 +103,7 @@ class BaselineTests(unittest.TestCase):
|
|||||||
for x in left:
|
for x in left:
|
||||||
self.assertEqual(x.action, "restored", str(x))
|
self.assertEqual(x.action, "restored", str(x))
|
||||||
self.assertEqual(self._read("cnc/motor_lock"), "0")
|
self.assertEqual(self._read("cnc/motor_lock"), "0")
|
||||||
self.assertEqual(self._read("cnc/step_freq"), "28160")
|
self.assertEqual(self._read("cnc/step_freq"), DEFAULT_TICK)
|
||||||
self.assertEqual(self._read("cnc/streaming"), "0")
|
self.assertEqual(self._read("cnc/streaming"), "0")
|
||||||
self.assertEqual(items["cnc/motor_lock"].found, "15")
|
self.assertEqual(items["cnc/motor_lock"].found, "15")
|
||||||
self.assertEqual(items["cnc/motor_lock"].expected, "0")
|
self.assertEqual(items["cnc/motor_lock"].expected, "0")
|
||||||
@@ -158,14 +166,16 @@ class BaselineTests(unittest.TestCase):
|
|||||||
dev.on_command = lambda line: self._pos(0, 0, 0) if line.startswith("$J=") else None
|
dev.on_command = lambda line: self._pos(0, 0, 0) if line.startswith("$J=") else None
|
||||||
b = self.bl()
|
b = self.bl()
|
||||||
cap = b.capture()
|
cap = b.capture()
|
||||||
self._pos(221, 0, 0) # 4.144 mm into the held move
|
# 4.144 mm into the held move, in the steps of the mode in force
|
||||||
|
held_mm = 4.144
|
||||||
|
self._pos(round(held_mm * baseline.xy_steps_per_mm(baseline.XY_MODE_DEFAULT)), 0, 0)
|
||||||
left = b.enforce("post", captured=cap)
|
left = b.enforce("post", captured=cap)
|
||||||
items = {x.item: x for x in left}
|
items = {x.item: x for x in left}
|
||||||
self.assertTrue(items["position"].action.startswith("restored"), items["position"].action)
|
self.assertTrue(items["position"].action.startswith("restored"), items["position"].action)
|
||||||
self.assertEqual(dev.sent[0], "^X")
|
self.assertEqual(dev.sent[0], "^X")
|
||||||
jogs = [l for l in dev.sent if l.startswith("$J=")]
|
jogs = [l for l in dev.sent if l.startswith("$J=")]
|
||||||
self.assertEqual(len(jogs), 1)
|
self.assertEqual(len(jogs), 1)
|
||||||
self.assertIn("X-4.144", jogs[0])
|
self.assertIn("X-%.3f" % held_mm, jogs[0])
|
||||||
self.assertTrue(any("reset out of Hold:0" in l for l in self.lines), self.lines)
|
self.assertTrue(any("reset out of Hold:0" in l for l in self.lines), self.lines)
|
||||||
finally:
|
finally:
|
||||||
dev.stop()
|
dev.stop()
|
||||||
@@ -250,7 +260,7 @@ class BaselineTests(unittest.TestCase):
|
|||||||
def test_fixed_constants_checked_against_a_dump(self):
|
def test_fixed_constants_checked_against_a_dump(self):
|
||||||
ref = {"sysfs": {"cnc/motor_lock": "0", "cnc/step_freq": "10000"}}
|
ref = {"sysfs": {"cnc/motor_lock": "0", "cnc/step_freq": "10000"}}
|
||||||
diffs = baseline.check_fixed_against(ref, self.lines.append)
|
diffs = baseline.check_fixed_against(ref, self.lines.append)
|
||||||
self.assertEqual(diffs, ["cnc/step_freq: boot=10000 constant=28160"])
|
self.assertEqual(diffs, ["cnc/step_freq: boot=10000 constant=%s" % DEFAULT_TICK])
|
||||||
|
|
||||||
|
|
||||||
# -- the reference is taken after the controller applied its config -----
|
# -- the reference is taken after the controller applied its config -----
|
||||||
@@ -269,7 +279,8 @@ class BaselineTests(unittest.TestCase):
|
|||||||
def sleep(_s):
|
def sleep(_s):
|
||||||
calls["n"] += 1
|
calls["n"] += 1
|
||||||
if calls["n"] == 3: # the controller's init writes land
|
if calls["n"] == 3: # the controller's init writes land
|
||||||
for attr, val in baseline.CONFIGURED_MARKERS:
|
# at the mode wait_controller_configured watches by default
|
||||||
|
for attr, val in baseline.configured_markers():
|
||||||
self._attr(attr, val)
|
self._attr(attr, val)
|
||||||
ok = baseline.wait_controller_configured(
|
ok = baseline.wait_controller_configured(
|
||||||
self.lines.append, {"controller": "running", "mode": "grbl", "motion": "verified"},
|
self.lines.append, {"controller": "running", "mode": "grbl", "motion": "verified"},
|
||||||
@@ -307,11 +318,15 @@ class BaselineTests(unittest.TestCase):
|
|||||||
def test_preconfig_reference_is_recognized(self):
|
def test_preconfig_reference_is_recognized(self):
|
||||||
self.assertTrue(baseline.reference_preconfig(
|
self.assertTrue(baseline.reference_preconfig(
|
||||||
{"sysfs": {"cnc/motor_lock": "0", "cnc/step_freq": "10000", "cnc/y_mode": "1"}}))
|
{"sysfs": {"cnc/motor_lock": "0", "cnc/step_freq": "10000", "cnc/y_mode": "1"}}))
|
||||||
|
# The markers are read at the reference's own mode, so a dump taken
|
||||||
|
# on an x8 machine carries the setting that says so.
|
||||||
self.assertFalse(baseline.reference_preconfig(
|
self.assertFalse(baseline.reference_preconfig(
|
||||||
{"sysfs": {"cnc/motor_lock": "8", "cnc/step_freq": "28160", "cnc/y_mode": "8"}}))
|
{"sysfs": {"cnc/motor_lock": "8", "cnc/step_freq": "28160", "cnc/y_mode": "8"},
|
||||||
|
"forgectrl": {"/settings": {"xy_microsteps": "8"}}}))
|
||||||
# a genuinely different single constant is a machine fact, not pre-config
|
# a genuinely different single constant is a machine fact, not pre-config
|
||||||
self.assertFalse(baseline.reference_preconfig(
|
self.assertFalse(baseline.reference_preconfig(
|
||||||
{"sysfs": {"cnc/motor_lock": "8", "cnc/step_freq": "10000", "cnc/y_mode": "8"}}))
|
{"sysfs": {"cnc/motor_lock": "8", "cnc/step_freq": "10000", "cnc/y_mode": "8"},
|
||||||
|
"forgectrl": {"/settings": {"xy_microsteps": "8"}}}))
|
||||||
self.assertFalse(baseline.reference_preconfig({"sysfs": {}}))
|
self.assertFalse(baseline.reference_preconfig({"sysfs": {}}))
|
||||||
|
|
||||||
# -- the XY microstep mode drives the fixed values ------------------------
|
# -- the XY microstep mode drives the fixed values ------------------------
|
||||||
@@ -330,16 +345,18 @@ class BaselineTests(unittest.TestCase):
|
|||||||
self.assertEqual(x32["cnc/x_decay"], x8["cnc/x_decay"])
|
self.assertEqual(x32["cnc/x_decay"], x8["cnc/x_decay"])
|
||||||
|
|
||||||
def test_the_mode_is_read_from_the_settings_with_a_default(self):
|
def test_the_mode_is_read_from_the_settings_with_a_default(self):
|
||||||
|
# An unset or unusable key reads as the driver's default, x32.
|
||||||
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "16"}), 16)
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "16"}), 16)
|
||||||
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "32"}), 32)
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "32"}), 32)
|
||||||
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": ""}), 8)
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "8"}), 8)
|
||||||
self.assertEqual(baseline.xy_mode_of({}), 8)
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": ""}), 32)
|
||||||
self.assertEqual(baseline.xy_mode_of(None), 8)
|
self.assertEqual(baseline.xy_mode_of({}), 32)
|
||||||
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "24"}), 8)
|
self.assertEqual(baseline.xy_mode_of(None), 32)
|
||||||
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "abc"}), 8)
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "24"}), 32)
|
||||||
|
self.assertEqual(baseline.xy_mode_of({"xy_microsteps": "abc"}), 32)
|
||||||
self.assertEqual(baseline.ref_xy_mode({"forgectrl": {"/settings": {"xy_microsteps": "16"}}}), 16)
|
self.assertEqual(baseline.ref_xy_mode({"forgectrl": {"/settings": {"xy_microsteps": "16"}}}), 16)
|
||||||
self.assertEqual(baseline.ref_xy_mode({"forgectrl": {"/settings": None}}), 8)
|
self.assertEqual(baseline.ref_xy_mode({"forgectrl": {"/settings": None}}), 32)
|
||||||
self.assertEqual(baseline.ref_xy_mode({}), 8)
|
self.assertEqual(baseline.ref_xy_mode({}), 32)
|
||||||
|
|
||||||
def test_fixed_constants_are_checked_at_the_reference_mode(self):
|
def test_fixed_constants_are_checked_at_the_reference_mode(self):
|
||||||
ref = {"sysfs": {"cnc/x_mode": "16", "cnc/step_freq": "56320", "cnc/ramp_rate": "250000"},
|
ref = {"sysfs": {"cnc/x_mode": "16", "cnc/step_freq": "56320", "cnc/ramp_rate": "250000"},
|
||||||
@@ -576,7 +593,7 @@ class BaselineModeTests(BaselineTests):
|
|||||||
self._attr("pic/lid_led", "77")
|
self._attr("pic/lid_led", "77")
|
||||||
left = self.bl().enforce("pre", captured=None)
|
left = self.bl().enforce("pre", captured=None)
|
||||||
self.assertEqual(sorted(x.item for x in left), ["cnc/step_freq", "pic/lid_led"])
|
self.assertEqual(sorted(x.item for x in left), ["cnc/step_freq", "pic/lid_led"])
|
||||||
self.assertEqual(self._read("cnc/step_freq"), "28160")
|
self.assertEqual(self._read("cnc/step_freq"), DEFAULT_TICK)
|
||||||
self.assertEqual(self._read("pic/lid_led"), "236")
|
self.assertEqual(self._read("pic/lid_led"), "236")
|
||||||
|
|
||||||
def test_cloud_mode_leaves_the_clients_config_lamp_and_counters(self):
|
def test_cloud_mode_leaves_the_clients_config_lamp_and_counters(self):
|
||||||
|
|||||||
@@ -144,7 +144,17 @@ import time
|
|||||||
|
|
||||||
BIN = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "build-native/grblHAL_glowforge")
|
BIN = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "build-native/grblHAL_glowforge")
|
||||||
PORT = 2399
|
PORT = 2399
|
||||||
STEPS_PER_MM = 53.333
|
|
||||||
|
# The XY microstep mode. No session below writes xy_microsteps, so every
|
||||||
|
# one of them runs at the driver's default (boards/glowforge.h
|
||||||
|
# XY_MICROSTEPS_DEFAULT). $100/$101 and the machine tick are the x8 base
|
||||||
|
# scaled by the mode, so both move together and a mode change is one
|
||||||
|
# edit here.
|
||||||
|
XY_MICROSTEPS_BASE = 8 # the factory x8 reference the scaling divides by
|
||||||
|
XY_MICROSTEPS_DEFAULT = 32 # the mode an unset xy_microsteps key reads as
|
||||||
|
XY_SCALE = XY_MICROSTEPS_DEFAULT // XY_MICROSTEPS_BASE
|
||||||
|
STEPS_PER_MM_X8 = 53.333 # DEFAULT_X/Y_STEPS_PER_MM, the x8 base
|
||||||
|
STEPS_PER_MM = STEPS_PER_MM_X8 * XY_SCALE # $100/$101 at the mode in force
|
||||||
|
|
||||||
# The S -> level mapping the board defaults produce: $30 = 1000, $31 = 0,
|
# The S -> level mapping the board defaults produce: $30 = 1000, $31 = 0,
|
||||||
# and a $35 floor (boards/glowforge.h DEFAULT_SPINDLE_PWM_MIN_VALUE)
|
# and a $35 floor (boards/glowforge.h DEFAULT_SPINDLE_PWM_MIN_VALUE)
|
||||||
@@ -163,14 +173,20 @@ def duty_for(s):
|
|||||||
"""Duty the core computes for an S word, floor included."""
|
"""Duty the core computes for an S word, floor included."""
|
||||||
return int(s * (PWM_PERIOD - PWM_MIN) / RPM_MAX) + PWM_MIN
|
return int(s * (PWM_PERIOD - PWM_MIN) / RPM_MAX) + PWM_MIN
|
||||||
|
|
||||||
|
# The machine tick: one stream byte per tick, so byte counts are
|
||||||
|
# durations. The factory travel tick is the x8 one (GF_TICK_X8_HZ),
|
||||||
|
# scaled with the mode so the ticks per step stay the same.
|
||||||
|
MACHINE_TICK_X8_HZ = 28160.0
|
||||||
|
MACHINE_TICK_HZ = MACHINE_TICK_X8_HZ * XY_SCALE
|
||||||
# Longest stepless run allowed to carry FIRE, in machine ticks. The
|
# Longest stepless run allowed to carry FIRE, in machine ticks. The
|
||||||
# slowest legitimate between-step interval in these jobs is the first
|
# slowest legitimate between-step interval in these jobs is the first
|
||||||
# step of an accel-from-rest: sqrt(2 * (1/53.333 mm) / 700 mm/s^2)
|
# step of an accel-from-rest: sqrt(2 * (1/STEPS_PER_MM mm) / 700 mm/s^2),
|
||||||
# = 7.3 ms = ~206 ticks at 28160 Hz. 500 gives >2x margin while staying
|
# which at x8 is 7.3 ms = ~206 ticks at 28160 Hz. A finer mode shortens
|
||||||
# far below any idle-gap pad run.
|
# that interval by sqrt(k) but speeds the tick by k, so the interval
|
||||||
FIRE_GAP_LIMIT_TICKS = 500
|
# measured in ticks grows as sqrt(k): ~412 ticks at x32. The limit scales
|
||||||
# The machine tick: one stream byte per tick, so byte counts are durations.
|
# with it to keep the same >2x margin while staying far below any
|
||||||
MACHINE_TICK_HZ = 28160.0
|
# idle-gap pad run.
|
||||||
|
FIRE_GAP_LIMIT_TICKS = int(500 * XY_SCALE ** 0.5)
|
||||||
|
|
||||||
WAIT_IDLE = ("wait_idle",)
|
WAIT_IDLE = ("wait_idle",)
|
||||||
|
|
||||||
@@ -254,8 +270,15 @@ ANALOG_FLOOR_DEFAULT_PCT = 16.0
|
|||||||
ANALOG_CONF = ("laser_power_model = analog\n"
|
ANALOG_CONF = ("laser_power_model = analog\n"
|
||||||
"laser_dose_curve = off\n"
|
"laser_dose_curve = off\n"
|
||||||
"laser_floor_analog = %g\n" % PWM_MIN_PCT)
|
"laser_floor_analog = %g\n" % PWM_MIN_PCT)
|
||||||
|
# Both keys are in ticks of the x8 reference tick, and the stream scales
|
||||||
|
# them to the tick in force, so the period is a time at every microstep
|
||||||
|
# mode (glowforge_laser.c, laser_pulse_ticks). The config carries the x8
|
||||||
|
# numbers; the burst lengths measured out of the stream are in the mode's
|
||||||
|
# own machine ticks, so the expectations are scaled.
|
||||||
DENSITY_PERIOD = 20
|
DENSITY_PERIOD = 20
|
||||||
DENSITY_MIN_TICKS = 3
|
DENSITY_MIN_TICKS = 3
|
||||||
|
DENSITY_PERIOD_TICKS = DENSITY_PERIOD * XY_SCALE
|
||||||
|
DENSITY_MIN_MACHINE_TICKS = DENSITY_MIN_TICKS * XY_SCALE
|
||||||
DENSITY_CONF_BASE = ("laser_pulse_ticks = %d\n"
|
DENSITY_CONF_BASE = ("laser_pulse_ticks = %d\n"
|
||||||
"laser_pulse_min_ticks = %d\n"
|
"laser_pulse_min_ticks = %d\n"
|
||||||
% (DENSITY_PERIOD, DENSITY_MIN_TICKS))
|
% (DENSITY_PERIOD, DENSITY_MIN_TICKS))
|
||||||
@@ -634,11 +657,12 @@ def holds_in(ticks, min_run=2000):
|
|||||||
|
|
||||||
|
|
||||||
# The deceleration into a hold from F3000 (50 mm/s) at the board's
|
# The deceleration into a hold from F3000 (50 mm/s) at the board's
|
||||||
# 700 mm/s^2: 71 ms, about 2000 ticks. A gate that closes with the
|
# 700 mm/s^2: 71 ms, about 2000 ticks at x8. A gate that closes with the
|
||||||
# hold darkens all of it but the producer's lead (10 ms, ~280 ticks);
|
# hold darkens all of it but the producer's lead (10 ms, ~280 ticks at
|
||||||
# a fire state that outlives the gate lights it to the last step.
|
# x8); a fire state that outlives the gate lights it to the last step.
|
||||||
|
# Both are durations, so the tick counts scale with the mode's tick.
|
||||||
DECEL_TICKS = int(50.0 / 700.0 * MACHINE_TICK_HZ)
|
DECEL_TICKS = int(50.0 / 700.0 * MACHINE_TICK_HZ)
|
||||||
DECEL_DARK_MIN = DECEL_TICKS - 600
|
DECEL_DARK_MIN = DECEL_TICKS - 600 * XY_SCALE
|
||||||
|
|
||||||
|
|
||||||
def dark_lead(ticks, hold):
|
def dark_lead(ticks, hold):
|
||||||
@@ -662,10 +686,11 @@ def check_decel_dark(name, ticks, hold, what):
|
|||||||
# A lit deceleration ends within the producer's lead (10 ms) plus one
|
# A lit deceleration ends within the producer's lead (10 ms) plus one
|
||||||
# shipper period (10 ms) of the stop: the gate closes when the core
|
# shipper period (10 ms) of the stop: the gate closes when the core
|
||||||
# reports the hold complete, and the bytes produced ahead of the cursor
|
# reports the hold complete, and the bytes produced ahead of the cursor
|
||||||
# by then ship dark. 1000 ticks is 35 ms, under 0.2 mm at the end of a
|
# by then ship dark. 1000 x8 ticks is 35 ms, under 0.2 mm at the end of
|
||||||
# ramp from 50 mm/s; a gate that closed before the head stopped shows
|
# a ramp from 50 mm/s; a gate that closed before the head stopped shows
|
||||||
# as the whole deceleration (~2000 ticks) or more.
|
# as the whole deceleration (DECEL_TICKS) or more. The budget is a
|
||||||
DECEL_LIT_MAX = 1000
|
# duration, so it scales with the mode's tick.
|
||||||
|
DECEL_LIT_MAX = 1000 * XY_SCALE
|
||||||
|
|
||||||
|
|
||||||
def check_decel_lit(name, ticks, hold, what):
|
def check_decel_lit(name, ticks, hold, what):
|
||||||
@@ -829,10 +854,15 @@ def check_power_ladder(name, data, expect):
|
|||||||
return counts
|
return counts
|
||||||
|
|
||||||
|
|
||||||
def fire_spans(ticks, gap=500):
|
RUNG_SPLIT_TICKS = 500 * XY_SCALE
|
||||||
|
|
||||||
|
|
||||||
|
def fire_spans(ticks, gap=RUNG_SPLIT_TICKS):
|
||||||
"""Tick spans carrying fire, split on dark gaps (the G0 between
|
"""Tick spans carrying fire, split on dark gaps (the G0 between
|
||||||
rungs). Within a rung the model's own dark stretches are at most a
|
rungs). Within a rung the model's own dark stretches are at most a
|
||||||
couple of base periods, far below the split."""
|
couple of base periods, far below the split. Both the G0 gap and
|
||||||
|
those stretches are measured in machine ticks, so the split scales
|
||||||
|
with the microstep mode's tick."""
|
||||||
spans = []
|
spans = []
|
||||||
start = last = None
|
start = last = None
|
||||||
for i, b in enumerate(ticks):
|
for i, b in enumerate(ticks):
|
||||||
@@ -1006,8 +1036,8 @@ def main():
|
|||||||
# Unfloored through the config key (laser_floor_density = 0), which
|
# Unfloored through the config key (laser_floor_density = 0), which
|
||||||
# the arm loads into $35.
|
# the arm loads into $35.
|
||||||
dens = run_session("density", JOB_LADDER, conf=DENSITY_CONF)
|
dens = run_session("density", JOB_LADDER, conf=DENSITY_CONF)
|
||||||
rendered = check_density("density", dens, DENSITY_LEVEL, DENSITY_PERIOD,
|
rendered = check_density("density", dens, DENSITY_LEVEL, DENSITY_PERIOD_TICKS,
|
||||||
DENSITY_MIN_TICKS)
|
DENSITY_MIN_MACHINE_TICKS)
|
||||||
check_termination("density", dens)
|
check_termination("density", dens)
|
||||||
if "laser armed (density, floor 0 %, curve off)" not in run_session.text:
|
if "laser armed (density, floor 0 %, curve off)" not in run_session.text:
|
||||||
fail("[density] the arm did not select the density model at floor 0")
|
fail("[density] the arm did not select the density model at floor 0")
|
||||||
@@ -1069,7 +1099,7 @@ def main():
|
|||||||
cur = b & 0x7F
|
cur = b & 0x7F
|
||||||
elif b & 0x10:
|
elif b & 0x10:
|
||||||
fire_by_duty[cur] = fire_by_duty.get(cur, 0) + 1
|
fire_by_duty[cur] = fire_by_duty.get(cur, 0) + 1
|
||||||
want_ticks = IDLE_S_MM / (IDLE_S_FEED / 60.0) * 28160
|
want_ticks = IDLE_S_MM / (IDLE_S_FEED / 60.0) * MACHINE_TICK_HZ
|
||||||
for level in IDLE_S_LEVELS:
|
for level in IDLE_S_LEVELS:
|
||||||
duty = duty_for(level)
|
duty = duty_for(level)
|
||||||
got = fire_by_duty.get(duty, 0)
|
got = fire_by_duty.get(duty, 0)
|
||||||
@@ -1117,8 +1147,8 @@ def main():
|
|||||||
# every rung renders through it.
|
# every rung renders through it.
|
||||||
floored = run_session("floor-derived", JOB_DENSITY, conf=DENSITY_CONF_FLOORED)
|
floored = run_session("floor-derived", JOB_DENSITY, conf=DENSITY_CONF_FLOORED)
|
||||||
expect_levels = tuple(duty_for(x) for x in LADDER_S)
|
expect_levels = tuple(duty_for(x) for x in LADDER_S)
|
||||||
check_density("floor-derived", floored, expect_levels, DENSITY_PERIOD,
|
check_density("floor-derived", floored, expect_levels, DENSITY_PERIOD_TICKS,
|
||||||
DENSITY_MIN_TICKS)
|
DENSITY_MIN_MACHINE_TICKS)
|
||||||
if "laser armed (density, floor %g %%, curve off)" % PWM_MIN_PCT not in run_session.text:
|
if "laser armed (density, floor %g %%, curve off)" % PWM_MIN_PCT not in run_session.text:
|
||||||
fail("[floor-derived] the arm report does not name the derived floor "
|
fail("[floor-derived] the arm report does not name the derived floor "
|
||||||
"(text: %r)" % run_session.text[-400:])
|
"(text: %r)" % run_session.text[-400:])
|
||||||
@@ -1197,8 +1227,8 @@ def main():
|
|||||||
# first-window check is what pins the M3 resume, which once ran dark
|
# first-window check is what pins the M3 resume, which once ran dark
|
||||||
# for a segment buffer because the segments prepped while held
|
# for a segment buffer because the segments prepped while held
|
||||||
# carried no spindle update.
|
# carried no spindle update.
|
||||||
HOLD_WIN = 704 # 25 ms at 28160 Hz
|
HOLD_WIN = int(0.025 * MACHINE_TICK_HZ) # 25 ms at the machine tick
|
||||||
HOLD_EDGE = 120 # one pulse straddles each edge
|
HOLD_EDGE = int(120 * XY_SCALE) # one pulse straddles each edge
|
||||||
for mode, job in (("m4", ["G90", "G21", "M4 S0", "G1 X150 F6000 S500"]),
|
for mode, job in (("m4", ["G90", "G21", "M4 S0", "G1 X150 F6000 S500"]),
|
||||||
("m3", ["G90", "G21", "M3 S500", "G1 X150 F6000"])):
|
("m3", ["G90", "G21", "M3 S500", "G1 X150 F6000"])):
|
||||||
steps = job + [("sleep", 0.7), ("rt", b"!"), ("wait_state", "Hold:0"),
|
steps = job + [("sleep", 0.7), ("rt", b"!"), ("wait_state", "Hold:0"),
|
||||||
@@ -1249,7 +1279,7 @@ def main():
|
|||||||
# and the stretch it moved in between ships dark. The clean verdict
|
# and the stretch it moved in between ships dark. The clean verdict
|
||||||
# then resumes the hold the client took, and the rest of the line
|
# then resumes the hold the client took, and the rest of the line
|
||||||
# cuts lit; M2 closes the window and locks.
|
# cuts lit; M2 closes the window and locks.
|
||||||
TICK_HZ = 28160
|
TICK_HZ = MACHINE_TICK_HZ
|
||||||
steps = ["G90", "G21", "M3 S500", "G1 X150 F3000", ("sleep", 0.7),
|
steps = ["G90", "G21", "M3 S500", "G1 X150 F3000", ("sleep", 0.7),
|
||||||
("verdict", "hold"), ("wait_state", "Hold:0"), ("sleep", 0.5),
|
("verdict", "hold"), ("wait_state", "Hold:0"), ("sleep", 0.5),
|
||||||
("rt", b"~"), ("sleep", 1.2), ("wait_state", "Hold:0"),
|
("rt", b"~"), ("sleep", 1.2), ("wait_state", "Hold:0"),
|
||||||
@@ -1481,8 +1511,9 @@ def main():
|
|||||||
# Runs sit back to back in the dump, so the three moves are told
|
# Runs sit back to back in the dump, so the three moves are told
|
||||||
# apart by their steps: each is 10 mm, and the cut is the last third.
|
# apart by their steps: each is 10 mm, and the cut is the last third.
|
||||||
total = sum(1 for t in ticks if t & 0x01)
|
total = sum(1 for t in ticks if t & 0x01)
|
||||||
if abs(total - 3 * 533) > 12:
|
want_jog = 3 * round(10 * STEPS_PER_MM)
|
||||||
fail("[jog-dark] %d X steps, expected about %d (three 10 mm moves)" % (total, 3 * 533))
|
if abs(total - want_jog) > 12 * XY_SCALE:
|
||||||
|
fail("[jog-dark] %d X steps, expected about %d (three 10 mm moves)" % (total, want_jog))
|
||||||
cut_from = 2 * (total // 3) - 2 # a fire tick may lead the cut's first step
|
cut_from = 2 * (total // 3) - 2 # a fire tick may lead the cut's first step
|
||||||
jog_fire = cut_fire = steps = 0
|
jog_fire = cut_fire = steps = 0
|
||||||
for t in ticks:
|
for t in ticks:
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ never takes them typed: $100/$101, the machine tick, and the kernel stop
|
|||||||
ramp. This harness drives the native grblHAL_glowforge binary in
|
ramp. This harness drives the native grblHAL_glowforge binary in
|
||||||
null-sink mode (no hardware, no root) over TCP, one process per config:
|
null-sink mode (no hardware, no root) over TCP, one process per config:
|
||||||
|
|
||||||
1. no key: x8, $100/$101 = 53.333, the 28160 Hz tick
|
1. no key: x32, $100/$101 = 213.333, the 112640 Hz tick
|
||||||
2. xy_microsteps = 16: 106.667, the 56320 Hz tick; a typed $100 is
|
2. xy_microsteps = 16: 106.667, the 56320 Hz tick; a typed $100 is
|
||||||
overwritten on the spot; $110 is left alone under a tick that
|
overwritten on the spot; $110 is left alone under a tick that
|
||||||
carries it
|
carries it
|
||||||
3. xy_microsteps = 32: 213.333, the 112640 Hz tick
|
3. xy_microsteps = 32: 213.333, the 112640 Hz tick
|
||||||
4. a value that is not a mode: x8 with a warning in the log
|
4. a value that is not a mode: x32 with a warning in the log
|
||||||
5. GFSINK_RATE lowered under the mode's tick: $110/$111 are held at
|
5. GFSINK_RATE lowered under the mode's tick: $110/$111 are held at
|
||||||
the feed the tick carries, one step per tick per axis
|
the feed the tick carries, one step per tick per axis
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user