#!/usr/bin/env python3 """LASER_PWM stream-path test - runs ON the board. Streams POWER BYTES ONLY (bit 7 set) through /dev/glowforge and lets the kernel pulse engine play them, so the scope on LASER_PWM verifies the real power path the laser milestone will use, including two contract rules (run-start duty reset; consecutive power bytes dropped). Safety posture: - The stream contains ZERO step bytes and ZERO FIRE (bit 4) bits. - motor_lock=15 masks step output on all axes regardless. - laser_latch re-asserted locked; lid closed; HV watchdog untouched. - Position counters compared before/after - must be identical. - streaming stays 0: end-of-data is a normal completion. Runs with the controller and forgectrl stopped (the bench page's takeover). Exit status 0 when the counters did not move, the run ended in an idle state and no FIRE/emission was read back; 1 otherwise. """ import fcntl, mmap, os, struct, sys, time TICK_HZ = 10000 PWM2_BASE = 0x02084000 def wr(attr, val): with open('/sys/glowforge/' + attr, 'w') as f: f.write(str(val)) def rd(attr): with open('/sys/glowforge/' + attr) as f: return f.read().strip() def rd_pos(): with open('/sys/glowforge/cnc/position', 'rb') as f: raw = f.read(32) x, y, z, done, total = struct.unpack('<5i', raw[:20]) return x, y, z, done, total def pwmsar(): with open('/dev/mem', 'rb') as f: m = mmap.mmap(f.fileno(), 4096, mmap.MAP_SHARED, mmap.PROT_READ, offset=PWM2_BASE) sar = struct.unpack('