Cooling engine¶
The cooling engine is the part of forgectrl that owns every piece of thermal hardware (fans, pump, TEC, and the flow-check heater) and answers one question at a time for whichever controller is running: is it safe to fire right now? This page describes how it decides: the two client channels, the fan phases and the airflow gates, coolant flow verification, the temperature gates, the fire watch, and the gate settings.
What the hardware is (the loop, the fans, the sensors) is on Coolant and airflow. What the operator sees and tunes (the settings reference, turning a gate off, the quick reference of what the machine does when) is on Cooling and fans. The operator procedure for the flow diagnostics is on Diagnostics. The beam itself is gated in hardware (Safing chain).
The engine lives in forgectrl (src/cool.c, the gates in src/gates.c, the
airflow gates in src/airflow.c, the crash watch in src/accel.c).
Tunables are the cool_* keys in the shared machine settings, re-read at
every run start, with GFCOOL_* environment overrides for bench work.
GET /cool/status reports the engine state for the UI and bench tooling.
One owner, two clients¶
The cooling engine is the only thing that writes fans, pump, TEC, and heater. Whichever controller is running, GRBL or cloud, is a client of it, over two channels:
- The controller reports its job state about once a second: idle, running, or cooling down, whether the laser is armed, and (in cloud mode) the fan duties and the limits the job asks for. The reports are level-triggered, so a lost one corrects itself on the next (Job-state reports).
- The engine publishes a verdict the controller reads and enforces in its own process: may the laser fire, should the job hold, may it resume (The verdict file).
Both controllers write no thermal hardware except through the emergency fallback described under the verdict file. Properties of that split:
A missing verdict is a bad verdict. If the verdict is absent or more than two seconds old, a controller treats it as fire blocked, hold. The engine going away looks exactly like a fault, never like permission.
Arming requires being seen. The engine only grants fire when it is receiving fresh job reports. A controller about to fire is, by contract, one that is reporting. An armed window the engine cannot see never gets a green light.
If a controller goes silent past five seconds, the engine blocks fire immediately and stands the machine down through the normal cooldown, because a smoke clear is the right physical response to a job that died mid-cut. If the silence happens while the laser is armed, or while the pulse engine still says a program is playing, the engine additionally stops motion and locks the laser latch itself. It also refuses to let exhaust and intake drop below cooldown duty while a program is still running. A controller the supervisor stops on purpose does not count as silent: the supervisor clears the last report at the stop, and the engine has no reporter until the next controller speaks, so the supervisor's own liveness probe plays unwatched.
A cloud job brings its own envelope. The pulse file the Glowforge
service sends opens with the job's operating limits, and the cloud client
hands the ones the engine has a use for along with every report: the coolant
window and the fans' minimum speeds. The engine takes each only where it is
stricter than the setting on the Machine tab: a ceiling can only come down
for a job, a floor can only go up, a looser value is noted in the log and
ignored, and a gate the operator turned off stays off whatever the job says.
The coolant ceiling is the one limit a job tightens in practice (the service
sends 33 °C on a cut, which is also the shipped default); the fan floors
feed the airflow gates. The effective set shows in the log as
effective limits: and in /cool/status as limits. A GRBL job has no
header and runs on the settings alone.
If a diagnostic takes the hardware over, the engine suspends its own writes and publishes fire-blocked until the diagnostic finishes (Diagnostics: verifying and calibrating flow).
If the engine itself is provably gone while the laser is armed, the controller writes the factory run duties to the fans once, holds the job, and stands down. That is the single sanctioned exception to single-owner control, and the duties are compiled in so that a lost configuration file cannot take the fans with it.
What the fans do, and when¶
The engine runs in phases. Duties are the factory machine's own values.
| Phase | Pump | Air assist | Exhaust | Intake | Heater |
|---|---|---|---|---|---|
| Idle | on | 204 | off | off | off |
Warm-up hold (a session opened under cool_temp_start) |
on | idle | off | off | on |
| Run (or armed, whatever the reported mode) | on | 1023 | 65535 | 43278 | flow checks only |
| Cooldown, smoke clear (15 s) | on | run duty | run duty | run duty | off |
| Cooldown, thermal | on | idle | 32768 | 21639 | off |
| Over-temp or fault hold | on | run duty | forced | forced | off |
Notes on the phases:
- The pump runs whenever the machine is on, including at idle. Circulation is cheap; a stagnant loop with a warm tube is not. The one exception is the quiet hold below, an idle-only listening with the laser latched.
- The heater is off at idle by design. An always-on flow heater measurably warms the loop within minutes, eating headroom below the start gate for no benefit while nothing can fire. The warm-up hold is the one phase that runs it for heat (Low temperature).
- Being armed counts as running. If the laser is armed, the engine forces the run profile and the flow checks regardless of what mode the controller reported. Fire never happens without cut airflow and active flow verification.
- Cooldown has two stages: a smoke clear at full run duty, then reduced airflow (the radiator cools the loop measurably) until the upstream coolant temperature is back under the resume gate or the cooldown budget expires. A run session that never had the armed window open (a homing motion, a hunt, a dark job) has no smoke to clear: its smoke phase is zero length and the cooldown goes straight to the thermal gate and the idle profile.
- The TEC is driven only when the operator declares one present (The TEC).
In GRBL mode the run profile follows the sender's M8/M9 (LightBurn's
per-layer Air Assist), OR'd with the armed window. In cloud mode the
job's own header carries the duties and the client passes them through, so a
print gets the fan profile the service designed for it and a lens hunt stays
quiet.
The quiet hold. A listening to the head accelerometer wants the machine
quiet, because a running fan or pump is in the reading. The commissioning
focus card's stop finder takes the engine's quiet hold for its listening:
the air assist, exhaust, intake and purge fans off whatever the phase says,
the pump still running (the finder was proven that way). The bench tools
take the same hold through POST /cool/quiet?on=1, and with pump=1 the
coolant pump and the TEC go off as well, so the machine is silent: the one
pump-off state besides the heater-only flow diagnostics, and a dry one,
since the laser stays latched at idle. Release with on=0;
GET /cool/status shows the hold as quiet_hold. The hold is taken only
from an idle machine with no diagnostic running, and the engine releases it
itself the moment a run session opens or after 600 s, so a job never runs
with the machine held quiet and a listener that died cannot leave it so.
Airflow gates: a fan that is not moving the air¶
Commanding a fan and getting airflow are two different things, and the machine can tell them apart: the exhaust, the two intakes, and the air assist carry tachometers, and the purge-air fan in the head reports its current. While the run profile is applied, the engine holds every one of them to a floor, each floor the effective one (a header's tach window can raise it for a job).
- The floors are settings:
cool_tach_exhaust_min_rpm,cool_tach_intake_min_rpm(either intake),cool_tach_air_assist_min_rpm, andcool_purge_min_current. Each ships at 55 percent of the steady speed that fan reaches at the cut profile on the bench reference (the measured speeds and spin-up times are on Coolant and airflow), with a recommended band of 50 to 60 percent. The commissioning airflow check measures a machine's own and writes them. A cloud job's header can raise a tach floor for that job, never lower it. - A fan is judged at the operating point where its floor is measured.
While the laser is armed every fan is judged, and a job's own fan profile
(a cloud header's run duties, via
POST /cool/state) 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 bareM8from a GRBL job), and a fan the job runs slower is measured, published asunjudged, 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 on whenever the machine is not held quiet for a listening) and is judged in every run. - A spin-up grace (
cool_fan_grace_s) runs from the moment the run profile is written, and again when a fan is commanded faster mid-run (the armed window opening raises a lowered fan). Nothing counts inside it, because the big exhaust fan takes seconds to reach speed. - The first fire waits for the fans. The engine acknowledges the
armed window (
armedin the verdict) only once every gated fan reads at or above its floor, and the controller waits for that acknowledgment before it opens the window: the button lights, the fans come up, then the beam. The controller's wait is the grace plus 5 s; an acknowledgment that never comes refuses the arm (ALARM:3, "the cooling service did not take the job"), which is what a fan that cannot reach its floor looks like. - Three consecutive 1 Hz ticks under the floor trip the gate, and a single reading at or above it in between clears the count, so a tach reading that wanders does not end a job.
- A trip is a fault, not a pause. The verdict goes
AIRFLOW,fire_ok=false,hold=true, and there is noresume_okfor the rest of that run session: a fan that has stopped moving air is not a condition to cut through. The fans stay at run duty (a stalled extraction fan needs every other fan around it running), and the reason names the fan, the reading, and the floor. The fault ends with the session (logged): at idle the verdict isOKagain (a standing hold would cancel jogs and refuse the next job before it could re-prove the fan), and the next session judges every fan afresh after the grace. - A floor of zero is that gate off. It still measures: the first reading in a job that would have tripped the shipped default is logged.
Outside a run the gates read idle. /cool/status carries each fan's
reading, floor, and state (grace, ok, under, TRIPPED, off,
unjudged, or idle) as fan_gates.
Coolant flow verification¶
The problem¶
A pump can stop, an impeller can slip, a line can airlock, and none of it shows up in a temperature reading until the tube is already in trouble. Absolute coolant temperature only tracks a loop that is circulating, and "coolant should warm up while cutting" is not a usable signal either: a light engrave may add no measurable heat at all.
The method¶
The small heater sits between the two thermistors. Each check runs it at a fixed duty for a fixed window and watches how far the downstream sensor climbs:
- flowing coolant carries that heat away: the downstream sensor rises a little;
- a stagnant loop cooks the sensor: the downstream sensor rises a lot.
The discriminator is the rise, not the difference between sensors, and the operating point is measured rather than assumed:
| Parameter | Value | Why |
|---|---|---|
| Heater duty | 40 % | Below about 40 %, natural convection sheds the heat well enough to mimic flow: dead-pump trials have looked healthier than a working pump. At 40 % heat input outruns convection, and it is the cheapest duty that does. |
| Window | 50 s | Long enough for the bands to separate cleanly. |
| Fault threshold | 14.4 °C rise | Midway between the observed flowing band and the observed stagnant band. |
| Re-check interval | 150 s | A pump that stops mid-job is invisible otherwise. |
The derivation of the operating point, from a 60-run design matrix, is on The bench.
Each check costs the loop under a degree of heating, and with cut-profile fans running the loop still nets cooler over a long job.
The record behind the operating point, all on the bench reference: 25 of 25 correct classifications at 40 percent duty, plus all three settle cases. The bands hold across the loop temperatures a real machine sees, with the margin widening as the loop warms:
| Baseline | Flowing, worst | Stagnant, worst | Gap |
|---|---|---|---|
| 19 to 23 C | 12.75 C | 16.04 C | 3.3 C |
| 24 to 25 C | 12.15 C | 18.07 C | 5.9 C |
| 26 to 27 C | 11.89 C | 18.00 C | 6.1 C |
A warmer loop sheds the heater's heat no worse with the pump on and holds it better with the pump off, so the threshold needs no warm-end value through 27 C. Above that only a running tube warms this loop, and the check takes the tube's share off (below).
Checks start from a settled loop¶
Measuring a rise from a baseline captured while the loop is still cooling from earlier heat produces garbage, and it fails in the dangerous direction: it can report flow with the pump stopped. So a check is requested, and starts only once the two sensors agree within 1.5 °C and the downstream reading has stopped drifting.
Stationarity is judged by comparing the mean of the first half of a 15-second window against the second half, not by peak-to-peak spread. On a settled loop, peak-to-peak noise is about 0.5 °C while the split-half difference is about 0.1 °C. Any peak-to-peak threshold tight enough to catch real drift would sit below the noise floor and never open the gate.
The tube's share of the rise¶
Under laser load the tube itself heats the coolant, and that heat would read as a stagnant loop. It is the right size to matter: on the bench reference a fully lit 50 s check window adds about 1.5 C to the rise, against a margin of about 1.6 C, and about 0.46 C at 45 percent density. The heat arrives 10 to 20 seconds after the first emission, as a smooth ramp on both sensors together, never as a step at fire start. Left uncorrected, a check that overlaps a cut reads about 14 C where the same loop reads 11.7 to 12.1 C dark, which is a hair from a false suspicion.
Two tunables ride with the flow gate and are not gates:
cool_laser_heat_cw and cool_laser_heat_density, the tube's share of a
heater rise in °C per raw-second of pic/hv_current under each power model
(defaults 3.06e-5 and 2.36e-5, legal 0 to 2e-4; the commissioning sheet's
flow-load card measures them, with the check held for the card so the
heater does not swamp the tube's share: cool_flow_check_hold, released
at the run's end or after ten minutes whatever happens). The model the controller
reports with its job state selects which applies (density, on every
machine), and density is assumed when the report carries none. The check
reads its baseline as the mean of the settled window the gate verified and
its end as the mean of its last 5 s, never single samples. It counts the
tube current from 15 s before the window to 15 s before its end (the heat's
lag to the sensor), subtracts that share from the rise, at most 3 °C, and
judges the remainder against cool_flow_rise. The verdict line carries the
share taken off and the raw rise.
The air-assist offset on the coolant readings¶
A third tunable rides with the coolant readings themselves:
cool_aa_offset_counts (ADC counts, default 0, legal 0 to 60). It corrects
the shift the air-assist fan's return current puts on both coolant sensors,
about 20 counts or 1.2 °C at the run duty on the bench reference; the
mechanism and its measurement are on
Sensors.
The engine commands that fan, takes the setting's
share off both raw readings before the conversion at every tick (more counts
read colder, so the lift reads as a drop), and /status applies the same
correction, so the over-temperature gates and the panel read the coolant as
it is. The value is the machine's own: the aa-offset-calibrate diagnostic
(the fan stepped idle to run and back three times, tube dark, heater off,
the step on both sensors at every edge read as the difference of two 3 s
windows, each the interquartile mean of 48 samples, so the rare excursion
a sample carries falls out; the module itself keeps the CPU busy before
every PIC transaction, so a reading no longer depends on whether the reader
had just woken) recommends it and the panel's Apply writes it. Zero, the default, is the factory behavior, which
never corrected the shift.
One bad reading is a suspicion, not a fault¶
Transients happen: cycling the pump by hand can burp an airlock that clears itself within minutes. So the engine runs a two-step decision:
- First over-limit check:
COOLANT FLOW SUSPECT. A warning, a hold request, and an immediate re-check, with no waiting for the normal cadence. - The next completed check decides. Over-limit again, with no clean
check in between:
COOLANT FLOW FAULT. Clean: the suspicion clears and the job continues.
Two more rules close the loopholes:
- A suspicion that cannot resolve escalates. If no verdict can be produced within the confirmation budget (default 480 s), it becomes a fault: a loop that will not settle after a fault-level reading has shown no evidence of health.
- Cleared suspicions still count. Three of them in one job earn an aggregated "check your coolant" warning; the counter resets when cooldown reaches idle.
A clean check from the fault state logs a recovery.
Expect a legitimate suspicion on the first checks after manually stopping and starting the pump. That is an airlock, the machinery above absorbs it, and it clears on its own.
What the verdicts do¶
| Verdict | Effect |
|---|---|
OK |
Fire permitted. |
SUSPECT |
Hold requested, cut airflow held; auto-resumes on a clean re-check. |
FAULT |
Fire gated and the hold stands, for the operator to resolve. |
OVERTEMP |
Hold with forced cooling airflow; auto-resumes below the resume gate. |
COLD |
The coolant under cool_temp_min: fire blocked, hold; back a degree above it. |
WARMUP |
A session opened under cool_temp_start: held with the loop heater on and the fans idle until the gate is reached, then run with the flow check requested. |
CRITICAL |
The coolant at or over the critical line in a run session: fire blocked, hold, no resume this job. |
SENSOR |
A coolant sensor unreadable for two ticks in a row: fire blocked, hold, heater off; released the moment both sensors read again. The other coolant gates keep their state while the engine is blind. A flow check in flight is abandoned and asked for again; a warm-up gets its heater back. |
AIRFLOW |
A fan under its floor: fire blocked, hold, no resume this job. |
FLAME |
The fire watch's pause tier: hold, fire blocked; released when the reading clears. |
FIRE |
The fire watch's fail tier: motion stopped, latch locked, hold until the next run session. |
BUMP |
The crash watch's pause tier: hold, fire blocked; released once the head sits quiet. |
CRASH |
The crash watch's fail tier: motion stopped, latch locked, hold for the rest of the run session. |
Over-temperature¶
The engine uses the factory's coolant windows:
- Run ceiling 33 °C (
cool_temp_max): above this, the verdict goesOVERTEMPwith a hold request and cooling airflow forced on. - Resume gate 31 °C (
cool_temp_resume): below this, recovery is signaled and the controller resumes automatically. It is always kept below the ceiling, and when a header tightens the ceiling the resume gate follows it down by the configured gap. - Critical line 38 °C (
cool_temp_critical_c): a second tier above the ceiling, and a different kind. At or over it during a run session the verdict goesCRITICAL, fire is blocked, the job holds, and there is no resume for the rest of that session, because a loop that ran through the pause tier and kept climbing is not a condition to cut through. The fault ends with the session; the ceiling's pause keeps holding while the loop is hot, and the next session judges the line afresh. A cloud job's header carries no critical line for the coolant, so this one is always the local setting. The settings API keeps it above the ceiling while the ceiling is a gate (a ceiling at its off end leaves the line standing alone), and at its top (70 °C) it is the gate turned off.
The upstream sensor gates, because it reads the coolant actually entering the tube.
What the operator sees depends on what the machine is doing. A running cycle takes a feed hold and resumes by itself once the loop recovers; the sender shows the hold state and a warning message. A jog is canceled instead (a jog cannot be held). Fire stays gated for the whole excursion.
Low temperature: the floor and the warm-up hold¶
Two gates sit below the operating window:
- The floor (
cool_temp_min, default 5 °C) is a fire gate: coolant under it readsCOLD, fire blocked, hold; the gate clears 1 °C above itself. A header floor can only raise it. At 0 the gate is off. - The warm-up gate (
cool_temp_start, default 16 °C, kept above the floor): a session that opens under it holds with the loop heater on and the fans idle until the gate is reached (verdictWARMUP, phasewarm-upin/cool/status), then runs with the flow check requested. At 0 the gate is off.
The release judges a one-minute rolling minimum of the upstream reading, not the instant one. With the pump running, a slug of the heater's output reaches the upstream sensor within seconds and lifts the instant reading by a degree, while the bulk warms about half a degree a minute; releasing on the instant reading let a hold end in eleven seconds with the loop still cold. Between slugs the rolling minimum falls back to the bulk, which is the number that matters.
The hold has no time limit by design. The heater plateaus 8 to 9 C over ambient, so a loop that stops warming short of the gate is named once and keeps holding: a shop more than about 8 C below the gate needs the gate lowered or the room warmed.
The TEC¶
thermal/tec_on is a bare output with no readback, so presence cannot be
detected. The TEC is driven only when cool_tec_present is 1 (the
Machine tab's "TEC (Pro chiller)"; default 0, and ForgeFIRM never touches
the line otherwise). It is the operator's word, which also covers retrofits.
When present: hysteresis on the upstream reading, on above cool_tec_on_c
(default 20 °C), off below cool_tec_off_c (default 18 °C), and only while
the fans run (the run, smoke-clear, and thermal phases, or a forced
cooldown), because the cooler's heat sink sits in their airflow. Off at
idle, off in the warm-up hold, off within a degree of the coolant floor.
Turning off is immediate; turning on waits a 30 s dwell after the last
switch so sensor noise cannot chatter the part. The factory drives a Pro's
TEC above its own threshold on the filtered upstream reading; what a Pro
sets is unknown (no Pro capture), so the defaults here are chosen, not
inherited.
Diagnostics: verifying and calibrating flow¶
The web panel's Diagnostics tab runs the two cooling tools. The operator procedure is on Diagnostics; this section is the method.
Both tools take the hardware over: the active controller is suspended for the duration, the engine stands aside, and the controller is restored on every exit path (completion, error, or Abort). The laser stays latched throughout. Progress, both coolant temperatures, and a scrolling log stream to the page while a tool runs.
A diagnostic owns the thermal hardware between cool_diag_take and
cool_diag_release. Take succeeds only from an idle engine, every tool
write goes through the engine's guarded diag helpers (one owner), the tick
publishes phase diag with fire_ok=false, hold=true and touches nothing,
and the release reasserts the idle posture in one place.
Both tools run at the configured duty, window, and threshold, so the verdict applies to the check the machine actually performs, and both use cut-profile chassis fans, the condition under which the numbers are characterized. Any pump-off window aborts immediately if the downstream sensor passes 48 °C.
Flow verify runs one check with the pump running and one with it commanded off, and passes when the threshold separates the two readings. Flow calibrate runs three trials of each case, alternating, with settle gates between them, and recommends a threshold midway between the highest flowing reading and the lowest stagnant one; it refuses to recommend anything when the gap between the bands is under 3 °C, because a threshold in a narrow gap is a threshold that will misclassify.
The operator's procedure, what each result means and when to run either is on Diagnostics.
The fire watch¶
Alongside the flow work, the engine runs the physical-evidence witnesses at its 1 Hz tick.
- Emission evidence.
cnc/laser_on_sampledcounts the last ~1 s window's emitting samples on the gated output of the hardware AND-gate: evidence, not a commanded state. Emission with no armed window in the recent past gets the hung-controller treatment (cnc/stop+cnc/laser_latch=1, repeated while the evidence persists). - Laser supply power-good is watched during an armed window: when fewer than half of the last second's samples read good, the engine warns once per session. On a healthy supply the line is good in every sample, so the warning means the supply's supervisor reported a fault.
- Lid IR fire watch. The four
pic/lid_ir_*channels are polled every tick; each job logs its baseline and peaks (the characterization dataset). The channels are first of all a photometer for the lid lamp, and a cut and a candle move them by the same few counts, which is what shapes this watch: the measurements are on Sensors. Through the run, smoke, and thermal phases the four readings sorted ascending (the quartiles, the factory's statistic) are judged against two tiers per quartile, the factory's own shape. A first or second quartile over its alert threshold for two ticks is the pause tier (verdictFLAME,hold, fire blocked; released once the reading is back under the alert for five ticks). Over its critical threshold is the fail tier (motion stopped, latch locked, verdictFIREwithholduntil the next run session, smoke-clear airflow held). The defaults are the thresholds the factory ships in every pulse header (quartiles three and four it leaves at zero). They sit far above a fully lit lid lamp, so the lamp never trips them, and a candle-sized flame stays under them too: this catches a developed fire. Zero turns a tier off. The header's ownIR??values stay declared-ignored by the cloud client; the knobs are local./cool/statuscarries the watch state asfire_watch: watch | armed | alert | ALARM. - Head-accelerometer crash watch (
src/accel.c): the factory's own mechanism, the head LIS2HH12's two on-chip interrupt generators, armed over i2c-dev (I2C_SLAVE_FORCE) whilest_accelstays bound and polled at the 1 Hz tick (the sources latch, so a poll misses nothing). IG1 takes the per-axis X and Y alert thresholds (the pause tier: verdictBUMP, hold, fire blocked; released after five quiet polls). IG2 takes the shared abort threshold (the fail tier: motion stopped, latch locked, verdictCRASHwithholdfor the rest of the run session). Thresholds are IG register units at the ±4 g run full scale, so 1 g is about 64; the defaults are the factory's own header values, about 2 g, far above normal commanded motion. Z is never armed: gravity rides it, and the factory ships Z zero too. The registers, the threshold scale and the factory's own values are on Sensors. The watch arms only inside the laser's armed window. The liveness probe and cloud homing read the accelerometer throughst_accelin unarmed sessions, and the armed watch owns the part's ODR and full scale (st_accelleaves it powered down between one-shots; the watch sets 800 Hz and ±4 g, re-asserts them every poll, and restores what it found on disarm). A head that stops answering stands the watch down for the session, said once./cool/statuscarries the watch state asaccel_watch: off | watch | armed | alert | ALARM(off= the part absent or not answering;watch= thresholds set, window not armed). - Telemetry.
cnc/faultstransitions to nonzero during a run window are warned;pic/hv_current(the only live HV telemetry) is ranged per job in the same log line. The chassis, SoC die, and supply temperatures are ranged over every run session and named once at its end (cool: temps this job: chassis 24.1..31.8 C, soc 42.8..61.0 C, supply raw 401..455, withCPU THROTTLED for N sappended when the kernel's thermal governor throttled the CPU during the job). No gate stands behind any of them (Planned). The SoC guards itself: the i.MX6 thermal zone throttles the CPU (and the GPU) at its passive trip and powers the board off at its critical one; the engine names a throttle when it starts and when it ends (SoC throttled: CPU cooling state N (die T C))./statusexposes the sampled laser evidence, faults, HV, and lid IR values.
The fire watch is not a fire alarm
The lid IR tiers catch a developed fire, not a small flame. Never leave a running laser unattended.
Gate settings: range, band, off end¶
A gate is a comparison the engine makes against a setting. Every gate
setting is a plain number on the Machine tab, and there is no separate
switch for any gate: the one table in src/gates.c gives each its legal
range (wide on purpose), its recommended band (the shipped default
with the margin one machine's loop needs), and its off end,
the end of the legal range at which the gate never trips. A value outside
the band is legal and warned about; a value at the off end is legal and
reported as the gate being off. The validators, the engine, the settings
reply, and the panel all read that table. The operator's settings reference
is on Cooling and fans.
| Key | Gate | Default | Legal | Recommended | Off at |
|---|---|---|---|---|---|
cool_temp_max |
coolant_max (the run ceiling) |
33 °C | 5 to 60 °C | 25 to 38 °C | 60 °C |
cool_temp_resume |
(follows the ceiling; kept below it) | 31 °C | 5 to 59 °C | 20 to 36 °C | never |
cool_temp_critical_c |
coolant_critical (the fail tier above the ceiling; kept above it while the ceiling gates) |
38 °C | 6 to 70 °C | 36 to 45 °C | 70 °C |
cool_temp_min |
coolant_min (the floor, a fire gate; clears 1 °C above itself; a header floor can only raise it) |
5 °C | 0 to 40 °C | 3 to 8 °C | 0 |
cool_temp_start |
warm_up (a session opening under it holds with the heater on until it is reached; kept above the floor) |
16 °C | 0 to 40 °C | 12 to 20 °C | 0 |
cool_tec_on_c |
(the TEC's on threshold; kept above cool_tec_off_c) |
20 °C | 6 to 32 °C | 18 to 24 °C | never |
cool_tec_off_c |
(the TEC's off threshold; the runtime clamp holds the TEC off within a degree of the floor) | 18 °C | 5 to 31 °C | 16 to 22 °C | never |
cool_fire_q1_alert |
flame_q1_alert (lowest sorted lid-IR reading; pause tier) |
275 | 0 to 1023 | 250 to 450 | 0 |
cool_fire_q1_critical |
flame_q1_critical (fail tier; kept above the alert) |
688 | 0 to 1023 | 500 to 1023 | 0 |
cool_fire_q2_alert |
flame_q2_alert (second-lowest sorted reading; pause tier) |
374 | 0 to 1023 | 300 to 500 | 0 |
cool_fire_q2_critical |
flame_q2_critical (fail tier; kept above the alert) |
1022 | 0 to 1023 | 500 to 1023 | 0 |
cool_accel_x_alert |
crash_x_alert (head-accel X high event; pause tier) |
132 | 0 to 255 | 100 to 170 | 0 |
cool_accel_y_alert |
crash_y_alert (head-accel Y high event; pause tier) |
112 | 0 to 255 | 85 to 145 | 0 |
cool_accel_abort |
crash_abort (head-accel shared abort; fail tier) |
133 | 0 to 255 | 100 to 170 | 0 |
cool_flow_check_s |
flow (flow verification) |
50 s | 0 to 300 s | 30 to 120 s | 0 |
cool_flow_rise |
(tunes flow; set from flow calibrate) |
14.4 °C | 1 to 40 °C | 8 to 16 °C | never |
cool_tach_exhaust_min_rpm |
exhaust |
6400 rpm | 0 to 20000 | 5800 to 7000 | 0 |
cool_tach_intake_min_rpm |
intake (either tach) |
2290 rpm | 0 to 20000 | 2100 to 2500 | 0 |
cool_tach_air_assist_min_rpm |
air_assist |
6000 rpm | 0 to 30000 | 5500 to 6600 | 0 |
cool_purge_min_current |
purge (current, raw) |
300 | 0 to 1023 | 150 to 500 | 0 |
cool_fan_grace_s |
(the spin-up window, no gate) | 15 s | 0 to 120 s | 5 to 30 s | never |
What an off gate does: the engine skips the comparison (no verdict, no
hold from it) and keeps measuring. With coolant_max off, the first reading
in a run session over the shipped default is logged once as what the gate
would have done; with a fan floor off, the first reading under the shipped
default likewise; with flow off there is no heater interrogation at all
and the run start says so. A header limit never overrules an off gate.
What an off gate is not: a way to reach anything that is not a thermal gate. The hardware chain, the laser latch, the emission witness, the controller-silence dead-man, and the motion-liveness gate are not numbers on the Machine tab (the fire watch's thresholds are; its actions are not).
Where it shows: at every run start the engine logs one line per gate
setting (cool: gate coolant_max OFF: cool_temp_max = 60 ...,
cool: cool_temp_max = 45 is outside the recommended 25 to 38 ..., or the
plain value). GET /settings carries a gates object (per key: gate,
def, lo, hi, band, off, value, state of ok | warn | off,
classified from the stored value). GET /cool/status and GET /status
carry gates_off, the gate names at their off end as the engine resolved
them at the last run start. The panel classifies the stored values it gets
from GET /settings, which the engine adopts at the next run start, so
between a save and that start, or under a GFCOOL_* override, the banner
and gates_off can differ. The panel warns beside a field outside its band,
says "this gate is OFF" at the off end, and shows a standing banner on the
Status tab while any gate is off. None of it is reported to the cloud.
Job-state reports¶
The controller reports to forgectrl with POST /cool/state, query or form
parameters:
mode=idle|run|cooldown & armed=0|1 [& model=density|analog]
[ & air_assist=0..1023 & exhaust=0..65535 & intake=0..65535 ]
[ & coolant_max_c=<C> & coolant_min_c=<C>
& exhaust_min_rpm=<rpm> & intake_min_rpm=<rpm> & air_assist_min_rpm=<rpm> ]
- Level-triggered, repeated at ~1 Hz while the controller runs, not edge-triggered events. A lost report self-heals on the next one.
armed= the laser is armed (fire possible). The engine forces the run fan profile and flow interrogation wheneverarmedis true, whatevermodesays.- The duty parameters are the optional per-job run fan profile (cloud mode passes the factory pulse-header duties; GRBL mode omits them, so the configured/factory defaults apply). Out-of-range values fall back to defaults.
- The limit parameters are the job's envelope: the pulse header's
CMrxandCMrn(millidegrees, sent as degrees) and the tach maximum periodsEFrx,IFrx, andAArx(sent as the minimum speed they mean in the kernel's units: ns at 2 pulses/rev for exhaust and intake, µs at 8 for the air assist). The client drops a tag that is absent, a sentinel (0, 1023, the signed extremes, the unsigned rail), or absurd. Tighten only: the engine resolves each limit as the stricter of its own configured value and the header's. A ceiling only ever comes down and a floor only ever goes up, a looser header value is named once per run session and ignored, and a gate the operator set to its off end stays off whatever a header says. When a header tightens the coolant ceiling the resume gate follows it down by the configured gap; the fan floors feed the airflow gates; the coolant critical line is local only, since no header carries one. The effective set is resolved, logged, and published. Level-triggered like the duties: the limits ride every report while the job is loaded and leave with it. The engine logscool: effective limits: ...whenever the effective set changes and carries it inGET /cool/statusaslimits(coolant_max_c,coolant_resume_c,coolant_sourceoflocal | header, and the floors). - Silence: if the active controller stops reporting past a timeout
(5 s), the engine publishes
fire_ok=falseimmediately and stands down through the normal cooldown path (smoke clear is the right physical behavior for a job that died mid-cut), ending at the idle profile (pump on, fans idle, heater off).GET /cool/statusshowsarmedfalse from that point (report_age_stells the age of the last report). - Silence with the armed window open, or with
cnc/statestill readingrunning(cloud mode preloads whole jobs, so the ring can play for minutes with no live feeder), is the hung-controller dead-man: the engine itself writescnc/stop+cnc/laser_latch=1(the supervisor safes controller deaths; this covers hangs), and exhaust and intake never drop below cooldown duty while the kernel still reports a run in progress.
The verdict file¶
/run/forgefirm/cooling.state, JSON, atomically replaced (write-temp
plus rename) at ~1 Hz and on every verdict change:
{ "seq": 1234, "ts_mono": 5678.9,
"fire_ok": true, "verdict": "OK",
"hold": false, "resume_ok": true, "armed": true,
"reason": "", "down_c": 21.3, "up_c": 21.1 }
ts_monoisCLOCK_MONOTONICseconds. Readers must treat a missing file orts_monoolder than 2 s asfire_ok=false, hold=true. A body without its closing brace is a torn read, not a verdict; an absentfire_ok,hold,resume_ok, orarmedkey takes the fail-safe value (false,true,false,false). The publisher never writes a document longer than its buffer.armedis the engine's own view of the armed window, and it is what makes a verdict an answer to the session it is read against. A controller must not fire inside its armed window on a verdict whosearmedis false. The window opens before the engine has seen the job-state report, and the verdict standing on file until then was computed for the idle session that preceded the arm: at idle nothing is wrong, so it readsfire_ok=truewhile the fans are still at their idle duty. The engine setsarmedfrom the reported window before it applies the run profile and before it publishes, so a verdict carryingarmed=truewas computed with the run session open. A controller waits for it at the arm rather than firing, and refuses the job if it does not arrive.verdictis one ofOK,SUSPECT,FAULT,OVERTEMP,COLD,WARMUP,CRITICAL,SENSOR,AIRFLOW,FLAME,FIRE,BUMP,CRASH(What the verdicts do).hold=trueasks the active controller for a feed hold, and for it again if the job is resumed under it;resume_ok=truesignals recovery (auto-resume is the controller's call).OVERTEMP,COLD,WARMUP,SENSOR,FLAME, andBUMPare pause tiers.CRITICAL,AIRFLOW,CRASH, andFIREare the fail tier: they hold for the rest of the run session and never offer a resume in it;CRITICAL,AIRFLOW, andCRASHend with the session (the ceiling's pause tier keeps holding while the loop is hot),FIREholds until the next one starts. Controllers key on the flags, not the name; an unknown name withhold=trueholds.- Enforcement stays in the controller. The fire gate and hold/resume
issuance run in-process in each controller (the GRBL controller as a feed
hold and cycle start, the cloud client as its laser-off pause and retraced
resume, bounded by
cloud_hold_max_s); the verdict file is an input they must survive losing. The channel is not fast enough for anything safety-critical. The hardware AND-gate is the safety boundary; this is equipment protection. fire_okadditionally requires a fresh job-state report: an armed window the engine cannot see never readsfire_ok=true, and never readsarmed=trueeither. A controller about to fire is, by this contract, reporting at 1 Hz.- Emergency fallback: if the verdict goes stale while the laser is armed, the controller (besides gating fire and holding) writes the run fan duties directly once, compiled-in factory values with no config dependency, then stands down. The one sanctioned exception to single-writer, taken only when the single writer is provably absent.
Planned¶
- Chassis, SoC, and supply ceilings. Three temperatures are measured and
not gated: the chassis LM75 and the SoC die in degrees, the supply sensor
as a raw count, in
/statusastemps(with the kernel's CPU throttle state beside them), on the Status tab, and ranged over every job in one log line. The SoC already guards itself (the kernel throttles the CPU at 85 °C and powers the board off at 90 °C on this part). A ceiling for each is planned once the record says where one belongs. The supply's conversion stays unverified by decision (its heatsink is not reachable with a thermometer while the machine runs), so its reading stays a raw count and any ceiling for it would be set in raw counts too. - Fan floors measured on more than one machine. The shipped floors are a fraction of the bench reference's run-duty speeds; a machine whose fans read differently sets its own, and a floor of zero turns that gate off while it does.