Audit follow-through: runbook, bench tools, recipes, release tooling

BRINGUP describes the present: the 54-test catalog and its seven-test
always core, the tier counts, the shipped low-temperature gates, the
density floor ($35 = 10), the two local core commits, the ffboot env
write, the aa-offset route, the current bench image, and the bench
measurements the audit asks for (pooled into the next session). The
workstation shell notes and every em dash are gone.

forgetest: the takeover waits for the cloud client too (found by its
command line); the unauthenticated /boot probe names the endpoint's
parameter; the UI prose is American English. Recipes: forgetest
fetches its package directory and init script only and drops
__pycache__ at unpack; the dev image no longer re-adds forgectrl; the
release image's remove list drops the gfui-client the BSP no longer
has; the platform identity strips the kernel's local-version hash
from the modules directory name, so a re-patched kernel keeps its
fingerprints. grblhal restart is stop then start. release.sh --dev
packs the dev image. fixture.sh refuses a readable env file.

Bench tools: the live-fire drills measure the lid-IR baseline before
every run and point at the fire-watch thresholds the engine reads;
one thermistor conversion (gfbench.degc) serves every drill; the six
dated measurement records leave the tool directory; feeder.c names the
two sysfs writes its caller makes.

Host tests: forgetest 258 pass; the coverage lint reports no uncovered
path across 54 tests. Acceptance: forgectrl.auth covers the /boot
probe; update.* cover ffboot and the manifest identity; the runbook
and bench-tool changes have no catalog consequence.
This commit is contained in:
ScottW514
2026-09-02 09:51:23 -04:00
parent 6002da8d12
commit 88ec984e28
29 changed files with 265 additions and 5296 deletions
+3 -3
View File
@@ -8,8 +8,8 @@
> [community forum](https://community.openglow.org).
Open-source firmware for Glowforge brand CNC lasers. ForgeFIRM replaces the
cloud-dependent factory software on the **stock control board** — no hardware
modification — and gives the machine a local controller, a local web control
cloud-dependent factory software on the **stock control board** - no hardware
modification - and gives the machine a local controller, a local web control
panel, and a standard Grbl interface.
* [Latest Release](https://github.com/openglow-org/forgefirm/releases)
@@ -50,7 +50,7 @@ machine is idle:**
The control board is common to Glowforge Basic, Plus, and Pro. The 5 MP
(OV5648) camera modules are fully supported and hardware-validated. The 8 MP
(OV8856) modules found in "HD" units have a complete capture path — the kernel
(OV8856) modules found in "HD" units have a complete capture path - the kernel
patches, device tree and sensor-aware capture profile they need are all in the
build, but it has never run on an 8 MP machine, so treat it as untested.
+129 -124
View File
@@ -1,11 +1,11 @@
# ForgeFIRM bring-up status & cold-start runbook
Last updated: **2026-08-26**.
Last updated: **2026-09-02**.
This is the present state of the machine, the bench runbook, the measured
hardware facts, and the authoritative list of open work. **The dated record —
hardware facts, and the authoritative list of open work. **The dated record -
bench campaigns, drills, scope gates, the audit remediation, the acceptance
campaigns — is [`CAMPAIGN-LOG.md`](CAMPAIGN-LOG.md)**; come here for what is
campaigns - is [`CAMPAIGN-LOG.md`](CAMPAIGN-LOG.md)**; come here for what is
true now, go there for how it was proven.
Read together with:
@@ -24,12 +24,12 @@ hardware-validated.**
39.98 kHz; `CONFIG_PREEMPT=y`; both OV5648 cameras on the mainline
imx-media pipeline with VPU JPEG encode; A/B slot install, signed `.fw`
releases and factory restore.
- **GRBL mode cuts real jobs.** grblHAL (the stock core plus one local fix, and
- **GRBL mode cuts real jobs.** grblHAL (the stock core plus two local fixes, and
the ForgeFIRM driver) speaks Grbl 1.1f over TCP:23; LightBurn drives motion, the laser and the
camera stream. First light landed 2026-08-11.
- **Cloud mode runs the factory experience end to end**, deliberately kept
and maintained: sign-in, camera homing, prints, pause/resume, cancel.
- **forgectrl is the one machine-services daemon** behind both modes —
- **forgectrl is the one machine-services daemon** behind both modes -
cooling engine, controller supervision, pulse-device broker, motion-liveness
gate, cameras, telemetry, settings, diagnostics, logging, updates, web panel.
- **Laser safety is hardware-first and bench-proven.** The chain
@@ -42,15 +42,15 @@ hardware-validated.**
modes (cancel-and-return on a lid or interlock open, button pause/resume),
bench-validated 2026-08-17.
- **Releases are gated by the acceptance tool** (`forgetest`, dev image only):
a 46-test catalog, domain-scoped inheritance, an always-required safety core,
a 54-test catalog, domain-scoped inheritance, an always-required safety core,
a bench actuator that works the lid, the interlock and the button so most of
the operator's part runs unattended, and a release gate that reads the
exported artifact. The latest full campaign, on dev image `20260824230512`,
satisfied 45 of 45 from nothing in 27 minutes (36 tests unattended) and
authorizes a release; **no release is cut yet.**
exported artifact. The latest full campaign satisfied every test from
nothing and authorizes a release (the record is in `CAMPAIGN-LOG.md`);
**no release is cut yet.**
Current bench state: dev image `20260824230512`, the board resting on the SD
dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
Current bench state: dev image `20260901225926` on the SD card (eMMC slot 1 =
factory 2024, slot 2 = a ForgeFIRM pin-file image, archives in
`/data/forgefirm/archive`).
## The bench
@@ -64,7 +64,7 @@ dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
works: U-Boot (in eMMC boot0) reads the saved env at eMMC user-area 0x80000,
which selects the boot device (bench board: `mmcdev=0
mmcroot=/dev/mmcblk1p1` = SD), then loads `/boot/uEnv.txt` and `/boot/zImage`
from that rootfs partition — so the kernel always comes from the burned SD.
from that rootfs partition - so the kernel always comes from the burned SD.
Full map: "eMMC boot & recovery architecture" in the facts bank below.
**Module-only changes hot-swap**: scp `glowforge.ko` over
`/lib/modules/<kver>/extras/`, then `rmmod glowforge && modprobe glowforge`.
@@ -75,7 +75,7 @@ dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
- **Module hot-swap vs kernel re-stamps**: the hot-swap only loads if the
module was built against the FLASHED kernel's patch state. Any edit under the
kernel recipe's overlay (e.g. `glowforge.dts`) re-stamps
`CONFIG_LOCALVERSION_AUTO` — and the stamp does NOT reproduce by reverting
`CONFIG_LOCALVERSION_AUTO` - and the stamp does NOT reproduce by reverting
the edit (the kernel patch tree is a fresh git commit each `do_patch`, not
sstate-restored), so after any overlay edit the module can only ship with a
full image flash. **Kernel/BSP changes therefore ride one image flash,
@@ -91,11 +91,11 @@ dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
- **fwup lab (host)**: a host directory (`<fwup-lab>`) holds host-built
`fwup-0.14.2` (factory-era) and `fwup-v1.16.0` under `bin/` and the DEV
signing keypair `devkeys/fwup-key.{priv,pub}` (`fwup-key-raw.pub` = raw
32-byte form — what fwup 0.14.2 expects; 1.x reads both). Cross-version
32-byte form - what fwup 0.14.2 expects; 1.x reads both). Cross-version
compatibility is proven both ways (modern-packed signed archives apply with
0.14.2; modern fwup verifies and applies the factory `.fw` — signer key
0.14.2; modern fwup verifies and applies the factory `.fw` - signer key
2017-05-001.pub). **The production release key is held offline by the
operator** — the installer embeds its public key, so releases sign with that
operator** - the installer embeds its public key, so releases sign with that
key only. Pack releases with `scripts/mkfw.sh`; the full pipeline is
`scripts/release.sh`, invoked as:
`FWUP=<fwup-lab>/bin/fwup-v1.16.0 FWUP_COMPAT=<fwup-lab>/bin/fwup-0.14.2
@@ -108,19 +108,16 @@ dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
the end of the session. Bench tools worth keeping live in
`forgefirm/scripts/bench/` and ship on the dev image under
`/usr/share/forgetest/bench/`.
- **Shell gotchas** (cost real time): PowerShell mangles embedded double quotes
in git-commit here-strings (avoid `"` in messages); `wsl -- bash -c '...'`
eats `$VAR` expansions (use script files run via PowerShell, not Git Bash,
which MSYS-mangles `/mnt/c` paths).
## Running the controller (grblHAL-glowforge on the board)
Source: the `grblHAL-glowforge` sibling repo — the **canonical grblHAL driver
Source: the `grblHAL-glowforge` sibling repo - the **canonical grblHAL driver
repo** (github.com/openglow-org/grblHAL-glowforge, branch `main`): core as a
submodule at `src/grbl` (→ openglow-org/grblHAL-core fork, branch `forgefirm` = upstream
master plus one local commit, the `step_us_min` buffer sizing that keeps a
fortified build from aborting in `settings_init`; the settings-write crash fix
merged upstream 2026-08-04 as grblHAL/core PR #999). `driver.c` implements the
master plus two local commits: the `step_us_min` buffer sizing that keeps a
fortified build from aborting in `settings_init`, and the laser-power
re-assert on the first segment after a hold, which is what keeps a resumed M3
job lit; the settings-write crash fix merged upstream as grblHAL/core PR #999). `driver.c` implements the
HAL; machine constants live in `src/boards/glowforge.h`.
**The controller is spawned and supervised by forgectrl**: the supervisor
@@ -129,29 +126,29 @@ child, respawns it on a crash (after safing the machine), and switches modes
live via `POST /mode` / the Status-tab selector. The `grblhal` and `gfcloud`
init scripts defer to it (they remain as manual emergency levers, routed
through `POST /controller/stop|start`). The pulse device arrives as a
broker-inherited fd (`GF_PULSE_FD`) — the device never closes across mode
broker-inherited fd (`GF_PULSE_FD`) - the device never closes across mode
switches, homing handovers or respawns, so the 40 V rail never cycles as a side
effect — and the supervisor verifies **physical motion** (head-accelerometer
effect - and the supervisor verifies **physical motion** (head-accelerometer
liveness probe) before the first controller spawn of each session.
Architecture: a wall-paced producer thread runs the core stepper ISR against a
virtual step clock (1000× machine tick) and maps step events to pulse bytes; a
SCHED_FIFO shipper feeds `/dev/glowforge` through a bounded queue; a recursive
core mutex stands in for interrupt masking. `GFSINK` unset = null-sink mode
(full engine, no hardware I/O — host testing and CI).
(full engine, no hardware I/O - host testing and CI).
1. Build: `bash <repo>/forgefirm/scripts/bench/build-glowforge.sh` in the build
environment (from Windows, launch it through the WSL distro from PowerShell
— Git Bash mangles `/mnt/c` paths). Produces `build-arm/grblHAL_glowforge`
environment (on a Windows host, inside the WSL distro). Produces
`build-arm/grblHAL_glowforge`
in the checkout (`-O1 -g`; machine constants force-included into the core:
53.333 µsteps/mm XY @ ×8, 2.832 half-steps/mm Z, 0.417" Z travel,
12000 mm/min max, 700/590 mm/s² accel — factory-derived, see
12000 mm/min max, 700/590 mm/s² accel - factory-derived, see
`puls_profile.py`).
2. Deploy: move the new binary over `/usr/bin/grblHAL_glowforge` (mv replaces
the inode, so the running instance is untouched), then kill the running
controller — the supervisor respawns it on the new binary within about a
controller - the supervisor respawns it on the new binary within about a
second.
3. Standalone start (bench/debug only — requires forgectrl stopped, since the
3. Standalone start (bench/debug only - requires forgectrl stopped, since the
broker's exclusive hold on `/dev/glowforge` makes any self-open fail EBUSY):
`cd /data && GFSINK=/dev/glowforge grblHAL_glowforge -p 23 -e
/data/EEPROM-glowforge.DAT`. Env knobs: `GFSINK_RATE` (machine tick, default
@@ -162,7 +159,7 @@ core mutex stands in for interrupt masking. `GFSINK` unset = null-sink mode
applies the full analog machine config at init either way (×8 modes, decay 1,
motor_lock 8, laser latched, PIC hold currents) and swaps PIC run/hold
currents around motion. Each motion run logs a producer-stats line
(callbacks, µs/call, max-behind, clamped) — `clamped` should stay 0.
(callbacks, µs/call, max-behind, clamped) - `clamped` should stay 0.
4. Connect LightBurn/UGS to `<machine-ip>:23`, or jog raw: `$J=G91X40F1200`.
`^X` mid-motion aborts via kernel `cnc/stop` (controlled decel) and raises an
alarm; TCP disconnects never kill the process (the dead-man fd stays held).
@@ -175,15 +172,15 @@ still the one loaded at start until the controller restarts (a mode switch,
a `POST /controller/stop` + `start`, or a boot). Verified host-side: after a
runtime `$35=0` the shipped duties stay floored.
**Stored `$`-settings beat freshly baked defaults** — after changing
**Stored `$`-settings beat freshly baked defaults** - after changing
`GLOWFORGE_DEFAULTS` values, run `$RST=$` once on the board (settings persist
in the eeprom file in `/data`).
**Protocol-loop pacing is fd-blocking.** `serial_wait()` drains TX then
`ppoll()`s the listen/client fds with a state-dependent timeout: idle and alarm
at 10 ms (1 ms while a delay callback is pending), motion at 200 µs, and the
parked states — a completed feed hold, a parked door ajar or closed, and sleep
— at the coarse idle poll, while the motion sub-phases (`Hold_Pending` decel,
parked states - a completed feed hold, a parked door ajar or closed, and sleep
- at the coarse idle poll, while the motion sub-phases (`Hold_Pending` decel,
`Parking_Retracting`/`Resuming`) keep the tight pace. Measured on the bench:
idle 2.7 %, active move 34–35 %, parked 2.7–3.0 %. Client RX is armed only
while the ring has a full read's worth of room, so a flow-control-violating
@@ -274,21 +271,21 @@ planner left the following moves cutting at a stale duty, or dark.
Contract rules enforced structurally: a power byte leads every kernel run
before any fire bit (a run start resets duty to ~100 %), transitions are
coalesced per tick so power bytes are never consecutive, and power bytes cost
no machine tick. Fire only ever rides motion segments of laser blocks — jogs,
G0 and homing are fire-free by construction — and the end-of-data backstop
no machine tick. Fire only ever rides motion segments of laser blocks - jogs,
G0 and homing are fire-free by construction - and the end-of-data backstop
covers every stream end. **Duty persists after end-of-data** (PWMSAR retains
its last value): the laser-off guarantee rests entirely on FIRE.
**Arming — the operator's button press is required.** The first laser-on of a
**Arming - the operator's button press is required.** The first laser-on of a
job (M3/M4, planner-synced) refuses outright if a coolant fire gate stands or
if no head is detected (`ALARM:3`, "laser fire blocked: no head detected"),
else forces the run fan profile on, unlocks the kernel laser latch, lights the
button white and blocks the gcode stream — pumping real-time traffic — until
button white and blocks the gcode stream - pumping real-time traffic - until
the operator presses the physical button (EV_SW bit 2), a soft reset aborts, a
lid or interlock open cancels, or `laser_button_timeout_s` (default 300 s)
expires into alarm 3. The coolant verdict is re-checked immediately after the
wait, before the window opens. The armed window survives S changes and M5/M3
toggles (no re-prompt mid-job) and closes — relocking the latch — at program
toggles (no re-prompt mid-job) and closes - relocking the latch - at program
end (`M2`/`M30`/`%`), when the sender connection changes, after
`laser_disarm_s` (default 60 s) of spindle-off idle, or immediately on
alarm/homing/reset/stream fault. The disarm grace counts down in Hold, Door and
@@ -339,7 +336,7 @@ factory 2.6.0-2228 session; measured numbers in the facts bank).
the head returns to the position the job started from **with the lid still
open**, the kernel latch relocks and the armed window closes. The
return-home park ignores the lid and always runs to completion. The next job
re-arms with a button press — the same press the hardware button latch needs,
re-arms with a button press - the same press the hardware button latch needs,
so software and hardware agree by construction.
- **During the pre-run button wait:** a lid or interlock open cancels the job
with the reason named (clean soft reset, no alarm); a press with the lid open
@@ -372,9 +369,9 @@ doors = bit 3 (the series combination the safety chain itself uses), remote
interlock = bit 5 (inverted sense), button = bit 2. The door signal is hidden
from the core while it is IDLE, JOG or HOMING (`gfsw_visible`) so a lid cycle at
idle cannot park the controller in `Door:0` and leave a sender waiting.
**hv_enable (bit 4) is never gated on** — it is the readback of the chain's
**hv_enable (bit 4) is never gated on** - it is the readback of the chain's
HV_ENABLE output, telemetry only. **The interlock latch (bit 6) is not gated on
either** — the hardware chain enforces it. No switch device (host builds) = no
either** - the hardware chain enforces it. No switch device (host builds) = no
capability advertised. `GF_SWITCH_FILE` is the file-backed EV_SW word that lets
null-sink builds drive these edges in CI.
@@ -384,15 +381,15 @@ Runtime-selectable through `homing_mode` in `/data/forgefirm.conf` (forgectrl
`GET/POST /settings`, panel selector); the driver re-reads the file on every
`$H`:
- `gfcloud` — factory camera homing via the Glowforge web service. **Live
- `gfcloud` - factory camera homing via the Glowforge web service. **Live
verified**; a full cycle runs in 50–65 s.
- `switches` — the planned limit-switch cycle (falls through to the core, still
- `switches` - the planned limit-switch cycle (falls through to the core, still
disabled `$22=0`).
- `none` — `$H` rejects with error 5.
- `none` - `$H` rejects with error 5.
Architecture: `glowforge_homing.c` registers a driver `$H` that shadows the
core's; for gfcloud it suspends the stream engine (only from a fully idle
kernel — closing the flock'd fd mid-program is an e-stop), spawns
kernel - closing the flock'd fd mid-program is an e-stop), spawns
`/usr/sbin/gfhome.py` (config `/data/etc/gfhome.conf`, first-run copy from
`/etc/gfhome.conf.sample`), pumps the protocol so senders keep getting status,
then reacquires the device and re-applies the analog config and `step_freq`.
@@ -400,7 +397,7 @@ then reacquires the device and re-applies the analog config and `step_freq`.
`ALARM:18` like a failed core cycle (`gfcloud_home_timeout_s`, default 300).
The runner drives the GFUIService dispatch itself (the stock `run()` loop can
neither stop nor close the socket) and treats hunt + ≥1
accelerometer-witnessed motion window + quiet (10 s) as complete — the modern
accelerometer-witnessed motion window + quiet (10 s) as complete - the modern
v2.6.0 sequence, captured from a live service session, is settings → hunt → lid_image
→ single corner move → lid_image → silence. It then re-homes
the lens against the hall for a deterministic Z. **A quiet service without an
@@ -409,7 +406,7 @@ accel-witnessed motion window is a failure, not a homing.**
Position semantics: factory home = machine origin (back-left corner, +Y =
FRONT, workspace all-positive 0..495 × 0..279); Z top-of-travel = 10.6.
`gfcloud_home_x/y/z` calibrate the post-home coordinates once measured
(defaults 0 / 0 / Z max). GRBL mode permits unhomed cutting — position shows
(defaults 0 / 0 / Z max). GRBL mode permits unhomed cutting - position shows
counters-only and painted red until anchored.
## The machine-services daemon (forgectrl, port 8080)
@@ -420,15 +417,15 @@ supervision**, the **pulse-device broker**, the **motion-liveness gate**, the
**cooling engine** (single owner of fans/pump/TEC/heater for both modes), plus
cameras, telemetry, settings, diagnostics, the web panel, updates and the
**logging tree**. It runs under a respawn wrapper (its init script); a
restarted daemon retakes supervision once the machine is idle — an unmanaged
restarted daemon retakes supervision once the machine is idle - an unmanaged
controller left running mid-move is replaced at idle, not adopted (the old
inherited fd cannot be taken over). The meta-forgefirm recipe pins its SRCREV
in `forgectrl-pin.inc` (bump deliberately after pushing) and installs the
sysvinit script from the repo's `init/`; bench builds cross-compile with
`forgefirm/scripts/bench/build-forgectrl.sh`. The **machine-services
contract** — EV_SW switch map, sensor conversions, hardware single-writer
contract** - EV_SW switch map, sensor conversions, hardware single-writer
ownership, cooling channels, mode supervision, pulse-device ownership, logging
— is [forgectrl](https://docs.forgefirm.org/technical/forgefirm/forgectrl/) on the documentation site.
- is [forgectrl](https://docs.forgefirm.org/technical/forgefirm/forgectrl/) on the documentation site.
Every state-changing endpoint requires the first-boot bearer token in `/data`
(embedded in the panel), a Host address-literal check, and
@@ -438,7 +435,7 @@ additionally require the physical button held.
One ulfius daemon serves it all:
- `GET /` — the tabbed control panel (**Status / Machine / GF Cloud / GRBL /
- `GET /` - the tabbed control panel (**Status / Machine / GF Cloud / GRBL /
Diagnostics / Logs / System**; sources in `forgectrl/src/ui/`,
`index.html`, `panel.js`, `forms.js`, `help.js` and `theme.css`, bundled into the binary by
`embed.cmake`; `tools/devserver.py` and the repo's `.devcontainer/` serve the
@@ -449,13 +446,13 @@ One ulfius daemon serves it all:
reboot. All settings controls disable (with a banner) while the machine is not
idle **or a diagnostic is running**. `/?action=stream|snapshot` remain the
mjpg-streamer-compatible aliases (lid camera; LightBurn uses the stream one).
- `GET /status` — motion state and true machine position (kernel step counters
anchored at homing via `/run/grblhal.homed` — the Grbl socket is never polled,
- `GET /status` - motion state and true machine position (kernel step counters
anchored at homing via `/run/grblhal.homed` - the Grbl socket is never polled,
a connection there displaces the sender), coolant temps, pump/TEC, all four
fan tachs, the sensed laser evidence (emission samples, HV current, lid IR),
faults, the safety switches via `EVIOCGSW` (head = real presence, i.e. the
head sysfs group exists), and a `diag` flag for the UI lock.
- `GET/POST /settings` — the shared machine settings store
- `GET/POST /settings` - the shared machine settings store
(`/data/forgefirm.conf`, 0600, validated keys, empty-value-clears via query
params; `gf_password` write-only). **Writes 409 unless `cnc/state` is idle**
and 409 while a diagnostic owns the hardware; a multi-key POST lands as one
@@ -465,51 +462,51 @@ One ulfius daemon serves it all:
`laser_disarm_s`, `rail_settle_s`, `lid_lamp_idle`, `lid_policy`,
`cloud_pause_backtrack_ticks`, `cloud_resume_lead_ticks`, `cloud_hold_max_s`, the twelve
`log_<logger>_disk|_remote` levels and `syslog_server|port|proto`.
- `GET /mode`, `POST /mode?controller=grbl|cloud` — the supervisor: current
- `GET /mode`, `POST /mode?controller=grbl|cloud` - the supervisor: current
mode, controller state (`running | stopped | standby | motion-fault`), pid,
and the motion-liveness verdict (`verified | unverified | fault`); the POST is
the live idle-gated mode switch and the retry lever after a motion fault.
- `POST /controller/stop|start` — the routed emergency levers the init scripts
- `POST /controller/stop|start` - the routed emergency levers the init scripts
use. Stop writes `cnc/stop` + `cnc/laser_latch=1` **before** the SIGTERM
(kernel-level, instantaneous) and holds supervision suspended.
- `POST /cool/state` (job-state reports from the active controller, level-
triggered ~1 Hz) and `GET /cool/status` (engine phase, verdict, temps, report
age). The verdict the controllers enforce is the
`/run/forgefirm/cooling.state` file.
- `POST /diag/flow-verify|flow-calibrate|abort`, `GET /diag/status` — the
- `POST /diag/flow-verify|flow-calibrate|aa-offset-calibrate|abort`, `GET /diag/status` - the
diagnostics runner (below).
- `GET /cam/stream?cam=lid|head` — multipart MJPEG at half the sensor's frame
- `GET /cam/stream?cam=lid|head` - multipart MJPEG at half the sensor's frame
in each axis, 1296×972 on a 5 MP machine (2×2 Bayer-superpixel demosaic,
JPEG q75; `FORGECTRL_STREAM_Q` overrides, `FORGECTRL_STREAM_FPS` caps the
frame rate, unset/0 = sensor max).
- `GET /cam/snapshot?cam=lid|head&res=full|half&q=1..100` — single JPEG,
- `GET /cam/snapshot?cam=lid|head&res=full|half&q=1..100` - single JPEG,
default the sensor's full frame, 2592×1944 on a 5 MP machine (own MIT
bilinear demosaic).
- `GET /cam/status` — JSON (running/cam/clients/frames/fps/fps_cap/encoder/
- `GET /cam/status` - JSON (running/cam/clients/frames/fps/fps_cap/encoder/
buffers/sensor, the stream + snapshot geometry the fitted sensor implies,
and the privacy gate's `capture_allowed` / `stopped_by_lid`). Stream and
snapshot answer 409 while the lid is open.
- `GET /slots`, `POST /boot`, `POST /update/check|download|apply|upload`,
`GET /update/status`, `POST /restore/factory`, `POST /system/reboot` — the
`GET /update/status`, `POST /restore/factory`, `POST /system/reboot` - the
A/B update manager ([install and update](https://docs.forgefirm.org/technical/forgefirm/install-and-update/)). Upload is auth + idle + job
gated; a booted-slot write is refused under any `root=` spelling.
- `GET /logs`, `GET /logs/tail`, `POST /logs/export` — the logging tree
- `GET /logs`, `GET /logs/tail`, `POST /logs/export` - the logging tree
(below).
- `GET /fuse-identity` — serial, derived hostname and the SRK password, behind
- `GET /fuse-identity` - serial, derived hostname and the SRK password, behind
the token AND the physical button; fetched on demand only.
**Panel conventions:** the header identifies the machine by its **fuse
identity** (the factory hostname derived from the OCOTP serial), regardless of
any cloud identity override. **Units** are a display-only preference
(`ui_units`): the backend stores metric, and saves post only fields whose
display string changed. **Position always shows** — counters-only and painted
display string changed. **Position always shows** - counters-only and painted
red while unreferenced, normal once anchored.
**Camera engine.** One worker owns the V4L2 node persistently (media-ctl /
v4l2-ctl sequences identical to `gfhardware/cam.py`, factory exposure/gain/WB,
software hflip in the demosaic); it starts on demand and tears down fully after
10 s idle so gfhardware one-shot grabs still work. **Privacy gate: neither
camera captures unless the lid is closed** — `machine_lid_closed()` (EV_SW
camera captures unless the lid is closed** - `machine_lid_closed()` (EV_SW
bit 3, fail-closed) is checked at every entry point and once per frame, so an
open lid refuses stream and snapshot with HTTP 409 and a lid opened mid-capture
tears the pipeline down; `gfhardware.cam.capture()` enforces the same rule for
@@ -518,7 +515,7 @@ disables it, and the factory's lid-open focus hunt now fails as a result
([the video pipeline](https://docs.forgefirm.org/technical/forgefirm/video-pipeline/)). Geometry, Bayer depth and
the manual control set come from a **sensor profile** chosen by whichever
driver bound on that camera's I2C bus, so one image serves both the 5 MP
OV5648 (2592×1944) and the 8 MP OV8856 (3264×2448) — both 8-bit BGGR, so the
OV5648 (2592×1944) and the 8 MP OV8856 (3264×2448) - both 8-bit BGGR, so the
capture word and the demosaic are the same and only the geometry changes;
`/cam/status` reports the model and the frame sizes that follow from it.
A frame the capture queue flags errored is dropped rather than demosaiced,
@@ -527,18 +524,18 @@ engine; `/cam/status` carries the running `health` counts (`src/camhealth.c`,
host test `camhealth_test`). The cameras share the
hardware video-mux and the NEWEST request wins it: **streams preempt** (the
current stream's clients end cleanly), **snapshots borrow** (pause, switch,
grab one frame, switch back — a ~1–2 s freeze). The per-camera lamp
grab one frame, switch back - a ~1–2 s freeze). The per-camera lamp
(`pic/lid_led` / `head/white_led`) is raised to `FORGECTRL_LAMP` (default 132)
while capturing and restored to the resting level on idle. The resting lid lamp
is the `lid_lamp_idle` setting (0–255, default 236), asserted at daemon start,
on a live settings change, and at every controller spawn.
Measured performance (bench): **15.0 fps sustained** at 1296×972, sensor-
limited — NEON superpixel→YUV420 convert (18–20 ms) plus CODA960 VPU JPEG
limited - NEON superpixel→YUV420 convert (18–20 ms) plus CODA960 VPU JPEG
encode (7 ms) on cached (non-coherent) V4L2 capture buffers, so there is no
bounce copy; daemon ~41 % CPU with one viewer. Full-res snapshot 2.4 s warm /
2.7 s cold. Fallbacks, each bench-verified: `FORGECTRL_NO_CACHED_BUFS` (bounce
copy, needed on a kernel without the `allow_cache_hints` patch — detected via
copy, needed on a kernel without the `allow_cache_hints` patch - detected via
the `MMAP_CACHE_HINTS` capability bit), `FORGECTRL_NO_NEON` (scalar convert,
bit-identical), `FORGECTRL_NO_VPU` (libjpeg; also the snapshot path).
The default path is newer and bench-proven: the GC880 GPU demosaic feeding the
@@ -555,7 +552,7 @@ stream directly** while jogging from the same session; motion coexistence is
proven (`clamped 0`, max behind 4.5–7.2 ms of the 200 ms queue at 15 fps).
Run by hand: `/usr/bin/forgectrl &` after `/etc/init.d/forgectrl stop` (kill
before scp when redeploying — text-file-busy). It logs through syslog
before scp when redeploying - text-file-busy). It logs through syslog
(`/data/log/forgefirm/forgectrl/forgectrl.log`; a terminal, or `FFLOG_STDERR=1`,
echoes the lines).
@@ -608,7 +605,7 @@ override and wins for the process lifetime).
**Flow verification, as it runs:** a one-shot check at flood start (M8) heats
the loop at `cool_flow_heater_pct` for `cool_flow_check_s` and discriminates on
downstream temperature RISE, with periodic re-checks every `cool_recheck_s` —
downstream temperature RISE, with periodic re-checks every `cool_recheck_s` -
a stopped pump is undetectable any other way. A check starts only once the
sensors agree and the downstream reading is stationary (split-half mean
difference, not peak-to-peak). An over-limit check is a **suspicion**, not a
@@ -623,7 +620,7 @@ a JOG gets a jog-cancel.
rsyslog is the system logger and the only log writer. forgectrl and the grblHAL
driver emit through the shared non-blocking `fflog` emitter (drops, never waits
— a stalled log daemon can never park a controller thread), gfcloud/gfhome
- a stalled log daemon can never park a controller thread), gfcloud/gfhome
through `SysLogHandler`, the kernel through `imklog`; a controller's stray
stdout/stderr rides a per-controller `logger` relay under its own name. Tree:
`/data/log/forgefirm/{forgectrl,grblhal,gfcloud,gfhome,kernel,system}/`,
@@ -631,7 +628,7 @@ size-capped and rotated at boot and hourly (the `forgefirm-logging` recipe
renders the rsyslog rules from the settings at S19 via
`forgectrl --render-syslog`). Levels (`log_<logger>_disk` /
`_remote`) and the remote target (`syslog_server/port/proto`) are machine
settings **applied at reboot** — the panel's Logs tab shows configured vs.
settings **applied at reboot** - the panel's Logs tab shows configured vs.
effective and offers the reboot, plus a live viewer and a sanitized `tar.gz`
export for issue reports (`POST /logs/export`; `src/sanitize.c` replaces
serial, hostname, cloud credentials, panel token, SSID/PSK, IPs, MACs and
@@ -640,21 +637,21 @@ e-mails with stable placeholders). Design and contract:
## Release acceptance (forgetest, port 8090)
The release acceptance tool — catalog, campaigns, domain fingerprints,
The release acceptance tool - catalog, campaigns, domain fingerprints,
inheritance, the always-required core, invalidate-all, the release gate and the
coverage currency rule — is specified on the site (Developers, "Acceptance"); the tool lives in
coverage currency rule - is specified on the site (Developers, "Acceptance"); the tool lives in
`forgetest/` and ships only on the dev image (`/etc/init.d/forgetest`, HTTP
:8090). It is **bench-validated**: the full campaign on dev image
`20260824230512` (`c-20260824231028-b7ca`) satisfied 45 of 45 from nothing,
36 of them unattended with the bench actuator in the loop, in 27 minutes, and
the export reads "Release authorized: YES" for that image's manifest. That
:8090). It is **bench-validated**: a full campaign has satisfied the whole catalog
from nothing with the bench actuator in the loop (the record is in
`CAMPAIGN-LOG.md`), and its export reads "Release authorized: YES". That
authorizes a release; it is not one until `releases/v<version>/acceptance.json`
is committed.
- **Catalog: 46 tests** in `forgetest/forgetest/suite/`, every one a port of a
- **Catalog: 54 tests** in `forgetest/forgetest/suite/`, every one a port of a
proven bench drill or a bench-verified check: the always-required core
(`image.health`, `kernel.latch-locked-idle`, `kernel.k1-k2`,
`kernel.fire-line`), `forgectrl.*`, `logs.*`, `update.*`, `motion.*`
`kernel.deadman-close`, `kernel.backtrack-bounds`, `kernel.fire-line`,
`laser.emission-witness`), `forgectrl.*`, `logs.*`, `update.*`, `motion.*`
(pacing, jog round-trip, liveness probe, cancel/abort, dead-man, the lid,
interlock and button parity tests), `cooling.*` (flow verification, fans
quiet after motion, a gate setting tripping and off by value, a fan under
@@ -667,7 +664,7 @@ is committed.
the cloud client driven from a local socket with a synthesized
laser-free job, no account, no network, nothing on the bed). Tests that
share a setup are merged; the `auto` tests stay separate for failure
isolation. 28 are `auto`, 9 `operator`, 9 `live`; with the bench actuator up,
isolation. 34 are `auto`, 10 `operator`, 10 `live`; with the bench actuator up,
eight of the operator tests run in the unattended queue.
- **The operator's part is asked for by name, not by popup**
(the site, Developers, "Acceptance", "The operator's part"): a Ready prompt before a
@@ -687,7 +684,7 @@ is committed.
`do_deploy`), `forgefirm-image-manifest.bbclass` assembles them plus the layer
content hashes into `/etc/forgefirm-manifest.json` (also deployed beside the
image as `*.forgefirm-manifest.json`), and `scripts/manifest-from-tree.py`
computes the byte-identical thing on a workstation — the identity is
computes the byte-identical thing on a workstation - the identity is
content-defined, independent of the checkout's commit or dirty state.
**Component pins live in `<recipe>-pin.inc`** (SRCREV + the PV that moves
with it, nothing else) and
@@ -696,7 +693,7 @@ is committed.
platform change and invalidates everything.
- **Baseline rule:** every test and bench tool is bracketed by a baseline pass
(`forgetest/baseline.py`), against a fresh-boot reference taken once per boot
**after a power cycle** (a soft reboot leaves the lid lamp dark — the PIC
**after a power cycle** (a soft reboot leaves the lid lamp dark - the PIC
lights it at power-on). Takeover runs capture the controller-owned kernel
attributes on entry and write them back before forgectrl restarts, so a
leftover `motor_lock` mask can never read as a wedged driver.
@@ -767,7 +764,7 @@ is committed.
The per-job range in raw counts is the record, and a ceiling, if one is
ever wanted, is set in raw counts from it (`temp_calibrate.py supply-*`
stays for a machine where the heatsink is reachable).
- **The SoC guards itself.** The i.MX6DL (rev 1.3) on-die monitor is
- **The SoC guards itself.** The i.MX6 Solo (rev 1.3) on-die monitor is
`thermal_zone0` (`imx_thermal_zone`, the same node as `hwmon0`), governor
`step_wise`, trips at **85 C passive** and **90 C critical** (the
consumer-grade points the driver derives from the fuses: hot point 95,
@@ -799,7 +796,7 @@ is committed.
- **DRV8825 stepper drivers wedge on 40 V rail glitches** (factory board; the
TMC2130s belong to the upgraded OpenGlow board only). A glitch can leave the
drivers unserviceable: SDMA playback and the position counters run normally
while the motors produce nothing. The supply itself is fine — this is a
while the motors produce nothing. The supply itself is fine - this is a
driver failure mode, not a marginal rail. The kernel drives their nRESET and
nSLEEP pins (`reset-gpio` gpio3 18, `sleep-gpio` gpio3 16), but only as a
pair inside every enable/disable cycle, together with the rail: the reset
@@ -813,24 +810,24 @@ is committed.
motion**; keep the rail up (every power-up is a wedge lottery), which is why
the pulse-device broker exists and why there is no idle-rail-off policy.
- **Motion liveness = the head accelerometer** (`glowforge.dts` `head-accel`,
i2c-3 @0x1e — resolve iio devices by bus path, never by index; lid = i2c-0
i2c-3 @0x1e - resolve iio devices by bus path, never by index; lid = i2c-0
@0x1e, board = i2c-3 @0x1d). Signatures on an identical commanded move: real
motion **1800–2900 counts peak-to-peak** on X/Y (noise floor at 1 g ≈ 16384),
a genuinely dead/wedged axis ≤ ~250, and the rail-on / current-step jolt up to
~700 — which is why the forgectrl probe gates controller start at
~700 - which is why the forgectrl probe gates controller start at
**p2p ≥ 800** (`P2P_MOVING`), writes `cnc/motor_lock=0` for its own move (a
leftover mask from any tool must not read as a wedge) and settles 300 ms
after the run-current step before sampling. A masked axis reads 144–480.
gfhome requires at least one accel-witnessed motion window before a quiet
service counts as homed. Raw sysfs accel reads are slow (~150 ms each) —
service counts as homed. Raw sysfs accel reads are slow (~150 ms each) -
enough for a binary verdict over a multi-second window, not for waveforms.
- **Any probe/liveness move goes RIGHT (+X) first, then back**: a cable lives
at the end of LEFT travel and must never be crushed.
- **Rail-contact signature** (from the retired accelerometer-homing spike,
relevant to any future contact sensing; tools `accel_fast.py`,
`bump_seek.py`): creep baseline ≈0.5–2 k counts, contact jumps to 29–42 k
within ~4 ms (20–40×). But **slow approaches are near-silent** — belt
compliance turns slow-speed skipping into sub-threshold grinding — so any
within ~4 ms (20–40×). But **slow approaches are near-silent** - belt
compliance turns slow-speed skipping into sub-threshold grinding - so any
contact-sensing scheme must strike fast. Direct I²C (unbind st-accel,
CTRL1=0x6F = 800 Hz ODR) reads ~530 Hz from Python; st_accel sysfs one-shots
are ~6 Hz and the kernel has no IIO triggers.
@@ -895,7 +892,7 @@ is committed.
(authoritative).
- **Z**: bit 6 SET = lens UP = +Z (hardware-verified). Home = hall trigger at
TOP; usable travel ≈ 30 half-steps ≈ 10.6 mm ≈ 0.417"; 0.3534 mm/half-step.
Never blind-drive Z — hall-supervised only.
Never blind-drive Z - hall-supervised only.
- **XY**: 0.15 mm per full step; DIR bit set = −X / +Y (Y1/Y2 complementary).
**+Y physically moves the gantry toward the FRONT.** Home corner (convention,
for the planned limit-switch homing) = back-left (X min, Y min), workspace
@@ -917,14 +914,14 @@ is committed.
(6.4 % measured vs 6.3 % commanded at PWMSAR=8).
- **Laser duty thresholds** (ladder on scrap at F300, constant power): the
tube has two thresholds, far apart. The discharge **strikes between 2 % and
3 %** duty — 2 % (PWMSAR 2) draws no measurable `hv_current` and leaves
nothing at all, 3 % (PWMSAR 3) draws current — but it does **not lase
3 %** duty - 2 % (PWMSAR 2) draws no measurable `hv_current` and leaves
nothing at all, 3 % (PWMSAR 3) draws current - but it does **not lase
usefully until 16 %** (PWMSAR 20), the lowest duty leaving a continuous
mark. Between them (3–14 %) is a **dead band**: current flows and climbs,
and each line shows only a spot at its start (the strike transient) with a
dark line after it. So the usable analog range is ~16–100 %, and `$35`
(`DEFAULT_SPINDLE_PWM_MIN_VALUE`) ships at **16** to hold every nonzero S
above it. Raw `hv_current` counts are a presence/absence witness only: the
dark line after it. So the usable range on the retired analog model was ~16 to 100 %. On the
density model that ships, `$35` (`DEFAULT_SPINDLE_PWM_MIN_VALUE`) is **10**,
the derived density floor (`laser_floor_density`). Raw `hv_current` counts are a presence/absence witness only: the
per-rung means are non-monotonic at the top of the ladder and the signal
has no characterized transfer function.
- **Feed hold and resume in GRBL mode** (stream-measured on the null-sink
@@ -952,7 +949,7 @@ is committed.
- **Factory power model** (three cloud cuts of one 1" square, same location,
material and speed, only the UI power setting changed, pulse files captured
from each): the **power byte is pinned at 127**
in all three runs — three occurrences each, one as the cut begins and a
in all three runs - three occurrences each, one as the cut begins and a
refresh every ~27 000 ticks (~2.7 s). Analog duty is never a power control.
**Dose is FIRE-bit density on a fixed 7-tick period** (700 µs at
`STfr` = 10 000, ~1.43 kHz), the on-count dithered between adjacent integers
@@ -961,7 +958,7 @@ is committed.
= 7 of 7 (0.9965, continuous). The period was exactly 7 in all 570 measured
cycles of both dithered runs, and the mix of adjacent on-counts matches the
fractional part exactly (PP 1 wants 1.371; 2-runs are 212 of 571 = 0.371).
The three **headers are identical** — the power setting never reaches the
The three **headers are identical** - the power setting never reaches the
machine, so the whole model is service-side. Motion is identical too: 5420
steps, 101.62 mm, 10.81 s at 9.44 mm/s. **Density tracks velocity through
corners**, by the same relative factor at every power setting (corner/cruise
@@ -995,10 +992,11 @@ is committed.
at 40 %: 25/25 correct classifications, plus all three settle cases. Settled-
loop noise is 0.52 °C peak-to-peak but only 0.11 °C split-half, which is why
the stationarity gate uses split-half means. Coolant windows: run ceiling
33 °C, resume 31 °C (factory job-header CMrx/…); the factory's low side
(floors ≈1.0/4.0 °C, ~16 °C warm-up gate) is not implemented yet. The
33 °C, resume 31 °C (factory job-header CMrx/…); the low side ships as the
coolant floor (`cool_temp_min`, 5 °C, a fire gate) and the warm-up gate
(`cool_temp_start`, 16 °C), the `COLD` and `WARMUP` verdicts. The
coolant thermistor conversion is the factory B-equation recovered from the
v2.6.0 binary — derivation on [sensors](https://docs.forgefirm.org/technical/machine/sensors/); the old
v2.6.0 binary - derivation on [sensors](https://docs.forgefirm.org/technical/machine/sensors/); the old
UAPI "best guess" linear formula was 3–5 °C high and everything derived from
it had to be re-derived. The flow check's bands hold from 19 to 27 C, the
loop heater's ceiling in a 20 C room, with the margin widening warm; above
@@ -1037,7 +1035,7 @@ is committed.
against a 177 ceiling plus drift), so no lamp change can trip them; each
job still logs baseline and peaks.
- **Emission and HV witnesses**: `cnc/laser_on_sampled` goes to its full 255
count on a commanded fire window and returns to 0 at Idle — the reliable
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` (J1_14, GPIO4_21) is the supply's power-good,
@@ -1123,8 +1121,8 @@ is committed.
the v2.6.0 app carries a complete but config-gated subsystem.
- **Switches**: truthy = closed/OK for lid/doors/button. **SW_INTERLOCK is
INVERTED**: the remote interlock (the regulatory 2-pin lockout connector)
reads ACTIVE only when the loop is OPEN. Basic/Plus — including the bench
machine — ship the connector factory-jumpered, so the bit reads 0 =
reads ACTIVE only when the loop is OPEN. Basic/Plus - including the bench
machine - ship the connector factory-jumpered, so the bit reads 0 =
satisfied; Pro brings it out for an external lockout chain. It must NOT gate
motion (the beam is hardware-gated), but ForgeFIRM's kernel module does drive
INTERLOCK_RESET high whenever the loop reads open, so the CD4043B latch
@@ -1132,24 +1130,24 @@ is committed.
(bench-verified: loop pulled → `interlock_latch`=1, `interlock_circuit` b4
set, all within one 50 ms sample; reinserted → all clear).
- **`hv_enable` (EV_SW bit 4, GPIO4_06) is the readback of the safety chain's
HV_ENABLE output** through the U24 inverter — not an input. Active for the
HV_ENABLE output** through the U24 inverter - not an input. Active for the
whole duration of any run, inactive at idle, and it drops 454 ± 3 ms after
the last charge-pump pulse (one-shot t_w measured pulse-to-drop with
`scripts/bench/cp_watchdog_timing.py`: 451.8 / 455.6 ms; feed period
199.98 ms; matching the measured R·C ≈ 500 kΩ × ≈900 nF). It gates nothing —
199.98 ms; matching the measured R·C ≈ 500 kΩ × ≈900 nF). It gates nothing -
it is telemetry (`/status` `switches.hv_enable`, panel "HV enable"), read
alongside `cnc/charge_pump_alive` (`interlock_circuit` b5). **Across a pause
and a resume** (measured at the pads with `scripts/bench/resume_dark_lead.py`,
~2 kHz through /dev/mem): a pause stops motion 317 ms after the command and
HV_ENABLE drops with the watchdog 550 ms after it, so a pause shorter than
about half a second never drops HV at all; on the resume HV_ENABLE and the
watchdog are back within ~3 ms while motion only restarts at ~219 ms — the
watchdog are back within ~3 ms while motion only restarts at ~219 ms - the
chain re-arms ~216 ms **before** the first step, so a resumed cut loses
nothing and no dark dwell is warranted. Naming note: the factory design
labels this net **E-STOP**; entries in `CAMPAIGN-LOG.md` written before the
2026-08-15 rename call it `estop`/`SW_ESTOP` with the pre-rename polarity
(the DTS then declared the pin active-high, so the bit read HIGH at idle and
LOW through a run — the same physical behavior, inverted). The DTS now
LOW through a run - the same physical behavior, inverted). The DTS now
declares it active-low, and the former `estop_halts_motion` /
`MOTION.ESTOP_HALTS_MOTION` opt-in is gone: a real e-stop belongs in the
lid-switch chain ([the safing chain](https://docs.forgefirm.org/technical/machine/safing-chain/)). Doors/door1/door2 stay stable during
@@ -1160,7 +1158,7 @@ is committed.
idle in 86–91 ms, the return-home park starting ~300–340 ms after the edge and
running to completion **with the lid still open**, the job reported
`:cancelled`. A cancel from the app takes the same path. The button pauses a
print — controlled stop, then a 2000-tick laser-off backtrack — and resumes it
print - controlled stop, then a 2000-tick laser-off backtrack - and resumes it
with a 1950-tick laser-off lead; the button **flashes white while paused**. A
lid open while paused cancels the job and parks from where it stands. The lens
hunt is not lid-gated.
@@ -1178,7 +1176,7 @@ is committed.
`cnc/laser_latch` is write-only, so lock state is read from b3.
- **Machine identity from OCOTP nvmem**: HW_OCOTP_MAC0 is the serial,
base-23-encoded to the factory hostname (`BCDFGHJKMQRTVWXY2346789`, XXX-YYY)
— fuse-verified against the factory label, and the C implementation matches
- fuse-verified against the factory label, and the C implementation matches
gfhardware `id.py` over 200 k random serials. The bench machine's actual
values are deliberately not recorded here: this is a public document and a
fuse identity cannot be rotated.
@@ -1188,17 +1186,17 @@ is committed.
- eMMC (`mmcblk2`): 3.6 GiB user area + two 16 MiB hardware boot partitions
(`mmcblk2boot0/1`). Factory user-area MBR (per the factory `.fw` manifest):
p1/p2 = 200 MiB rootfs A/B at blocks 8192/417792, p3 = `/data` from block
827392 to end of disk. (The bench board ran the legacy ForgeFIRM layout —
p3 shrunk plus a p4 — until `slotmigrate` reclaimed it to the byte-exact
827392 to end of disk. (The bench board ran the legacy ForgeFIRM layout -
p3 shrunk plus a p4 - until `slotmigrate` reclaimed it to the byte-exact
factory geometry.)
- **U-Boot lives in boot0** at 1 KiB (IMX IVT header), not in the user area.
Any boot0 rewrite below 0xC0000 risks the bootloader.
- **Saved env**: user area 0x80000 with a redundant copy at 0x82000 (what
`ffboot`/`fw_setenv` target; boot0's own 0x80000 region is zeros). Slot
selection = `mmcdev`/`mmchwpart`/`mmcpart`/`mmcroot`. Gap: `ffboot` sets
three of the four but never `mmchwpart` — it relies on the saved 0.
selection = `mmcdev`/`mmchwpart`/`mmcpart`/`mmcroot`. `ffboot` writes all four in
one transaction (`mmchwpart` 0, the user area).
- **Default (compiled-in) env boots recovery**: `mmcdev=1 mmchwpart=1
boot_recovery=yes` — a blank or corrupt env lands in recovery mode, not a
boot_recovery=yes` - a blank or corrupt env lands in recovery mode, not a
brick. `bootcmd`: select mmc dev+hwpart → load and import `/boot/uEnv.txt`
from the selected partition → if `boot_recovery=yes`, boot kernel+DTB from
raw boot0 sectors, else load `/boot/zImage` from the slot's rootfs. U-Boot
@@ -1225,7 +1223,7 @@ is committed.
headroom.
- **Facts about the factory 2024 firmware** (learned during the slot install):
no `/factory/imgN` mounts, the generic `fw_env.config` points at the WRONG
device (use the per-device `fw_env_mmcblk2.config` — ffboot's selection
device (use the per-device `fw_env_mmcblk2.config` - ffboot's selection
logic), no SSH (serial console only), and the factory kernel cannot see the
SD card (`ffboot -s` needs `-f` from factory). Factory `/etc/version` is a
numeric datetime stamp, so newest-slot selection is integer comparison.
@@ -1270,8 +1268,9 @@ feature requests, enhancements) will eventually be tracked as GitHub issues.
from the authorized export, `scripts/release.sh`, the kas flip and the
first GitHub release, per the site (Developers, "Release flow"), once
ready to publish. Repoint the core submodule to
upstream if the `step_us_min` sizing fix merges.
5. **Update system — recovery refresh.** A refreshed recovery image in
upstream once both local commits (the `step_us_min` sizing and the
hold-resume power re-assert) merge.
5. **Update system - recovery refresh.** A refreshed recovery image in
boot0; the design is on [install and update](https://docs.forgefirm.org/technical/forgefirm/install-and-update/).
6. **Head IRQ (exploratory).**
Owed for the head IRQ, only if a coarse hardware interrupt is wanted
@@ -1329,7 +1328,7 @@ covers the warm-up hold), the supply temperature window (the service sends
the whole ADC range and the factory binds it to nothing; the supply is
watched per job instead), the head, lid, interconnect and fused temperature
ceilings (no sensor at those locations; the chassis is watched per job), the
head accelerometer thresholds (item 6), the lid IR thresholds (the fire
head accelerometer thresholds (the crash watch, on local knobs), the lid IR thresholds (the fire
watch runs on local knobs; the header values stay ignored), the
HV current caps (the sampled emission witness covers the idle case, and HV
current is ranged per job), the thermal report upload conditions and the
@@ -1349,4 +1348,10 @@ pump flag. Beam detect stays with item 6.
bound (`cloud_hold_max_s`); `FORGEFIRM_RELEASE` is 0.0.1, so the bench slot
at v0.1.0 will meet the installer's downgrade prompt; the `faultpos`
live-fire drill is gone; a coolant sensor unreadable for two ticks is the
SENSOR verdict.
SENSOR verdict. Bench measurements the audit asks for, pooled into the
same session: the reset-to-probe state of the 40 V enable, the heater
enable, the TEC enable and the laser-enable nets at the connector during
a cold boot (the pads are 100 k pull-ups until a driver probes; a net
that floats high gets a pull-down pad in the device tree); one forced
kernel hard hang to confirm that the bootloader's watchdog-timeout path
boots the factory recovery and that a power cycle returns from it.
+6
View File
@@ -45,6 +45,12 @@ cmd_env() {
cmd_build() {
[ -e fixture.env ] || { echo "no fixture.env: run ./fixture.sh env first" >&2; exit 1; }
# The file holds the WiFi PSK and the API key: nobody but the owner
# reads it (an MSYS shell cannot express the mode, so it is not judged).
if [ "$(uname -o 2>/dev/null)" != "Msys" ] && [ -n "$(find fixture.env -perm /077 2>/dev/null)" ]; then
echo "fixture.env is group- or world-readable: chmod 600 fixture.env" >&2
exit 1
fi
if have idf.py; then
idf.py set-target "$CHIP" >/dev/null
idf.py build
+19
View File
@@ -337,6 +337,25 @@ def pidof(comm):
return out
def pgrep_f(needle):
"""PIDs whose /proc/<pid>/cmdline contains needle (a script run by an
interpreter has the interpreter's comm, so pidof cannot see it)."""
out = []
try:
for pid in os.listdir("/proc"):
if not pid.isdigit():
continue
try:
with open("/proc/%s/cmdline" % pid, "rb") as f:
if needle.encode() in f.read():
out.append(int(pid))
except OSError:
pass
except OSError:
pass
return out
def run(cmd, timeout=60):
"""Run a command list; returns (rc, combined output)."""
try:
+6 -2
View File
@@ -500,9 +500,13 @@ class Takeover:
rc, out = hw.initd("forgectrl", "stop")
log("takeover: forgectrl stop -> rc %s" % rc)
deadline = time.time() + 15
while time.time() < deadline and (hw.pidof("forgectrl") or hw.pidof("grblHAL_glowfor")):
# Every holder of the pulse device: the daemon, the GRBL controller,
# and the cloud client (a script, so found by its command line).
def holders():
return hw.pidof("forgectrl") + hw.pidof("grblHAL_glowfor") + hw.pgrep_f("gfcloud.py")
while time.time() < deadline and holders():
time.sleep(0.5)
left = hw.pidof("forgectrl") + hw.pidof("grblHAL_glowfor")
left = holders()
if left:
self.__exit__(None, None, None)
raise Failed("takeover: processes still alive after stop: %s" % left)
+11 -11
View File
@@ -1215,7 +1215,7 @@ def lid_interlock_abort_body(ctx, ev, off, job, offset):
"The job is longer than the ring holds, so the ring is filled before the button "
"lights; that takes a minute. When the button lights white, do NOT press it - open "
"the lid, and close it when told. Nothing moves and nothing fires."],
description="A cloud print waiting for the button is cancelled by the lid: the wait ends "
description="A cloud print waiting for the button is canceled by the lid: the wait ends "
"with the lid named as the reason, the laser latch relocks, the armed window "
"closes, no run starts, and the job ends ':cancelled'. The job is longer than "
"the ring, so its feeder is alive through the wait with the rest of the print "
@@ -1285,7 +1285,7 @@ def lid_during_button_wait_body(ctx, ev, off, job, offset):
ctx.act("lid", "close")
settle_cloud(ctx, offset)
ev["events"] = offline_events(off)
ctx.log("PASS: lid open at the button prompt cancelled the print; latch locked, armed=false, "
ctx.log("PASS: lid open at the button prompt canceled the print; latch locked, armed=false, "
"button dark, ring empty with the feed stopped")
@@ -1422,7 +1422,7 @@ def pause_resume(ctx):
relocked = [ln for ln in log_lines_since(GFCLOUD_LOG, offset)
if "relocking the laser" in ln or ("print [" in ln and CANCELLED in ln)]
ev["relock_or_cancel_lines"] = len(relocked)
ctx.check(not relocked, "the pause relocked or cancelled the job (%s)", relocked[:2])
ctx.check(not relocked, "the pause relocked or canceled the job (%s)", relocked[:2])
# The job's lifecycle, from the same print: a warm-up before the first
# fire and a rest after the park are equipment protection the service
@@ -1587,8 +1587,8 @@ def oversize_stream_body(ctx, ev, off, job, offset, before):
# or interlock abort - stop, park back to the job start, relock,
# disarm, ':cancelled' - judged in full.
off.cancel(9004)
ctx.log("cancelled the print as the app would")
svc_stop = "action cancelled mid-run; stopping motion"
ctx.log("canceled the print as the app would")
svc_stop = "action canceled mid-run; stopping motion"
got = wait_log(ctx, offset, [svc_stop, "start return home", "return home complete"], 300)
fin = wait_action_finished(ctx, offset, "print", 60)
ev["service_cancel"] = {k: message(v) for k, v in got.items()}
@@ -1596,7 +1596,7 @@ def oversize_stream_body(ctx, ev, off, job, offset, before):
for k, v in ev["service_cancel"].items():
ctx.log(" [cancel] %s: %s", k, "seen" if v else "MISSING")
ctx.check(got[svc_stop], "the app's cancel did not stop the run")
ctx.check(got["return home complete"], "the cancelled print did not park to completion")
ctx.check(got["return home complete"], "the canceled print did not park to completion")
judge_abort_tail(ctx, ev, offset, "app", fin)
ctx.check(hw.sysfs_int("cnc/streaming", 0) == 0,
"the device was left in live-feed mode after the job ended")
@@ -1610,7 +1610,7 @@ def oversize_stream_body(ctx, ev, off, job, offset, before):
"stopped it, parked, relocked and reported ':cancelled'")
@test("cloud.paused-lid-cancel", title="A paused cloud print is cancelled by the lid",
@test("cloud.paused-lid-cancel", title="A paused cloud print is canceled by the lid",
subsystem="cloud", kind="live", est_min=6,
covers=_MACHINE_RUN, requires=["cloud.pause-resume", "cloud.lid-interlock-abort"],
actions=["button", "lid"],
@@ -1618,7 +1618,7 @@ def oversize_stream_body(ctx, ev, off, job, offset, before):
"The head needs 40 mm of free +X and +Y travel.",
"Press the button to start; when asked, press it again (pause), then open the lid and "
"leave it open until the head is back; close it when told."],
description="A job paused on the button is cancelled by the lid, from the state the factory "
description="A job paused on the button is canceled by the lid, from the state the factory "
"ends it in - there is no resume past a lid open. The same tail as every abort: "
"the motion stops, the head parks back at the job start, the latch relocks and "
"the armed window closes, the button goes dark, and the print ends ':cancelled'. "
@@ -1666,12 +1666,12 @@ def paused_lid_cancel_body(ctx, ev, off, job, offset):
st, cs = ctx.forgectrl.get("/cool/status")
ev["armed_after"] = cs.get("armed") if isinstance(cs, dict) else None
ev["latch_locked_after"] = latch_locked()
ctx.check(not ev["armed_after"], "armed window still open after the paused print was cancelled")
ctx.check(not ev["armed_after"], "armed window still open after the paused print was canceled")
ctx.check(ev["latch_locked_after"],
"kernel latch not locked after the paused print was cancelled")
"kernel latch not locked after the paused print was canceled")
ev["button_dark"] = hw.button_lit()
ctx.check(ev["button_dark"] is False, "the button is still lit after the cancel (%s)", ev["button_dark"])
ctx.act("lid", "close")
settle_cloud(ctx, offset)
ev["events"] = offline_events(off)
ctx.log("PASS: a paused print cancelled by the lid stopped, parked, relocked and reported ':cancelled'")
ctx.log("PASS: a paused print canceled by the lid stopped, parked, relocked and reported ':cancelled'")
+1 -1
View File
@@ -45,7 +45,7 @@ def auth(ctx):
for path, params in (("/controller/stop", None), ("/controller/start", None),
("/mode", {"controller": "grbl"}), ("/settings", {"ui_units": "mm"}),
("/diag/flow-verify", None), ("/diag/abort", None),
("/update/apply", None), ("/boot", {"slot": "a"}),
("/update/apply", None), ("/boot", {"target": "a"}),
("/system/reboot", None), ("/restore/factory", None)):
st, body = fc.post(path, params=params, auth=False)
ctx.log("POST %s (no token) -> %s %s", path, st, body if isinstance(body, dict) else "")
+2 -2
View File
@@ -715,7 +715,7 @@ def arm_wait_lid(ctx):
ev["messages"] = [ln for ln in text.splitlines() if ln.startswith("[MSG:") or ln.startswith("ALARM")]
ctx.log("controller: %s", ev["messages"])
ctx.check("lid opened during arm - job canceled" in text,
"the lid open was not reported as cancelling the arm")
"the lid open was not reported as canceling the arm")
ctx.check("help]" in text, "no reset banner after the lid-open cancel (it must be a clean cancel)")
ctx.check("ALARM" not in text, "an alarm was raised on the lid-open cancel")
# Armed window closed and the kernel latch locked.
@@ -869,7 +869,7 @@ def pause_resume_lid_cancel(ctx):
ctx.check(zero_at is not None and zero_at < 3.0,
"emission did not stop after the lid opened (first 0 at %s)", zero_at)
ctx.check(tail_zero, "emission returned after the lid opened")
ctx.check("lid opened - job canceled" in text, "the lid open was not reported as cancelling the job")
ctx.check("lid opened - job canceled" in text, "the lid open was not reported as canceling the job")
ctx.check("help]" in text, "no reset banner after the cancel")
ctx.check("ALARM" not in text, "an alarm was raised on the cancel (position should be kept)")
t3 = time.time()
+3 -3
View File
@@ -12,7 +12,7 @@
* only updated in place. A poll that rebuilt them would swallow the click
* it landed on: the button that took the mousedown would be gone before
* the mouseup, so no click event would ever be raised. Every action also
* greys its control out on the press and pulls the next poll forward, so
* grays its control out on the press and pulls the next poll forward, so
* the page answers the operator rather than the timer. The help popovers
* (help.js) sit on static elements only, so no rebuild ever orphans one.
*
@@ -56,7 +56,7 @@ function schedule(ms){if(pollTimer)window.clearTimeout(pollTimer);pollTimer=wind
function kick(){schedule(120)}
function poll(){if(polling){schedule(150);return}polling=true;
/* While an action is still in flight the state is asked for in full:
a 304 would skip the render that releases the greyed-out buttons. */
a 304 would skip the render that releases the grayed-out buttons. */
var h=(stateEtag&&!pending)?{'If-None-Match':stateEtag}:null;
api('GET','/state',null,function(s,d,x){polling=false;
if(s===200){stateEtag=x.getResponseHeader('ETag');state=d;
@@ -72,7 +72,7 @@ function setIgnoreReq(on){ignoreReq=!!on;try{window.localStorage.setItem('forget
$('ignreq').checked=ignoreReq;setHtml($('ignreqon'),ignoreReq?"<span class='on'>ON - prerequisites are not enforced</span>":'');
if(state&&catalog)renderGroups()}
/* A start already sent but not yet seen in the state counts as busy, so
the buttons grey out on the click rather than on the next poll. The
the buttons gray out on the click rather than on the next poll. The
window is capped in case the answer never arrives. A queue holds the
machine between its tests as well as during them. */
function batchActive(){return !!(state&&state.batch&&!state.batch.finished)}
+4 -4
View File
@@ -757,7 +757,7 @@ class CloudSuiteTests(unittest.TestCase):
self.hunted(1522)
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready"])
lines = [l.replace("machine:_resume_retraced resuming (laser lead 1950 ticks)",
"machine:_resume_retraced resume refused ([Errno 22] Invalid argument); cancelling")
"machine:_resume_retraced resume refused ([Errno 22] Invalid argument); canceling")
for l in fixture("pause")]
pre, rest = cut(lines, "current state: MachineState.RUNNING")
pre, rest = pre + [rest[0]], rest[1:]
@@ -972,7 +972,7 @@ class CloudSuiteTests(unittest.TestCase):
hooks = {"press it. The print then waits": lambda: self.append(wait + run[:1], delay=0.05)}
self.assertFails(cloud.verdict_hold, "the run started under the warm-up hold", hooks=hooks)
# -- a paused print cancelled by the lid, a running one by the app --------
# -- a paused print canceled by the lid, a running one by the app --------
def cancel_parts(self):
"""(print prologue, the pause lines, the lid stop + park + cancel,
the same tail with the app's cancel as the trigger - what
@@ -986,7 +986,7 @@ class CloudSuiteTests(unittest.TestCase):
"2026-08-17T09:42:26.100000+00:00 gfcloud[1522] INFO machine:_run_loop "
"paused at Position(x=41.2, y=17.0, z=0.0)"]
app_cancel = [l.replace("lid opened mid-run; stopping motion",
"action cancelled mid-run; stopping motion") for l in rest]
"action canceled mid-run; stopping motion") for l in rest]
return pre, paused, rest, app_cancel
def test_paused_lid_cancel_on_the_bench_excerpt(self):
@@ -1009,7 +1009,7 @@ class CloudSuiteTests(unittest.TestCase):
self.assertFalse(ev["armed_after"])
self.assertFalse(ev["button_dark"])
self.assertEqual(self.fc.posts, [])
self.assertTrue(any("PASS: a paused print cancelled by the lid" in l for l in run.lines), run.lines)
self.assertTrue(any("PASS: a paused print canceled by the lid" in l for l in run.lines), run.lines)
def test_paused_lid_cancel_fails_when_the_paused_print_resumes_instead(self):
# a lid that resumed (or was ignored) leaves the print ':completed'
+1 -1
View File
@@ -1,5 +1,5 @@
# ============================================================================
# ForgeFIRM — debug-kernel build variant
# ForgeFIRM - debug-kernel build variant
# ============================================================================
# One target: a dev image whose kernel carries the lock-correctness options
# (DEBUG_MUTEXES, PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_SPINLOCK). It rides
+6 -6
View File
@@ -1,5 +1,5 @@
# ============================================================================
# ForgeFIRM — kas build configuration (factory Glowforge control board)
# ForgeFIRM - kas build configuration (factory Glowforge control board)
# ============================================================================
# The forgefirm repo is the BASE: it controls the build, the output firmware
# images land here (build/tmp/deploy/images/glowforge/), and the status docs
@@ -31,10 +31,10 @@ target: forgefirm-image
# kas uses KAS_WORK_DIR (the dir you run kas from = the forgefirm repo root).
# Upstream layers are cloned into ./layers/, the build runs in ./build/, and
# caches go in ./downloads + ./sstate-cache — all gitignored, all inside forgefirm.
# caches go in ./downloads + ./sstate-cache - all gitignored, all inside forgefirm.
repos:
# --- This repo (forgefirm) — auto-detected, no url/path needed -------------
# --- This repo (forgefirm) - auto-detected, no url/path needed -------------
forgefirm:
layers:
meta-forgefirm:
@@ -73,7 +73,7 @@ repos:
layers:
.:
# --- meta-openglow — Glowforge BSP layers ---------------------------------
# --- meta-openglow - Glowforge BSP layers ---------------------------------
# Referenced as a local sibling checkout (no url => kas performs no git ops,
# so in-place BSP edits are what gets built). The kernel-module-glowforge
# sources are NOT a layer; the kernel-module-glowforge.bb recipe fetches them
@@ -98,14 +98,14 @@ repos:
# local.conf additions
# ----------------------------------------------------------------------------
local_conf_header:
# Accept the NXP/Freescale firmware EULA — required to unpack firmware-imx
# Accept the NXP/Freescale firmware EULA - required to unpack firmware-imx
# (i.MX SDMA/VPU firmware blobs) pulled in by the i.MX6 BSP. ForgeFIRM targets
# the factory i.MX6 Glowforge board, so this firmware is intrinsic to the image.
eula: |
ACCEPT_FSL_EULA = "1"
# The kernel defaults to linux-fslc 6.12 in conf/machine/glowforge.conf
# (with the factory drivers forward-ported — EPIT/SDMA/OV5648/glowforge.ko —
# (with the factory drivers forward-ported - EPIT/SDMA/OV5648/glowforge.ko -
# see the site, Developers, "Build"). This explicit pin is redundant but harmless.
kernel: |
PREFERRED_PROVIDER_virtual/kernel = "linux-fslc"
@@ -114,7 +114,7 @@ def forgefirm_manifest_sha256_file(path):
return h.hexdigest()
python forgefirm_manifest_assemble() {
import glob, hashlib, json, os
import glob, hashlib, json, os, re
rootfs = d.getVar('IMAGE_ROOTFS')
components = {}
@@ -145,7 +145,12 @@ python forgefirm_manifest_assemble() {
layer_identity, layer_build = forgefirm_manifest_layers(d)
platform = {'machine': d.getVar('MACHINE'), 'layers': layer_identity}
platform['kernel_modules'] = sorted(os.listdir(os.path.join(rootfs, 'lib', 'modules'))) \
# The modules directory carries the kernel's LOCALVERSION_AUTO hash,
# which does not reproduce across a re-patch of the same source; the
# kernel's identity is its pinned SRCREV and config (the @srcrev and
# @config entries above), so the hash is stripped from the name here.
platform['kernel_modules'] = sorted(re.sub(r'[+-]g[0-9a-f]{7,}$', '', n)
for n in os.listdir(os.path.join(rootfs, 'lib', 'modules'))) \
if os.path.isdir(os.path.join(rootfs, 'lib', 'modules')) else []
dtbs = {}
for p in sorted(glob.glob(os.path.join(rootfs, 'boot', '**', '*.dtb'), recursive=True)):
@@ -12,11 +12,21 @@ LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda
# that builds the images, so the catalog and the gate agree by construction.
FILESEXTRAPATHS:prepend := "${THISDIR}/../../../:"
# The package directory and the init script, not the tool's whole tree: the
# host unit tests and their caches would otherwise enter the fetch checksum
# and rebuild the dev image after a workstation test run.
SRC_URI = " \
file://forgetest/ \
file://forgetest/forgetest/ \
file://forgetest/forgetest.init \
file://scripts/bench/ \
"
do_unpack:append() {
import subprocess
subprocess.call(['find', d.getVar('WORKDIR') + '/forgetest', d.getVar('WORKDIR') + '/scripts',
'-name', '__pycache__', '-type', 'd', '-prune', '-exec', 'rm', '-rf', '{}', '+'])
}
S = "${WORKDIR}"
inherit python3-dir update-rc.d forgefirm-manifest
@@ -56,6 +56,7 @@ case "$1" in
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
@@ -9,7 +9,6 @@ DESCRIPTION = "OpenGlow/ForgeFIRM development image for Glowforge"
# emulator fixtures are the canned frames its service-protocol test answers
# the service with.
IMAGE_INSTALL += " \
forgectrl \
forgetest \
python3-gfutilities-emulator \
python3-mmap \
@@ -20,7 +20,7 @@ DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge"
# nothing here imports. Each Python recipe declares the module packages it
# imports (python3-core plus the few it uses), so the meta-package goes.
FORGEFIRM_RELEASE_TRIM ?= "nano"
IMAGE_INSTALL:remove = "gfui-client python3 ${FORGEFIRM_RELEASE_TRIM}"
IMAGE_INSTALL:remove = "python3 ${FORGEFIRM_RELEASE_TRIM}"
# grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23).
# forgectrl: the ForgeFIRM machine-services daemon (HTTP :8080): controller
+4 -4
View File
@@ -46,15 +46,15 @@ page's takeover does that; from a host, stop them first.
| `offset_probe.py` | Coolant-sensor offset probe (board; forgectrl idle; dark, no press): switches one actuator at a time (exhaust at 100/50/25 %, intakes, air assist, purge, heater, pump, TEC, lid lamp, then all run fans) with both thermistors sampled at 25 Hz and scores the common-mode step at every edge and the level toggling inside every dwell; `offset_probe.py ladder` runs the air-assist duty ladder alone. Every value is restored on exit. JSON record in the bench data directory. |
| `flow_warm_validate.py` | Runs the real check from a heater-warmed baseline (board or host; forgectrl and controller stopped; `flow_warm_validate.py [cycles_per_case]`; results/log in the bench data directory; exit 1 if any run is misclassified). Note the ceiling: 100 % duty pushes the downstream sensor past 50 °C in 30 s while the bulk barely moves, so warm-loop validation above ~23 °C needs the laser, not the heater. |
| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric (board or host; forgectrl and controller stopped; `flow_recheck_char.py [heater_pct] [window_s]`); shows why over-temp cannot see a stopped pump and why passive warming trends are ambiguous. |
| `flow_confirm_drill.py` | Coolant flow suspicion/confirmation drill (runs on the board): one continuous M8 session walks the verdict state machine through real pump-off transients — verified → SUSPECT (+ immediate re-check) → cleared → SUSPECT → FAULT (consecutive) → recovered — printing PASS/FAIL per transition. Leaves the machine idle (M9, pump on, heater off). |
| `flow_confirm_drill.py` | Coolant flow suspicion/confirmation drill (runs on the board): one continuous M8 session walks the verdict state machine through real pump-off transients - verified → SUSPECT (+ immediate re-check) → cleared → SUSPECT → FAULT (consecutive) → recovered - printing PASS/FAIL per transition. Leaves the machine idle (M9, pump on, heater off). |
| `flow_escalate_drill.py` | Coolant starved-re-check escalation drill (runs on the board, controller running): sets the engine's confirmation budget `cool_confirm_max_s` to a short value through forgectrl's settings (`flow_escalate_drill.py [budget_s]`, default 60, the setting's minimum) and restores it after; with the pump off the job-start check reads SUSPECT, the stagnant loop cannot pass the settle gate inside the budget, and the engine must escalate to FAULT. PASS/FAIL (exit status), leaves the machine idle. |
| `flow_sampler.py` | Board-side coolant sampler used by the flow tools (`flow_sampler.py <duration_s> <interval_s>`, prints `elapsed,raw_down,raw_up`); run on the board (dev image: `/usr/share/forgetest/bench/`) so cadence does not depend on ssh latency. |
| `temp_calibrate.py` | Coolant temperature spot-check helper (board or host): `watch [seconds]` / `point <measured_C> [note]` / `fit` — pairs a measured temperature with averaged raw ADC readings and fits a per-machine line to sanity-check the factory curve against a thermometer. Points accumulate in `temp_calibration.json` in the bench data directory. |
| `temp_calibrate.py` | Coolant temperature spot-check helper (board or host): `watch [seconds]` / `point <measured_C> [note]` / `fit` - pairs a measured temperature with averaged raw ADC readings and fits a per-machine line to sanity-check the factory curve against a thermometer. Points accumulate in `temp_calibration.json` in the bench data directory. |
| `build-glowforge.sh` | Cross-compiles **grblHAL-glowforge** (the canonical driver repo) in the Yocto build environment, borrowing the recipe toolchain. Run: `bash <path>/build-glowforge.sh` (from Windows, launch it through the WSL distro from PowerShell; Git Bash mangles /mnt/c paths). Env: `FF_SRC_TOP`, `FF_BUILD_TOP`. This is the production controller build. |
| `build-forgectrl.sh` | Cross-compiles **forgectrl** (the canonical control-daemon repo) the same way, borrowing the toolchain from the forgectrl recipe workdir (regenerate with `bitbake forgectrl` after a clean). |
| `accel_fast.py` | Direct-I2C sampler for the two head-bus LIS2HH12s (runs on the board; unbinds/rebinds st-accel around the capture, 800 Hz ODR, ~270 Hz per device polled): optional mid-capture jogs via local grblHAL TCP. CSV to /tmp/accel.csv. The head accel is i2c-3 0x1e. |
| `bump_seek.py` | Accelerometer bump-seek homing prototype (runs on the board): creeps toward a rail in bounded jog segments via grblHAL TCP, learns the moving-noise baseline per segment, detects the contact jolt (~530 Hz sampling, 2-sample confirm), jog-cancels (0x85) and backs off. CSV to /tmp/bump.csv. |
| `accel_crash_probe.py` | Head-accel crash-detector de-risk drill (runs on the board; BRINGUP item 6): arms the head LIS2HH12's on-chip interrupt generator (IG_CFG1/IG_THS/IG_DUR1) and polls the latched IG_SRC1 for a strike, reporting the axes and raw magnitude. `coexist` (default) reaches the IG registers with I2C_SLAVE_FORCE while st_accel stays bound, so it proves whether the detector can be forgectrl-only with the liveness path untouched; `unbind` frees the device (stop the controller and forgectrl first). Touches only the IG registers, no full-scale change, no emission, no commanded motion by default (provoke a trip by hand or with one `--jog`). CSV to /tmp/accel_crash.csv. |
| `accel_crash_probe.py` | Head-accel crash-detector de-risk drill (runs on the board; the head crash detector): arms the head LIS2HH12's on-chip interrupt generator (IG_CFG1/IG_THS/IG_DUR1) and polls the latched IG_SRC1 for a strike, reporting the axes and raw magnitude. `coexist` (default) reaches the IG registers with I2C_SLAVE_FORCE while st_accel stays bound, so it proves whether the detector can be forgectrl-only with the liveness path untouched; `unbind` frees the device (stop the controller and forgectrl first). Touches only the IG registers, no full-scale change, no emission, no commanded motion by default (provoke a trip by hand or with one `--jog`). CSV to /tmp/accel_crash.csv. |
| `build-feeder.sh` | Cross-compiles `feeder.c` the same way. |
| `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. |
@@ -80,7 +80,7 @@ forgectrl's cooling engine refuses to let the laser fire unless a
heater-based flow check passes: with the pump commanded on, the loop heater
runs at `COOL_FLOW_HEATER_PCT` (40 %) for `COOL_FLOW_CHECK_S` (50 s) and the
downstream sensor's rise over its settled baseline must stay below
`COOL_FLOW_RISE_C` (14.4 °C) — a stopped pump lets the heater's output pool
`COOL_FLOW_RISE_C` (14.4 °C) - a stopped pump lets the heater's output pool
at the downstream sensor instead of being carried away. The three constants
live in `forgectrl/src/cool.h` and are the compiled defaults behind the
`cool_flow_*` settings.
+3 -2
View File
@@ -4,8 +4,9 @@
*
* Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to
* hold a bounded queue depth (like a real grblHAL backend would),
* with the deadman flock held. Motors must be locked and the laser latch
* locked by the caller (bench_phase6.sh does this).
* with the deadman flock held. The caller locks the motors and the laser
* latch first: write 1 to /sys/glowforge/cnc/motor_lock and 1 to
* /sys/glowforge/cnc/laser_latch, and read both back before starting.
*
* Reports: feed statistics, worst scheduling stall, ENOMEM count, and the
* final driver state (expect "running" throughout, "underrun" only after
+1 -3
View File
@@ -62,9 +62,7 @@ def temps():
adc_f = 1024.0 * 1.3
rinf = 10000.0 * math.exp(-3380.0 / 298.15)
def degc(raw):
r = 10000.0 / (adc_f / float(raw) - 1.0)
return 3380.0 / math.log(r / rinf) - 273.15
from gfbench import degc
with open('/sys/glowforge/pic/water_temp_1') as f:
d = degc(f.read())
with open('/sys/glowforge/pic/water_temp_2') as f:
-205
View File
@@ -1,205 +0,0 @@
=== flow matrix started 2026-08-02 20:20:59
duties=[10, 15, 20, 30, 40, 50] repeats=5 run=75s durations=[15, 20, 25, 30, 40, 50, 60, 75]
controller stopped for the experiment
initial settle (180 s with fans)...
baseline: down=22.62 up=22.46 (target for every run: <= 22.96)
[ 1/60] rep1 duty 10% FLOW start=22.77 down_rise@30s=3.93
elapsed 0.1 h
[ 2/60] rep1 duty 10% NO-FLOW start=22.38 down_rise@30s=3.10
elapsed 0.1 h
[ 3/60] rep1 duty 15% FLOW start=22.85 down_rise@30s=4.73
elapsed 0.1 h
[ 4/60] rep1 duty 15% NO-FLOW start=22.38 down_rise@30s=3.45
elapsed 0.2 h
[ 5/60] rep1 duty 20% FLOW start=22.77 down_rise@30s=3.64
elapsed 0.2 h
[ 6/60] rep1 duty 20% NO-FLOW start=22.46 down_rise@30s=6.82
elapsed 0.2 h
[ 7/60] rep1 duty 30% FLOW start=22.23 down_rise@30s=8.41
elapsed 0.2 h
[ 8/60] rep1 duty 30% NO-FLOW start=22.30 down_rise@30s=11.03
elapsed 0.3 h
[ 9/60] rep1 duty 40% FLOW start=22.93 down_rise@30s=11.46
elapsed 0.3 h
[10/60] rep1 duty 40% NO-FLOW start=22.46 down_rise@30s=15.27
elapsed 0.3 h
[11/60] rep1 duty 50% FLOW start=22.23 down_rise@30s=14.75
elapsed 0.3 h
[12/60] rep1 duty 50% NO-FLOW start=22.54 down_rise@30s=18.74
elapsed 0.4 h
[13/60] rep2 duty 10% FLOW start=21.84 down_rise@30s=3.14
elapsed 0.4 h
[14/60] rep2 duty 10% NO-FLOW start=22.30 down_rise@30s=2.29
elapsed 0.4 h
[15/60] rep2 duty 15% FLOW start=22.15 down_rise@30s=3.46
elapsed 0.5 h
[16/60] rep2 duty 15% NO-FLOW start=22.77 down_rise@30s=4.08
elapsed 0.5 h
[17/60] rep2 duty 20% FLOW start=22.15 down_rise@30s=4.69
elapsed 0.5 h
[18/60] rep2 duty 20% NO-FLOW start=22.77 down_rise@30s=5.51
elapsed 0.5 h
[19/60] rep2 duty 30% FLOW start=21.99 down_rise@30s=7.17
elapsed 0.6 h
[20/60] rep2 duty 30% NO-FLOW start=22.93 down_rise@30s=7.10
elapsed 0.6 h
[21/60] rep2 duty 40% FLOW start=22.62 down_rise@30s=8.82
elapsed 0.6 h
[22/60] rep2 duty 40% NO-FLOW start=22.62 down_rise@30s=13.46
elapsed 0.6 h
[23/60] rep2 duty 50% FLOW start=22.30 down_rise@30s=12.66
elapsed 0.7 h
[24/60] rep2 duty 50% NO-FLOW start=22.54 down_rise@30s=18.18
elapsed 0.7 h
[25/60] rep3 duty 10% FLOW start=22.07 down_rise@30s=2.43
elapsed 0.7 h
[26/60] rep3 duty 10% NO-FLOW start=22.07 down_rise@30s=3.40
elapsed 0.7 h
[27/60] rep3 duty 15% FLOW start=22.62 down_rise@30s=4.40
elapsed 0.8 h
[28/60] rep3 duty 15% NO-FLOW start=21.76 down_rise@30s=4.62
elapsed 0.8 h
[29/60] rep3 duty 20% FLOW start=22.85 down_rise@30s=4.35
elapsed 0.8 h
[30/60] rep3 duty 20% NO-FLOW start=21.68 down_rise@30s=4.79
elapsed 0.8 h
[31/60] rep3 duty 30% FLOW start=22.30 down_rise@30s=9.11
elapsed 0.9 h
[32/60] rep3 duty 30% NO-FLOW start=22.15 down_rise@30s=7.28
elapsed 0.9 h
[33/60] rep3 duty 40% FLOW start=21.91 down_rise@30s=10.00
elapsed 0.9 h
[34/60] rep3 duty 40% NO-FLOW start=22.85 down_rise@30s=14.55
elapsed 0.9 h
[35/60] rep3 duty 50% FLOW start=22.62 down_rise@30s=12.71
elapsed 1.0 h
[36/60] rep3 duty 50% NO-FLOW start=22.62 down_rise@30s=18.19
elapsed 1.0 h
[37/60] rep4 duty 10% FLOW start=22.54 down_rise@30s=2.75
elapsed 1.0 h
[38/60] rep4 duty 10% NO-FLOW start=20.75 down_rise@30s=2.92
elapsed 1.1 h
[39/60] rep4 duty 15% FLOW start=22.15 down_rise@30s=4.17
elapsed 1.1 h
[40/60] rep4 duty 15% NO-FLOW start=22.62 down_rise@30s=4.71
elapsed 1.1 h
[41/60] rep4 duty 20% FLOW start=21.37 down_rise@30s=5.48
elapsed 1.1 h
[42/60] rep4 duty 20% NO-FLOW start=22.23 down_rise@30s=4.77
elapsed 1.2 h
[43/60] rep4 duty 30% FLOW start=22.46 down_rise@30s=6.41
elapsed 1.2 h
[44/60] rep4 duty 30% NO-FLOW start=22.62 down_rise@30s=6.84
elapsed 1.2 h
[45/60] rep4 duty 40% FLOW start=22.54 down_rise@30s=10.50
elapsed 1.2 h
[46/60] rep4 duty 40% NO-FLOW start=22.69 down_rise@30s=14.56
elapsed 1.3 h
[47/60] rep4 duty 50% FLOW start=22.38 down_rise@30s=13.75
elapsed 1.3 h
[48/60] rep4 duty 50% NO-FLOW start=22.23 down_rise@30s=18.61
elapsed 1.3 h
[49/60] rep5 duty 10% FLOW start=22.38 down_rise@30s=2.67
elapsed 1.3 h
[50/60] rep5 duty 10% NO-FLOW start=22.07 down_rise@30s=4.10
elapsed 1.4 h
[51/60] rep5 duty 15% FLOW start=21.45 down_rise@30s=3.94
elapsed 1.4 h
[52/60] rep5 duty 15% NO-FLOW start=22.15 down_rise@30s=3.19
elapsed 1.4 h
[53/60] rep5 duty 20% FLOW start=21.91 down_rise@30s=4.26
elapsed 1.4 h
[54/60] rep5 duty 20% NO-FLOW start=22.46 down_rise@30s=5.25
elapsed 1.5 h
[55/60] rep5 duty 30% FLOW start=22.23 down_rise@30s=7.34
elapsed 1.5 h
[56/60] rep5 duty 30% NO-FLOW start=22.77 down_rise@30s=11.06
elapsed 1.5 h
[57/60] rep5 duty 40% FLOW start=22.93 down_rise@30s=9.65
elapsed 1.5 h
[58/60] rep5 duty 40% NO-FLOW start=22.38 down_rise@30s=13.48
elapsed 1.6 h
[59/60] rep5 duty 50% FLOW start=22.38 down_rise@30s=12.59
elapsed 1.6 h
[60/60] rep5 duty 50% NO-FLOW start=21.68 down_rise@30s=20.05
elapsed 1.6 h
=== COST: bulk (upstream) rise during a check, flow case, degrees C
duty 15s 20s 25s 30s 40s 50s 60s 75s
10% 0.34 0.62 0.67 0.33 0.30 0.58 0.09 0.34
15% 0.03 0.76 0.50 0.67 0.53 0.69 0.39 0.64
20% -0.08 -0.17 0.23 0.09 0.19 0.23 0.02 0.50
30% 0.79 0.73 1.39 0.83 1.11 1.17 1.42 1.22
40% 0.55 0.36 1.05 1.06 0.95 0.76 1.25 1.53
50% 1.26 1.83 1.72 1.59 2.03 2.03 2.19 2.69
=== PRECISION: downstream-rise discrimination (flow vs no-flow)
duty dur flow mean+-sd noflow mean+-sd sep worst d-prime
10% 15s 2.68+-0.45 2.03+-0.71 -0.65 -2.20 -1.1
10% 20s 2.90+-0.69 2.81+-0.37 -0.09 -1.65 -0.2
10% 25s 2.76+-0.69 3.10+-0.60 0.33 -1.80 0.5
10% 30s 2.98+-0.59 3.16+-0.66 0.18 -1.64 0.3
10% 40s 2.90+-0.86 3.30+-0.42 0.40 -1.08 0.6
10% 50s 3.05+-0.85 3.88+-0.44 0.83 -1.09 1.2
10% 60s 2.37+-0.54 3.61+-0.54 1.24 -0.21 2.3
10% 75s 2.60+-0.83 4.12+-0.54 1.52 -0.36 2.2
15% 15s 2.93+-0.63 2.93+-0.36 -0.01 -1.21 -0.0
15% 20s 3.97+-0.33 3.54+-0.35 -0.42 -1.31 -1.2
15% 25s 3.89+-0.86 3.95+-0.45 0.06 -1.46 0.1
15% 30s 4.14+-0.48 4.01+-0.68 -0.13 -1.55 -0.2
15% 40s 4.11+-0.79 4.43+-0.65 0.32 -1.54 0.4
15% 50s 4.17+-0.78 5.01+-0.34 0.83 -0.24 1.4
15% 60s 4.17+-0.52 5.09+-0.48 0.92 -0.21 1.8
15% 75s 4.21+-0.41 5.75+-0.45 1.54 +0.37 3.6
20% 15s 3.84+-0.78 3.57+-0.83 -0.27 -2.08 -0.3
20% 20s 4.18+-0.53 4.14+-0.83 -0.03 -1.36 -0.0
20% 25s 4.71+-0.92 4.90+-1.03 0.19 -1.91 0.2
20% 30s 4.48+-0.67 5.43+-0.84 0.95 -0.71 1.2
20% 40s 4.84+-0.58 5.79+-1.16 0.96 -0.61 1.0
20% 50s 4.98+-0.39 6.41+-0.79 1.43 +0.22 2.3
20% 60s 4.53+-0.66 6.67+-1.39 2.14 -0.44 2.0
20% 75s 4.95+-0.65 7.34+-1.02 2.39 +0.64 2.8
30% 15s 6.32+-0.81 5.63+-1.72 -0.69 -3.43 -0.5
30% 20s 7.13+-1.31 6.86+-2.24 -0.27 -3.67 -0.1
30% 25s 7.65+-0.92 7.73+-1.93 0.08 -2.63 0.1
30% 30s 7.69+-1.07 8.66+-2.18 0.97 -2.27 0.6
30% 40s 7.69+-0.90 9.96+-2.65 2.27 -1.00 1.1
30% 50s 8.15+-0.75 10.24+-2.37 2.09 -0.93 1.2
30% 60s 8.20+-0.70 10.77+-2.34 2.57 -0.35 1.5
30% 75s 8.27+-1.28 10.86+-2.26 2.59 -0.82 1.4
40% 15s 8.06+-0.87 10.14+-0.65 2.08 +0.37 2.7
40% 20s 8.93+-1.07 12.07+-0.48 3.13 +1.06 3.8
40% 25s 9.61+-1.22 13.47+-0.98 3.86 +0.96 3.5
40% 30s 10.09+-0.98 14.26+-0.78 4.18 +2.00 4.7
40% 40s 10.29+-1.41 16.01+-0.88 5.72 +2.77 4.9
40% 50s 10.14+-0.87 16.95+-0.74 6.81 +4.58 8.4
40% 60s 10.71+-1.22 17.27+-0.96 6.56 +3.72 6.0
40% 75s 10.46+-1.22 17.88+-0.77 7.42 +5.38 7.2
50% 15s 10.73+-0.65 13.02+-0.71 2.29 +0.22 3.3
50% 20s 12.71+-0.89 15.58+-0.85 2.87 +0.55 3.3
50% 25s 12.71+-0.96 17.10+-0.78 4.39 +1.66 5.0
50% 30s 13.29+-0.95 18.75+-0.76 5.46 +3.43 6.4
50% 40s 14.04+-0.80 20.38+-1.16 6.34 +3.48 6.3
50% 50s 14.38+-0.57 21.61+-0.93 7.23 +5.08 9.4
50% 60s 14.24+-0.48 22.14+-1.02 7.90 +5.94 9.9
50% 75s 14.18+-0.78 23.17+-1.10 8.99 +6.51 9.4
=== RANKED by d-prime (separation in pooled standard deviations)
rank duty dur d-prime worst-margin bulk-cost threshold
1 50% 60s 9.9 +5.94 2.19 18.19
2 50% 75s 9.4 +6.51 2.69 18.68
3 50% 50s 9.4 +5.08 2.03 18.00
4 40% 50s 8.4 +4.58 0.76 13.54
5 40% 75s 7.2 +5.38 1.53 14.17
6 50% 30s 6.4 +3.43 1.59 16.02
7 50% 40s 6.3 +3.48 2.03 17.21
8 40% 60s 6.0 +3.72 1.25 13.99
9 50% 25s 5.0 +1.66 1.72 14.91
10 40% 40s 4.9 +2.77 0.95 13.15
11 40% 30s 4.7 +2.00 1.06 12.18
12 40% 20s 3.8 +1.06 0.36 10.50
(worst-margin = min(no-flow) - max(flow); positive means every
observed no-flow run exceeded every observed flow run.
bulk-cost = degrees C added to the loop per check.)
controller restarted; total elapsed 1.63 h
File diff suppressed because it is too large Load Diff
-96
View File
@@ -1,96 +0,0 @@
=== warm-baseline validation 2026-08-02 22:43:41 duty=40% window=50s threshold=13.7
cycle 1/3 FLOW
warmed to down=21.84 up=21.91 in 1.3 min
base_up=22.07 down_rise=12.18 -> ok CORRECT (peak down 34.4 C)
cycle 1/3 NO-FLOW
warmed to down=22.46 up=21.84 in 1.0 min
base_up=21.84 down_rise=18.16 -> FAULT CORRECT (peak down 40.4 C)
cycle 2/3 FLOW
warmed to down=21.84 up=22.07 in 1.0 min
base_up=21.29 down_rise=12.75 -> ok CORRECT (peak down 34.5 C)
cycle 2/3 NO-FLOW
warmed to down=22.07 up=21.91 in 1.0 min
base_up=21.45 down_rise=18.15 -> FAULT CORRECT (peak down 40.5 C)
cycle 3/3 FLOW
warmed to down=22.07 up=21.91 in 1.0 min
base_up=21.60 down_rise=10.95 -> ok CORRECT (peak down 34.3 C)
cycle 3/3 NO-FLOW
warmed to down=22.30 up=22.30 in 1.0 min
base_up=21.76 down_rise=17.85 -> FAULT CORRECT (peak down 40.4 C)
=== warm-baseline results (baselines 21.3 - 22.1 C)
flow n=3 mean 11.96 sd 0.92 max 12.75
no-flow n=3 mean 18.06 sd 0.17 min 17.85
worst-case margin: +5.11 C threshold 13.7 sits +0.95 above flow max, +4.15 below no-flow min
misclassified: 0 of 6
controller restarted
=== warm-baseline validation 2026-08-02 22:56:44 duty=40% window=50s threshold=13.7
cycle 1/3 FLOW
warmed to down=22.30 up=22.07 in 1.3 min
base_up=22.23 down_rise=12.62 -> ok CORRECT (peak down 34.0 C)
cycle 1/3 NO-FLOW
warmed to down=22.23 up=21.84 in 1.0 min
base_up=21.76 down_rise=18.71 -> FAULT CORRECT (peak down 40.6 C)
cycle 2/3 FLOW
warmed to down=22.30 up=22.07 in 1.0 min
base_up=21.76 down_rise=11.03 -> ok CORRECT (peak down 34.3 C)
cycle 2/3 NO-FLOW
warmed to down=22.15 up=21.84 in 1.0 min
base_up=21.37 down_rise=18.26 -> FAULT CORRECT (peak down 40.1 C)
cycle 3/3 FLOW
warmed to down=22.54 up=22.15 in 1.0 min
base_up=21.99 down_rise=11.08 -> ok CORRECT (peak down 34.3 C)
cycle 3/3 NO-FLOW
warmed to down=22.07 up=21.84 in 1.0 min
base_up=21.91 down_rise=18.42 -> FAULT CORRECT (peak down 40.5 C)
=== warm-baseline results (baselines 21.4 - 22.2 C)
flow n=3 mean 11.58 sd 0.91 max 12.62
no-flow n=3 mean 18.46 sd 0.23 min 18.26
worst-case margin: +5.63 C threshold 13.7 sits +1.08 above flow max, +4.56 below no-flow min
misclassified: 0 of 6
controller restarted
=== warm-baseline validation 2026-08-02 23:10:04 duty=40% window=50s threshold=13.7
cycle 1/2 FLOW
warm abort: downstream 51.95 at 0.5 min
warmed to down=22.15 up=21.84 in 1.3 min
=== warm-baseline validation 2026-08-02 23:11:51 duty=40% window=50s threshold=13.7
cycle 1/2 FLOW
base_up=22.23 down_rise=15.53 -> FAULT *** WRONG *** (peak down 37.7 C)
cycle 1/2 NO-FLOW
warm abort: downstream 49.34 at 0.3 min
warm target reached: up=25.84 at 0.8 min
warmed to down=21.84 up=21.60 in 1.0 min
warmed to down=22.15 up=22.07 in 1.6 min
base_up=21.37 down_rise=10.69 -> ok *** WRONG *** (peak down 34.1 C)
cycle 2/2 FLOW
base_up=20.91 down_rise=11.48 -> ok CORRECT (peak down 34.4 C)
cycle 1/2 NO-FLOW
warming: 0.8 min down=37.53 up=23.71
warming: 0.8 min down=38.17 up=24.65
warming: 1.8 min down=37.80 up=23.94
warming: 1.8 min down=37.99 up=24.50
(warming plateaued at 24.18 C after 2.6 min)
warming: 2.9 min down=26.56 up=23.47
(warming plateaued at 23.47 C after 2.9 min)
warmed to down=21.68 up=21.60 in 3.4 min
warmed to down=28.57 up=21.84 in 3.6 min
base_up=21.37 down_rise=12.18 -> ok *** WRONG *** (peak down 34.1 C)
cycle 2/2 FLOW
base_up=21.91 down_rise=3.76 -> ok CORRECT (peak down 35.8 C)
cycle 2/2 NO-FLOW
warm abort: downstream 48.90 at 0.3 min
warm target reached: up=25.68 at 0.5 min
warmed to down=22.23 up=21.84 in 1.0 min
warmed to down=21.29 up=21.68 in 1.3 min
base_up=21.45 down_rise=11.58 -> ok *** WRONG *** (peak down 34.4 C)
base_up=21.06 down_rise=11.30 -> ok CORRECT (peak down 34.2 C)
cycle 2/2 NO-FLOW
=== warm-baseline results (baselines 21.4 - 22.2 C)
flow n=2 mean 9.65 sd 8.32 max 15.53
no-flow n=2 mean 11.13 sd 0.63 min 10.69
worst-case margin: -4.84 C threshold 13.7 sits -1.83 above flow max, -3.01 below no-flow min
misclassified: 3 of 4
controller restarted
warming: 0.8 min down=37.71 up=24.50
-32
View File
@@ -1,32 +0,0 @@
[
{
"flow": true,
"base_up": 20.90741361785507,
"base_down": 21.837331209719537,
"down_rise": 11.480714695811116,
"up_rise": 2.254025184475836,
"peak_down": 34.43702357179029,
"t": 49.15,
"cycle": 0
},
{
"flow": false,
"base_up": 21.371932016053336,
"base_down": 21.914988917022924,
"down_rise": 12.176080123337613,
"up_rise": 1.5552007482007184,
"peak_down": 34.09106904036054,
"t": 49.17,
"cycle": 0
},
{
"flow": true,
"base_up": 21.06216007856807,
"base_down": 21.75970016295645,
"down_rise": 11.302272149723365,
"up_rise": 2.6471638813487743,
"peak_down": 34.1774170676689,
"t": 49.16,
"cycle": 1
}
]
-601
View File
@@ -1,601 +0,0 @@
t_s,lid_ir_1,lid_ir_2,lid_ir_3,lid_ir_4,hv_current,water_1,water_2,pwr_temp,laser_on_sampled
4665.89,37,37,40,40,0,672,669,586,0
4666.45,37,36,40,40,0,674,674,586,0
4667.01,37,36,40,40,0,668,673,586,0
4667.57,37,36,39,40,0,667,669,593,0
4668.14,36,36,39,40,0,667,674,592,0
4668.70,38,36,39,40,0,668,674,593,0
4669.27,37,35,38,41,0,665,669,591,0
4669.83,39,37,39,39,0,667,666,592,0
4670.40,38,36,40,40,0,674,667,589,0
4670.96,37,36,38,41,0,668,667,593,0
4671.52,37,37,41,40,0,671,674,590,0
4672.09,38,36,41,40,0,671,666,589,0
4672.65,37,36,40,40,0,669,666,590,0
4673.22,37,36,38,41,0,669,676,593,0
4673.78,38,37,40,41,0,675,674,590,0
4674.35,36,38,40,40,0,669,667,590,0
4674.91,37,36,39,40,0,663,667,590,0
4675.48,38,38,39,40,0,667,666,592,0
4676.05,38,36,40,40,0,666,670,587,0
4676.62,38,36,38,39,0,672,667,586,0
4677.18,38,36,38,40,0,670,667,589,0
4677.75,37,36,39,40,0,666,664,592,0
4678.31,36,37,40,39,0,665,672,593,0
4678.88,37,36,40,40,0,667,669,593,0
4679.44,37,36,40,40,0,674,678,591,0
4680.01,37,36,40,39,0,667,665,587,0
4680.57,37,36,39,38,0,676,677,587,0
4681.14,37,35,40,40,0,675,670,587,0
4681.70,38,37,39,41,0,672,671,593,0
4682.27,36,36,39,40,0,675,671,592,0
4682.84,38,37,38,41,0,675,667,592,0
4683.40,37,36,38,40,0,667,666,593,0
4683.97,36,36,40,40,0,668,666,591,0
4684.53,37,36,39,40,0,669,669,594,0
4685.10,38,36,40,40,0,674,667,592,0
4685.66,37,36,39,39,0,673,669,586,0
4686.23,37,37,40,40,0,668,667,586,0
4686.79,36,36,39,39,0,668,666,593,0
4687.36,37,37,39,40,0,673,666,586,0
4687.92,37,36,40,41,0,666,669,593,0
4688.49,37,37,39,40,0,674,666,587,0
4689.06,36,36,39,41,0,666,674,586,0
4689.63,37,37,40,40,0,673,675,588,0
4690.19,37,36,40,40,0,662,674,593,0
4690.76,38,35,39,40,0,673,673,586,0
4691.32,37,37,40,41,0,671,671,593,0
4691.89,38,36,40,40,0,673,665,592,0
4692.45,37,36,40,40,0,674,672,591,0
4693.02,37,36,40,40,0,675,677,587,0
4693.58,38,37,40,40,0,677,668,592,0
4694.15,37,36,40,39,0,667,675,587,0
4694.71,37,36,40,39,0,676,675,593,0
4695.27,37,37,40,40,0,666,671,588,0
4695.84,38,36,40,40,0,674,673,587,0
4696.40,37,36,40,40,0,670,672,590,0
4696.97,37,36,38,40,0,669,674,588,0
4697.53,37,37,39,40,0,676,674,589,0
4698.10,38,36,40,39,0,677,669,591,0
4698.67,37,38,39,38,0,673,676,593,0
4699.23,38,36,39,40,0,670,675,587,0
4699.80,38,36,40,40,0,675,671,588,0
4700.37,38,36,40,40,0,673,668,583,0
4700.93,37,37,39,39,0,671,670,591,0
4701.50,37,35,41,39,0,675,674,587,0
4702.06,37,37,39,40,0,668,675,588,0
4702.63,37,36,39,40,0,669,666,591,0
4703.19,37,37,39,41,0,667,666,588,0
4703.75,36,36,39,40,0,672,667,588,0
4704.32,37,36,40,38,0,671,673,587,0
4704.88,37,36,39,40,0,676,673,586,0
4705.44,38,36,38,40,0,676,667,586,0
4706.01,37,36,40,40,0,670,669,587,0
4706.58,36,36,40,39,0,677,673,586,0
4707.15,37,37,40,41,0,674,670,589,0
4707.72,37,36,40,41,0,672,668,593,0
4708.28,38,37,39,40,0,669,665,591,0
4708.85,37,35,40,41,0,667,672,586,0
4709.41,37,36,40,41,0,671,674,586,0
4709.98,37,37,40,40,0,675,667,587,0
4710.54,37,37,39,41,0,666,667,590,0
4711.11,38,37,40,40,0,667,666,591,0
4711.67,37,36,38,40,0,670,668,586,0
4712.23,37,36,38,41,0,667,667,593,0
4712.80,38,36,39,41,0,666,673,592,0
4713.36,37,37,39,40,0,675,669,592,0
4713.93,37,36,40,39,0,671,672,589,0
4714.49,38,37,41,40,0,672,666,587,0
4715.05,37,36,38,39,0,666,672,586,0
4715.62,37,36,40,39,0,667,671,592,0
4716.18,39,36,40,40,0,665,665,590,0
4716.75,38,37,40,41,0,673,665,589,0
4717.31,36,36,39,40,0,678,673,593,0
4717.87,37,36,39,39,0,674,665,589,0
4718.44,38,37,40,42,0,668,669,593,0
4719.00,38,36,40,40,0,671,674,586,0
4719.57,37,36,39,40,0,668,667,592,0
4720.13,37,36,40,40,0,673,668,590,0
4720.69,38,37,40,40,0,674,667,592,0
4721.26,37,36,41,40,0,664,672,591,0
4721.83,37,37,39,40,0,671,669,588,0
4722.40,36,37,40,39,0,665,666,587,0
4722.96,38,37,39,40,0,676,675,586,0
4723.53,37,37,40,41,0,672,667,591,0
4724.10,38,36,39,40,0,669,668,594,0
4724.66,37,37,39,41,0,666,671,586,0
4725.23,37,37,40,40,0,673,674,586,0
4725.79,38,37,39,41,0,676,674,591,0
4726.36,37,37,40,41,0,675,670,586,0
4726.92,37,36,38,40,0,675,669,592,0
4727.49,37,37,39,40,0,673,676,590,0
4728.05,38,36,40,39,0,669,669,588,0
4728.62,37,36,40,40,0,676,674,586,0
4729.19,37,36,40,40,0,666,674,586,0
4729.75,37,36,38,41,0,667,674,587,0
4730.32,37,36,39,39,0,669,670,590,0
4730.89,38,36,39,41,0,668,666,594,0
4731.45,38,37,40,40,0,673,672,587,0
4732.02,38,36,39,40,0,674,665,587,0
4732.58,37,35,39,41,0,676,672,588,0
4733.14,38,35,39,40,0,677,669,589,0
4733.71,38,36,40,40,0,674,667,592,0
4734.27,36,36,39,39,0,673,666,593,0
4734.84,37,36,38,39,0,667,669,587,0
4735.40,38,37,39,40,0,669,666,593,0
4735.96,38,36,40,40,0,668,674,587,0
4736.53,37,36,38,40,0,667,675,587,0
4737.09,38,37,40,40,0,674,668,586,0
4737.66,38,36,39,41,0,667,673,588,0
4738.22,37,37,40,40,0,674,672,593,0
4738.78,37,36,39,40,0,670,667,587,0
4739.35,36,36,39,40,0,668,669,593,0
4739.92,37,37,40,40,0,667,671,591,0
4740.48,37,37,39,39,0,668,674,590,0
4741.05,37,36,39,39,0,668,666,586,0
4741.62,37,36,40,40,0,674,675,592,0
4742.18,37,36,39,39,0,670,676,586,0
4742.75,38,36,39,40,0,669,666,591,0
4743.32,37,36,40,40,0,675,674,587,0
4743.88,36,37,40,40,0,672,665,587,0
4744.45,37,36,39,40,0,674,671,592,0
4745.01,37,36,39,40,0,674,669,588,0
4745.58,37,36,39,40,0,675,665,587,0
4746.15,38,36,40,40,0,673,667,587,0
4746.72,37,35,41,40,0,667,665,590,0
4747.28,37,37,40,41,0,668,674,594,0
4747.85,38,36,39,41,0,668,674,588,0
4748.42,38,36,39,41,0,667,668,587,0
4748.99,36,37,39,39,0,674,674,586,0
4749.56,37,36,39,41,0,674,670,593,0
4750.12,37,36,40,40,0,675,672,592,0
4750.69,37,37,39,39,0,670,668,589,0
4751.25,38,37,39,40,0,672,670,590,0
4751.82,37,36,41,39,0,667,666,591,0
4752.38,38,37,39,40,0,669,666,587,0
4752.95,37,36,39,40,0,669,666,593,0
4753.51,38,36,39,40,0,676,669,592,0
4754.08,37,37,39,40,0,668,667,587,0
4754.65,38,35,38,40,0,667,670,594,0
4755.22,37,36,39,39,0,677,673,587,0
4755.79,38,36,39,40,0,672,666,587,0
4756.35,37,36,39,41,0,670,671,587,0
4756.92,38,36,39,41,0,675,669,593,0
4757.48,37,36,39,39,0,675,674,588,0
4758.05,37,36,39,40,0,672,674,593,0
4758.61,37,36,39,40,0,674,665,586,0
4759.18,37,37,39,39,0,673,669,588,0
4759.75,37,35,39,40,0,674,669,585,0
4760.32,37,37,40,39,0,671,673,587,0
4760.88,38,37,39,40,0,674,673,592,0
4761.45,38,36,39,40,0,673,673,591,0
4762.01,37,36,39,41,0,674,673,592,0
4762.58,37,36,39,41,0,666,672,589,0
4763.14,39,37,39,40,0,665,669,593,0
4763.70,37,36,40,40,0,673,675,587,0
4764.27,36,36,39,40,0,668,670,585,0
4764.83,37,36,39,40,0,668,675,587,0
4765.40,37,37,40,40,0,667,664,592,0
4765.97,38,37,40,40,0,670,676,593,0
4766.54,37,36,39,41,0,665,670,593,0
4767.10,37,36,39,40,0,675,665,587,0
4767.67,37,37,40,40,0,668,666,591,0
4768.23,37,36,40,40,0,668,670,592,0
4768.80,37,36,40,39,0,665,671,589,0
4769.36,37,36,40,40,0,673,671,586,0
4769.93,38,36,39,39,0,676,667,585,0
4770.49,38,36,39,40,0,672,668,586,0
4771.06,38,37,39,41,0,667,674,593,0
4771.62,37,37,38,40,0,668,668,587,0
4772.19,37,36,40,40,0,669,667,586,0
4772.76,37,36,41,40,0,674,666,588,0
4773.33,38,37,39,40,0,666,673,586,0
4773.89,37,36,39,40,0,674,671,591,0
4774.46,37,36,39,40,0,676,671,587,0
4775.03,37,37,40,39,0,669,673,592,0
4775.59,37,37,39,39,0,674,669,587,0
4776.16,38,36,40,40,0,673,672,591,0
4776.72,37,37,40,40,0,674,667,587,0
4777.29,37,36,40,40,0,670,676,592,0
4777.85,38,37,40,41,0,667,670,592,0
4778.42,38,37,39,40,0,669,665,586,0
4778.99,38,37,39,40,0,669,670,594,0
4779.56,38,37,39,40,0,670,673,593,0
4780.12,38,37,38,40,0,667,675,587,0
4780.68,37,36,41,40,0,666,668,592,0
4781.25,37,37,40,40,0,666,666,591,0
4781.82,37,36,40,41,0,665,667,592,0
4782.39,38,37,39,41,0,667,673,587,0
4782.96,37,36,40,40,0,666,667,591,0
4783.52,38,36,40,40,0,674,675,593,0
4784.09,38,36,39,41,0,662,673,586,0
4784.65,37,36,40,41,0,676,671,587,0
4785.22,37,38,40,40,0,669,666,591,0
4785.78,37,37,39,40,0,668,668,592,0
4786.35,37,36,40,40,0,676,673,590,0
4786.91,37,36,40,40,0,669,675,592,0
4787.48,37,37,40,40,0,668,667,587,0
4788.04,37,37,41,39,0,676,674,586,0
4788.60,37,36,39,40,0,669,672,588,0
4789.17,38,37,40,40,0,667,674,592,0
4789.74,38,37,40,40,0,674,676,592,0
4790.30,37,36,39,41,0,662,669,592,0
4790.87,38,36,39,40,0,666,666,588,0
4791.44,37,37,40,40,0,675,675,590,0
4792.01,37,36,40,40,0,668,668,587,0
4792.57,37,36,40,40,0,672,667,587,0
4793.14,37,36,39,40,0,673,669,593,0
4793.70,37,36,41,41,0,666,669,588,0
4794.27,37,37,40,40,0,676,673,588,0
4794.83,37,36,38,40,0,671,674,588,0
4795.40,36,36,40,40,0,671,667,588,0
4795.97,37,36,39,39,0,672,670,586,0
4796.54,37,36,39,40,0,667,667,587,0
4797.10,37,35,40,40,0,675,673,586,0
4797.67,37,37,40,40,0,670,669,592,0
4798.24,37,36,40,40,0,669,671,588,0
4798.81,37,37,40,39,0,675,674,592,0
4799.37,37,37,38,40,0,670,669,591,0
4799.94,37,36,39,40,0,674,673,593,0
4800.51,36,36,40,40,0,674,672,587,0
4801.07,37,36,40,40,0,673,673,589,0
4801.64,37,36,40,40,0,671,667,587,0
4802.20,37,35,39,41,0,670,668,586,0
4802.77,37,36,40,39,0,674,667,588,0
4803.33,37,37,40,39,0,670,678,589,0
4803.91,37,35,40,40,0,671,675,592,0
4804.47,38,36,40,41,0,666,675,592,0
4805.04,37,36,39,40,0,668,672,593,0
4805.60,38,36,40,40,0,676,670,588,0
4806.17,38,36,38,39,0,670,677,590,0
4806.74,37,37,39,40,0,675,671,587,0
4807.30,37,36,40,40,0,672,668,587,0
4807.87,38,37,39,40,0,667,668,591,0
4808.43,37,36,40,39,0,666,666,586,0
4809.00,37,36,41,39,0,673,667,591,0
4809.56,36,37,38,40,0,675,668,586,0
4810.13,36,36,40,40,0,672,669,592,0
4810.70,38,36,40,39,0,673,667,592,0
4811.27,38,36,40,41,0,672,673,587,0
4811.83,37,36,40,40,0,668,667,586,0
4812.40,37,37,39,39,0,666,669,586,0
4812.96,37,36,40,41,0,671,667,589,0
4813.52,37,37,39,40,0,674,673,587,0
4814.09,38,36,39,40,0,668,671,592,0
4814.65,37,36,39,38,0,669,669,594,0
4815.22,37,36,39,40,0,667,669,587,0
4815.78,37,36,39,40,0,671,674,591,0
4816.35,37,37,40,39,0,666,663,589,0
4816.92,37,37,40,40,0,672,668,588,0
4817.49,37,37,39,39,0,675,671,589,0
4818.05,38,37,40,41,0,675,666,592,0
4818.62,38,37,40,40,0,666,666,591,0
4819.18,37,37,39,40,0,671,667,591,0
4819.75,37,36,39,40,0,673,669,591,0
4820.32,37,37,39,40,0,674,674,587,0
4820.88,37,37,40,40,0,675,666,585,0
4821.45,36,36,40,41,0,667,675,593,0
4822.02,38,37,39,39,0,668,675,591,0
4822.58,36,37,41,40,0,667,672,590,0
4823.15,37,36,38,40,0,676,671,589,0
4823.72,37,37,39,41,0,673,671,587,0
4824.28,38,36,40,41,0,666,670,592,0
4824.85,37,36,40,40,0,666,672,590,0
4825.41,37,36,40,41,0,674,674,586,0
4825.99,38,36,40,41,0,675,671,593,0
4826.55,37,36,38,40,0,673,670,588,0
4827.12,37,37,39,39,0,668,665,587,0
4827.68,38,37,40,41,0,666,666,587,0
4828.25,37,36,39,39,0,673,668,585,0
4828.81,38,36,40,40,0,668,666,588,0
4829.38,37,37,39,40,0,668,675,592,0
4829.95,37,37,40,39,0,665,667,589,0
4830.51,36,37,39,40,0,669,675,593,0
4831.08,38,36,40,40,0,667,669,586,0
4831.65,37,36,40,40,0,677,668,592,0
4832.21,37,38,39,40,0,668,667,589,0
4832.78,37,37,40,41,0,665,675,589,0
4833.34,37,36,39,40,0,676,673,586,0
4833.91,37,37,38,40,0,666,667,587,0
4834.49,37,37,40,39,0,673,668,587,0
4835.05,36,37,40,39,0,667,667,586,0
4835.62,37,37,40,40,0,671,667,587,0
4836.18,38,36,39,40,0,673,674,588,0
4836.75,37,37,39,39,0,668,669,589,0
4837.31,38,37,39,40,0,672,667,589,0
4837.88,37,37,39,40,0,670,674,592,0
4838.45,37,36,40,41,0,667,674,593,0
4839.02,38,35,39,40,0,671,666,586,0
4839.59,38,37,40,40,0,670,669,591,0
4840.15,38,36,40,40,0,673,674,594,0
4840.72,37,35,39,41,0,666,667,588,0
4841.28,37,36,39,40,0,667,666,593,0
4841.85,37,36,39,39,0,674,671,588,0
4842.41,37,37,38,40,0,672,671,587,0
4842.98,38,36,41,41,0,674,666,587,0
4843.54,37,37,40,40,0,667,668,587,0
4844.10,38,36,39,41,0,673,675,588,0
4844.67,37,36,38,40,0,671,669,592,0
4845.23,38,37,39,40,0,676,665,587,0
4845.80,38,37,40,40,0,674,675,590,0
4846.36,37,37,40,40,0,671,672,586,0
4846.94,38,36,40,40,0,674,676,587,0
4847.50,38,38,40,40,0,679,673,589,0
4848.07,38,36,40,41,0,671,666,592,0
4848.64,36,37,40,40,0,668,665,590,0
4849.20,37,36,40,40,0,670,668,586,0
4849.77,37,35,40,40,0,669,670,588,0
4850.33,37,37,39,39,0,666,666,594,0
4850.90,37,36,40,39,0,672,672,586,0
4851.47,37,36,39,39,0,675,675,593,0
4852.04,37,36,40,40,0,669,671,592,0
4852.61,38,37,39,40,0,669,676,593,0
4853.18,38,36,40,40,0,671,671,586,0
4853.75,37,36,40,40,0,675,670,589,0
4854.32,38,37,39,40,0,675,668,589,0
4854.88,38,36,39,40,0,668,667,592,0
4855.45,37,36,40,40,0,674,668,591,0
4856.01,37,36,39,40,0,675,667,587,0
4856.58,36,37,40,39,0,665,667,588,0
4857.14,38,37,40,40,0,669,665,586,0
4857.71,37,36,39,40,0,675,667,587,0
4858.27,37,36,40,40,0,673,672,590,0
4858.84,38,36,39,40,0,677,674,587,0
4859.41,37,36,41,39,0,668,675,587,0
4859.98,37,36,39,39,0,676,670,586,0
4860.54,37,36,40,40,0,676,666,588,0
4861.11,37,36,39,40,0,676,673,591,0
4861.67,38,36,39,40,0,669,671,589,0
4862.24,37,36,40,39,0,676,675,589,0
4862.80,37,36,39,40,0,669,668,593,0
4863.37,38,36,40,41,0,673,674,588,0
4863.93,38,36,40,40,0,668,669,589,0
4864.50,38,36,40,40,0,669,674,594,0
4865.06,37,36,40,40,0,673,665,594,0
4865.63,37,37,39,40,0,674,675,594,0
4866.20,38,36,40,40,0,673,667,594,0
4866.76,37,36,39,40,0,669,668,586,0
4867.33,37,37,38,39,0,665,666,586,0
4867.90,37,36,39,39,0,667,669,592,0
4868.46,37,37,40,39,0,668,677,589,0
4869.03,37,37,39,40,0,674,675,587,0
4869.60,37,36,39,39,0,674,669,593,0
4870.17,38,37,39,40,0,676,673,587,0
4870.73,37,37,40,40,0,676,667,587,0
4871.30,38,37,39,40,0,669,674,588,0
4871.86,38,36,40,40,0,663,666,592,0
4872.43,38,37,39,40,0,670,670,594,0
4873.00,38,36,40,40,0,668,666,590,0
4873.56,37,35,39,40,0,667,674,591,0
4874.12,37,36,40,39,0,675,666,587,0
4874.70,37,37,40,39,0,674,673,592,0
4875.26,36,36,39,40,0,668,677,588,0
4875.83,38,36,39,41,0,673,667,593,0
4876.40,36,36,39,40,0,671,669,593,0
4876.96,37,36,40,40,0,669,672,588,0
4877.53,36,37,39,40,0,666,667,594,0
4878.10,38,35,39,40,0,667,667,586,0
4878.67,38,36,38,40,0,676,674,587,0
4879.24,37,36,39,40,0,667,676,588,0
4879.80,37,36,40,39,0,675,673,594,0
4880.36,38,35,40,40,0,676,668,592,0
4880.93,38,37,39,41,0,665,671,588,0
4881.49,36,35,38,41,0,667,673,594,0
4882.06,38,36,39,40,0,664,667,592,0
4882.62,37,36,40,41,0,671,674,593,0
4883.19,37,37,38,41,0,672,667,587,0
4883.75,37,37,40,40,0,670,673,592,0
4884.31,37,37,40,40,0,672,669,586,0
4884.88,38,36,39,40,0,674,674,593,0
4885.44,38,37,40,40,0,667,674,588,0
4886.01,38,36,39,41,0,669,669,593,0
4886.57,37,36,40,41,0,667,674,586,0
4887.14,38,36,38,41,0,674,674,588,0
4887.71,38,37,39,41,0,672,668,592,0
4888.28,39,38,39,40,0,667,668,591,0
4888.84,37,37,39,38,0,669,672,592,0
4889.41,36,36,40,39,0,669,667,591,0
4889.97,38,36,39,40,0,674,677,593,0
4890.54,37,37,40,39,0,674,670,586,0
4891.10,37,36,39,41,0,667,675,591,0
4891.67,37,36,39,41,0,668,666,593,0
4892.23,37,37,39,40,0,674,670,588,0
4892.80,38,37,39,41,0,675,671,591,0
4893.36,38,37,39,41,0,667,670,592,0
4893.92,37,36,39,39,0,670,673,592,0
4894.49,37,36,40,40,0,669,667,593,0
4895.05,37,36,38,40,0,674,674,588,0
4895.62,37,36,41,39,0,674,675,587,0
4896.19,38,36,40,40,0,674,667,588,0
4896.76,37,37,40,41,0,666,667,591,0
4897.33,37,35,39,40,0,676,678,592,0
4897.89,37,36,40,39,0,669,672,588,0
4898.46,37,37,40,40,0,669,666,587,0
4899.02,37,36,41,40,0,673,666,586,0
4899.58,36,35,39,39,0,665,674,592,0
4900.15,37,37,40,40,0,670,667,586,0
4900.71,37,36,40,41,0,666,664,589,0
4901.28,37,36,40,40,0,674,674,594,0
4901.85,37,36,40,40,0,669,671,592,0
4902.42,37,36,40,40,0,668,667,592,0
4902.98,38,36,39,39,0,673,667,592,0
4903.55,38,37,38,40,0,674,674,588,0
4904.12,39,37,40,41,0,669,665,587,0
4904.68,37,36,40,41,0,666,673,592,0
4905.25,38,37,39,39,0,664,675,591,0
4905.81,37,36,39,40,0,669,667,587,0
4906.38,37,35,40,39,0,675,671,593,0
4906.94,37,37,39,40,0,674,674,591,0
4907.51,38,37,39,39,0,669,665,592,0
4908.07,37,37,40,40,0,669,666,586,0
4908.64,38,37,40,39,0,676,669,589,0
4909.21,38,36,39,40,0,666,668,593,0
4909.78,38,36,40,40,0,670,671,592,0
4910.34,38,36,39,39,0,665,670,588,0
4910.91,38,36,39,40,0,672,669,593,0
4911.47,37,36,40,39,0,669,665,587,0
4912.03,38,36,40,40,0,669,673,589,0
4912.60,37,37,39,40,0,674,673,587,0
4913.17,36,37,39,40,0,673,675,587,0
4913.73,37,38,39,40,0,674,674,594,0
4914.30,36,37,38,40,0,668,674,592,0
4914.87,38,37,39,40,0,670,666,591,0
4915.44,37,36,40,40,0,672,675,589,0
4916.00,37,37,39,41,0,666,666,588,0
4916.56,37,36,38,39,0,667,666,588,0
4917.13,37,37,39,40,0,672,672,586,0
4917.70,37,37,40,40,0,668,665,587,0
4918.26,37,37,39,40,0,674,674,586,0
4918.82,38,35,40,40,0,675,675,588,0
4919.39,37,36,39,41,0,670,674,590,0
4919.95,37,37,40,41,0,676,673,594,0
4920.52,38,36,39,41,0,672,669,593,0
4921.08,37,35,40,40,0,669,667,586,0
4921.65,39,37,39,40,0,671,673,590,0
4922.21,37,36,38,40,0,669,676,593,0
4922.78,37,36,40,40,0,670,666,586,0
4923.35,37,36,40,40,0,669,669,586,0
4923.92,37,37,40,41,0,674,674,588,0
4924.49,37,36,40,40,0,666,673,593,0
4925.05,38,37,40,39,0,674,675,586,0
4925.62,37,38,38,40,0,673,674,593,0
4926.19,38,36,39,40,0,675,674,588,0
4926.76,37,36,40,40,0,672,674,587,0
4927.32,37,36,39,40,0,669,676,593,0
4927.89,38,36,40,40,0,670,671,588,0
4928.45,37,36,40,40,0,675,668,588,0
4929.02,38,38,40,40,0,669,673,588,0
4929.58,37,36,39,39,0,670,674,592,0
4930.15,37,37,39,38,0,668,668,586,0
4930.71,37,37,39,40,0,672,667,587,0
4931.28,37,37,40,40,0,673,667,589,0
4931.84,37,36,39,41,0,673,671,587,0
4932.41,38,37,38,41,0,675,669,594,0
4932.98,38,37,39,40,0,668,667,594,0
4933.55,38,37,40,41,0,673,667,588,0
4934.11,37,36,40,41,0,666,670,591,0
4934.68,38,36,40,40,0,674,674,585,0
4935.24,37,36,40,40,0,674,668,591,0
4935.81,37,36,40,40,0,676,674,587,0
4936.37,37,37,40,40,0,668,673,587,0
4936.94,38,35,39,40,0,673,675,588,0
4937.50,37,36,40,40,0,668,668,593,0
4938.07,37,35,40,40,0,668,671,588,0
4938.64,38,36,40,40,0,675,667,594,0
4939.21,37,35,40,40,0,673,669,593,0
4939.77,37,36,39,40,0,671,672,592,0
4940.34,37,35,40,41,0,673,673,588,0
4940.90,37,36,40,39,0,667,673,589,0
4941.47,38,36,39,39,0,667,675,588,0
4942.03,38,37,39,40,0,674,670,587,0
4942.60,37,36,39,41,0,675,674,590,0
4943.16,37,37,40,41,0,671,664,587,0
4943.73,37,36,38,40,0,666,669,588,0
4944.30,37,36,40,40,0,667,667,585,0
4944.87,37,36,40,40,0,669,667,588,0
4945.43,37,37,40,40,0,673,675,590,0
4946.00,36,36,39,40,0,676,668,594,0
4946.56,36,36,39,40,0,668,670,592,0
4947.13,39,37,39,40,0,674,669,588,0
4947.69,37,35,39,39,0,676,669,593,0
4948.26,37,36,40,39,0,676,673,587,0
4948.82,38,36,40,40,0,668,671,592,0
4949.39,37,36,39,40,0,669,668,593,0
4949.96,38,37,38,40,0,666,674,587,0
4950.53,37,36,40,40,0,670,666,586,0
4951.09,37,36,40,40,0,673,674,587,0
4951.66,37,36,40,40,0,666,665,591,0
4952.22,36,36,39,40,0,676,676,589,0
4952.79,37,37,40,40,0,670,669,593,0
4953.35,37,36,40,40,0,678,672,592,0
4953.92,37,37,39,40,0,669,674,587,0
4954.48,38,36,39,40,0,667,666,592,0
4955.06,37,37,39,40,0,674,676,587,0
4955.62,38,36,40,40,0,668,669,593,0
4956.19,38,36,39,40,0,667,666,588,0
4956.76,37,36,40,40,0,667,672,592,0
4957.32,37,35,40,40,0,666,672,594,0
4957.88,37,36,40,40,0,674,674,590,0
4958.45,37,37,40,40,0,669,664,586,0
4959.01,38,36,39,40,0,674,667,587,0
4959.58,38,37,40,40,0,673,673,593,0
4960.15,37,36,40,41,0,669,674,587,0
4960.72,37,36,41,40,0,674,675,592,0
4961.29,38,36,39,40,0,671,673,588,0
4961.85,36,36,40,40,0,670,665,594,0
4962.42,37,36,39,41,0,669,667,589,0
4962.98,37,36,39,38,0,669,674,593,0
4963.55,37,37,39,40,0,669,667,591,0
4964.11,38,35,41,41,0,671,669,594,0
4964.68,37,36,40,39,0,674,668,588,0
4965.24,37,36,40,40,0,675,673,593,0
4965.81,38,36,39,40,0,671,676,594,0
4966.38,38,37,39,40,0,674,669,586,0
4966.94,36,36,40,40,0,675,674,594,0
4967.51,37,36,40,39,0,666,666,589,0
4968.07,37,35,40,41,0,669,674,592,0
4968.64,37,36,40,40,0,669,668,587,0
4969.21,39,36,38,40,0,667,674,595,0
4969.77,37,36,40,40,0,665,675,586,0
4970.33,38,37,40,40,0,669,669,588,0
4970.90,38,36,40,40,0,668,666,588,0
4971.46,37,36,39,40,0,668,667,593,0
4972.03,37,37,39,40,0,672,671,594,0
4972.59,37,36,40,40,0,674,669,594,0
4973.16,38,37,39,40,0,668,669,594,0
4973.72,37,37,39,40,0,677,671,590,0
4974.28,38,37,40,40,0,673,673,588,0
4974.85,37,36,39,42,0,675,675,592,0
4975.41,37,37,40,40,0,671,670,591,0
4975.98,37,36,39,40,0,669,667,589,0
4976.54,37,36,38,40,0,667,672,587,0
4977.10,37,37,41,40,0,671,675,587,0
4977.67,37,35,38,41,0,671,668,593,0
4978.23,36,36,40,40,0,676,673,592,0
4978.80,38,36,40,39,0,667,677,588,0
4979.36,38,37,40,40,0,672,673,587,0
4979.92,38,36,40,41,0,669,670,588,0
4980.49,37,35,40,40,0,670,669,593,0
4981.05,37,37,39,40,0,671,672,593,0
4981.62,37,37,40,40,0,669,672,589,0
4982.19,38,36,39,40,0,669,673,591,0
4982.76,38,36,40,39,0,666,674,593,0
4983.33,39,36,39,39,0,671,671,593,0
4983.89,37,37,40,40,0,669,668,593,0
4984.46,37,36,39,40,0,669,668,592,0
4985.03,37,37,40,40,0,670,664,592,0
4985.60,37,36,39,39,0,676,673,589,0
4986.17,38,37,40,40,0,668,668,586,0
4986.73,37,36,38,39,0,666,671,591,0
4987.30,37,36,39,40,0,673,666,587,0
4987.86,38,36,40,40,0,667,667,587,0
4988.43,37,36,38,40,0,668,676,592,0
4989.00,37,37,39,40,0,675,673,589,0
4989.57,36,37,39,40,0,674,667,593,0
4990.13,37,36,40,40,0,661,674,594,0
4990.70,38,37,39,39,0,670,675,590,0
4991.26,38,36,39,40,0,669,668,594,0
4991.83,37,36,40,39,0,668,668,587,0
4992.39,37,37,39,41,0,668,672,587,0
4992.96,38,37,40,40,0,676,665,594,0
4993.52,36,36,39,41,0,669,671,594,0
4994.08,38,37,39,40,0,676,666,588,0
4994.65,37,36,39,40,0,668,666,591,0
4995.21,38,35,39,40,0,674,665,590,0
4995.78,38,36,40,40,0,670,667,589,0
4996.35,37,36,40,41,0,666,672,588,0
4996.92,38,36,40,40,0,676,673,589,0
4997.48,38,35,38,39,0,675,666,589,0
4998.05,38,36,40,41,0,672,670,588,0
4998.61,37,37,40,40,0,676,677,593,0
4999.18,37,38,40,40,0,669,671,593,0
4999.75,38,36,38,40,0,673,667,586,0
5000.31,36,36,39,41,0,668,666,590,0
5000.88,37,35,40,40,0,669,673,584,0
5001.45,38,36,40,40,0,666,666,594,0
5002.02,37,36,40,40,0,666,672,589,0
5002.59,37,36,38,40,0,667,666,587,0
5003.15,37,36,40,39,0,675,672,586,0
5003.72,37,36,38,41,0,677,670,590,0
5004.29,38,36,39,40,0,674,669,594,0
5004.86,37,36,40,41,0,675,674,592,0
1 t_s lid_ir_1 lid_ir_2 lid_ir_3 lid_ir_4 hv_current water_1 water_2 pwr_temp laser_on_sampled
2 4665.89 37 37 40 40 0 672 669 586 0
3 4666.45 37 36 40 40 0 674 674 586 0
4 4667.01 37 36 40 40 0 668 673 586 0
5 4667.57 37 36 39 40 0 667 669 593 0
6 4668.14 36 36 39 40 0 667 674 592 0
7 4668.70 38 36 39 40 0 668 674 593 0
8 4669.27 37 35 38 41 0 665 669 591 0
9 4669.83 39 37 39 39 0 667 666 592 0
10 4670.40 38 36 40 40 0 674 667 589 0
11 4670.96 37 36 38 41 0 668 667 593 0
12 4671.52 37 37 41 40 0 671 674 590 0
13 4672.09 38 36 41 40 0 671 666 589 0
14 4672.65 37 36 40 40 0 669 666 590 0
15 4673.22 37 36 38 41 0 669 676 593 0
16 4673.78 38 37 40 41 0 675 674 590 0
17 4674.35 36 38 40 40 0 669 667 590 0
18 4674.91 37 36 39 40 0 663 667 590 0
19 4675.48 38 38 39 40 0 667 666 592 0
20 4676.05 38 36 40 40 0 666 670 587 0
21 4676.62 38 36 38 39 0 672 667 586 0
22 4677.18 38 36 38 40 0 670 667 589 0
23 4677.75 37 36 39 40 0 666 664 592 0
24 4678.31 36 37 40 39 0 665 672 593 0
25 4678.88 37 36 40 40 0 667 669 593 0
26 4679.44 37 36 40 40 0 674 678 591 0
27 4680.01 37 36 40 39 0 667 665 587 0
28 4680.57 37 36 39 38 0 676 677 587 0
29 4681.14 37 35 40 40 0 675 670 587 0
30 4681.70 38 37 39 41 0 672 671 593 0
31 4682.27 36 36 39 40 0 675 671 592 0
32 4682.84 38 37 38 41 0 675 667 592 0
33 4683.40 37 36 38 40 0 667 666 593 0
34 4683.97 36 36 40 40 0 668 666 591 0
35 4684.53 37 36 39 40 0 669 669 594 0
36 4685.10 38 36 40 40 0 674 667 592 0
37 4685.66 37 36 39 39 0 673 669 586 0
38 4686.23 37 37 40 40 0 668 667 586 0
39 4686.79 36 36 39 39 0 668 666 593 0
40 4687.36 37 37 39 40 0 673 666 586 0
41 4687.92 37 36 40 41 0 666 669 593 0
42 4688.49 37 37 39 40 0 674 666 587 0
43 4689.06 36 36 39 41 0 666 674 586 0
44 4689.63 37 37 40 40 0 673 675 588 0
45 4690.19 37 36 40 40 0 662 674 593 0
46 4690.76 38 35 39 40 0 673 673 586 0
47 4691.32 37 37 40 41 0 671 671 593 0
48 4691.89 38 36 40 40 0 673 665 592 0
49 4692.45 37 36 40 40 0 674 672 591 0
50 4693.02 37 36 40 40 0 675 677 587 0
51 4693.58 38 37 40 40 0 677 668 592 0
52 4694.15 37 36 40 39 0 667 675 587 0
53 4694.71 37 36 40 39 0 676 675 593 0
54 4695.27 37 37 40 40 0 666 671 588 0
55 4695.84 38 36 40 40 0 674 673 587 0
56 4696.40 37 36 40 40 0 670 672 590 0
57 4696.97 37 36 38 40 0 669 674 588 0
58 4697.53 37 37 39 40 0 676 674 589 0
59 4698.10 38 36 40 39 0 677 669 591 0
60 4698.67 37 38 39 38 0 673 676 593 0
61 4699.23 38 36 39 40 0 670 675 587 0
62 4699.80 38 36 40 40 0 675 671 588 0
63 4700.37 38 36 40 40 0 673 668 583 0
64 4700.93 37 37 39 39 0 671 670 591 0
65 4701.50 37 35 41 39 0 675 674 587 0
66 4702.06 37 37 39 40 0 668 675 588 0
67 4702.63 37 36 39 40 0 669 666 591 0
68 4703.19 37 37 39 41 0 667 666 588 0
69 4703.75 36 36 39 40 0 672 667 588 0
70 4704.32 37 36 40 38 0 671 673 587 0
71 4704.88 37 36 39 40 0 676 673 586 0
72 4705.44 38 36 38 40 0 676 667 586 0
73 4706.01 37 36 40 40 0 670 669 587 0
74 4706.58 36 36 40 39 0 677 673 586 0
75 4707.15 37 37 40 41 0 674 670 589 0
76 4707.72 37 36 40 41 0 672 668 593 0
77 4708.28 38 37 39 40 0 669 665 591 0
78 4708.85 37 35 40 41 0 667 672 586 0
79 4709.41 37 36 40 41 0 671 674 586 0
80 4709.98 37 37 40 40 0 675 667 587 0
81 4710.54 37 37 39 41 0 666 667 590 0
82 4711.11 38 37 40 40 0 667 666 591 0
83 4711.67 37 36 38 40 0 670 668 586 0
84 4712.23 37 36 38 41 0 667 667 593 0
85 4712.80 38 36 39 41 0 666 673 592 0
86 4713.36 37 37 39 40 0 675 669 592 0
87 4713.93 37 36 40 39 0 671 672 589 0
88 4714.49 38 37 41 40 0 672 666 587 0
89 4715.05 37 36 38 39 0 666 672 586 0
90 4715.62 37 36 40 39 0 667 671 592 0
91 4716.18 39 36 40 40 0 665 665 590 0
92 4716.75 38 37 40 41 0 673 665 589 0
93 4717.31 36 36 39 40 0 678 673 593 0
94 4717.87 37 36 39 39 0 674 665 589 0
95 4718.44 38 37 40 42 0 668 669 593 0
96 4719.00 38 36 40 40 0 671 674 586 0
97 4719.57 37 36 39 40 0 668 667 592 0
98 4720.13 37 36 40 40 0 673 668 590 0
99 4720.69 38 37 40 40 0 674 667 592 0
100 4721.26 37 36 41 40 0 664 672 591 0
101 4721.83 37 37 39 40 0 671 669 588 0
102 4722.40 36 37 40 39 0 665 666 587 0
103 4722.96 38 37 39 40 0 676 675 586 0
104 4723.53 37 37 40 41 0 672 667 591 0
105 4724.10 38 36 39 40 0 669 668 594 0
106 4724.66 37 37 39 41 0 666 671 586 0
107 4725.23 37 37 40 40 0 673 674 586 0
108 4725.79 38 37 39 41 0 676 674 591 0
109 4726.36 37 37 40 41 0 675 670 586 0
110 4726.92 37 36 38 40 0 675 669 592 0
111 4727.49 37 37 39 40 0 673 676 590 0
112 4728.05 38 36 40 39 0 669 669 588 0
113 4728.62 37 36 40 40 0 676 674 586 0
114 4729.19 37 36 40 40 0 666 674 586 0
115 4729.75 37 36 38 41 0 667 674 587 0
116 4730.32 37 36 39 39 0 669 670 590 0
117 4730.89 38 36 39 41 0 668 666 594 0
118 4731.45 38 37 40 40 0 673 672 587 0
119 4732.02 38 36 39 40 0 674 665 587 0
120 4732.58 37 35 39 41 0 676 672 588 0
121 4733.14 38 35 39 40 0 677 669 589 0
122 4733.71 38 36 40 40 0 674 667 592 0
123 4734.27 36 36 39 39 0 673 666 593 0
124 4734.84 37 36 38 39 0 667 669 587 0
125 4735.40 38 37 39 40 0 669 666 593 0
126 4735.96 38 36 40 40 0 668 674 587 0
127 4736.53 37 36 38 40 0 667 675 587 0
128 4737.09 38 37 40 40 0 674 668 586 0
129 4737.66 38 36 39 41 0 667 673 588 0
130 4738.22 37 37 40 40 0 674 672 593 0
131 4738.78 37 36 39 40 0 670 667 587 0
132 4739.35 36 36 39 40 0 668 669 593 0
133 4739.92 37 37 40 40 0 667 671 591 0
134 4740.48 37 37 39 39 0 668 674 590 0
135 4741.05 37 36 39 39 0 668 666 586 0
136 4741.62 37 36 40 40 0 674 675 592 0
137 4742.18 37 36 39 39 0 670 676 586 0
138 4742.75 38 36 39 40 0 669 666 591 0
139 4743.32 37 36 40 40 0 675 674 587 0
140 4743.88 36 37 40 40 0 672 665 587 0
141 4744.45 37 36 39 40 0 674 671 592 0
142 4745.01 37 36 39 40 0 674 669 588 0
143 4745.58 37 36 39 40 0 675 665 587 0
144 4746.15 38 36 40 40 0 673 667 587 0
145 4746.72 37 35 41 40 0 667 665 590 0
146 4747.28 37 37 40 41 0 668 674 594 0
147 4747.85 38 36 39 41 0 668 674 588 0
148 4748.42 38 36 39 41 0 667 668 587 0
149 4748.99 36 37 39 39 0 674 674 586 0
150 4749.56 37 36 39 41 0 674 670 593 0
151 4750.12 37 36 40 40 0 675 672 592 0
152 4750.69 37 37 39 39 0 670 668 589 0
153 4751.25 38 37 39 40 0 672 670 590 0
154 4751.82 37 36 41 39 0 667 666 591 0
155 4752.38 38 37 39 40 0 669 666 587 0
156 4752.95 37 36 39 40 0 669 666 593 0
157 4753.51 38 36 39 40 0 676 669 592 0
158 4754.08 37 37 39 40 0 668 667 587 0
159 4754.65 38 35 38 40 0 667 670 594 0
160 4755.22 37 36 39 39 0 677 673 587 0
161 4755.79 38 36 39 40 0 672 666 587 0
162 4756.35 37 36 39 41 0 670 671 587 0
163 4756.92 38 36 39 41 0 675 669 593 0
164 4757.48 37 36 39 39 0 675 674 588 0
165 4758.05 37 36 39 40 0 672 674 593 0
166 4758.61 37 36 39 40 0 674 665 586 0
167 4759.18 37 37 39 39 0 673 669 588 0
168 4759.75 37 35 39 40 0 674 669 585 0
169 4760.32 37 37 40 39 0 671 673 587 0
170 4760.88 38 37 39 40 0 674 673 592 0
171 4761.45 38 36 39 40 0 673 673 591 0
172 4762.01 37 36 39 41 0 674 673 592 0
173 4762.58 37 36 39 41 0 666 672 589 0
174 4763.14 39 37 39 40 0 665 669 593 0
175 4763.70 37 36 40 40 0 673 675 587 0
176 4764.27 36 36 39 40 0 668 670 585 0
177 4764.83 37 36 39 40 0 668 675 587 0
178 4765.40 37 37 40 40 0 667 664 592 0
179 4765.97 38 37 40 40 0 670 676 593 0
180 4766.54 37 36 39 41 0 665 670 593 0
181 4767.10 37 36 39 40 0 675 665 587 0
182 4767.67 37 37 40 40 0 668 666 591 0
183 4768.23 37 36 40 40 0 668 670 592 0
184 4768.80 37 36 40 39 0 665 671 589 0
185 4769.36 37 36 40 40 0 673 671 586 0
186 4769.93 38 36 39 39 0 676 667 585 0
187 4770.49 38 36 39 40 0 672 668 586 0
188 4771.06 38 37 39 41 0 667 674 593 0
189 4771.62 37 37 38 40 0 668 668 587 0
190 4772.19 37 36 40 40 0 669 667 586 0
191 4772.76 37 36 41 40 0 674 666 588 0
192 4773.33 38 37 39 40 0 666 673 586 0
193 4773.89 37 36 39 40 0 674 671 591 0
194 4774.46 37 36 39 40 0 676 671 587 0
195 4775.03 37 37 40 39 0 669 673 592 0
196 4775.59 37 37 39 39 0 674 669 587 0
197 4776.16 38 36 40 40 0 673 672 591 0
198 4776.72 37 37 40 40 0 674 667 587 0
199 4777.29 37 36 40 40 0 670 676 592 0
200 4777.85 38 37 40 41 0 667 670 592 0
201 4778.42 38 37 39 40 0 669 665 586 0
202 4778.99 38 37 39 40 0 669 670 594 0
203 4779.56 38 37 39 40 0 670 673 593 0
204 4780.12 38 37 38 40 0 667 675 587 0
205 4780.68 37 36 41 40 0 666 668 592 0
206 4781.25 37 37 40 40 0 666 666 591 0
207 4781.82 37 36 40 41 0 665 667 592 0
208 4782.39 38 37 39 41 0 667 673 587 0
209 4782.96 37 36 40 40 0 666 667 591 0
210 4783.52 38 36 40 40 0 674 675 593 0
211 4784.09 38 36 39 41 0 662 673 586 0
212 4784.65 37 36 40 41 0 676 671 587 0
213 4785.22 37 38 40 40 0 669 666 591 0
214 4785.78 37 37 39 40 0 668 668 592 0
215 4786.35 37 36 40 40 0 676 673 590 0
216 4786.91 37 36 40 40 0 669 675 592 0
217 4787.48 37 37 40 40 0 668 667 587 0
218 4788.04 37 37 41 39 0 676 674 586 0
219 4788.60 37 36 39 40 0 669 672 588 0
220 4789.17 38 37 40 40 0 667 674 592 0
221 4789.74 38 37 40 40 0 674 676 592 0
222 4790.30 37 36 39 41 0 662 669 592 0
223 4790.87 38 36 39 40 0 666 666 588 0
224 4791.44 37 37 40 40 0 675 675 590 0
225 4792.01 37 36 40 40 0 668 668 587 0
226 4792.57 37 36 40 40 0 672 667 587 0
227 4793.14 37 36 39 40 0 673 669 593 0
228 4793.70 37 36 41 41 0 666 669 588 0
229 4794.27 37 37 40 40 0 676 673 588 0
230 4794.83 37 36 38 40 0 671 674 588 0
231 4795.40 36 36 40 40 0 671 667 588 0
232 4795.97 37 36 39 39 0 672 670 586 0
233 4796.54 37 36 39 40 0 667 667 587 0
234 4797.10 37 35 40 40 0 675 673 586 0
235 4797.67 37 37 40 40 0 670 669 592 0
236 4798.24 37 36 40 40 0 669 671 588 0
237 4798.81 37 37 40 39 0 675 674 592 0
238 4799.37 37 37 38 40 0 670 669 591 0
239 4799.94 37 36 39 40 0 674 673 593 0
240 4800.51 36 36 40 40 0 674 672 587 0
241 4801.07 37 36 40 40 0 673 673 589 0
242 4801.64 37 36 40 40 0 671 667 587 0
243 4802.20 37 35 39 41 0 670 668 586 0
244 4802.77 37 36 40 39 0 674 667 588 0
245 4803.33 37 37 40 39 0 670 678 589 0
246 4803.91 37 35 40 40 0 671 675 592 0
247 4804.47 38 36 40 41 0 666 675 592 0
248 4805.04 37 36 39 40 0 668 672 593 0
249 4805.60 38 36 40 40 0 676 670 588 0
250 4806.17 38 36 38 39 0 670 677 590 0
251 4806.74 37 37 39 40 0 675 671 587 0
252 4807.30 37 36 40 40 0 672 668 587 0
253 4807.87 38 37 39 40 0 667 668 591 0
254 4808.43 37 36 40 39 0 666 666 586 0
255 4809.00 37 36 41 39 0 673 667 591 0
256 4809.56 36 37 38 40 0 675 668 586 0
257 4810.13 36 36 40 40 0 672 669 592 0
258 4810.70 38 36 40 39 0 673 667 592 0
259 4811.27 38 36 40 41 0 672 673 587 0
260 4811.83 37 36 40 40 0 668 667 586 0
261 4812.40 37 37 39 39 0 666 669 586 0
262 4812.96 37 36 40 41 0 671 667 589 0
263 4813.52 37 37 39 40 0 674 673 587 0
264 4814.09 38 36 39 40 0 668 671 592 0
265 4814.65 37 36 39 38 0 669 669 594 0
266 4815.22 37 36 39 40 0 667 669 587 0
267 4815.78 37 36 39 40 0 671 674 591 0
268 4816.35 37 37 40 39 0 666 663 589 0
269 4816.92 37 37 40 40 0 672 668 588 0
270 4817.49 37 37 39 39 0 675 671 589 0
271 4818.05 38 37 40 41 0 675 666 592 0
272 4818.62 38 37 40 40 0 666 666 591 0
273 4819.18 37 37 39 40 0 671 667 591 0
274 4819.75 37 36 39 40 0 673 669 591 0
275 4820.32 37 37 39 40 0 674 674 587 0
276 4820.88 37 37 40 40 0 675 666 585 0
277 4821.45 36 36 40 41 0 667 675 593 0
278 4822.02 38 37 39 39 0 668 675 591 0
279 4822.58 36 37 41 40 0 667 672 590 0
280 4823.15 37 36 38 40 0 676 671 589 0
281 4823.72 37 37 39 41 0 673 671 587 0
282 4824.28 38 36 40 41 0 666 670 592 0
283 4824.85 37 36 40 40 0 666 672 590 0
284 4825.41 37 36 40 41 0 674 674 586 0
285 4825.99 38 36 40 41 0 675 671 593 0
286 4826.55 37 36 38 40 0 673 670 588 0
287 4827.12 37 37 39 39 0 668 665 587 0
288 4827.68 38 37 40 41 0 666 666 587 0
289 4828.25 37 36 39 39 0 673 668 585 0
290 4828.81 38 36 40 40 0 668 666 588 0
291 4829.38 37 37 39 40 0 668 675 592 0
292 4829.95 37 37 40 39 0 665 667 589 0
293 4830.51 36 37 39 40 0 669 675 593 0
294 4831.08 38 36 40 40 0 667 669 586 0
295 4831.65 37 36 40 40 0 677 668 592 0
296 4832.21 37 38 39 40 0 668 667 589 0
297 4832.78 37 37 40 41 0 665 675 589 0
298 4833.34 37 36 39 40 0 676 673 586 0
299 4833.91 37 37 38 40 0 666 667 587 0
300 4834.49 37 37 40 39 0 673 668 587 0
301 4835.05 36 37 40 39 0 667 667 586 0
302 4835.62 37 37 40 40 0 671 667 587 0
303 4836.18 38 36 39 40 0 673 674 588 0
304 4836.75 37 37 39 39 0 668 669 589 0
305 4837.31 38 37 39 40 0 672 667 589 0
306 4837.88 37 37 39 40 0 670 674 592 0
307 4838.45 37 36 40 41 0 667 674 593 0
308 4839.02 38 35 39 40 0 671 666 586 0
309 4839.59 38 37 40 40 0 670 669 591 0
310 4840.15 38 36 40 40 0 673 674 594 0
311 4840.72 37 35 39 41 0 666 667 588 0
312 4841.28 37 36 39 40 0 667 666 593 0
313 4841.85 37 36 39 39 0 674 671 588 0
314 4842.41 37 37 38 40 0 672 671 587 0
315 4842.98 38 36 41 41 0 674 666 587 0
316 4843.54 37 37 40 40 0 667 668 587 0
317 4844.10 38 36 39 41 0 673 675 588 0
318 4844.67 37 36 38 40 0 671 669 592 0
319 4845.23 38 37 39 40 0 676 665 587 0
320 4845.80 38 37 40 40 0 674 675 590 0
321 4846.36 37 37 40 40 0 671 672 586 0
322 4846.94 38 36 40 40 0 674 676 587 0
323 4847.50 38 38 40 40 0 679 673 589 0
324 4848.07 38 36 40 41 0 671 666 592 0
325 4848.64 36 37 40 40 0 668 665 590 0
326 4849.20 37 36 40 40 0 670 668 586 0
327 4849.77 37 35 40 40 0 669 670 588 0
328 4850.33 37 37 39 39 0 666 666 594 0
329 4850.90 37 36 40 39 0 672 672 586 0
330 4851.47 37 36 39 39 0 675 675 593 0
331 4852.04 37 36 40 40 0 669 671 592 0
332 4852.61 38 37 39 40 0 669 676 593 0
333 4853.18 38 36 40 40 0 671 671 586 0
334 4853.75 37 36 40 40 0 675 670 589 0
335 4854.32 38 37 39 40 0 675 668 589 0
336 4854.88 38 36 39 40 0 668 667 592 0
337 4855.45 37 36 40 40 0 674 668 591 0
338 4856.01 37 36 39 40 0 675 667 587 0
339 4856.58 36 37 40 39 0 665 667 588 0
340 4857.14 38 37 40 40 0 669 665 586 0
341 4857.71 37 36 39 40 0 675 667 587 0
342 4858.27 37 36 40 40 0 673 672 590 0
343 4858.84 38 36 39 40 0 677 674 587 0
344 4859.41 37 36 41 39 0 668 675 587 0
345 4859.98 37 36 39 39 0 676 670 586 0
346 4860.54 37 36 40 40 0 676 666 588 0
347 4861.11 37 36 39 40 0 676 673 591 0
348 4861.67 38 36 39 40 0 669 671 589 0
349 4862.24 37 36 40 39 0 676 675 589 0
350 4862.80 37 36 39 40 0 669 668 593 0
351 4863.37 38 36 40 41 0 673 674 588 0
352 4863.93 38 36 40 40 0 668 669 589 0
353 4864.50 38 36 40 40 0 669 674 594 0
354 4865.06 37 36 40 40 0 673 665 594 0
355 4865.63 37 37 39 40 0 674 675 594 0
356 4866.20 38 36 40 40 0 673 667 594 0
357 4866.76 37 36 39 40 0 669 668 586 0
358 4867.33 37 37 38 39 0 665 666 586 0
359 4867.90 37 36 39 39 0 667 669 592 0
360 4868.46 37 37 40 39 0 668 677 589 0
361 4869.03 37 37 39 40 0 674 675 587 0
362 4869.60 37 36 39 39 0 674 669 593 0
363 4870.17 38 37 39 40 0 676 673 587 0
364 4870.73 37 37 40 40 0 676 667 587 0
365 4871.30 38 37 39 40 0 669 674 588 0
366 4871.86 38 36 40 40 0 663 666 592 0
367 4872.43 38 37 39 40 0 670 670 594 0
368 4873.00 38 36 40 40 0 668 666 590 0
369 4873.56 37 35 39 40 0 667 674 591 0
370 4874.12 37 36 40 39 0 675 666 587 0
371 4874.70 37 37 40 39 0 674 673 592 0
372 4875.26 36 36 39 40 0 668 677 588 0
373 4875.83 38 36 39 41 0 673 667 593 0
374 4876.40 36 36 39 40 0 671 669 593 0
375 4876.96 37 36 40 40 0 669 672 588 0
376 4877.53 36 37 39 40 0 666 667 594 0
377 4878.10 38 35 39 40 0 667 667 586 0
378 4878.67 38 36 38 40 0 676 674 587 0
379 4879.24 37 36 39 40 0 667 676 588 0
380 4879.80 37 36 40 39 0 675 673 594 0
381 4880.36 38 35 40 40 0 676 668 592 0
382 4880.93 38 37 39 41 0 665 671 588 0
383 4881.49 36 35 38 41 0 667 673 594 0
384 4882.06 38 36 39 40 0 664 667 592 0
385 4882.62 37 36 40 41 0 671 674 593 0
386 4883.19 37 37 38 41 0 672 667 587 0
387 4883.75 37 37 40 40 0 670 673 592 0
388 4884.31 37 37 40 40 0 672 669 586 0
389 4884.88 38 36 39 40 0 674 674 593 0
390 4885.44 38 37 40 40 0 667 674 588 0
391 4886.01 38 36 39 41 0 669 669 593 0
392 4886.57 37 36 40 41 0 667 674 586 0
393 4887.14 38 36 38 41 0 674 674 588 0
394 4887.71 38 37 39 41 0 672 668 592 0
395 4888.28 39 38 39 40 0 667 668 591 0
396 4888.84 37 37 39 38 0 669 672 592 0
397 4889.41 36 36 40 39 0 669 667 591 0
398 4889.97 38 36 39 40 0 674 677 593 0
399 4890.54 37 37 40 39 0 674 670 586 0
400 4891.10 37 36 39 41 0 667 675 591 0
401 4891.67 37 36 39 41 0 668 666 593 0
402 4892.23 37 37 39 40 0 674 670 588 0
403 4892.80 38 37 39 41 0 675 671 591 0
404 4893.36 38 37 39 41 0 667 670 592 0
405 4893.92 37 36 39 39 0 670 673 592 0
406 4894.49 37 36 40 40 0 669 667 593 0
407 4895.05 37 36 38 40 0 674 674 588 0
408 4895.62 37 36 41 39 0 674 675 587 0
409 4896.19 38 36 40 40 0 674 667 588 0
410 4896.76 37 37 40 41 0 666 667 591 0
411 4897.33 37 35 39 40 0 676 678 592 0
412 4897.89 37 36 40 39 0 669 672 588 0
413 4898.46 37 37 40 40 0 669 666 587 0
414 4899.02 37 36 41 40 0 673 666 586 0
415 4899.58 36 35 39 39 0 665 674 592 0
416 4900.15 37 37 40 40 0 670 667 586 0
417 4900.71 37 36 40 41 0 666 664 589 0
418 4901.28 37 36 40 40 0 674 674 594 0
419 4901.85 37 36 40 40 0 669 671 592 0
420 4902.42 37 36 40 40 0 668 667 592 0
421 4902.98 38 36 39 39 0 673 667 592 0
422 4903.55 38 37 38 40 0 674 674 588 0
423 4904.12 39 37 40 41 0 669 665 587 0
424 4904.68 37 36 40 41 0 666 673 592 0
425 4905.25 38 37 39 39 0 664 675 591 0
426 4905.81 37 36 39 40 0 669 667 587 0
427 4906.38 37 35 40 39 0 675 671 593 0
428 4906.94 37 37 39 40 0 674 674 591 0
429 4907.51 38 37 39 39 0 669 665 592 0
430 4908.07 37 37 40 40 0 669 666 586 0
431 4908.64 38 37 40 39 0 676 669 589 0
432 4909.21 38 36 39 40 0 666 668 593 0
433 4909.78 38 36 40 40 0 670 671 592 0
434 4910.34 38 36 39 39 0 665 670 588 0
435 4910.91 38 36 39 40 0 672 669 593 0
436 4911.47 37 36 40 39 0 669 665 587 0
437 4912.03 38 36 40 40 0 669 673 589 0
438 4912.60 37 37 39 40 0 674 673 587 0
439 4913.17 36 37 39 40 0 673 675 587 0
440 4913.73 37 38 39 40 0 674 674 594 0
441 4914.30 36 37 38 40 0 668 674 592 0
442 4914.87 38 37 39 40 0 670 666 591 0
443 4915.44 37 36 40 40 0 672 675 589 0
444 4916.00 37 37 39 41 0 666 666 588 0
445 4916.56 37 36 38 39 0 667 666 588 0
446 4917.13 37 37 39 40 0 672 672 586 0
447 4917.70 37 37 40 40 0 668 665 587 0
448 4918.26 37 37 39 40 0 674 674 586 0
449 4918.82 38 35 40 40 0 675 675 588 0
450 4919.39 37 36 39 41 0 670 674 590 0
451 4919.95 37 37 40 41 0 676 673 594 0
452 4920.52 38 36 39 41 0 672 669 593 0
453 4921.08 37 35 40 40 0 669 667 586 0
454 4921.65 39 37 39 40 0 671 673 590 0
455 4922.21 37 36 38 40 0 669 676 593 0
456 4922.78 37 36 40 40 0 670 666 586 0
457 4923.35 37 36 40 40 0 669 669 586 0
458 4923.92 37 37 40 41 0 674 674 588 0
459 4924.49 37 36 40 40 0 666 673 593 0
460 4925.05 38 37 40 39 0 674 675 586 0
461 4925.62 37 38 38 40 0 673 674 593 0
462 4926.19 38 36 39 40 0 675 674 588 0
463 4926.76 37 36 40 40 0 672 674 587 0
464 4927.32 37 36 39 40 0 669 676 593 0
465 4927.89 38 36 40 40 0 670 671 588 0
466 4928.45 37 36 40 40 0 675 668 588 0
467 4929.02 38 38 40 40 0 669 673 588 0
468 4929.58 37 36 39 39 0 670 674 592 0
469 4930.15 37 37 39 38 0 668 668 586 0
470 4930.71 37 37 39 40 0 672 667 587 0
471 4931.28 37 37 40 40 0 673 667 589 0
472 4931.84 37 36 39 41 0 673 671 587 0
473 4932.41 38 37 38 41 0 675 669 594 0
474 4932.98 38 37 39 40 0 668 667 594 0
475 4933.55 38 37 40 41 0 673 667 588 0
476 4934.11 37 36 40 41 0 666 670 591 0
477 4934.68 38 36 40 40 0 674 674 585 0
478 4935.24 37 36 40 40 0 674 668 591 0
479 4935.81 37 36 40 40 0 676 674 587 0
480 4936.37 37 37 40 40 0 668 673 587 0
481 4936.94 38 35 39 40 0 673 675 588 0
482 4937.50 37 36 40 40 0 668 668 593 0
483 4938.07 37 35 40 40 0 668 671 588 0
484 4938.64 38 36 40 40 0 675 667 594 0
485 4939.21 37 35 40 40 0 673 669 593 0
486 4939.77 37 36 39 40 0 671 672 592 0
487 4940.34 37 35 40 41 0 673 673 588 0
488 4940.90 37 36 40 39 0 667 673 589 0
489 4941.47 38 36 39 39 0 667 675 588 0
490 4942.03 38 37 39 40 0 674 670 587 0
491 4942.60 37 36 39 41 0 675 674 590 0
492 4943.16 37 37 40 41 0 671 664 587 0
493 4943.73 37 36 38 40 0 666 669 588 0
494 4944.30 37 36 40 40 0 667 667 585 0
495 4944.87 37 36 40 40 0 669 667 588 0
496 4945.43 37 37 40 40 0 673 675 590 0
497 4946.00 36 36 39 40 0 676 668 594 0
498 4946.56 36 36 39 40 0 668 670 592 0
499 4947.13 39 37 39 40 0 674 669 588 0
500 4947.69 37 35 39 39 0 676 669 593 0
501 4948.26 37 36 40 39 0 676 673 587 0
502 4948.82 38 36 40 40 0 668 671 592 0
503 4949.39 37 36 39 40 0 669 668 593 0
504 4949.96 38 37 38 40 0 666 674 587 0
505 4950.53 37 36 40 40 0 670 666 586 0
506 4951.09 37 36 40 40 0 673 674 587 0
507 4951.66 37 36 40 40 0 666 665 591 0
508 4952.22 36 36 39 40 0 676 676 589 0
509 4952.79 37 37 40 40 0 670 669 593 0
510 4953.35 37 36 40 40 0 678 672 592 0
511 4953.92 37 37 39 40 0 669 674 587 0
512 4954.48 38 36 39 40 0 667 666 592 0
513 4955.06 37 37 39 40 0 674 676 587 0
514 4955.62 38 36 40 40 0 668 669 593 0
515 4956.19 38 36 39 40 0 667 666 588 0
516 4956.76 37 36 40 40 0 667 672 592 0
517 4957.32 37 35 40 40 0 666 672 594 0
518 4957.88 37 36 40 40 0 674 674 590 0
519 4958.45 37 37 40 40 0 669 664 586 0
520 4959.01 38 36 39 40 0 674 667 587 0
521 4959.58 38 37 40 40 0 673 673 593 0
522 4960.15 37 36 40 41 0 669 674 587 0
523 4960.72 37 36 41 40 0 674 675 592 0
524 4961.29 38 36 39 40 0 671 673 588 0
525 4961.85 36 36 40 40 0 670 665 594 0
526 4962.42 37 36 39 41 0 669 667 589 0
527 4962.98 37 36 39 38 0 669 674 593 0
528 4963.55 37 37 39 40 0 669 667 591 0
529 4964.11 38 35 41 41 0 671 669 594 0
530 4964.68 37 36 40 39 0 674 668 588 0
531 4965.24 37 36 40 40 0 675 673 593 0
532 4965.81 38 36 39 40 0 671 676 594 0
533 4966.38 38 37 39 40 0 674 669 586 0
534 4966.94 36 36 40 40 0 675 674 594 0
535 4967.51 37 36 40 39 0 666 666 589 0
536 4968.07 37 35 40 41 0 669 674 592 0
537 4968.64 37 36 40 40 0 669 668 587 0
538 4969.21 39 36 38 40 0 667 674 595 0
539 4969.77 37 36 40 40 0 665 675 586 0
540 4970.33 38 37 40 40 0 669 669 588 0
541 4970.90 38 36 40 40 0 668 666 588 0
542 4971.46 37 36 39 40 0 668 667 593 0
543 4972.03 37 37 39 40 0 672 671 594 0
544 4972.59 37 36 40 40 0 674 669 594 0
545 4973.16 38 37 39 40 0 668 669 594 0
546 4973.72 37 37 39 40 0 677 671 590 0
547 4974.28 38 37 40 40 0 673 673 588 0
548 4974.85 37 36 39 42 0 675 675 592 0
549 4975.41 37 37 40 40 0 671 670 591 0
550 4975.98 37 36 39 40 0 669 667 589 0
551 4976.54 37 36 38 40 0 667 672 587 0
552 4977.10 37 37 41 40 0 671 675 587 0
553 4977.67 37 35 38 41 0 671 668 593 0
554 4978.23 36 36 40 40 0 676 673 592 0
555 4978.80 38 36 40 39 0 667 677 588 0
556 4979.36 38 37 40 40 0 672 673 587 0
557 4979.92 38 36 40 41 0 669 670 588 0
558 4980.49 37 35 40 40 0 670 669 593 0
559 4981.05 37 37 39 40 0 671 672 593 0
560 4981.62 37 37 40 40 0 669 672 589 0
561 4982.19 38 36 39 40 0 669 673 591 0
562 4982.76 38 36 40 39 0 666 674 593 0
563 4983.33 39 36 39 39 0 671 671 593 0
564 4983.89 37 37 40 40 0 669 668 593 0
565 4984.46 37 36 39 40 0 669 668 592 0
566 4985.03 37 37 40 40 0 670 664 592 0
567 4985.60 37 36 39 39 0 676 673 589 0
568 4986.17 38 37 40 40 0 668 668 586 0
569 4986.73 37 36 38 39 0 666 671 591 0
570 4987.30 37 36 39 40 0 673 666 587 0
571 4987.86 38 36 40 40 0 667 667 587 0
572 4988.43 37 36 38 40 0 668 676 592 0
573 4989.00 37 37 39 40 0 675 673 589 0
574 4989.57 36 37 39 40 0 674 667 593 0
575 4990.13 37 36 40 40 0 661 674 594 0
576 4990.70 38 37 39 39 0 670 675 590 0
577 4991.26 38 36 39 40 0 669 668 594 0
578 4991.83 37 36 40 39 0 668 668 587 0
579 4992.39 37 37 39 41 0 668 672 587 0
580 4992.96 38 37 40 40 0 676 665 594 0
581 4993.52 36 36 39 41 0 669 671 594 0
582 4994.08 38 37 39 40 0 676 666 588 0
583 4994.65 37 36 39 40 0 668 666 591 0
584 4995.21 38 35 39 40 0 674 665 590 0
585 4995.78 38 36 40 40 0 670 667 589 0
586 4996.35 37 36 40 41 0 666 672 588 0
587 4996.92 38 36 40 40 0 676 673 589 0
588 4997.48 38 35 38 39 0 675 666 589 0
589 4998.05 38 36 40 41 0 672 670 588 0
590 4998.61 37 37 40 40 0 676 677 593 0
591 4999.18 37 38 40 40 0 669 671 593 0
592 4999.75 38 36 38 40 0 673 667 586 0
593 5000.31 36 36 39 41 0 668 666 590 0
594 5000.88 37 35 40 40 0 669 673 584 0
595 5001.45 38 36 40 40 0 666 666 594 0
596 5002.02 37 36 40 40 0 666 672 589 0
597 5002.59 37 36 38 40 0 667 666 587 0
598 5003.15 37 36 40 39 0 675 672 586 0
599 5003.72 37 36 38 41 0 677 670 590 0
600 5004.29 38 36 39 40 0 674 669 594 0
601 5004.86 37 36 40 41 0 675 674 592 0
+39 -29
View File
@@ -28,8 +28,9 @@ Drills (pass a name):
(default 300) on scrap, sampled like `witness`. Prints the
per-channel peak delta over the ambient baseline and the
engine's own "run telemetry" line is the record. Run it
>= 3 times on representative material; the highest peak
delta sizes cool_fire_ir_delta.
>= 3 times on representative material; the highest peaks
are what the engine's cool_fire_q1/q2 alert and critical
thresholds (absolute counts) must sit above.
ircut [S] [F] e.g. ircut 1000 300
pthresh Laser power-threshold ladder: one line per power level on
scrap, climbing from 2 % to 30 % of full, at constant power
@@ -196,8 +197,29 @@ def panel_token():
except OSError:
return ''
# Ambient lid-IR baseline (2026-08-14, lid closed, idle): per-channel means.
IR_BASELINE = [37.3, 36.3, 39.5, 40.0]
def sample_ir_baseline(seconds=3.0):
"""Per-channel lid-IR means at idle, lid closed, taken before the job:
the baseline this run is judged against (never a stored one)."""
acc = [0.0, 0.0, 0.0, 0.0]
n = 0
t_end = time.time() + seconds
while time.time() < t_end:
s = sample_forgectrl()
if s and s['ir'] and len(s['ir']) == 4:
for i in range(4):
acc[i] += s['ir'][i]
n += 1
time.sleep(0.25)
if not n:
return None
return [round(a / n, 1) for a in acc]
def ir_delta(peak, base):
if base is None:
return ['n/a'] * 4
return [round(peak[i] - base[i], 1) for i in range(4)]
def get_json(path):
@@ -394,6 +416,7 @@ def drill_witness(g):
'G90',
'M2', # program end: X-3 job-based disarm trigger
]
ir_base = sample_ir_baseline()
samples = run_and_sample(g, job)
# Analysis.
emis = [s['emission'] for s in samples if s['emission'] is not None]
@@ -415,8 +438,7 @@ def drill_witness(g):
print('pgood_samples during job: peak=%s (>=128 = power-good)'
% (max(pgood_vals) if pgood_vals else '-'))
print('lid_ir peak=%s vs baseline=%s delta=%s'
% (ir_peak, IR_BASELINE,
[round(ir_peak[i] - IR_BASELINE[i], 1) for i in range(4)]))
% (ir_peak, ir_base, ir_delta(ir_peak, ir_base)))
print('hv_current range: %s..%s' % (min(hv_vals) if hv_vals else '-',
max(hv_vals) if hv_vals else '-'))
print('armed observed during job: %s' % armed_seen)
@@ -436,10 +458,11 @@ def drill_witness(g):
% (round(disarm_dt, 1) if disarm_dt is not None else '>75 (REVIEW)'))
ok = peak_emis > 0 and end_emis == 0
print('WITNESS emission %s' % ('PASS' if ok else 'REVIEW - see values above'))
# Emit the recommended cool_fire_ir_delta floor.
worst = max(ir_peak[i] - IR_BASELINE[i] for i in range(4))
print('suggest cool_fire_ir_delta >= max(15, %.0f) once several jobs '
'confirm the peak delta' % (2 * worst if worst > 0 else 15))
# The engine's fire watch trips on absolute lid-IR counts, per quartile
# pair (cool_fire_q1_alert / _critical, cool_fire_q2_alert / _critical):
# the peaks above are what those thresholds must clear on a clean job.
print('fire-watch thresholds must sit above these peaks: %s '
'(cool_fire_q1_* for channels 1-2, cool_fire_q2_* for 3-4)' % ir_peak)
return samples
@@ -502,6 +525,7 @@ def drill_ircut(g):
'G1 X-30 F%d' % feed, 'G1 Y-30 F%d' % feed,
'M5', 'G90', 'M2',
]
ir_base = sample_ir_baseline()
samples = run_and_sample(g, job, overall_timeout=400)
ir_peak = [0, 0, 0, 0]
ir_min = [10 ** 6] * 4
@@ -522,14 +546,13 @@ def drill_ircut(g):
print('\n--- results ---')
print('samples: %d emission peak=%s fire_watch states=%s'
% (len(samples), max(emis) if emis else '-', sorted(fw)))
delta = [round(ir_peak[i] - IR_BASELINE[i], 1) for i in range(4)]
delta = ir_delta(ir_peak, ir_base)
print('lid_ir min=%s peak=%s baseline=%s peak delta=%s'
% (ir_min, ir_peak, IR_BASELINE, delta))
% (ir_min, ir_peak, ir_base, delta))
print('hv_current range: %s..%s' % (min(hv_vals) if hv_vals else '-',
max(hv_vals) if hv_vals else '-'))
worst = max(delta)
print('worst peak delta this job: %s counts -> cool_fire_ir_delta must sit '
'above the worst across ALL jobs (>= 2x it, never < 15)' % worst)
print('worst peaks this job: %s counts -> the cool_fire_q1/q2 alert '
'thresholds must sit above the worst across ALL jobs' % ir_peak)
print('the engine logged its own "run telemetry: lid IR ..." line for this job')
return samples
@@ -961,20 +984,7 @@ def _linfit(xs, ys):
return a, b, r2
def _degc(raw):
"""The factory coolant conversion when gfbench is importable (on the
board, or with GF_HOST set), else None and the raw count is quoted.
The helper lives beside this file in the repo and under the bench
directory on the dev image; a copy staged elsewhere still finds it."""
for d in (os.path.dirname(os.path.abspath(__file__)),
'/usr/share/forgetest/bench'):
if d not in sys.path:
sys.path.append(d)
try:
from gfbench import degc
except (ImportError, SystemExit):
return None
return degc(raw)
from gfbench import degc as _degc
def pcurve_levels(g, pcts):
+1 -5
View File
@@ -43,11 +43,7 @@ ADC_F = 1024.0 * 1.3
RINF = 10000.0 * math.exp(-3380.0 / 298.15)
def degc(raw):
if raw is None or raw <= 0 or raw >= ADC_F:
return float('nan')
r = 10000.0 / (ADC_F / raw - 1.0)
return 3380.0 / math.log(r / RINF) - 273.15
from gfbench import degc
def rd(path):
-11
View File
@@ -1,11 +0,0 @@
{
"points": [
{
"measured_c": 25.67,
"raw1": 672.4,
"raw2": 672.6,
"note": "IR on reservoir+hoses 78.2F, ambient 78.0F, idle 1h, pump on, heater off",
"when": "2026-08-02 18:13:41"
}
]
}
+6 -1
View File
@@ -10,7 +10,7 @@
# sign, checksums, stage, publish cmd
# (the acceptance gate reads
# releases/v<version>/acceptance.json)
# release.sh --dev build + pack a dev-signed .fw for
# release.sh --dev build + pack a dev-signed .fw of the dev image for
# the GUI upload path; no staging
#
# Environment:
@@ -89,6 +89,11 @@ if [ "$MODE" = "dev" ]; then
KEY="${FORGEFIRM_DEV_KEY:?set FORGEFIRM_DEV_KEY to the dev signing key}"
build_images
resolve_ext4
# The dev archive carries the dev image (forgetest, the bench tools), not
# the release rootfs: what the panel's upload path installs on the bench
# is what the bench runs.
EXT4="${EXT4/forgefirm-image-glowforge/forgefirm-image-dev-glowforge}"
[ -f "$EXT4" ] || die "dev rootfs not found: $EXT4"
check_size
REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB")
DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)"