Retiring 8x2TB for 4x8TB + a 16TB moved the hottest drive to 40-41c, exactly the old 40c ramp start, and the fan flipped 10<->15% every 30 seconds. The ramp is now 44-56c, which puts the drives back on the floor with 19c of headroom to their 60c limit. Adds the deadband from fan_speed.sh so the shelf does not chase every degree once drives do climb into the ramp, with a downward-only periodic settle so damping cannot leave it permanently loud. selftest and disks no longer take the serial lock - they do not open the port, and refusing to run them while the service is up is unhelpful. The new tests caught a real bug: in `local a=$1 b=$a` bash expands the right-hand sides before the locals exist, so the held value was silently empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.9 KiB
md1200-fan-control
Temperature-aware fan control for a Dell PowerVault MD1200 disk shelf, driven over the EMM's serial debug console.
Out of the box an MD1200 runs its fans at a fixed, very loud speed. The EMM firmware
accepts a _shutup <percent> command that overrides it — but it forgets the setting
after a few seconds, and it has no idea how hot your drives actually are. This repo
reads drive temperatures on the host and re-sends an appropriate fan speed on a
keepalive loop.
Hardware you need
The MD1200 EMM exposes its debug console on the top-left RJ45 port of the left EMM (the one labelled for service use, not the SAS ports). It is not Ethernet — it is RS-232 behind an RJ45 jack, so you need the Dell service cable:
Dell Password Reset / Service Cable — part MN657, also sold as CT109.
Same cable is listed for the MD1000 / MD1220 / MD3000 / MD3200 family, so search on any
of those. It terminates in a DB9 serial connector; if your host has no physical serial
port, add any USB-to-DB9 adapter and point the script at the resulting /dev/ttyUSB*.
On the machine this was written for, the cable lands on an onboard UART at /dev/ttyS1.
Serial settings: 38400 8-N-1, no flow control.
Note: Dell's own KB articles list 115200 for MD3xxx/ME3xxx controllers. That is a different port on different hardware. The MD1200 EMM debug console is 38400, which is what this repo uses and what was verified against the real shelf.
Once connected you get a prompt like:
BlueDress.106.000 >
Debug commands
There is no help — the firmware answers unknown_cmd to help, _help and ?. The
two commands this project relies on:
| Command | Effect |
|---|---|
_shutup <0-100> |
Set fan speed to that percentage. Forgotten if not re-sent. |
_temp_rd |
Print the enclosure temperature sensors. |
_temp_rd output looks like:
BP_1[2] = 30c <- drive backplane
BP_2[3] = 31c <- drive backplane
SIM0[0] = 36c <- enclosure management module
EXP0[4] = 59c <- SAS expander chip, runs hot by design
AVG = 39c
Commands are terminated with a bare \r (not \n).
What this does
das_fanctl.sh runs as a systemd service and loops:
- Read the temperature of every drive in the shelf with
smartctl, take the hottest. - Interpolate a fan percentage between the low and high thresholds.
- Send
_shutup <percent>, and re-send it every 5s so the EMM doesn't revert. - Re-read temperatures every 30s.
Drive temperature, not enclosure air. The BP_* sensors read backplane intake air,
which sat at 30c while the drives themselves were 32–39c — it lags what you actually
care about. Drive temps come from smartctl -n standby, so a sleeping drive is skipped
rather than spun up just to be measured. If no drive answers (all in standby, smartctl
missing), it falls back to the hottest BP_* sensor with its own threshold pair.
EXP0 is deliberately ignored. The SAS expander idles around 59c by design; including
it in the curve pegs the fans permanently.
Usage
das_fanctl.sh # the service loop (default)
das_fanctl.sh once # one read + set, prints what it did
das_fanctl.sh disks # per-drive temperatures and the hottest
das_fanctl.sh temps # raw _temp_rd output from the shelf
das_fanctl.sh selftest # asserts the parser, the curve, and that drives are readable
Install
The script sources functions.sh by absolute path, so it expects to live in
/root/fan_speed/:
install -m 755 das_fanctl.sh functions.sh /root/fan_speed/
mkdir -p /root/fan_speed/log
install -m 644 fan_speed_das.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now fan_speed_das.service
Check it: das_fanctl.sh selftest, then tail -f /root/fan_speed/log/das_fan.log.
2026-08-24 23:26:03 || DAS disk:39c | Fan Speed:10
Tuning
All knobs are at the top of das_fanctl.sh:
| Variable | Default | Meaning |
|---|---|---|
PORT |
/dev/ttyS1 |
Serial device the service cable lands on |
CHECK_INTERVAL |
30 |
Seconds between temperature readings |
KEEPALIVE_INTERVAL |
5 |
How often to re-send _shutup |
DISK_LOW_TEMPERATURE_THRESHOLD |
44 |
At/below this, run at LOW_FAN_SPEED |
DISK_HIGH_TEMPERATURE_THRESHOLD |
56 |
At/above this, run at HIGH_FAN_SPEED |
FAN_SPEED_DEADBAND |
8 |
Hold the current speed until the request moves this far |
SETTLE_INTERVAL |
300 |
Allow a downward settle this often |
LOW_FAN_SPEED |
10 |
Percent |
HIGH_FAN_SPEED |
60 |
Percent |
BP_*_THRESHOLD |
30 / 45 |
Fallback curve, backplane air sensors |
Start the ramp above the drives' normal working range. This is the single most common way to make a shelf cycle audibly: if the ramp starts inside the range the drives already occupy, ordinary drift crosses it and the fans flip every cycle. The curve is ~4%/degC, so one degree moves the fan noticeably.
Worked example: the original 40–50c was sized for 8×2TB drives idling at 32–39c. Retiring those for 4×8TB + a 16TB moved the hottest drive to 40–41c — exactly the old ramp start — and the fan flipped 10↔15% every 30 seconds (18 samples at 10, 22 at 15). Moving the ramp to 44–56c put the drives back on the floor: 11 consecutive samples at 10%, and they still have 19c of headroom to their 60c limit.
FAN_SPEED_DEADBAND damps what remains, so once drives do climb into the ramp it does not
chase every degree. A settle is permitted every SETTLE_INTERVAL, downward only —
a symmetric deadband would catch the fan on the way up and never let it back down.
KEEPALIVE_INTERVAL is inherited from the 5s cadence the original setup used, not
measured. How fast the EMM actually reverts is unknown; if the fans audibly surge
between keepalives, lower it.
Notes
- Only one process may talk to the serial port. The script takes an exclusive
flockon/run/das_fanctl.lockand exits rather than interleaving garbage with another writer. DetachattachDAS.sh'sscreensession before starting the service. DISK_GLOBmust name the shelf's HBA on any host with more than one. The default*-sas-exp*-lun-0means "behind a SAS expander", which is not the same as "in the shelf": a server's own backplane is frequently behind an expander too. Oniz-pve0the internal drives sit onpci-0000:02:00.0and the shelf onpci-0000:04:00.0, so the default glob matched all 17 drives and would have set the shelf's fans from the server's internal drive temperatures. SetEnvironment=DISK_GLOB=/dev/disk/by-path/*04:00.0*-lun-0in the unit.- The server's own fan controller needs the mirror-image setting. Whichever host the
shelf is plugged into must set
DISK_EXCLUDE_PATTERNto that same HBA infan_speed.service, or the shelf's drives drive the server fans as well. Moving the shelf between hosts means moving both settings. PORTis per host too. Check which UART actually answers rather than assuming:grep -v uart:unknown /proc/tty/driver/seriallists the real ones, then send_temp_rdto each and watch for theBlueDressprompt.
Files
| File | |
|---|---|
das_fanctl.sh |
The fan controller |
fan_speed_das.service |
systemd unit for it |
functions.sh |
Only calculate_interpolated_fan_speed is used here — the rest is iDRAC/IPMI |
attachDAS.sh |
Drop into an interactive screen session on the shelf console |
get_das_temp.sh |
Superseded by das_fanctl.sh temps — it never read the reply back |
log_rotate.txt |
logrotate snippet for the log directory |
Credit
functions.sh comes from the
Dell iDRAC fan controller
project; this repo only uses its calculate_interpolated_fan_speed. Prior art on the MD1200 console specifically:
tonybaltovski/md1200-reduce-fans-systemd
and iamjoshgilman/md1200-fan-controller.