Fan floors from the measurement; a hunt is measured, not judged

forgectrl pin 47e4256: the airflow floors set from the bench measurement
(exhaust 6400, intake 2290, air assist 6000 rpm, purge current 300, grace
15 s) and the operating-point rule: a fan is judged while the laser is
armed, when a job's profile may raise it but never lower it below the run
duty, or whenever it is commanded at the run duty; the service's hunts,
sent with the extraction fans off, are measured and published unjudged.

Catalog: cloud.mode-switch now waits out the connect-time hunt sampling
/cool/status and requires no AIRFLOW, the exhaust row unjudged and the
exhaust actually off; its covers gain forgectrl src/cool.* and
src/airflow.*. cooling.fan-gate-trips uses an 8 s test grace (the
intakes take 7 s to 90 percent and tripped under the old 2 s at the new
floor) and its off leg waits for the row state as well as gates_off.
fan_floor_measure.py names a reply that is not JSON and calls the purge
readings idle and run (the pump is always on).

Docs: COOLING 3a and the settings table, BRINGUP item 19 and a facts-bank
entry with the measured fan speeds, the bench README, and a CAMPAIGN-LOG
entry for the measurement, the two status-document finds, the hunt find
and the hot-deployed bench runs of both tests.
This commit is contained in:
ScottW514
2026-08-22 09:56:53 -04:00
parent 71a79b6675
commit adcd1adb9a
9 changed files with 165 additions and 32 deletions
+24 -7
View File
@@ -623,6 +623,18 @@ until `releases/v<version>/acceptance.json` is committed.
## Hardware facts bank (measured)
- **Fan speeds at the cut profile** (exhaust duty 65535, intake 43278, air
assist 1023; sampled at 1 Hz over 120 s from idle, the exhaust duct's
inline booster fan off): exhaust **11640 rpm** steady (spread 11444 to
11947, 90 percent of steady in 5 s), intakes **4157 / 4158 rpm** (spread
under 100, 7 s), air assist **11050 rpm** (spread 30, 1 s); at idle the
exhaust and intakes read 0 / ~745 rpm and the air assist ~1900 rpm
(idle duty 204). The purge-air fan is always on and reads **~625 counts**
of `head/purge_air_current` (~1 when off). The airflow floors are 55
percent of these (bands 50 to 60 percent); an inline booster fan changes
the exhaust's back pressure and can move its reading by a few percent
either way, well inside the margin.
- **DRV8825 stepper drivers wedge on 40 V rail glitches** (factory board; the
TMC2130s belong to the upgraded OpenGlow board only). A glitch can leave the
drivers unserviceable: SDMA playback and the position counters run normally
@@ -1340,13 +1352,18 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
while the run profile is applied (exhaust, intakes and air assist by
tachometer, purge by current), a spin-up grace, three ticks under the
floor, a fault for the rest of the session (`AIRFLOW`, no resume), a
header window only ever raising a floor; `cooling.fan-gate-trips` in
the catalog. The shipped floors are provisional, 55 percent of one
run-duty snapshot (exhaust 3700, intake 1800, air assist 6000 rpm,
purge current 300, grace 15 s) until `fan_floor_measure.py` has been
run on the bench and the numbers in `forgectrl/src/gates.c` set from
it. The coolant critical tier and the watch-only board temperatures
follow.
header window only ever raising a floor. A fan is judged at the
operating point its floor was measured at: always while armed (when a
job's profile may raise a fan but never lower it below the run duty),
and unarmed whenever it is commanded at the run duty; a cloud hunt,
sent with the extraction fans off, is measured and not judged.
`cooling.fan-gate-trips` and the hunt leg of `cloud.mode-switch` in
the catalog. The shipped floors are 55 percent of the bench machine's
measured steady speeds (facts bank: exhaust 6400, intake 2290, air
assist 6000 rpm, purge current 300, grace 15 s); the bench run of the
gate tests on an image carrying them, and the unplugged-tach drill,
are still owed. The coolant critical tier and the watch-only board
temperatures follow.
Nothing here can put energy where it was not commanded: the hardware chain
is the emission boundary and no header field touches it, and forgectrl runs
+54
View File
@@ -3254,6 +3254,60 @@ value), and the test quoted the session's first job-limits line, a hunt's,
where the print's is the one worth keeping (it now takes the first line
after the print's action request).
## 2026-08-22: the fan floors measured, and a hunt that would have tripped them
`fan_floor_measure.py spinup` (bench page `fan-floor`, 120 s at the cut
profile from idle, GRBL mode, the exhaust duct's inline booster fan off, so
the exhaust worked against more back pressure than a normal cut):
| fan | steady rpm | min | max | sd | t90 |
|---|---|---|---|---|---|
| exhaust | 11638 | 11444 | 11947 | 103 | 5 s |
| intake 1 | 4157 | 4102 | 4193 | 12 | 7 s |
| intake 2 | 4158 | 4128 | 4173 | 6 | 7 s |
| air assist | 11048 | 11029 | 11061 | 8 | 1 s |
Purge current 627 at idle and 625 at run duty: the engine holds purge air on
continuously, so both are the "on" reading (the off reading, ~1, is from an
earlier observation). The floors shipped from this: exhaust 6400, intake
2290, air assist 6000 rpm (55 percent of steady, bands 50 to 60 percent),
purge current 300, grace 15 s (twice the slowest time to 90 percent). The
provisional floors had come from a snapshot at a lower exhaust speed; the
measured margin is larger.
The run also sent the first `/cool/status` with the limits and the fan rows
through a 512-byte reply buffer, and then a 160-byte limits fragment: twice
a cut-off document, found by the measurement tool refusing to parse it.
Both fixed in forgectrl with a host test that renders the widest legal
document and parses it (`tests/coolfmt_test.c`).
Reading the measurement through cloud mode found a defect in the gates as
first built: the service reports every action as a run, and its hunt and
motion headers command the exhaust and the intakes off and the air assist
at idle, so a hunt longer than the grace would have tripped `AIRFLOW` at
any floor. Decision (operator): a fan is judged only at the operating
point its floor was measured at: always while the laser is armed, when a
job's profile may raise a fan but never lower it below the run duty; and
unarmed when commanded at the run duty. A hunt is measured, published as
`unjudged`, and not judged. `cloud.mode-switch` now watches the connect-time
hunt's gate rows for it.
Both tests ran the same day on a hot-deployed forgectrl (the working tree
cross-built and installed over dev image `20260821230723`; informational,
not acceptance). `cloud.mode-switch` PASS: the hunt reported two run ticks
with the exhaust off and `unjudged`, verdict `OK` throughout.
`cooling.fan-gate-trips` FAIL first, for two reasons worth keeping: the
run-start tick resolved the effective limits before it reloaded the
settings, so for one tick `gates_off` named the exhaust while its row still
carried the old floor (fixed: the session start re-resolves the limits after
the reload, so rows, `gates_off` and the log line agree); and the test's
2 s grace, chosen against the provisional 1800 rpm intake floor, now let
both intakes trip at ~1850 rpm on their 7 s spin-up (the test grace is 8 s).
Rerun PASS in 59 s: only the exhaust tripped in the exhaust leg, only the
purge in the purge leg, the exhaust read `off` with floor 0 in the off leg,
and the restore showed every fan `ok` at the shipped floors (exhaust 11723,
intakes 4157 and 4162, air assist 11078 rpm, purge 628 counts).
## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13
+19 -7
View File
@@ -145,9 +145,20 @@ floor.
- **The floors** are settings (§8): `cool_tach_exhaust_min_rpm`,
`cool_tach_intake_min_rpm` (either intake), `cool_tach_air_assist_min_rpm`
and `cool_purge_min_current`, each a fraction of what the fan reaches at
run duty on the bench machine. A cloud job's header can raise a tach floor
for that job, never lower it (§2).
and `cool_purge_min_current`, each 55 percent of the steady speed the fan
reaches at the cut profile on the bench machine (exhaust 11640, intakes
4160, air assist 11050 rpm; the recommended bands are 50 to 60 percent).
A cloud job's header can raise a tach floor for that job, never lower it
(§2).
- **A fan is judged at the operating point its floor was measured at.**
While the laser is armed every fan is judged, and a job's own fan profile
(a cloud header's run duties) may raise a fan above the cut profile but
never lower it while armed. Unarmed, a fan is judged whenever it is
commanded at or above the cut profile (a bare `M8` from a GRBL job), and a
fan the job runs slower is measured, published as `unjudged`, and not
judged: the factory's hunts and homing moves run with the exhaust and the
intakes off and the air assist at idle, and nothing can fire during them.
The purge fan has no duty (it is always on) and is judged in every run.
- **A spin-up grace** (`cool_fan_grace_s`) runs from the moment the run
profile is written; nothing counts inside it, because the big exhaust fan
takes seconds to reach speed.
@@ -164,7 +175,8 @@ floor.
reading in a job that would have tripped the shipped default is logged.
`/cool/status` carries each fan's reading, floor and state (`grace`, `ok`,
`under`, `TRIPPED`, `off`, or `idle` outside a run) as `fan_gates`.
`under`, `TRIPPED`, `off`, `unjudged` for a fan the job runs below the cut
profile unarmed, or `idle` outside a run) as `fan_gates`.
---
@@ -355,9 +367,9 @@ start of every run, so a change takes effect on your next job.
| `cool_temp_resume` | 31 °C | 5 to 59 °C | 20 to 36 °C | Resume gate: below it, continue. Always kept below the ceiling. |
| `cool_cooldown_s` | 15 s | 0 to 1800 s | | Smoke-clear phase at run duty after a job. |
| `cool_cooldown_max_s` | 300 s | 0 to 1800 s | | Cap on the thermal cooldown phase. |
| `cool_tach_exhaust_min_rpm` | 3700 rpm | 0 to 20000 | 2500 to 5000 | Exhaust fan floor at run duty (§3a). `0` turns the gate off. |
| `cool_tach_intake_min_rpm` | 1800 rpm | 0 to 20000 | 1200 to 2500 | Intake fan floor, either intake (§3a). `0` turns the gate off. |
| `cool_tach_air_assist_min_rpm` | 6000 rpm | 0 to 30000 | 4000 to 8000 | Air-assist fan floor (§3a). `0` turns the gate off. |
| `cool_tach_exhaust_min_rpm` | 6400 rpm | 0 to 20000 | 5800 to 7000 | Exhaust fan floor at run duty (§3a). `0` turns the gate off. |
| `cool_tach_intake_min_rpm` | 2290 rpm | 0 to 20000 | 2100 to 2500 | Intake fan floor, either intake (§3a). `0` turns the gate off. |
| `cool_tach_air_assist_min_rpm` | 6000 rpm | 0 to 30000 | 5500 to 6600 | Air-assist fan floor (§3a). `0` turns the gate off. |
| `cool_purge_min_current` | 300 raw | 0 to 1023 | 150 to 500 | Purge-air fan current floor (the fan has no tachometer; about 1 off, about 630 on). `0` turns the gate off. |
| `cool_fan_grace_s` | 15 s | 0 to 120 s | 5 to 30 s | Spin-up window after the run profile is written, during which no floor counts. |