Files
forgefirm/scripts/bench/pwm_hold.py
T
ScottW514 cd8a01a3d9 bench: every board-runnable tool ported to the bench page
The remaining bench diagnostics run from forgetest's #bench tab. The
tools that also run from a LAN host share scripts/bench/gfbench.py:
GF_HOST names a remote machine (host mode, sysfs through ssh, Grbl and
forgectrl over the LAN); unset, the tool runs on the board itself
(local mode, sysfs directly, everything on 127.0.0.1), which is how the
page runs them - with GF_HOST=127.0.0.1, the panel token in GF_TOKEN
and their data files under <data>/bench/ (FORGETEST_BENCH_DATA). The
helper also reads a machine setting from forgectrl, or from the settings
file on the board while forgectrl is stopped.

Ported: pwm_sweep / pwm_hold (scope = a takeover; the latch relocked,
the write refused if FIRE or LASER_ON reads active), pwm_stream_test
(PASS/FAIL exit), flow_characterize, flow_recheck_char,
flow_warm_validate and flow_matrix (takeovers: forgectrl owns the
thermal hardware, so the page's takeover replaces the tools' own
controller stop/restart, whose command line predated the supervisor;
results and logs in the bench data directory), flow_sustained,
fan_test, temp_calibrate (dry; watch bounded in seconds; the threshold
and the coolant conversion from the shared code), flow_escalate_drill
(cool_confirm_max_s shortened through forgectrl's settings for the
drill and restored; the setting's minimum is the default budget), and
live_fire_drills (<drill> [S] [F], all six drills, host from GF_HOST,
token from the board). flow_matrix joins the registry. What stays
unported cannot run against the machine at all: the two null-sink CI
harnesses and the .puls decoder.

Runner: a scope tool runs inside the takeover wrapper; the bench
environment above is passed to every tool. Tests: test_bench_registry
(registry <-> scripts/bench consistency, every ported tool builds its
command line, every script compiles, gfbench host/local modes) and the
server test (scope tool takeover, the environment reaching the tool).
Local mode smoke-run on the bench (temp_calibrate watch, setting, token)
from /tmp, removed after.

No catalog consequence: bench tools are not image components (dev-only
forgetest); the acceptance catalog is unchanged.
2026-08-16 16:28:39 -04:00

53 lines
1.8 KiB
Python

#!/usr/bin/env python3
"""Hold LASER_PWM at one duty value for a measurement window, then restore.
Usage: pwm_hold.py [sar] [seconds] (defaults: 8, 30)
The duty register (PWMSAR) is the laser power SETPOINT only. Locked
state only: run with the controller and forgectrl stopped (the bench
page's takeover), the pulse device closed. The latch is relocked here
as well, and the hold refuses to write if the FIRE line reads driven or
LASER_ON reads active."""
import mmap, struct, sys, time
PWM2_BASE = 0x02084000
SAR_OFF = 0x0C
sar = int(sys.argv[1]) if len(sys.argv) > 1 else 8
secs = int(sys.argv[2]) if len(sys.argv) > 2 else 30
def rd(name):
with open('/sys/glowforge/' + name) as f:
return f.read().strip()
def wr(name, val):
with open('/sys/glowforge/' + name, 'w') as f:
f.write(str(val))
try:
wr('cnc/laser_latch', 1)
except OSError as e:
raise SystemExit('could not lock the laser latch: %s' % e)
time.sleep(0.2)
en, on = rd('cnc/laser_enable'), rd('cnc/laser_on')
if en != '0' or on != '0':
raise SystemExit('not the locked state: laser_enable=%s laser_on=%s' % (en, on))
with open('/dev/mem', 'r+b') as f:
m = mmap.mmap(f.fileno(), 4096, mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE, offset=PWM2_BASE)
sar0 = struct.unpack('<I', m[SAR_OFF:SAR_OFF + 4])[0]
m[SAR_OFF:SAR_OFF + 4] = struct.pack('<I', sar)
print('PWMSAR=%d held for %d s (was %d)...' % (sar, secs, sar0))
sys.stdout.flush()
try:
time.sleep(secs)
finally:
m[SAR_OFF:SAR_OFF + 4] = struct.pack('<I', sar0)
print('restored PWMSAR=%d' % sar0)
print('laser_enable=%s laser_on=%s laser_on_sampled=%s'
% (rd('cnc/laser_enable'), rd('cnc/laser_on'), rd('cnc/laser_on_sampled')))
m.close()