Skip to content

Instantly share code, notes, and snippets.

@jeremy-wolfe
Last active October 17, 2019 19:22
Show Gist options
  • Save jeremy-wolfe/4c5ba83a455f277b86d36f7639a32eb9 to your computer and use it in GitHub Desktop.
Save jeremy-wolfe/4c5ba83a455f277b86d36f7639a32eb9 to your computer and use it in GitHub Desktop.
Automatic low-speed fan control for Dell PowerEdge systems via IPMI
#!/usr/bin/env node
/*
Automatic low-speed fan control for Dell PowerEdge systems via IPMI
https://gist.github.com/CaptainVascular/4c5ba83a455f277b86d36f7639a32eb9
MIT License
Tested on R710 only
Requires nodejs, lm_sensors, ipmitool, freeipmi
*/
// Manual fan speed %
const manualSpeed = 5;
// CPU temp max threshold %
// Go to auto fan speed if current temp of any core is this % of the sensor "max" as output by the "sensors" command
const cpuThresh = 50;
// Ambient max temp in degrees C
// Go to auto fan speed if current ambient temp is >= this temp
const ambientMax = 35;
// Sensor ID number for ambient temperature, from output of ipmi-sensors
const ambientId = 5;
// Poll time in seconds
const poll = 10;
// ===================================================================
const {execSync} = require('child_process');
let isAuto = true;
let manualSpeedHex = manualSpeed.toString(16);
if (manualSpeedHex.length < 2) manualSpeedHex = '0' + manualSpeedHex;
const cpuThreshDec = cpuThresh / 100;
function setAuto(auto, message) {
if (!auto) auto = false;
if (isAuto === auto) return;
isAuto = auto;
if (message) console.log(message);
console.log(`Setting speed to ${auto ? 'auto' : manualSpeed + '%'}`);
execSync('ipmitool raw 0x30 0x30 0x01 0x0' + (auto ? '1' : '0'));
if (!auto) execSync('ipmitool raw 0x30 0x30 0x02 0xff 0x' + manualSpeedHex);
}
function T(degrees) {
return `${degrees}°C / ${Math.round(degrees * 1.8 + 32)}°F`;
}
process.on('exit', () => {
setAuto(true, 'Exiting');
});
process.on('SIGHUP', process.exit);
process.on('SIGTERM', process.exit);
process.on('SIGINT', process.exit);
process.on('SIGUSR1', process.exit);
process.on('SIGUSR2', process.exit);
process.on('uncaughtException', process.exit);
setInterval(() => {
const ambientRaw = execSync(`ipmi-sensors -r ${ambientId} --no-header-output`);
const ambientParts = ambientRaw && ambientRaw.toString().split('|').map((part) => part.trim());
const ambient = ambientParts && ambientParts[3] && parseInt(ambientParts[3]);
if (!ambient) return setAuto(true, 'Could not read ambient temp');
if (ambient >= ambientMax) return setAuto(true, `Ambient Current: ${T(ambient)}, Max: ${T(ambientMax)}`)
const readingsRaw = execSync('sensors -j');
const readings = readingsRaw && JSON.parse(readingsRaw.toString());
for (const dev in readings) {
for (const core in readings[dev]) {
if (!/^Core/.test(core)) continue;
const tempsRaw = readings[dev][core];
const temps = {};
for (const temp in tempsRaw) temps[temp.replace(/^temp\d*_/, '')] = tempsRaw[temp];
if (!temps.max) return setAuto(true, `Could not read ${core} max temp`);
const max = Math.round(temps.max * cpuThreshDec);
if (temps.input >= max) return setAuto(true, `${core} Current: ${T(temps.input)}, Max: ${T(temps.max)}, Thresh: ${T(max)} (${cpuThresh}%)`);
}
}
setAuto(false, 'No temperatures above thresholds');
}, poll * 1000);
[Unit]
Description=Dell PowerEdge fan control
[Service]
ExecStart=/usr/local/bin/ipmi-dell-fans
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment