Hold fan speed unless the change is worth making

The setpoint was a continuous function of instantaneous temperature and
was pushed to the BMC every pass, so ~1% of fan per degree of CPU noise
meant the fans never settled. Measured on iz-pve0: stock iDRAC held one
speed for 5 minutes through 57-59c jitter while this script made 7
changes in 16 minutes off the same signal.

Adds a 5% deadband around the applied speed, with full-speed requests
never held back and a periodic re-assert in case the BMC forgets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Zeb Hering
2026-08-28 20:07:33 -07:00
parent 64e6e03dba
commit 56ae7542a2
3 changed files with 69 additions and 3 deletions

View File

@@ -24,6 +24,19 @@ speed = max( interpolate(hottest of CPU/GPU, 45 -> 75),
Sources are never compared as raw temperatures — an 85c CPU and a 45c disk are both
"fine", and a single `max()` over the numbers would be meaningless.
The result is then **held unless it moves by `FAN_SPEED_DEADBAND` (5%)**. This matters
more than it sounds. The CPU curve is roughly 1% of fan per degree and idle CPU noise is
±2c, so without a deadband the setpoint changes on almost every pass and the fans never
settle — measurably worse than stock. Measured on iz-pve0: stock iDRAC held one speed for
5 minutes straight through 57→59c jitter, while this script made 7 distinct changes in 16
minutes off the same signal. A request for full speed is never held back, and the held
value is re-pushed every `FAN_REASSERT_INTERVAL` in case the BMC forgets it.
Note that iDRAC's own profile regulates to a target *RPM* (closed loop, rock steady),
while manual control sets a *PWM* percentage (open loop, so measured RPM still wanders a
percent or two at a fixed setpoint). That residual is inherent to manual control, not
something the deadband can remove.
### Why disks are measured against their own limits
Every drive reports its own maximum operating temperature (SATA: `Min/Max Temperature
@@ -118,6 +131,8 @@ systemctl enable --now fan_speed.service
|---|---|---|
| `CHECK_INTERVAL` | `10` | Seconds between passes |
| `LOW_FAN_SPEED` / `HIGH_FAN_SPEED` | `18` / `50` | Percent |
| `FAN_SPEED_DEADBAND` | `5` | Hold the current speed until the request moves this far |
| `FAN_REASSERT_INTERVAL` | `300` | Re-push the held speed this often regardless |
| `LOW_TEMPERATURE_THRESHOLD` | `45` | CPU/GPU ramp start |
| `CPU_TEMPERATURE_THRESHOLD` | `90` | Above this, Dell's profile takes over |
| `GPU_TEMPERATURE_THRESHOLD` | `75` | |