From 35add7cad077dfbe199a70e9915731dc4c6ebbab Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Tue, 13 Oct 2026 18:03:51 -0400 Subject: [PATCH] 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. --- forgetest/forgetest/suite/laser.py | 91 +++--------------------------- 1 file changed, 8 insertions(+), 83 deletions(-) diff --git a/forgetest/forgetest/suite/laser.py b/forgetest/forgetest/suite/laser.py index b6d78c0..161f37a 100644 --- a/forgetest/forgetest/suite/laser.py +++ b/forgetest/forgetest/suite/laser.py @@ -5,15 +5,14 @@ """laser.* - LIVE laser tests, ported from `scripts/bench/live_fire_drills.py`. -Every test here can emit, except `laser.power-floor`, which only reads -settings. The page starts a firing one only with the operator's -eye-protection / fire-watch / exhaust acknowledgment; the test then -prompts for the scrap and the button, streams a small job through the -controller, and the machine fires only after the operator presses the -physical arm button - nothing here defeats that gate, and forgetest -never touches the laser latch. One emission per test; on abort or error -the job is soft-reset (`^X`: controlled stop, latch relocked). The -witnesses are forgectrl's `/status` (`laser.emission_samples` = the +The live tests here emit. The page starts a firing one only with the +operator's eye-protection / fire-watch / exhaust acknowledgment; the +test then prompts for the scrap and the button, streams a small job +through the controller, and the machine fires only after the operator +presses the physical arm button - nothing here defeats that gate, and +forgetest never touches the laser latch. One emission per test; on abort +or error the job is soft-reset (`^X`: controlled stop, latch relocked). +The witnesses are forgectrl's `/status` (`laser.emission_samples` = the kernel's LASER_ON sample count, `hv_current_raw`, `lid_ir`), `/cool/status` (`armed`), the head's beam detector (`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/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. " "Put scrap under the head with %s clear, and close the lid.") @@ -405,45 +376,6 @@ def wait_disarm(ctx, timeout): 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", subsystem="laser", kind="live", mode="grbl", always=True, est_min=5, 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 - - @test("laser.armed-kill", title="Armed kill mid-fire: the expected stop, then a SIGKILL", subsystem="laser", kind="live", mode="grbl", est_min=6, 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) - - -DISARMED_MSG = "laser disarmed - latch locked" - - @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", subsystem="laser", kind="live", mode="grbl", est_min=7,