fan-control: continuous linear curve (replaces discrete step-bands)

Replace the step-band fan curve with a continuous linear ramp — the bands
flapped at edges (e.g. 45<->65%). Web-researched: linear + 2-3C hysteresis
is the homelab standard; PID is overkill for this slow thermal loop.
fan% now interpolates between env-tunable anchors:
  COOL  50C/30% -> 83C/100% (~2.1%/C; ~51% at the ~60C equilibrium)
  QUIET 68C/20% -> 83C/100% (near-silent until ~70C)
Both reach 100% at the 83C ceiling. Anti-oscillation: asymmetric
hysteresis (fc_decide) + a MIN_STEP (3%) min-change threshold.
41 bash tests green; deployed + verified live (59C -> 49%, smooth).

[ci skip]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-05 10:29:35 +00:00
parent 945c1936e3
commit 324f2dc3bf
4 changed files with 67 additions and 62 deletions

View file

@ -70,19 +70,24 @@ ceiling. QUIET is unchanged (already at the low-power floor: 20 % / 4,800 RPM /
`HOLD_SECS` (15 min) ⇒ someone's around ⇒ QUIET; otherwise COOL.
`house_mode` was rejected — it tracks *apartment* occupancy, irrelevant to
garage noise.
4. **Two curves**, picked by presence (COOL power-tuned 2026-06-05 — see
"Power characterization" below):
4. **Two continuous LINEAR curves**, picked by presence. (Originally discrete
step-bands; replaced 2026-06-05 — the bands flapped at edges, e.g. 45↔65%.
Web research: a linear curve + 23°C hysteresis is the homelab standard; PID
is overkill for this slow thermal loop and even PID projects "only lower, don't
chase a setpoint".) fan% interpolates between per-mode anchors, clamped flat
outside; both reach 100% right at the 83°C ceiling:
| CPU °C | COOL % (empty) | CPU °C | QUIET % (occupied) |
|--------|----------------|--------|--------------------|
| ≤54 | 30 | ≤72 | 20 (≈silent floor) |
| 5563 | 50 | 7377 | 40 |
| 6472 | 60 (knee) | 7881 | 65 |
| 7378 | 80 | ≥82 | 100 |
| ≥79 | 100 | | |
| Mode | T_LO → P_LO | T_HI → P_HI | slope |
|------|-------------|-------------|-------|
| COOL (garage empty) | 50°C → 30% | 83°C → 100% | ~2.1%/°C (≈51% at the ~60°C equilibrium) |
| QUIET (occupied) | 68°C → 20% | 83°C → 100% | ~4.7%/°C (near-silent until ~70°C) |
3°C downward hysteresis prevents flapping at band edges (ramp up immediately,
step down only once the curve still wants lower 3°C hotter).
Anchors are env-tunable (`COOL_T_LO/P_LO/T_HI/P_HI`, `QUIET_*`). Under normal
load the COOL equilibrium (~60°C → ~51%) sits near the measured ~60% power
knee; the ramp toward 100% only engages at genuinely high temp (safety).
Anti-oscillation: asymmetric hysteresis (ramp up immediately, ease down only
once the curve wants lower 3°C hotter) **plus** a `MIN_STEP` (3%) min-change
threshold so 12% wiggles don't churn IPMI writes.
## Safety