Skip to content

Instantly share code, notes, and snippets.

@rcx
Last active January 28, 2020 11:10
Show Gist options
  • Save rcx/8d3840d8c1721864a52e2c6481b1c778 to your computer and use it in GitHub Desktop.
Save rcx/8d3840d8c1721864a52e2c6481b1c778 to your computer and use it in GitHub Desktop.
Dell Poweredge manual fan controller
#!/usr/local/bin/python
import subprocess, os, time, collections, re
goal_temp = 40.0
k_p = 5
k_i = 0.1
k_d = 2
max_fan = 100
min_fan = 10 # keep the fans at x% at least so the server remains cool when idle.
# todo: kalman filtering using cpu load
def get_temp():
# average all core temps
temps = list(map(lambda kv: float(kv[1].strip().rstrip('C')), filter(lambda kv: re.match('dev\\.cpu\\.\\d+\\.temperature', kv[0]), map(lambda l: l.split(': '), subprocess.check_output(['sysctl', 'dev.cpu']).decode('utf-8').split('\n')))))
temp = sum(temps)/len(temps)
# exponential moving average filter (r=0.5)
get_temp.moving_average = (1-0.5)*temp + 0.5*get_temp.moving_average
return get_temp.moving_average
get_temp.moving_average = goal_temp
# wow you think your fans make you SO COOL huh???
devnull = open(os.devnull, 'w')
#sliding_window = collections.deque()
def assert_fan(pct):
pct = max(min(pct, 100), 0) # clamp 0-100%
# if len(sliding_window) >= 5:
# sliding_window.popleft()
# sliding_window.append(pct)
# pct = sum(sliding_window) / len(sliding_window)
# expontential moving average (r=0.7)
assert_fan.moving_average = (1-0.7)*pct + 0.7*assert_fan.moving_average
pct = assert_fan.moving_average
pct = int(max(min(round(pct), max_fan), min_fan)) # clamp output
print('-> %d%%' % (pct,))
subprocess.call(['ipmitool', 'raw', '0x30', '0x30', '0x02', '0xff', '0x%02x' % pct], stdout=devnull, stderr=devnull)
assert_fan.moving_average = 0
integral = 0
prev_diff = 0
while True:
t = get_temp()
diff = t - goal_temp
integral = min(max(integral + diff, min_fan/k_i), max_fan/k_i) # anti-windup. doesn't make sense having steady-state fan below min_fan% or above max_fan%.
derivative = diff - prev_diff
p = k_p * max(diff, 0) # our goal actually is goal_temp or below. don't react if temp is below goal.
i = k_i * integral
d = k_d * derivative
yeet = p+i+d
print('%.01fC (%.01f %.01f %.01f) %.01f%%' % (t,p,i,d,yeet), end=' ')
assert_fan(yeet)
prev_diff = diff
time.sleep(0.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment