Last active
January 28, 2020 11:10
-
-
Save rcx/8d3840d8c1721864a52e2c6481b1c778 to your computer and use it in GitHub Desktop.
Dell Poweredge manual fan controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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