mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
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:
@@ -8,8 +8,8 @@
|
|||||||
> [community forum](https://community.openglow.org).
|
> [community forum](https://community.openglow.org).
|
||||||
|
|
||||||
Open-source firmware for Glowforge brand CNC lasers. ForgeFIRM replaces the
|
Open-source firmware for Glowforge brand CNC lasers. ForgeFIRM replaces the
|
||||||
cloud-dependent factory software on the **stock control board** — no hardware
|
cloud-dependent factory software on the **stock control board** - no hardware
|
||||||
modification — and gives the machine a local controller, a local web control
|
modification - and gives the machine a local controller, a local web control
|
||||||
panel, and a standard Grbl interface.
|
panel, and a standard Grbl interface.
|
||||||
|
|
||||||
* [Latest Release](https://github.com/openglow-org/forgefirm/releases)
|
* [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
|
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
|
(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
|
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.
|
build, but it has never run on an 8 MP machine, so treat it as untested.
|
||||||
|
|
||||||
|
|||||||
+129
-124
@@ -1,11 +1,11 @@
|
|||||||
# ForgeFIRM bring-up status & cold-start runbook
|
# 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
|
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
|
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.
|
true now, go there for how it was proven.
|
||||||
|
|
||||||
Read together with:
|
Read together with:
|
||||||
@@ -24,12 +24,12 @@ hardware-validated.**
|
|||||||
39.98 kHz; `CONFIG_PREEMPT=y`; both OV5648 cameras on the mainline
|
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`
|
imx-media pipeline with VPU JPEG encode; A/B slot install, signed `.fw`
|
||||||
releases and factory restore.
|
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
|
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.
|
camera stream. First light landed 2026-08-11.
|
||||||
- **Cloud mode runs the factory experience end to end**, deliberately kept
|
- **Cloud mode runs the factory experience end to end**, deliberately kept
|
||||||
and maintained: sign-in, camera homing, prints, pause/resume, cancel.
|
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
|
cooling engine, controller supervision, pulse-device broker, motion-liveness
|
||||||
gate, cameras, telemetry, settings, diagnostics, logging, updates, web panel.
|
gate, cameras, telemetry, settings, diagnostics, logging, updates, web panel.
|
||||||
- **Laser safety is hardware-first and bench-proven.** The chain
|
- **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),
|
modes (cancel-and-return on a lid or interlock open, button pause/resume),
|
||||||
bench-validated 2026-08-17.
|
bench-validated 2026-08-17.
|
||||||
- **Releases are gated by the acceptance tool** (`forgetest`, dev image only):
|
- **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
|
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
|
the operator's part runs unattended, and a release gate that reads the
|
||||||
exported artifact. The latest full campaign, on dev image `20260824230512`,
|
exported artifact. The latest full campaign satisfied every test from
|
||||||
satisfied 45 of 45 from nothing in 27 minutes (36 tests unattended) and
|
nothing and authorizes a release (the record is in `CAMPAIGN-LOG.md`);
|
||||||
authorizes a release; **no release is cut yet.**
|
**no release is cut yet.**
|
||||||
|
|
||||||
Current bench state: dev image `20260824230512`, the board resting on the SD
|
Current bench state: dev image `20260901225926` on the SD card (eMMC slot 1 =
|
||||||
dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in
|
factory 2024, slot 2 = a ForgeFIRM pin-file image, archives in
|
||||||
`/data/forgefirm/archive`).
|
`/data/forgefirm/archive`).
|
||||||
|
|
||||||
## The bench
|
## 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,
|
works: U-Boot (in eMMC boot0) reads the saved env at eMMC user-area 0x80000,
|
||||||
which selects the boot device (bench board: `mmcdev=0
|
which selects the boot device (bench board: `mmcdev=0
|
||||||
mmcroot=/dev/mmcblk1p1` = SD), then loads `/boot/uEnv.txt` and `/boot/zImage`
|
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.
|
Full map: "eMMC boot & recovery architecture" in the facts bank below.
|
||||||
**Module-only changes hot-swap**: scp `glowforge.ko` over
|
**Module-only changes hot-swap**: scp `glowforge.ko` over
|
||||||
`/lib/modules/<kver>/extras/`, then `rmmod glowforge && modprobe glowforge`.
|
`/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 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
|
module was built against the FLASHED kernel's patch state. Any edit under the
|
||||||
kernel recipe's overlay (e.g. `glowforge.dts`) re-stamps
|
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
|
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
|
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,
|
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 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
|
`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
|
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
|
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
|
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
|
key only. Pack releases with `scripts/mkfw.sh`; the full pipeline is
|
||||||
`scripts/release.sh`, invoked as:
|
`scripts/release.sh`, invoked as:
|
||||||
`FWUP=<fwup-lab>/bin/fwup-v1.16.0 FWUP_COMPAT=<fwup-lab>/bin/fwup-0.14.2
|
`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
|
the end of the session. Bench tools worth keeping live in
|
||||||
`forgefirm/scripts/bench/` and ship on the dev image under
|
`forgefirm/scripts/bench/` and ship on the dev image under
|
||||||
`/usr/share/forgetest/bench/`.
|
`/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)
|
## 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
|
repo** (github.com/openglow-org/grblHAL-glowforge, branch `main`): core as a
|
||||||
submodule at `src/grbl` (→ openglow-org/grblHAL-core fork, branch `forgefirm` = upstream
|
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
|
master plus two local commits: the `step_us_min` buffer sizing that keeps a
|
||||||
fortified build from aborting in `settings_init`; the settings-write crash fix
|
fortified build from aborting in `settings_init`, and the laser-power
|
||||||
merged upstream 2026-08-04 as grblHAL/core PR #999). `driver.c` implements the
|
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`.
|
HAL; machine constants live in `src/boards/glowforge.h`.
|
||||||
|
|
||||||
**The controller is spawned and supervised by forgectrl**: the supervisor
|
**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`
|
live via `POST /mode` / the Status-tab selector. The `grblhal` and `gfcloud`
|
||||||
init scripts defer to it (they remain as manual emergency levers, routed
|
init scripts defer to it (they remain as manual emergency levers, routed
|
||||||
through `POST /controller/stop|start`). The pulse device arrives as a
|
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
|
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.
|
liveness probe) before the first controller spawn of each session.
|
||||||
|
|
||||||
Architecture: a wall-paced producer thread runs the core stepper ISR against a
|
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
|
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
|
SCHED_FIFO shipper feeds `/dev/glowforge` through a bounded queue; a recursive
|
||||||
core mutex stands in for interrupt masking. `GFSINK` unset = null-sink mode
|
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
|
1. Build: `bash <repo>/forgefirm/scripts/bench/build-glowforge.sh` in the build
|
||||||
environment (from Windows, launch it through the WSL distro from PowerShell
|
environment (on a Windows host, inside the WSL distro). Produces
|
||||||
— Git Bash mangles `/mnt/c` paths). Produces `build-arm/grblHAL_glowforge`
|
`build-arm/grblHAL_glowforge`
|
||||||
in the checkout (`-O1 -g`; machine constants force-included into the core:
|
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,
|
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`).
|
`puls_profile.py`).
|
||||||
2. Deploy: move the new binary over `/usr/bin/grblHAL_glowforge` (mv replaces
|
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
|
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.
|
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):
|
broker's exclusive hold on `/dev/glowforge` makes any self-open fail EBUSY):
|
||||||
`cd /data && GFSINK=/dev/glowforge grblHAL_glowforge -p 23 -e
|
`cd /data && GFSINK=/dev/glowforge grblHAL_glowforge -p 23 -e
|
||||||
/data/EEPROM-glowforge.DAT`. Env knobs: `GFSINK_RATE` (machine tick, default
|
/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,
|
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
|
motor_lock 8, laser latched, PIC hold currents) and swaps PIC run/hold
|
||||||
currents around motion. Each motion run logs a producer-stats line
|
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`.
|
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
|
`^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).
|
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
|
a `POST /controller/stop` + `start`, or a boot). Verified host-side: after a
|
||||||
runtime `$35=0` the shipped duties stay floored.
|
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
|
`GLOWFORGE_DEFAULTS` values, run `$RST=$` once on the board (settings persist
|
||||||
in the eeprom file in `/data`).
|
in the eeprom file in `/data`).
|
||||||
|
|
||||||
**Protocol-loop pacing is fd-blocking.** `serial_wait()` drains TX then
|
**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
|
`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
|
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
|
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,
|
- at the coarse idle poll, while the motion sub-phases (`Hold_Pending` decel,
|
||||||
`Parking_Retracting`/`Resuming`) keep the tight pace. Measured on the bench:
|
`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
|
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
|
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
|
Contract rules enforced structurally: a power byte leads every kernel run
|
||||||
before any fire bit (a run start resets duty to ~100 %), transitions are
|
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
|
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,
|
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
|
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
|
covers every stream end. **Duty persists after end-of-data** (PWMSAR retains
|
||||||
its last value): the laser-off guarantee rests entirely on FIRE.
|
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
|
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"),
|
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
|
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
|
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)
|
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
|
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
|
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
|
end (`M2`/`M30`/`%`), when the sender connection changes, after
|
||||||
`laser_disarm_s` (default 60 s) of spindle-off idle, or immediately on
|
`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
|
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
|
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
|
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
|
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.
|
so software and hardware agree by construction.
|
||||||
- **During the pre-run button wait:** a lid or interlock open cancels the job
|
- **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
|
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
|
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
|
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.
|
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
|
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
|
capability advertised. `GF_SWITCH_FILE` is the file-backed EV_SW word that lets
|
||||||
null-sink builds drive these edges in CI.
|
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
|
`GET/POST /settings`, panel selector); the driver re-reads the file on every
|
||||||
`$H`:
|
`$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.
|
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`).
|
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
|
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
|
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
|
`/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,
|
`/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`.
|
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).
|
`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
|
The runner drives the GFUIService dispatch itself (the stock `run()` loop can
|
||||||
neither stop nor close the socket) and treats hunt + ≥1
|
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
|
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
|
→ single corner move → lid_image → silence. It then re-homes
|
||||||
the lens against the hall for a deterministic Z. **A quiet service without an
|
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 =
|
Position semantics: factory home = machine origin (back-left corner, +Y =
|
||||||
FRONT, workspace all-positive 0..495 × 0..279); Z top-of-travel = 10.6.
|
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
|
`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.
|
counters-only and painted red until anchored.
|
||||||
|
|
||||||
## The machine-services daemon (forgectrl, port 8080)
|
## 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
|
**cooling engine** (single owner of fans/pump/TEC/heater for both modes), plus
|
||||||
cameras, telemetry, settings, diagnostics, the web panel, updates and the
|
cameras, telemetry, settings, diagnostics, the web panel, updates and the
|
||||||
**logging tree**. It runs under a respawn wrapper (its init script); a
|
**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
|
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
|
inherited fd cannot be taken over). The meta-forgefirm recipe pins its SRCREV
|
||||||
in `forgectrl-pin.inc` (bump deliberately after pushing) and installs the
|
in `forgectrl-pin.inc` (bump deliberately after pushing) and installs the
|
||||||
sysvinit script from the repo's `init/`; bench builds cross-compile with
|
sysvinit script from the repo's `init/`; bench builds cross-compile with
|
||||||
`forgefirm/scripts/bench/build-forgectrl.sh`. The **machine-services
|
`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
|
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`
|
Every state-changing endpoint requires the first-boot bearer token in `/data`
|
||||||
(embedded in the panel), a Host address-literal check, and
|
(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:
|
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/`,
|
Diagnostics / Logs / System**; sources in `forgectrl/src/ui/`,
|
||||||
`index.html`, `panel.js`, `forms.js`, `help.js` and `theme.css`, bundled into the binary by
|
`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
|
`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
|
reboot. All settings controls disable (with a banner) while the machine is not
|
||||||
idle **or a diagnostic is running**. `/?action=stream|snapshot` remain the
|
idle **or a diagnostic is running**. `/?action=stream|snapshot` remain the
|
||||||
mjpg-streamer-compatible aliases (lid camera; LightBurn uses the stream one).
|
mjpg-streamer-compatible aliases (lid camera; LightBurn uses the stream one).
|
||||||
- `GET /status` — motion state and true machine position (kernel step counters
|
- `GET /status` - motion state and true machine position (kernel step counters
|
||||||
anchored at homing via `/run/grblhal.homed` — the Grbl socket is never polled,
|
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
|
a connection there displaces the sender), coolant temps, pump/TEC, all four
|
||||||
fan tachs, the sensed laser evidence (emission samples, HV current, lid IR),
|
fan tachs, the sensed laser evidence (emission samples, HV current, lid IR),
|
||||||
faults, the safety switches via `EVIOCGSW` (head = real presence, i.e. the
|
faults, the safety switches via `EVIOCGSW` (head = real presence, i.e. the
|
||||||
head sysfs group exists), and a `diag` flag for the UI lock.
|
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
|
(`/data/forgefirm.conf`, 0600, validated keys, empty-value-clears via query
|
||||||
params; `gf_password` write-only). **Writes 409 unless `cnc/state` is idle**
|
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
|
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`,
|
`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
|
`cloud_pause_backtrack_ticks`, `cloud_resume_lead_ticks`, `cloud_hold_max_s`, the twelve
|
||||||
`log_<logger>_disk|_remote` levels and `syslog_server|port|proto`.
|
`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,
|
mode, controller state (`running | stopped | standby | motion-fault`), pid,
|
||||||
and the motion-liveness verdict (`verified | unverified | fault`); the POST is
|
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.
|
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
|
use. Stop writes `cnc/stop` + `cnc/laser_latch=1` **before** the SIGTERM
|
||||||
(kernel-level, instantaneous) and holds supervision suspended.
|
(kernel-level, instantaneous) and holds supervision suspended.
|
||||||
- `POST /cool/state` (job-state reports from the active controller, level-
|
- `POST /cool/state` (job-state reports from the active controller, level-
|
||||||
triggered ~1 Hz) and `GET /cool/status` (engine phase, verdict, temps, report
|
triggered ~1 Hz) and `GET /cool/status` (engine phase, verdict, temps, report
|
||||||
age). The verdict the controllers enforce is the
|
age). The verdict the controllers enforce is the
|
||||||
`/run/forgefirm/cooling.state` file.
|
`/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).
|
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,
|
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
|
JPEG q75; `FORGECTRL_STREAM_Q` overrides, `FORGECTRL_STREAM_FPS` caps the
|
||||||
frame rate, unset/0 = sensor max).
|
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
|
default the sensor's full frame, 2592×1944 on a 5 MP machine (own MIT
|
||||||
bilinear demosaic).
|
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,
|
buffers/sensor, the stream + snapshot geometry the fitted sensor implies,
|
||||||
and the privacy gate's `capture_allowed` / `stopped_by_lid`). Stream and
|
and the privacy gate's `capture_allowed` / `stopped_by_lid`). Stream and
|
||||||
snapshot answer 409 while the lid is open.
|
snapshot answer 409 while the lid is open.
|
||||||
- `GET /slots`, `POST /boot`, `POST /update/check|download|apply|upload`,
|
- `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
|
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.
|
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).
|
(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.
|
the token AND the physical button; fetched on demand only.
|
||||||
|
|
||||||
**Panel conventions:** the header identifies the machine by its **fuse
|
**Panel conventions:** the header identifies the machine by its **fuse
|
||||||
identity** (the factory hostname derived from the OCOTP serial), regardless of
|
identity** (the factory hostname derived from the OCOTP serial), regardless of
|
||||||
any cloud identity override. **Units** are a display-only preference
|
any cloud identity override. **Units** are a display-only preference
|
||||||
(`ui_units`): the backend stores metric, and saves post only fields whose
|
(`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.
|
red while unreferenced, normal once anchored.
|
||||||
|
|
||||||
**Camera engine.** One worker owns the V4L2 node persistently (media-ctl /
|
**Camera engine.** One worker owns the V4L2 node persistently (media-ctl /
|
||||||
v4l2-ctl sequences identical to `gfhardware/cam.py`, factory exposure/gain/WB,
|
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
|
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
|
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
|
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
|
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
|
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 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
|
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
|
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;
|
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.
|
`/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,
|
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
|
host test `camhealth_test`). The cameras share the
|
||||||
hardware video-mux and the NEWEST request wins it: **streams preempt** (the
|
hardware video-mux and the NEWEST request wins it: **streams preempt** (the
|
||||||
current stream's clients end cleanly), **snapshots borrow** (pause, switch,
|
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)
|
(`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
|
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,
|
is the `lid_lamp_idle` setting (0–255, default 236), asserted at daemon start,
|
||||||
on a live settings change, and at every controller spawn.
|
on a live settings change, and at every controller spawn.
|
||||||
|
|
||||||
Measured performance (bench): **15.0 fps sustained** at 1296×972, sensor-
|
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
|
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 /
|
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
|
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,
|
the `MMAP_CACHE_HINTS` capability bit), `FORGECTRL_NO_NEON` (scalar convert,
|
||||||
bit-identical), `FORGECTRL_NO_VPU` (libjpeg; also the snapshot path).
|
bit-identical), `FORGECTRL_NO_VPU` (libjpeg; also the snapshot path).
|
||||||
The default path is newer and bench-proven: the GC880 GPU demosaic feeding the
|
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).
|
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
|
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`,
|
(`/data/log/forgefirm/forgectrl/forgectrl.log`; a terminal, or `FFLOG_STDERR=1`,
|
||||||
echoes the lines).
|
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
|
**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
|
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
|
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
|
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
|
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
|
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
|
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
|
through `SysLogHandler`, the kernel through `imklog`; a controller's stray
|
||||||
stdout/stderr rides a per-controller `logger` relay under its own name. Tree:
|
stdout/stderr rides a per-controller `logger` relay under its own name. Tree:
|
||||||
`/data/log/forgefirm/{forgectrl,grblhal,gfcloud,gfhome,kernel,system}/`,
|
`/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
|
renders the rsyslog rules from the settings at S19 via
|
||||||
`forgectrl --render-syslog`). Levels (`log_<logger>_disk` /
|
`forgectrl --render-syslog`). Levels (`log_<logger>_disk` /
|
||||||
`_remote`) and the remote target (`syslog_server/port/proto`) are machine
|
`_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`
|
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
|
export for issue reports (`POST /logs/export`; `src/sanitize.c` replaces
|
||||||
serial, hostname, cloud credentials, panel token, SSID/PSK, IPs, MACs and
|
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)
|
## 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
|
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
|
`forgetest/` and ships only on the dev image (`/etc/init.d/forgetest`, HTTP
|
||||||
:8090). It is **bench-validated**: the full campaign on dev image
|
:8090). It is **bench-validated**: a full campaign has satisfied the whole catalog
|
||||||
`20260824230512` (`c-20260824231028-b7ca`) satisfied 45 of 45 from nothing,
|
from nothing with the bench actuator in the loop (the record is in
|
||||||
36 of them unattended with the bench actuator in the loop, in 27 minutes, and
|
`CAMPAIGN-LOG.md`), and its export reads "Release authorized: YES". That
|
||||||
the export reads "Release authorized: YES" for that image's manifest. That
|
|
||||||
authorizes a release; it is not one until `releases/v<version>/acceptance.json`
|
authorizes a release; it is not one until `releases/v<version>/acceptance.json`
|
||||||
is committed.
|
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
|
proven bench drill or a bench-verified check: the always-required core
|
||||||
(`image.health`, `kernel.latch-locked-idle`, `kernel.k1-k2`,
|
(`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,
|
(pacing, jog round-trip, liveness probe, cancel/abort, dead-man, the lid,
|
||||||
interlock and button parity tests), `cooling.*` (flow verification, fans
|
interlock and button parity tests), `cooling.*` (flow verification, fans
|
||||||
quiet after motion, a gate setting tripping and off by value, a fan under
|
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
|
the cloud client driven from a local socket with a synthesized
|
||||||
laser-free job, no account, no network, nothing on the bed). Tests that
|
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
|
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.
|
eight of the operator tests run in the unattended queue.
|
||||||
- **The operator's part is asked for by name, not by popup**
|
- **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
|
(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
|
`do_deploy`), `forgefirm-image-manifest.bbclass` assembles them plus the layer
|
||||||
content hashes into `/etc/forgefirm-manifest.json` (also deployed beside the
|
content hashes into `/etc/forgefirm-manifest.json` (also deployed beside the
|
||||||
image as `*.forgefirm-manifest.json`), and `scripts/manifest-from-tree.py`
|
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.
|
content-defined, independent of the checkout's commit or dirty state.
|
||||||
**Component pins live in `<recipe>-pin.inc`** (SRCREV + the PV that moves
|
**Component pins live in `<recipe>-pin.inc`** (SRCREV + the PV that moves
|
||||||
with it, nothing else) and
|
with it, nothing else) and
|
||||||
@@ -696,7 +693,7 @@ is committed.
|
|||||||
platform change and invalidates everything.
|
platform change and invalidates everything.
|
||||||
- **Baseline rule:** every test and bench tool is bracketed by a baseline pass
|
- **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
|
(`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
|
lights it at power-on). Takeover runs capture the controller-owned kernel
|
||||||
attributes on entry and write them back before forgectrl restarts, so a
|
attributes on entry and write them back before forgectrl restarts, so a
|
||||||
leftover `motor_lock` mask can never read as a wedged driver.
|
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
|
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-*`
|
ever wanted, is set in raw counts from it (`temp_calibrate.py supply-*`
|
||||||
stays for a machine where the heatsink is reachable).
|
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
|
`thermal_zone0` (`imx_thermal_zone`, the same node as `hwmon0`), governor
|
||||||
`step_wise`, trips at **85 C passive** and **90 C critical** (the
|
`step_wise`, trips at **85 C passive** and **90 C critical** (the
|
||||||
consumer-grade points the driver derives from the fuses: hot point 95,
|
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
|
- **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
|
TMC2130s belong to the upgraded OpenGlow board only). A glitch can leave the
|
||||||
drivers unserviceable: SDMA playback and the position counters run normally
|
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
|
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
|
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
|
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
|
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.
|
the pulse-device broker exists and why there is no idle-rail-off policy.
|
||||||
- **Motion liveness = the head accelerometer** (`glowforge.dts` `head-accel`,
|
- **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
|
@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),
|
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
|
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
|
**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
|
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.
|
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
|
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.
|
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
|
- **Any probe/liveness move goes RIGHT (+X) first, then back**: a cable lives
|
||||||
at the end of LEFT travel and must never be crushed.
|
at the end of LEFT travel and must never be crushed.
|
||||||
- **Rail-contact signature** (from the retired accelerometer-homing spike,
|
- **Rail-contact signature** (from the retired accelerometer-homing spike,
|
||||||
relevant to any future contact sensing; tools `accel_fast.py`,
|
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
|
`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
|
within ~4 ms (20–40×). But **slow approaches are near-silent** - belt
|
||||||
compliance turns slow-speed skipping into sub-threshold grinding — so any
|
compliance turns slow-speed skipping into sub-threshold grinding - so any
|
||||||
contact-sensing scheme must strike fast. Direct I²C (unbind st-accel,
|
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
|
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.
|
are ~6 Hz and the kernel has no IIO triggers.
|
||||||
@@ -895,7 +892,7 @@ is committed.
|
|||||||
(authoritative).
|
(authoritative).
|
||||||
- **Z**: bit 6 SET = lens UP = +Z (hardware-verified). Home = hall trigger at
|
- **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.
|
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).
|
- **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,
|
**+Y physically moves the gantry toward the FRONT.** Home corner (convention,
|
||||||
for the planned limit-switch homing) = back-left (X min, Y min), workspace
|
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).
|
(6.4 % measured vs 6.3 % commanded at PWMSAR=8).
|
||||||
- **Laser duty thresholds** (ladder on scrap at F300, constant power): the
|
- **Laser duty thresholds** (ladder on scrap at F300, constant power): the
|
||||||
tube has two thresholds, far apart. The discharge **strikes between 2 % and
|
tube has two thresholds, far apart. The discharge **strikes between 2 % and
|
||||||
3 %** duty — 2 % (PWMSAR 2) draws no measurable `hv_current` and leaves
|
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
|
nothing at all, 3 % (PWMSAR 3) draws current - but it does **not lase
|
||||||
usefully until 16 %** (PWMSAR 20), the lowest duty leaving a continuous
|
usefully until 16 %** (PWMSAR 20), the lowest duty leaving a continuous
|
||||||
mark. Between them (3–14 %) is a **dead band**: current flows and climbs,
|
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
|
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`
|
dark line after it. So the usable range on the retired analog model was ~16 to 100 %. On the
|
||||||
(`DEFAULT_SPINDLE_PWM_MIN_VALUE`) ships at **16** to hold every nonzero S
|
density model that ships, `$35` (`DEFAULT_SPINDLE_PWM_MIN_VALUE`) is **10**,
|
||||||
above it. Raw `hv_current` counts are a presence/absence witness only: the
|
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
|
per-rung means are non-monotonic at the top of the ladder and the signal
|
||||||
has no characterized transfer function.
|
has no characterized transfer function.
|
||||||
- **Feed hold and resume in GRBL mode** (stream-measured on the null-sink
|
- **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,
|
- **Factory power model** (three cloud cuts of one 1" square, same location,
|
||||||
material and speed, only the UI power setting changed, pulse files captured
|
material and speed, only the UI power setting changed, pulse files captured
|
||||||
from each): the **power byte is pinned at 127**
|
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.
|
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
|
**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
|
`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
|
= 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
|
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).
|
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
|
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
|
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
|
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-
|
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
|
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
|
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
|
33 °C, resume 31 °C (factory job-header CMrx/…); the low side ships as the
|
||||||
(floors ≈1.0/4.0 °C, ~16 °C warm-up gate) is not implemented yet. 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
|
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
|
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
|
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
|
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
|
against a 177 ceiling plus drift), so no lamp change can trip them; each
|
||||||
job still logs baseline and peaks.
|
job still logs baseline and peaks.
|
||||||
- **Emission and HV witnesses**: `cnc/laser_on_sampled` goes to its full 255
|
- **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
|
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
|
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,
|
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.
|
the v2.6.0 app carries a complete but config-gated subsystem.
|
||||||
- **Switches**: truthy = closed/OK for lid/doors/button. **SW_INTERLOCK is
|
- **Switches**: truthy = closed/OK for lid/doors/button. **SW_INTERLOCK is
|
||||||
INVERTED**: the remote interlock (the regulatory 2-pin lockout connector)
|
INVERTED**: the remote interlock (the regulatory 2-pin lockout connector)
|
||||||
reads ACTIVE only when the loop is OPEN. Basic/Plus — including the bench
|
reads ACTIVE only when the loop is OPEN. Basic/Plus - including the bench
|
||||||
machine — ship the connector factory-jumpered, so the bit reads 0 =
|
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
|
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
|
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
|
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
|
(bench-verified: loop pulled → `interlock_latch`=1, `interlock_circuit` b4
|
||||||
set, all within one 50 ms sample; reinserted → all clear).
|
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` (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
|
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
|
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
|
`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
|
it is telemetry (`/status` `switches.hv_enable`, panel "HV enable"), read
|
||||||
alongside `cnc/charge_pump_alive` (`interlock_circuit` b5). **Across a pause
|
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`,
|
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
|
~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
|
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
|
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
|
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
|
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
|
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
|
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
|
(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` /
|
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
|
`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
|
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
|
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
|
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
|
`: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
|
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
|
lid open while paused cancels the job and parks from where it stands. The lens
|
||||||
hunt is not lid-gated.
|
hunt is not lid-gated.
|
||||||
@@ -1178,7 +1176,7 @@ is committed.
|
|||||||
`cnc/laser_latch` is write-only, so lock state is read from b3.
|
`cnc/laser_latch` is write-only, so lock state is read from b3.
|
||||||
- **Machine identity from OCOTP nvmem**: HW_OCOTP_MAC0 is the serial,
|
- **Machine identity from OCOTP nvmem**: HW_OCOTP_MAC0 is the serial,
|
||||||
base-23-encoded to the factory hostname (`BCDFGHJKMQRTVWXY2346789`, XXX-YYY)
|
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
|
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
|
values are deliberately not recorded here: this is a public document and a
|
||||||
fuse identity cannot be rotated.
|
fuse identity cannot be rotated.
|
||||||
@@ -1188,17 +1186,17 @@ is committed.
|
|||||||
- eMMC (`mmcblk2`): 3.6 GiB user area + two 16 MiB hardware boot partitions
|
- eMMC (`mmcblk2`): 3.6 GiB user area + two 16 MiB hardware boot partitions
|
||||||
(`mmcblk2boot0/1`). Factory user-area MBR (per the factory `.fw` manifest):
|
(`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
|
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 —
|
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
|
p3 shrunk plus a p4 - until `slotmigrate` reclaimed it to the byte-exact
|
||||||
factory geometry.)
|
factory geometry.)
|
||||||
- **U-Boot lives in boot0** at 1 KiB (IMX IVT header), not in the user area.
|
- **U-Boot lives in boot0** at 1 KiB (IMX IVT header), not in the user area.
|
||||||
Any boot0 rewrite below 0xC0000 risks the bootloader.
|
Any boot0 rewrite below 0xC0000 risks the bootloader.
|
||||||
- **Saved env**: user area 0x80000 with a redundant copy at 0x82000 (what
|
- **Saved env**: user area 0x80000 with a redundant copy at 0x82000 (what
|
||||||
`ffboot`/`fw_setenv` target; boot0's own 0x80000 region is zeros). Slot
|
`ffboot`/`fw_setenv` target; boot0's own 0x80000 region is zeros). Slot
|
||||||
selection = `mmcdev`/`mmchwpart`/`mmcpart`/`mmcroot`. Gap: `ffboot` sets
|
selection = `mmcdev`/`mmchwpart`/`mmcpart`/`mmcroot`. `ffboot` writes all four in
|
||||||
three of the four but never `mmchwpart` — it relies on the saved 0.
|
one transaction (`mmchwpart` 0, the user area).
|
||||||
- **Default (compiled-in) env boots recovery**: `mmcdev=1 mmchwpart=1
|
- **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`
|
brick. `bootcmd`: select mmc dev+hwpart → load and import `/boot/uEnv.txt`
|
||||||
from the selected partition → if `boot_recovery=yes`, boot kernel+DTB from
|
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
|
raw boot0 sectors, else load `/boot/zImage` from the slot's rootfs. U-Boot
|
||||||
@@ -1225,7 +1223,7 @@ is committed.
|
|||||||
headroom.
|
headroom.
|
||||||
- **Facts about the factory 2024 firmware** (learned during the slot install):
|
- **Facts about the factory 2024 firmware** (learned during the slot install):
|
||||||
no `/factory/imgN` mounts, the generic `fw_env.config` points at the WRONG
|
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
|
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
|
SD card (`ffboot -s` needs `-f` from factory). Factory `/etc/version` is a
|
||||||
numeric datetime stamp, so newest-slot selection is integer comparison.
|
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
|
from the authorized export, `scripts/release.sh`, the kas flip and the
|
||||||
first GitHub release, per the site (Developers, "Release flow"), once
|
first GitHub release, per the site (Developers, "Release flow"), once
|
||||||
ready to publish. Repoint the core submodule to
|
ready to publish. Repoint the core submodule to
|
||||||
upstream if the `step_us_min` sizing fix merges.
|
upstream once both local commits (the `step_us_min` sizing and the
|
||||||
5. **Update system — recovery refresh.** A refreshed recovery image in
|
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/).
|
boot0; the design is on [install and update](https://docs.forgefirm.org/technical/forgefirm/install-and-update/).
|
||||||
6. **Head IRQ (exploratory).**
|
6. **Head IRQ (exploratory).**
|
||||||
Owed for the head IRQ, only if a coarse hardware interrupt is wanted
|
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
|
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
|
watched per job instead), the head, lid, interconnect and fused temperature
|
||||||
ceilings (no sensor at those locations; the chassis is watched per job), the
|
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
|
watch runs on local knobs; the header values stay ignored), the
|
||||||
HV current caps (the sampled emission witness covers the idle case, and HV
|
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
|
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
|
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`
|
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
|
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.
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ cmd_env() {
|
|||||||
|
|
||||||
cmd_build() {
|
cmd_build() {
|
||||||
[ -e fixture.env ] || { echo "no fixture.env: run ./fixture.sh env first" >&2; exit 1; }
|
[ -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
|
if have idf.py; then
|
||||||
idf.py set-target "$CHIP" >/dev/null
|
idf.py set-target "$CHIP" >/dev/null
|
||||||
idf.py build
|
idf.py build
|
||||||
|
|||||||
@@ -337,6 +337,25 @@ def pidof(comm):
|
|||||||
return out
|
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):
|
def run(cmd, timeout=60):
|
||||||
"""Run a command list; returns (rc, combined output)."""
|
"""Run a command list; returns (rc, combined output)."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -500,9 +500,13 @@ class Takeover:
|
|||||||
rc, out = hw.initd("forgectrl", "stop")
|
rc, out = hw.initd("forgectrl", "stop")
|
||||||
log("takeover: forgectrl stop -> rc %s" % rc)
|
log("takeover: forgectrl stop -> rc %s" % rc)
|
||||||
deadline = time.time() + 15
|
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)
|
time.sleep(0.5)
|
||||||
left = hw.pidof("forgectrl") + hw.pidof("grblHAL_glowfor")
|
left = holders()
|
||||||
if left:
|
if left:
|
||||||
self.__exit__(None, None, None)
|
self.__exit__(None, None, None)
|
||||||
raise Failed("takeover: processes still alive after stop: %s" % left)
|
raise Failed("takeover: processes still alive after stop: %s" % left)
|
||||||
|
|||||||
@@ -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 "
|
"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 "
|
"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."],
|
"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 "
|
"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 "
|
"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 "
|
"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")
|
ctx.act("lid", "close")
|
||||||
settle_cloud(ctx, offset)
|
settle_cloud(ctx, offset)
|
||||||
ev["events"] = offline_events(off)
|
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")
|
"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)
|
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)]
|
if "relocking the laser" in ln or ("print [" in ln and CANCELLED in ln)]
|
||||||
ev["relock_or_cancel_lines"] = len(relocked)
|
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
|
# 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
|
# 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,
|
# or interlock abort - stop, park back to the job start, relock,
|
||||||
# disarm, ':cancelled' - judged in full.
|
# disarm, ':cancelled' - judged in full.
|
||||||
off.cancel(9004)
|
off.cancel(9004)
|
||||||
ctx.log("cancelled the print as the app would")
|
ctx.log("canceled the print as the app would")
|
||||||
svc_stop = "action cancelled mid-run; stopping motion"
|
svc_stop = "action canceled mid-run; stopping motion"
|
||||||
got = wait_log(ctx, offset, [svc_stop, "start return home", "return home complete"], 300)
|
got = wait_log(ctx, offset, [svc_stop, "start return home", "return home complete"], 300)
|
||||||
fin = wait_action_finished(ctx, offset, "print", 60)
|
fin = wait_action_finished(ctx, offset, "print", 60)
|
||||||
ev["service_cancel"] = {k: message(v) for k, v in got.items()}
|
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():
|
for k, v in ev["service_cancel"].items():
|
||||||
ctx.log(" [cancel] %s: %s", k, "seen" if v else "MISSING")
|
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[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)
|
judge_abort_tail(ctx, ev, offset, "app", fin)
|
||||||
ctx.check(hw.sysfs_int("cnc/streaming", 0) == 0,
|
ctx.check(hw.sysfs_int("cnc/streaming", 0) == 0,
|
||||||
"the device was left in live-feed mode after the job ended")
|
"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'")
|
"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,
|
subsystem="cloud", kind="live", est_min=6,
|
||||||
covers=_MACHINE_RUN, requires=["cloud.pause-resume", "cloud.lid-interlock-abort"],
|
covers=_MACHINE_RUN, requires=["cloud.pause-resume", "cloud.lid-interlock-abort"],
|
||||||
actions=["button", "lid"],
|
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.",
|
"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 "
|
"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."],
|
"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: "
|
"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 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'. "
|
"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")
|
st, cs = ctx.forgectrl.get("/cool/status")
|
||||||
ev["armed_after"] = cs.get("armed") if isinstance(cs, dict) else None
|
ev["armed_after"] = cs.get("armed") if isinstance(cs, dict) else None
|
||||||
ev["latch_locked_after"] = latch_locked()
|
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"],
|
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()
|
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.check(ev["button_dark"] is False, "the button is still lit after the cancel (%s)", ev["button_dark"])
|
||||||
ctx.act("lid", "close")
|
ctx.act("lid", "close")
|
||||||
settle_cloud(ctx, offset)
|
settle_cloud(ctx, offset)
|
||||||
ev["events"] = offline_events(off)
|
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'")
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def auth(ctx):
|
|||||||
for path, params in (("/controller/stop", None), ("/controller/start", None),
|
for path, params in (("/controller/stop", None), ("/controller/start", None),
|
||||||
("/mode", {"controller": "grbl"}), ("/settings", {"ui_units": "mm"}),
|
("/mode", {"controller": "grbl"}), ("/settings", {"ui_units": "mm"}),
|
||||||
("/diag/flow-verify", None), ("/diag/abort", None),
|
("/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)):
|
("/system/reboot", None), ("/restore/factory", None)):
|
||||||
st, body = fc.post(path, params=params, auth=False)
|
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 "")
|
ctx.log("POST %s (no token) -> %s %s", path, st, body if isinstance(body, dict) else "")
|
||||||
|
|||||||
@@ -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")]
|
ev["messages"] = [ln for ln in text.splitlines() if ln.startswith("[MSG:") or ln.startswith("ALARM")]
|
||||||
ctx.log("controller: %s", ev["messages"])
|
ctx.log("controller: %s", ev["messages"])
|
||||||
ctx.check("lid opened during arm - job canceled" in text,
|
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("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")
|
ctx.check("ALARM" not in text, "an alarm was raised on the lid-open cancel")
|
||||||
# Armed window closed and the kernel latch locked.
|
# 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,
|
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)
|
"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(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("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)")
|
ctx.check("ALARM" not in text, "an alarm was raised on the cancel (position should be kept)")
|
||||||
t3 = time.time()
|
t3 = time.time()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* only updated in place. A poll that rebuilt them would swallow the click
|
* 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
|
* 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
|
* 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
|
* 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.
|
* (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 kick(){schedule(120)}
|
||||||
function poll(){if(polling){schedule(150);return}polling=true;
|
function poll(){if(polling){schedule(150);return}polling=true;
|
||||||
/* While an action is still in flight the state is asked for in full:
|
/* 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;
|
var h=(stateEtag&&!pending)?{'If-None-Match':stateEtag}:null;
|
||||||
api('GET','/state',null,function(s,d,x){polling=false;
|
api('GET','/state',null,function(s,d,x){polling=false;
|
||||||
if(s===200){stateEtag=x.getResponseHeader('ETag');state=d;
|
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>":'');
|
$('ignreq').checked=ignoreReq;setHtml($('ignreqon'),ignoreReq?"<span class='on'>ON - prerequisites are not enforced</span>":'');
|
||||||
if(state&&catalog)renderGroups()}
|
if(state&&catalog)renderGroups()}
|
||||||
/* A start already sent but not yet seen in the state counts as busy, so
|
/* 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
|
window is capped in case the answer never arrives. A queue holds the
|
||||||
machine between its tests as well as during them. */
|
machine between its tests as well as during them. */
|
||||||
function batchActive(){return !!(state&&state.batch&&!state.batch.finished)}
|
function batchActive(){return !!(state&&state.batch&&!state.batch.finished)}
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ class CloudSuiteTests(unittest.TestCase):
|
|||||||
self.hunted(1522)
|
self.hunted(1522)
|
||||||
self.append(["2026-08-17T09:41:00.500000+00:00 gfcloud[1522] INFO websocket:_on_open RX-EVENT: ready"])
|
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)",
|
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")]
|
for l in fixture("pause")]
|
||||||
pre, rest = cut(lines, "current state: MachineState.RUNNING")
|
pre, rest = cut(lines, "current state: MachineState.RUNNING")
|
||||||
pre, rest = pre + [rest[0]], rest[1:]
|
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)}
|
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)
|
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):
|
def cancel_parts(self):
|
||||||
"""(print prologue, the pause lines, the lid stop + park + cancel,
|
"""(print prologue, the pause lines, the lid stop + park + cancel,
|
||||||
the same tail with the app's cancel as the trigger - what
|
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 "
|
"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)"]
|
"paused at Position(x=41.2, y=17.0, z=0.0)"]
|
||||||
app_cancel = [l.replace("lid opened mid-run; stopping motion",
|
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
|
return pre, paused, rest, app_cancel
|
||||||
|
|
||||||
def test_paused_lid_cancel_on_the_bench_excerpt(self):
|
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["armed_after"])
|
||||||
self.assertFalse(ev["button_dark"])
|
self.assertFalse(ev["button_dark"])
|
||||||
self.assertEqual(self.fc.posts, [])
|
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):
|
def test_paused_lid_cancel_fails_when_the_paused_print_resumes_instead(self):
|
||||||
# a lid that resumed (or was ignored) leaves the print ':completed'
|
# a lid that resumed (or was ignored) leaves the print ':completed'
|
||||||
|
|||||||
@@ -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
|
# One target: a dev image whose kernel carries the lock-correctness options
|
||||||
# (DEBUG_MUTEXES, PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_SPINLOCK). It rides
|
# (DEBUG_MUTEXES, PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_SPINLOCK). It rides
|
||||||
|
|||||||
@@ -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
|
# 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
|
# 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).
|
# 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
|
# 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:
|
repos:
|
||||||
|
|
||||||
# --- This repo (forgefirm) — auto-detected, no url/path needed -------------
|
# --- This repo (forgefirm) - auto-detected, no url/path needed -------------
|
||||||
forgefirm:
|
forgefirm:
|
||||||
layers:
|
layers:
|
||||||
meta-forgefirm:
|
meta-forgefirm:
|
||||||
@@ -73,7 +73,7 @@ repos:
|
|||||||
layers:
|
layers:
|
||||||
.:
|
.:
|
||||||
|
|
||||||
# --- meta-openglow — Glowforge BSP layers ---------------------------------
|
# --- meta-openglow - Glowforge BSP layers ---------------------------------
|
||||||
# Referenced as a local sibling checkout (no url => kas performs no git ops,
|
# 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
|
# 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
|
# sources are NOT a layer; the kernel-module-glowforge.bb recipe fetches them
|
||||||
@@ -98,14 +98,14 @@ repos:
|
|||||||
# local.conf additions
|
# local.conf additions
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
local_conf_header:
|
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
|
# (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.
|
# the factory i.MX6 Glowforge board, so this firmware is intrinsic to the image.
|
||||||
eula: |
|
eula: |
|
||||||
ACCEPT_FSL_EULA = "1"
|
ACCEPT_FSL_EULA = "1"
|
||||||
|
|
||||||
# The kernel defaults to linux-fslc 6.12 in conf/machine/glowforge.conf
|
# 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.
|
# see the site, Developers, "Build"). This explicit pin is redundant but harmless.
|
||||||
kernel: |
|
kernel: |
|
||||||
PREFERRED_PROVIDER_virtual/kernel = "linux-fslc"
|
PREFERRED_PROVIDER_virtual/kernel = "linux-fslc"
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ def forgefirm_manifest_sha256_file(path):
|
|||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
python forgefirm_manifest_assemble() {
|
python forgefirm_manifest_assemble() {
|
||||||
import glob, hashlib, json, os
|
import glob, hashlib, json, os, re
|
||||||
|
|
||||||
rootfs = d.getVar('IMAGE_ROOTFS')
|
rootfs = d.getVar('IMAGE_ROOTFS')
|
||||||
components = {}
|
components = {}
|
||||||
@@ -145,7 +145,12 @@ python forgefirm_manifest_assemble() {
|
|||||||
|
|
||||||
layer_identity, layer_build = forgefirm_manifest_layers(d)
|
layer_identity, layer_build = forgefirm_manifest_layers(d)
|
||||||
platform = {'machine': d.getVar('MACHINE'), 'layers': layer_identity}
|
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 []
|
if os.path.isdir(os.path.join(rootfs, 'lib', 'modules')) else []
|
||||||
dtbs = {}
|
dtbs = {}
|
||||||
for p in sorted(glob.glob(os.path.join(rootfs, 'boot', '**', '*.dtb'), recursive=True)):
|
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.
|
# that builds the images, so the catalog and the gate agree by construction.
|
||||||
FILESEXTRAPATHS:prepend := "${THISDIR}/../../../:"
|
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 = " \
|
SRC_URI = " \
|
||||||
file://forgetest/ \
|
file://forgetest/forgetest/ \
|
||||||
|
file://forgetest/forgetest.init \
|
||||||
file://scripts/bench/ \
|
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}"
|
S = "${WORKDIR}"
|
||||||
|
|
||||||
inherit python3-dir update-rc.d forgefirm-manifest
|
inherit python3-dir update-rc.d forgefirm-manifest
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ case "$1" in
|
|||||||
;;
|
;;
|
||||||
restart)
|
restart)
|
||||||
$0 stop
|
$0 stop
|
||||||
|
$0 start
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 {start|stop|status|restart}"
|
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
|
# emulator fixtures are the canned frames its service-protocol test answers
|
||||||
# the service with.
|
# the service with.
|
||||||
IMAGE_INSTALL += " \
|
IMAGE_INSTALL += " \
|
||||||
forgectrl \
|
|
||||||
forgetest \
|
forgetest \
|
||||||
python3-gfutilities-emulator \
|
python3-gfutilities-emulator \
|
||||||
python3-mmap \
|
python3-mmap \
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ DESCRIPTION = "OpenGlow/ForgeFIRM image for Glowforge"
|
|||||||
# nothing here imports. Each Python recipe declares the module packages it
|
# nothing here imports. Each Python recipe declares the module packages it
|
||||||
# imports (python3-core plus the few it uses), so the meta-package goes.
|
# imports (python3-core plus the few it uses), so the meta-package goes.
|
||||||
FORGEFIRM_RELEASE_TRIM ?= "nano"
|
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).
|
# grblhal-glowforge: the grblHAL motion controller (Grbl over TCP:23).
|
||||||
# forgectrl: the ForgeFIRM machine-services daemon (HTTP :8080): controller
|
# forgectrl: the ForgeFIRM machine-services daemon (HTTP :8080): controller
|
||||||
|
|||||||
@@ -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. |
|
| `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_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_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_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. |
|
| `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-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). |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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
|
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
|
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
|
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
|
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
|
live in `forgectrl/src/cool.h` and are the compiled defaults behind the
|
||||||
`cool_flow_*` settings.
|
`cool_flow_*` settings.
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
*
|
*
|
||||||
* Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to
|
* Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to
|
||||||
* hold a bounded queue depth (like a real grblHAL backend would),
|
* hold a bounded queue depth (like a real grblHAL backend would),
|
||||||
* with the deadman flock held. Motors must be locked and the laser latch
|
* with the deadman flock held. The caller locks the motors and the laser
|
||||||
* locked by the caller (bench_phase6.sh does this).
|
* 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
|
* Reports: feed statistics, worst scheduling stall, ENOMEM count, and the
|
||||||
* final driver state (expect "running" throughout, "underrun" only after
|
* final driver state (expect "running" throughout, "underrun" only after
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ def temps():
|
|||||||
adc_f = 1024.0 * 1.3
|
adc_f = 1024.0 * 1.3
|
||||||
rinf = 10000.0 * math.exp(-3380.0 / 298.15)
|
rinf = 10000.0 * math.exp(-3380.0 / 298.15)
|
||||||
|
|
||||||
def degc(raw):
|
from gfbench import degc
|
||||||
r = 10000.0 / (adc_f / float(raw) - 1.0)
|
|
||||||
return 3380.0 / math.log(r / rinf) - 273.15
|
|
||||||
with open('/sys/glowforge/pic/water_temp_1') as f:
|
with open('/sys/glowforge/pic/water_temp_1') as f:
|
||||||
d = degc(f.read())
|
d = degc(f.read())
|
||||||
with open('/sys/glowforge/pic/water_temp_2') as f:
|
with open('/sys/glowforge/pic/water_temp_2') as f:
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -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
|
|
||||||
|
@@ -28,8 +28,9 @@ Drills (pass a name):
|
|||||||
(default 300) on scrap, sampled like `witness`. Prints the
|
(default 300) on scrap, sampled like `witness`. Prints the
|
||||||
per-channel peak delta over the ambient baseline and the
|
per-channel peak delta over the ambient baseline and the
|
||||||
engine's own "run telemetry" line is the record. Run it
|
engine's own "run telemetry" line is the record. Run it
|
||||||
>= 3 times on representative material; the highest peak
|
>= 3 times on representative material; the highest peaks
|
||||||
delta sizes cool_fire_ir_delta.
|
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
|
ircut [S] [F] e.g. ircut 1000 300
|
||||||
pthresh Laser power-threshold ladder: one line per power level on
|
pthresh Laser power-threshold ladder: one line per power level on
|
||||||
scrap, climbing from 2 % to 30 % of full, at constant power
|
scrap, climbing from 2 % to 30 % of full, at constant power
|
||||||
@@ -196,8 +197,29 @@ def panel_token():
|
|||||||
except OSError:
|
except OSError:
|
||||||
return ''
|
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):
|
def get_json(path):
|
||||||
@@ -394,6 +416,7 @@ def drill_witness(g):
|
|||||||
'G90',
|
'G90',
|
||||||
'M2', # program end: X-3 job-based disarm trigger
|
'M2', # program end: X-3 job-based disarm trigger
|
||||||
]
|
]
|
||||||
|
ir_base = sample_ir_baseline()
|
||||||
samples = run_and_sample(g, job)
|
samples = run_and_sample(g, job)
|
||||||
# Analysis.
|
# Analysis.
|
||||||
emis = [s['emission'] for s in samples if s['emission'] is not None]
|
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)'
|
print('pgood_samples during job: peak=%s (>=128 = power-good)'
|
||||||
% (max(pgood_vals) if pgood_vals else '-'))
|
% (max(pgood_vals) if pgood_vals else '-'))
|
||||||
print('lid_ir peak=%s vs baseline=%s delta=%s'
|
print('lid_ir peak=%s vs baseline=%s delta=%s'
|
||||||
% (ir_peak, IR_BASELINE,
|
% (ir_peak, ir_base, ir_delta(ir_peak, ir_base)))
|
||||||
[round(ir_peak[i] - IR_BASELINE[i], 1) for i in range(4)]))
|
|
||||||
print('hv_current range: %s..%s' % (min(hv_vals) if hv_vals else '-',
|
print('hv_current range: %s..%s' % (min(hv_vals) if hv_vals else '-',
|
||||||
max(hv_vals) if hv_vals else '-'))
|
max(hv_vals) if hv_vals else '-'))
|
||||||
print('armed observed during job: %s' % armed_seen)
|
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)'))
|
% (round(disarm_dt, 1) if disarm_dt is not None else '>75 (REVIEW)'))
|
||||||
ok = peak_emis > 0 and end_emis == 0
|
ok = peak_emis > 0 and end_emis == 0
|
||||||
print('WITNESS emission %s' % ('PASS' if ok else 'REVIEW - see values above'))
|
print('WITNESS emission %s' % ('PASS' if ok else 'REVIEW - see values above'))
|
||||||
# Emit the recommended cool_fire_ir_delta floor.
|
# The engine's fire watch trips on absolute lid-IR counts, per quartile
|
||||||
worst = max(ir_peak[i] - IR_BASELINE[i] for i in range(4))
|
# pair (cool_fire_q1_alert / _critical, cool_fire_q2_alert / _critical):
|
||||||
print('suggest cool_fire_ir_delta >= max(15, %.0f) once several jobs '
|
# the peaks above are what those thresholds must clear on a clean job.
|
||||||
'confirm the peak delta' % (2 * worst if worst > 0 else 15))
|
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
|
return samples
|
||||||
|
|
||||||
|
|
||||||
@@ -502,6 +525,7 @@ def drill_ircut(g):
|
|||||||
'G1 X-30 F%d' % feed, 'G1 Y-30 F%d' % feed,
|
'G1 X-30 F%d' % feed, 'G1 Y-30 F%d' % feed,
|
||||||
'M5', 'G90', 'M2',
|
'M5', 'G90', 'M2',
|
||||||
]
|
]
|
||||||
|
ir_base = sample_ir_baseline()
|
||||||
samples = run_and_sample(g, job, overall_timeout=400)
|
samples = run_and_sample(g, job, overall_timeout=400)
|
||||||
ir_peak = [0, 0, 0, 0]
|
ir_peak = [0, 0, 0, 0]
|
||||||
ir_min = [10 ** 6] * 4
|
ir_min = [10 ** 6] * 4
|
||||||
@@ -522,14 +546,13 @@ def drill_ircut(g):
|
|||||||
print('\n--- results ---')
|
print('\n--- results ---')
|
||||||
print('samples: %d emission peak=%s fire_watch states=%s'
|
print('samples: %d emission peak=%s fire_watch states=%s'
|
||||||
% (len(samples), max(emis) if emis else '-', sorted(fw)))
|
% (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'
|
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 '-',
|
print('hv_current range: %s..%s' % (min(hv_vals) if hv_vals else '-',
|
||||||
max(hv_vals) if hv_vals else '-'))
|
max(hv_vals) if hv_vals else '-'))
|
||||||
worst = max(delta)
|
print('worst peaks this job: %s counts -> the cool_fire_q1/q2 alert '
|
||||||
print('worst peak delta this job: %s counts -> cool_fire_ir_delta must sit '
|
'thresholds must sit above the worst across ALL jobs' % ir_peak)
|
||||||
'above the worst across ALL jobs (>= 2x it, never < 15)' % worst)
|
|
||||||
print('the engine logged its own "run telemetry: lid IR ..." line for this job')
|
print('the engine logged its own "run telemetry: lid IR ..." line for this job')
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
@@ -961,20 +984,7 @@ def _linfit(xs, ys):
|
|||||||
return a, b, r2
|
return a, b, r2
|
||||||
|
|
||||||
|
|
||||||
def _degc(raw):
|
from gfbench import degc as _degc
|
||||||
"""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)
|
|
||||||
|
|
||||||
|
|
||||||
def pcurve_levels(g, pcts):
|
def pcurve_levels(g, pcts):
|
||||||
|
|||||||
@@ -43,11 +43,7 @@ ADC_F = 1024.0 * 1.3
|
|||||||
RINF = 10000.0 * math.exp(-3380.0 / 298.15)
|
RINF = 10000.0 * math.exp(-3380.0 / 298.15)
|
||||||
|
|
||||||
|
|
||||||
def degc(raw):
|
from gfbench import degc
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def rd(path):
|
def rd(path):
|
||||||
|
|||||||
@@ -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
@@ -10,7 +10,7 @@
|
|||||||
# sign, checksums, stage, publish cmd
|
# sign, checksums, stage, publish cmd
|
||||||
# (the acceptance gate reads
|
# (the acceptance gate reads
|
||||||
# releases/v<version>/acceptance.json)
|
# 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
|
# the GUI upload path; no staging
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
@@ -89,6 +89,11 @@ if [ "$MODE" = "dev" ]; then
|
|||||||
KEY="${FORGEFIRM_DEV_KEY:?set FORGEFIRM_DEV_KEY to the dev signing key}"
|
KEY="${FORGEFIRM_DEV_KEY:?set FORGEFIRM_DEV_KEY to the dev signing key}"
|
||||||
build_images
|
build_images
|
||||||
resolve_ext4
|
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
|
check_size
|
||||||
REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB")
|
REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB")
|
||||||
DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)"
|
DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)"
|
||||||
|
|||||||
Reference in New Issue
Block a user