forgetest: laser.power-floor leaves the catalog; laser.py's dead names go

laser.power-floor read $$ off the controller and checked $35 is the
floor, that it lands at PWMSAR 12 or above, and that $31 is 0. The host
proves all of it now: laser_stream_test rule 18 reads $$ on a fresh
controller and after the arm (d7379d9), and laser_arm_test.c case J
holds a set floor key (honored, an out-of-range key falling back to the
default, 0 honored). forgectrl keeps no default for laser_floor_density:
an absent key is the controller's own floor.

- Its own lines go, and the names only it or the removed tests read:
  _floor, _grbl_settings, FLOOR_DEFAULT with the comment above it,
  PWM_PERIOD, PWMSAR_FLOOR_MIN, DISARMED_MSG, and the extra blank lines
  the earlier own-lines removals left.
- The module docstring no longer carves laser.power-floor out as the one
  test that does not emit; it says the live tests here emit, since
  laser.recorder-dark never fires.
- HV_DARK_MAX stays: it is the emission witness's limit.

Proof: the fingerprint parts of laser.py before and after: the shared
text changed, and every remaining test's own text is unchanged. The
coverage lint reports 0 uncovered paths and 0 host-tested row problems
over 108 tests (its one failure is setup.check-lens's src/lensfind.*
entry, which the forgectrl pin bump clears); the gate self-check refuses
an empty artifact cleanly; pyflakes adds no finding; the forgetest host
suite passes under Linux (539 tests).

Acceptance: laser.py's shared text moves the fingerprints of
laser.emission-witness, laser.armed-kill, laser.pause-resume-lid-cancel
and laser.recorder-dark. Against the image on the bench that adds no
run: 6a434bb already changed laser.py's shared text, so all four re-run
in the next campaign either way.
This commit is contained in:
ScottW514
2026-10-13 18:03:51 -04:00
parent d7379d9fc0
commit 35add7cad0
+8 -83
View File
@@ -5,15 +5,14 @@
"""laser.* - LIVE laser tests, ported from `scripts/bench/live_fire_drills.py`. """laser.* - LIVE laser tests, ported from `scripts/bench/live_fire_drills.py`.
Every test here can emit, except `laser.power-floor`, which only reads The live tests here emit. The page starts a firing one only with the
settings. The page starts a firing one only with the operator's operator's eye-protection / fire-watch / exhaust acknowledgment; the
eye-protection / fire-watch / exhaust acknowledgment; the test then test then prompts for the scrap and the button, streams a small job
prompts for the scrap and the button, streams a small job through the through the controller, and the machine fires only after the operator
controller, and the machine fires only after the operator presses the presses the physical arm button - nothing here defeats that gate, and
physical arm button - nothing here defeats that gate, and forgetest forgetest never touches the laser latch. One emission per test; on abort
never touches the laser latch. One emission per test; on abort or error or error the job is soft-reset (`^X`: controlled stop, latch relocked).
the job is soft-reset (`^X`: controlled stop, latch relocked). The The witnesses are forgectrl's `/status` (`laser.emission_samples` = the
witnesses are forgectrl's `/status` (`laser.emission_samples` = the
kernel's LASER_ON sample count, `hv_current_raw`, `lid_ir`), kernel's LASER_ON sample count, `hv_current_raw`, `lid_ir`),
`/cool/status` (`armed`), the head's beam detector `/cool/status` (`armed`), the head's beam detector
(`head/beam_detect_analog`, the tube's own emission seen from the head: (`head/beam_detect_analog`, the tube's own emission seen from the head:
@@ -37,34 +36,6 @@ _LASER_COVERS = [("grblhal-glowforge", "src/**"), ("kernel-module-glowforge", "*
("forgectrl", "src/super.c"), ("forgectrl", "src/cool.c"), ("forgectrl", "src/super.c"), ("forgectrl", "src/cool.c"),
("forgectrl", "src/status.c"), ("forgectrl", "src/main.c")] ("forgectrl", "src/status.c"), ("forgectrl", "src/main.c")]
# The S-range floor the controller derives $35 from at every precompute
# (glowforge_laser.c: laser_floor_density, this default when the config
# carries no key): the lowest pulse density that still marks, against
# the hardware's 127-count PWM period. Density is the only dose model.
FLOOR_DEFAULT = 10.0
PWM_PERIOD = 127
PWMSAR_FLOOR_MIN = 12
def _floor(fc):
"""The configured floor percent, default applied."""
raw = (fc.settings().get("laser_floor_density") or "").strip()
try:
return float(raw) if raw else FLOOR_DEFAULT
except ValueError:
return FLOOR_DEFAULT
def _grbl_settings(lines):
vals = {}
for ln in lines:
key, _, val = ln.partition("=")
if key.startswith("$"):
try:
vals[key] = float(val)
except ValueError:
pass
return vals
ARM_CUE = ("LIVE FIRE. Eye protection on. Exhaust on. Extinguisher in reach. " ARM_CUE = ("LIVE FIRE. Eye protection on. Exhaust on. Extinguisher in reach. "
"Put scrap under the head with %s clear, and close the lid.") "Put scrap under the head with %s clear, and close the lid.")
@@ -405,45 +376,6 @@ def wait_disarm(ctx, timeout):
return None return None
@test("laser.power-floor", title="The derived floor is the one in force",
subsystem="laser", kind="auto", mode="grbl", est_min=1,
covers=[("grblhal-glowforge", "src/**"), ("forgectrl", "src/main.c")],
description="The controller derives $35 from the floor key (laser_floor_density) at "
"every spindle precompute, boot included, and never from a typed value. "
"Unfloored, the low end of S asks for pulses too far apart to re-strike, "
"so a commanded 1 % would emit nothing. Reads the floor key from forgectrl "
"and $$ from the controller, and checks $35 is that floor, that it lands at "
"or above the measured minimum, and that $31 is 0 (the floor, not $31, sets "
"the bottom of the range).")
def power_floor(ctx):
ev = ctx.evidence
want = _floor(ctx.forgectrl)
ev["configured_floor"] = want
with ctx.grbl() as g:
lines = g.command("$$", timeout=5)
vals = _grbl_settings(lines)
ctx.check(vals, "no $-settings in the response to $$")
floor_pct = vals.get("$35")
rpm_min = vals.get("$31")
ev["settings"] = {k: vals.get(k) for k in ("$30", "$31", "$32", "$35", "$36")}
ctx.check(floor_pct is not None, "$35 missing from the settings report")
counts = int(PWM_PERIOD * floor_pct / 100.0)
ev["pwmsar_floor"] = counts
ctx.log("$35=%.1f%% -> PWMSAR %d of %d ($31=%s, $32=%s, $36=%s)", floor_pct, counts,
PWM_PERIOD, rpm_min, vals.get("$32"), vals.get("$36"))
ctx.check(abs(floor_pct - want) < 0.05,
"$35 is %.1f %%, expected the derived floor %.1f %% from the machine config",
floor_pct, want)
ctx.check(counts >= PWMSAR_FLOOR_MIN,
"the floor lands at PWMSAR %d, below the %d the tube needs",
counts, PWMSAR_FLOOR_MIN)
ctx.check(rpm_min == 0, "$31 is %s, not 0: the bottom of the S range is no longer the floor",
rpm_min)
ctx.log("PASS: nonzero S never commands less than PWMSAR %d", counts)
@test("laser.emission-witness", title="Live emission witness (S400 vector mark) and job-based disarm", @test("laser.emission-witness", title="Live emission witness (S400 vector mark) and job-based disarm",
subsystem="laser", kind="live", mode="grbl", always=True, est_min=5, subsystem="laser", kind="live", mode="grbl", always=True, est_min=5,
covers=_LASER_COVERS, covers=_LASER_COVERS,
@@ -658,8 +590,6 @@ def emission_witness(ctx):
HV_DARK_MAX = 20 # hv_current_raw reads 0 with the tube off; hundreds under fire HV_DARK_MAX = 20 # hv_current_raw reads 0 with the tube off; hundreds under fire
@test("laser.armed-kill", title="Armed kill mid-fire: the expected stop, then a SIGKILL", @test("laser.armed-kill", title="Armed kill mid-fire: the expected stop, then a SIGKILL",
subsystem="laser", kind="live", mode="grbl", est_min=6, subsystem="laser", kind="live", mode="grbl", est_min=6,
covers=_LASER_COVERS + [("forgectrl", "src/main.c")], covers=_LASER_COVERS + [("forgectrl", "src/main.c")],
@@ -772,11 +702,6 @@ def armed_kill(ctx):
"respawned with the latch still locked, button dark", ev["expected"]["zero_at_s"], zero_at) "respawned with the latch still locked, button dark", ev["expected"]["zero_at_s"], zero_at)
DISARMED_MSG = "laser disarmed - latch locked"
@test("laser.pause-resume-lid-cancel", title="One live cut: the button pauses and resumes it, the lid " @test("laser.pause-resume-lid-cancel", title="One live cut: the button pauses and resumes it, the lid "
"cancels it and sends the head home", "cancels it and sends the head home",
subsystem="laser", kind="live", mode="grbl", est_min=7, subsystem="laser", kind="live", mode="grbl", est_min=7,