mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
laser power-good: the line characterized, the kernel-drill guard, the probe, the dev image's mmap and ctypes
The supply's power-good line is active high, static across HV enable and emission, and driven; the facts bank and CAMPAIGN-LOG carry the measurement and the item closes. The kernel-drill latch-unlock guard read the old inverted value as HV not good, a check that was vacuous and would refuse every run once the module reads the line correctly; it now uses the chain's own witnesses, the charge-pump watchdog and the engine state. pgood_probe.py watches the line beside the chain through the kernel readbacks and is registered on the bench page. The dev image lists python3-mmap and python3-ctypes again for the pad-level bench tools the python trim had left without them.
This commit is contained in:
+21
-17
@@ -324,8 +324,10 @@ from the kernel counters. Contract: [forgectrl](https://docs.forgefirm.org/techn
|
||||
**Emission evidence.** `cnc/laser_on_sampled` (surfaced as `/status`
|
||||
`laser.emission_samples`) is the reliable live-emission witness; emission
|
||||
sensed with no armed window relocks the latch and stops motion. `pic/hv_current`
|
||||
is the only live HV telemetry on this PSU. `cnc/laser_pgood_sampled` is **not**
|
||||
a usable witness here — it reads 0 through real cutting.
|
||||
is the only live HV telemetry on this PSU. `cnc/laser_pgood` is the supply's
|
||||
power-good, driven high the whole time the supply is healthy (it follows
|
||||
neither HV_ENABLE nor emission): a supply-fault witness, never an emission
|
||||
witness (facts bank "Emission and HV witnesses").
|
||||
|
||||
## Lid, interlock and button policy
|
||||
|
||||
@@ -1031,8 +1033,22 @@ is committed.
|
||||
count on a commanded fire window and returns to 0 at Idle — the reliable
|
||||
witness. `pic/hv_current` tracks the cut (0 idle → hundreds/1023 raw while
|
||||
firing) and is the only live HV telemetry on this PSU (`hv_voltage` is
|
||||
grounded). `cnc/laser_pgood_sampled` stays 0 through real cutting: not usable
|
||||
here.
|
||||
grounded). **`cnc/laser_pgood` (J1_14, GPIO4_21) is the supply's power-good,
|
||||
active high**, measured 2026-09-01 through the kernel readbacks at ~780 Hz:
|
||||
the pin is high at idle, through four HV_ENABLE cycles of a dry run, and
|
||||
through an armed S400 cut (714 laser pulses, `hv_current` to 1023), with
|
||||
zero transitions in 225 s, and it stays high against a 100 kΩ pull-down
|
||||
switched in at the pad (IOMUXC `0x020E03C4`, restored to `0x100b0`), so the
|
||||
supply drives it. The supply's supervisor is a Weltrend WT7525 (PC-supply
|
||||
supervisor; open-drain PGO reports every DC output within spec, drops on an
|
||||
over/under-voltage or over-current fault, 300 ms delay after good), and the
|
||||
reverse-engineering pinout sheets label J1_14 `HV_PFC_STOP` (TP_A2C). The
|
||||
factory app reports the line as the `HVpg`/`HVps` tags and read it the same
|
||||
way (0 at idle under the old inverted convention). The kernel now reads it
|
||||
active high: `laser_pgood` 1 and `laser_pgood_sampled` 255 on a healthy
|
||||
supply; a drop during an armed window is the cooling engine's supply
|
||||
power-good warning, and it means a supply fault. The line has never been
|
||||
seen low on this machine.
|
||||
- **The head MCU flag register and HEAD_IRQ.** The head MCU (a KL17 at
|
||||
i2c-3 @0x47, I²C-slave-only to the SoC) samples four head-local GPIO input
|
||||
levels once per main loop into the read-only flag register 0x05: b0
|
||||
@@ -1270,19 +1286,7 @@ feature requests, enhancements) will eventually be tracked as GitHub issues.
|
||||
far): re-measure the two heat coefficients and the machine's
|
||||
air-assist offset; and if a lit check still trips, the
|
||||
void-on-emission design with the tube as its own flow tracer.
|
||||
8. **Laser power-good: what the line means.** `cnc/laser_pgood` and its
|
||||
sampled count are defined in the UAPI (active low, one sample every
|
||||
~3.9 ms), the facts bank records that the sampled count reads 0 through
|
||||
real cutting, and the cooling engine warns
|
||||
`laser power-good degraded during the armed window` whenever fewer
|
||||
than half the samples read low, so the warning fires at every session
|
||||
open and carries no information. Nobody knows what the line reports
|
||||
on this PSU: whether it is the supply's own power-good, an HV-present
|
||||
flag, a polarity we have inverted, or unconnected. Owed: the line on a
|
||||
scope against `hv_current` through an armed cut, its meaning written
|
||||
into the facts bank and the UAPI, and then either a warning that means
|
||||
something or no warning.
|
||||
9. **Initial commissioning: measure and set the machine's own numbers
|
||||
8. **Initial commissioning: measure and set the machine's own numbers
|
||||
methodically.** Every tunable that was measured on the bench machine
|
||||
and shipped as a default varies from machine to machine: the flow
|
||||
check's bands and `cool_flow_rise`, the tube's heat coefficients
|
||||
|
||||
@@ -6977,6 +6977,48 @@ from the LAN by `live_fire_drills.py`, the operator on the button:
|
||||
Items 7 and 8 are bench-proven. The board runs the hot-deployed binary
|
||||
until the next flash.
|
||||
|
||||
## 2026-09-01: the laser supply's power-good line, characterized without a scope
|
||||
|
||||
The "laser power-good" item asked what J1_14 reports. No scope on the
|
||||
bench, so the line was read through the kernel's own readbacks with a
|
||||
new probe (`scripts/bench/pgood_probe.py`, fed to the board over ssh
|
||||
stdin) at 770 to 790 Hz, against LASER_ON, FIRE, the charge-pump
|
||||
watchdog, HV_ENABLE and the doors from the switch device, and
|
||||
`hv_current` at 20 Hz. Image 20260901220626 (dev), fresh boot.
|
||||
|
||||
- **Dry run, 75 s.** Four jogs; `charge_pump_alive` and HV_ENABLE rose
|
||||
and fell together within 5 ms at every run start and end. The pin
|
||||
stayed high for every one of 59,518 samples.
|
||||
- **Armed run, 150 s**: the `witness` drill, a 20 mm square at S400 F600.
|
||||
714 LASER_ON pulses over 8.0 s, `hv_current` 0 to 1023, HV_ENABLE up
|
||||
for the run. The pin stayed high for every one of 115,872 samples.
|
||||
- **Driven, not floating.** With a cross-built register tool the pad's
|
||||
internal pull was switched to 100 kΩ pull-down (IOMUXC `0x020E03C4`,
|
||||
`0x100b0` to `0x130b0`), then pull-up, then restored; the pin read
|
||||
high under all three and the pinctrl view confirmed the restore.
|
||||
- **What the supply has.** The reverse-engineering archive holds the
|
||||
supply's datasheets and board photos: the supervisor board carries a
|
||||
Weltrend WT7525 (PC-supply supervisor: open-drain PGO high once every
|
||||
DC output is within spec, low on an over/under-voltage or over-current
|
||||
fault, 300 ms delay), LM2901 comparators and four PC817 optocouplers.
|
||||
The pinout and test-point sheets label J1_14 `HV_PFC_STOP` (TP_A2C).
|
||||
The factory app reports the line as the `HVpg`/`HVps` header tags and
|
||||
its logs show 0 at idle under the same inverted convention the module
|
||||
inherited.
|
||||
|
||||
Disposition: J1_14 is the supply's power-good, active high, static across
|
||||
HV enable and emission, and driven. The module now reads it active high
|
||||
(`laser_pgood` 1, `laser_pgood_sampled` counts good samples, 255 on a
|
||||
healthy supply); the cooling engine's once-per-session warning keeps its
|
||||
threshold and now means a supply fault; the catalog's kernel-drill
|
||||
precheck, which read the old value as "HV not good", moves to the chain's
|
||||
own witnesses. The line has never been seen low; a supply fault is the
|
||||
only thing that would take it there. Owed: the change rides the next image
|
||||
(kernel module), and the dev image regains `python3-mmap` and
|
||||
`python3-ctypes`, which the python trim removed and which
|
||||
`resume_dark_lead.py`, `cp_watchdog_timing.py` and the accelerometer
|
||||
probes need.
|
||||
|
||||
## Reference notes
|
||||
|
||||
### Head-IRQ source validation — the beam-emission hypothesis
|
||||
|
||||
@@ -228,6 +228,13 @@ TOOLS = [
|
||||
"a pause, how fast the chain re-arms, and - on a live run - the dark lead between FIRE and "
|
||||
"LASER_ON that a resumed cut loses. Dry by default; --run live needs the arm press, eye "
|
||||
"protection, fire watch, extinguisher, exhaust."},
|
||||
{"id": "pgood-probe", "title": "Supply power-good line against the chain", "script": "pgood_probe.py",
|
||||
"safety": "dry", "where": "board", "ported": True,
|
||||
"args": [_arg("secs", "float", 90.0, "sampling window", flag="--secs")],
|
||||
"desc": "Watches the supply's power-good line (cnc/laser_pgood, reported as the raw pin level) beside "
|
||||
"LASER_ON, FIRE, the charge-pump watchdog, HV_ENABLE and the doors through the kernel readbacks at a "
|
||||
"few hundred hertz, with hv_current at 20 Hz, and prints every transition. It only watches: drive "
|
||||
"the machine meanwhile (a jog, an armed cut, a pause, a lid open). Needs no pad mapping."},
|
||||
# -- host-side harnesses (CI) ------------------------------------------------------
|
||||
{"id": "laser-stream-test", "title": "Laser pulse-stream emission harness", "script": "laser_stream_test.py",
|
||||
"safety": "dry", "where": "host", "ported": False, "args": [],
|
||||
|
||||
@@ -9,9 +9,10 @@ laser_on_sampled (the gated LASER_ON output), interlock_circuit bit 3
|
||||
(the commanded latch), faults and underruns. Every drill forces duty to
|
||||
zero before any FIRE bit, keeps motor_lock=15 (no axis moves), and
|
||||
re-locks the latch on every exit path. K3 and fire B/U unlock the latch
|
||||
for their run and therefore refuse to proceed while laser_pgood reports
|
||||
the HV supply good (the operator opens the lid: the safety chain holds
|
||||
HV off).
|
||||
for their run and therefore refuse to proceed unless the safety chain
|
||||
holds HV off: the charge-pump watchdog dead and the pulse engine idle
|
||||
(HV_ENABLE follows the pump; laser_pgood is the supply's power-good and
|
||||
says nothing about HV).
|
||||
"""
|
||||
import errno
|
||||
import os
|
||||
@@ -130,33 +131,46 @@ class PulseDevice:
|
||||
return False
|
||||
|
||||
|
||||
def hv_not_good():
|
||||
"""Precheck for the drills that unlock the latch with a zero-duty
|
||||
stream: they run only while the HV supply does NOT report good. At
|
||||
idle the chain holds HV_ENABLE low, so this refuses a start only on a
|
||||
machine that is not idle the way it should be; the cure is the lid
|
||||
(the safety chain holds HV off with it open)."""
|
||||
pgood = rd("cnc/laser_pgood")
|
||||
if pgood is None:
|
||||
return "cnc/laser_pgood unreadable"
|
||||
if pgood != "0":
|
||||
return ("laser_pgood=%s: the HV supply reports good; this drill unlocks the latch with a "
|
||||
"zero-duty stream and needs HV not good (open the lid, then start again)" % pgood)
|
||||
def hv_off_reason():
|
||||
"""Why the safety chain is NOT holding HV off right now, or None. The
|
||||
chain asserts HV_ENABLE only while a run feeds the charge-pump
|
||||
watchdog, so a dead watchdog and an idle pulse engine mean HV off.
|
||||
laser_pgood is the supply's power-good, high whenever the supply is
|
||||
healthy, and says nothing about HV, so it plays no part here."""
|
||||
alive = rd("cnc/charge_pump_alive")
|
||||
state = rd("cnc/state")
|
||||
if alive is None or state is None:
|
||||
return "cnc/charge_pump_alive or cnc/state unreadable"
|
||||
if alive != "0" or state != "idle":
|
||||
return ("charge_pump_alive=%s state=%s: the chain may be holding HV_ENABLE up" % (alive, state))
|
||||
return None
|
||||
|
||||
|
||||
def require_hv_not_good(ctx):
|
||||
def hv_off():
|
||||
"""Precheck for the drills that unlock the latch with a zero-duty
|
||||
stream: they run only while the chain holds HV off. At idle nothing
|
||||
feeds the watchdog, so this refuses a start only on a machine that is
|
||||
not idle the way it should be."""
|
||||
why = hv_off_reason()
|
||||
if why:
|
||||
return ("%s; this drill unlocks the latch with a zero-duty stream and needs HV off "
|
||||
"(let the machine go idle, then start again)" % why)
|
||||
return None
|
||||
|
||||
|
||||
def require_hv_off(ctx):
|
||||
"""The same rule at the start of the run (the precheck ran a moment
|
||||
earlier; the machine must still agree)."""
|
||||
pgood = rd("cnc/laser_pgood")
|
||||
ctx.evidence["laser_pgood"] = pgood
|
||||
ctx.check(pgood == "0", "laser_pgood=%s (HV supply reports good) - refusing the latch unlock", pgood)
|
||||
why = hv_off_reason()
|
||||
ctx.evidence["charge_pump_alive"] = rd("cnc/charge_pump_alive")
|
||||
ctx.evidence["kernel_state"] = rd("cnc/state")
|
||||
ctx.check(why is None, "%s - refusing the latch unlock", why)
|
||||
|
||||
|
||||
def check_hv_not_good(ctx):
|
||||
def check_hv_off(ctx):
|
||||
"""The hard check right before an unlock (no prompt: forgectrl is down)."""
|
||||
pgood = rd("cnc/laser_pgood")
|
||||
ctx.check(pgood == "0", "laser_pgood=%s (HV supply reports good) - refusing the latch unlock", pgood)
|
||||
why = hv_off_reason()
|
||||
ctx.check(why is None, "%s - refusing the latch unlock", why)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- readbacks
|
||||
@@ -389,7 +403,7 @@ def _k3_phase(ctx):
|
||||
"""K3: the latch unlocked during the accel ramp must not restore the FIRE
|
||||
drive while the run is in flight. Runs inside the caller's takeover."""
|
||||
ev = ctx.evidence
|
||||
check_hv_not_good(ctx)
|
||||
check_hv_off(ctx)
|
||||
stream = POWER0 + FIRE * (3 * TICK_HZ) + PAD * (TICK_HZ // 2)
|
||||
ctx.log("K3: %d bytes = %.1f s of FIRE bits; ramp_rate 10000 Hz/s (~0.9 s accel "
|
||||
"window); unlock at t=+0.15 s", len(stream), len(stream) / TICK_HZ)
|
||||
@@ -442,7 +456,7 @@ def _fire_phase(ctx, mode):
|
||||
stream = _fire_stream()
|
||||
ctx.log("fire %s: stream %d bytes = %.3f s", mode, len(stream), len(stream) / TICK_HZ)
|
||||
if unlock:
|
||||
check_hv_not_good(ctx)
|
||||
check_hv_off(ctx)
|
||||
snap(ctx, "fire %s pre" % mode)
|
||||
wr("cnc/motor_lock", 15)
|
||||
wr("cnc/step_freq", TICK_HZ)
|
||||
@@ -518,7 +532,7 @@ def _fire_phase(ctx, mode):
|
||||
"mid-run unlock",
|
||||
subsystem="kernel", kind="auto", hardware="takeover", always=True, est_min=4,
|
||||
covers=_KERNEL_COVERS,
|
||||
requires=["kernel.k1-k2"], precheck=hv_not_good,
|
||||
requires=["kernel.k1-k2"], precheck=hv_off,
|
||||
steps=["Phases B, U and K3 unlock the latch with a zero-duty stream, so the drill starts only "
|
||||
"while the HV supply does not report good (true at idle; if it is refused, open the "
|
||||
"lid - the safety chain holds HV off - and start it again)."],
|
||||
@@ -532,7 +546,7 @@ def _fire_phase(ctx, mode):
|
||||
"flight - laser_enable stays 0 for the whole run.")
|
||||
def fire_line(ctx):
|
||||
ev = ctx.evidence
|
||||
require_hv_not_good(ctx)
|
||||
require_hv_off(ctx)
|
||||
with ctx.takeover():
|
||||
try:
|
||||
a = _fire_phase(ctx, "A")
|
||||
|
||||
@@ -12,9 +12,16 @@ IMAGE_INSTALL += " \
|
||||
forgectrl \
|
||||
forgetest \
|
||||
python3-gfutilities-emulator \
|
||||
python3-mmap \
|
||||
python3-ctypes \
|
||||
htop \
|
||||
"
|
||||
|
||||
# python3-mmap and python3-ctypes serve the bench scripts that read the SoC
|
||||
# pads through /dev/mem (resume_dark_lead.py, cp_watchdog_timing.py, the
|
||||
# accelerometer probes). They are bench tooling: the release image carries
|
||||
# neither, and forgetest itself imports neither.
|
||||
|
||||
# The release recipe trims nano from the shared base list (bench
|
||||
# convenience, 8.7 MB with libmagic); the dev image keeps it. Removal specs
|
||||
# accumulate and apply after every append, so the variable inside the
|
||||
|
||||
@@ -59,6 +59,7 @@ page's takeover does that; from a host, stop them first.
|
||||
| `puls_profile.py` | Decodes factory `.puls` streams (raw or GF1-headered) into velocity/accel profiles: peak speeds, ramp-slope fits, per-move segments, Z cadence. Runs anywhere (stdlib only). Source of the factory-true grblHAL defaults: 700/590 mm/s² accel, 200 mm/s max rate, 28160 Hz travel tick. |
|
||||
| `cp_watchdog_timing.py` | HV charge-pump watchdog one-shot timing (runs on the board): latches every CHG_PUMP feed pulse in GPIO3's edge detector (pin 24 only, IMR untouched, ICR2 restored on exit) and polls the `!Q` (`charge_pump_alive`) and `!HV_ENABLE` (`hv_enable`) pads through /dev/mem in a tight loop while it commands short local jogs; prints per-run t_w (last pulse → Q fall), Q → HV_ENABLE delay, priming latency and the feed period, with the loop's worst gap as the resolution. Motion only, laser locked, no other Grbl client attached. |
|
||||
| `resume_dark_lead.py` | Pause/resume safety-chain timing (runs on the board, as root): samples LASER_ON, FIRE, HV_ENABLE, the charge-pump watchdog, the button and the doors straight off the SoC pads through /dev/mem at ~2 kHz, with motion dated from the kernel step counters, across a pause and a resume driven by the operator's button presses. Reports how long HV_ENABLE survives the stream stopping, how fast the chain re-arms on the resume, and - on `--run live` - the dark lead between FIRE going back on and LASER_ON following it, in milliseconds and in millimeters at the job's feed. `--run dry` (default) commands no laser at all and `--auto P,R` drives the pause and resume with `!`/`~` for an unattended rehearsal; `--run live` needs the arm press, eye protection, fire watch, extinguisher and exhaust. GRBL mode, no other Grbl client attached. |
|
||||
| `pgood_probe.py` | The supply's power-good line (J1_14, `cnc/laser_pgood`) against the laser chain, without a scope (runs on the board, as root): polls the kernel readbacks (`laser_pgood` reported as the raw pin level, `laser_on`, `laser_enable`, `charge_pump_alive`) and the switch device's HV_ENABLE and doors bits at a few hundred hertz, `hv_current` at 20 Hz, and prints every transition with a timestamp plus a per-line summary. Drive the machine meanwhile (a dry jog, an armed cut, a pause, a lid open); the probe only watches. Feed it over ssh stdin: `ssh root@<board> 'python3 - --secs 90' < pgood_probe.py`. |
|
||||
| `bench_m2.py` | Motion-quality bench, runs against the board over TCP:23: bounded round-trip jogs (sanity, max-rate, diagonal) + feed-hold/resume mid-move, reporting peak feed, state transitions, and position drift. |
|
||||
|
||||
Data files kept beside the tools: `flow_matrix_results.json` /
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""LASER_PGOOD (J1_14) against the rest of the laser chain, through the
|
||||
kernel's readbacks.
|
||||
|
||||
Runs ON the board as root. Polls the kernel's GPIO readbacks (sysfs, one
|
||||
open descriptor each, re-read with pread) and the switch device's EV_SW
|
||||
word (EVIOCGSW) as fast as the loop allows, a few hundred hertz, and
|
||||
reports every transition of the watched lines with a timestamp, plus a
|
||||
per-line summary, so the meaning of the supply's line can be read off
|
||||
against what the chain and the supply were doing: idle, a dry run
|
||||
(HV_ENABLE follows the charge pump), an armed cut (LASER_ON, FIRE,
|
||||
hv_current), a pause and a resume, a lid open. hv_current comes from the
|
||||
PIC at a lower rate and rides along as a range.
|
||||
|
||||
PGOOD is reported as the RAW PIN LEVEL (the kernel's laser_pgood attribute
|
||||
is the logical, inverted value: 1 = pin low). Everything else is the
|
||||
kernel's logical value.
|
||||
|
||||
The loop must not hog the CPU: single core, the protocol thread is
|
||||
SCHED_OTHER, so the sampler sleeps between passes and reports its worst gap.
|
||||
|
||||
Usage: pgood_probe.py [--secs N] [--json FILE]
|
||||
Drive the machine from a sender or the button meanwhile; the probe only
|
||||
watches. GRBL mode, any state.
|
||||
"""
|
||||
import argparse
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
CNC = '/sys/glowforge/cnc/'
|
||||
ATTRS = [ # name, attribute, invert-to-raw
|
||||
('PGOOD', CNC + 'laser_pgood', True),
|
||||
('LASER_ON', CNC + 'laser_on', False),
|
||||
('FIRE', CNC + 'laser_enable', False),
|
||||
('CP_ALIVE', CNC + 'charge_pump_alive', False),
|
||||
]
|
||||
SWITCH_DEV = '/dev/input/event0'
|
||||
SW_BITS = [('HV_ENABLE', 4), ('DOORS', 3)] # EV_SW codes on the switch device
|
||||
EVIOCGSW = (2 << 30) | (8 << 16) | (0x45 << 8) | 0x1b
|
||||
HV_CURRENT = '/sys/glowforge/pic/hv_current'
|
||||
STATE = CNC + 'state'
|
||||
HV_HZ = 20.0
|
||||
SLEEP_S = 0.001
|
||||
|
||||
|
||||
def rd(fd):
|
||||
return os.pread(fd, 32, 0).strip()
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--secs', type=float, default=60.0)
|
||||
ap.add_argument('--json', default='')
|
||||
args = ap.parse_args()
|
||||
try:
|
||||
os.nice(5)
|
||||
except OSError:
|
||||
pass
|
||||
fds = [(n, os.open(p, os.O_RDONLY), inv) for n, p, inv in ATTRS]
|
||||
sw = os.open(SWITCH_DEV, os.O_RDONLY)
|
||||
hv_fd = os.open(HV_CURRENT, os.O_RDONLY)
|
||||
st_fd = os.open(STATE, os.O_RDONLY)
|
||||
|
||||
def sample():
|
||||
s = {}
|
||||
for n, fd, inv in fds:
|
||||
v = int(rd(fd) or b'0')
|
||||
s[n] = (1 - v) if inv else v
|
||||
buf = fcntl.ioctl(sw, EVIOCGSW, b'\0' * 8)
|
||||
for n, bit in SW_BITS:
|
||||
s[n] = (buf[bit >> 3] >> (bit & 7)) & 1
|
||||
return s
|
||||
|
||||
t0 = time.monotonic()
|
||||
state = sample()
|
||||
hv = int(rd(hv_fd) or b'0')
|
||||
print('t=%8.3f start %s hv=%d kstate=%s'
|
||||
% (0.0, ' '.join('%s=%d' % kv for kv in state.items()), hv, rd(st_fd).decode()))
|
||||
sys.stdout.flush()
|
||||
trans = []
|
||||
hv_log = []
|
||||
counts = {k: [0, 0] for k in state}
|
||||
worst_gap = 0.0
|
||||
n = 0
|
||||
next_hv = t0
|
||||
t_prev = t0
|
||||
while True:
|
||||
now = time.monotonic()
|
||||
if now - t0 > args.secs:
|
||||
break
|
||||
gap = now - t_prev
|
||||
if gap > worst_gap:
|
||||
worst_gap = gap
|
||||
t_prev = now
|
||||
new = sample()
|
||||
n += 1
|
||||
for k in new:
|
||||
if new[k] != state[k]:
|
||||
trans.append((now - t0, k, new[k]))
|
||||
print('t=%8.3f %-9s -> %d hv=%s kstate=%s'
|
||||
% (now - t0, k, new[k], rd(hv_fd).decode(), rd(st_fd).decode()))
|
||||
sys.stdout.flush()
|
||||
counts[k][new[k]] += 1
|
||||
state = new
|
||||
if now >= next_hv:
|
||||
hv_log.append((now - t0, int(rd(hv_fd) or b'0')))
|
||||
next_hv = now + 1.0 / HV_HZ
|
||||
time.sleep(SLEEP_S)
|
||||
secs = time.monotonic() - t0
|
||||
print('--- %.1f s, %d passes (%.0f Hz), worst gap %.1f ms' % (secs, n, n / secs, worst_gap * 1e3))
|
||||
for k, (c0, c1) in counts.items():
|
||||
print(' %-9s 0: %6.2f %% 1: %6.2f %%' % (k, 100.0 * c0 / n, 100.0 * c1 / n))
|
||||
if hv_log:
|
||||
vals = [v for _t, v in hv_log]
|
||||
lit = [t for t, v in hv_log if v > 20]
|
||||
print(' hv_current min %d max %d; > 20 raw for %.1f s%s'
|
||||
% (min(vals), max(vals), len(lit) / HV_HZ,
|
||||
(' (%.1f .. %.1f s)' % (lit[0], lit[-1])) if lit else ''))
|
||||
if args.json:
|
||||
with open(args.json, 'w') as f:
|
||||
json.dump({'secs': secs, 'passes': n, 'worst_gap_ms': worst_gap * 1e3,
|
||||
'counts': counts, 'transitions': trans, 'hv': hv_log}, f)
|
||||
print('record: %s' % args.json)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user