Last active
October 30, 2023 15:23
-
-
Save jpsutton/c8bf024f89606f4840f365b24971af33 to your computer and use it in GitHub Desktop.
Simple fan controller in Python using Linux hwmon sensors and liquidctl
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/bin/env python | |
import sys | |
import json | |
import time | |
import subprocess | |
import statistics | |
""" | |
{ "sensor_low": 35, "sensor_high": 40, "fan_level": 30 }, | |
{ "sensor_low": 39, "sensor_high": 43, "fan_level": 40 }, | |
{ "sensor_low": 42, "sensor_high": 47, "fan_level": 50 }, | |
{ "sensor_low": 46, "sensor_high": 50, "fan_level": 80 }, | |
{ "sensor_low": 49, "sensor_high": 52, "fan_level": 100 }, | |
""" | |
CHIP = "it8686-isa-0a60" | |
SENSOR = "EC_TEMP" | |
THRESHOLDS = [ | |
{ "sensor_low": 35, "sensor_high": 40, "fan_level": 30 }, | |
{ "sensor_low": 39, "sensor_high": 43, "fan_level": 75 }, | |
{ "sensor_low": 42, "sensor_high": 52, "fan_level": 100 }, | |
] | |
HISTORY = list() | |
ROLLING_COUNT=10 | |
FAN_LEVEL = THRESHOLDS[0]["fan_level"] | |
SAMPLING_FREQUENCY = 5 | |
def get_sensor_data(): | |
p = subprocess.run(['sensors', '-j', CHIP], stdout=subprocess.PIPE) | |
return json.loads(p.stdout) | |
def set_fan_level(level): | |
print(f"Set fan level to {level}") | |
subprocess.run(['liquidctl', 'set', 'fans', 'speed', str(level)]) | |
def get_sensor_sample(): | |
sensor_data = get_sensor_data()[CHIP][SENSOR] | |
for key, value in sensor_data.items(): | |
if key.endswith("_input"): | |
return value | |
raise RuntimeError(f"Unable to locate sensor data value for sensor {SENSOR} on chip {CHIP}") | |
set_fan_level(THRESHOLDS[0]["fan_level"]) | |
while True: | |
# Flush stdout stream for more responsive logging | |
sys.stdout.flush() | |
# Sleep X seconds between samplings | |
time.sleep(SAMPLING_FREQUENCY) | |
# Read the current value and record it | |
sensor_value = get_sensor_sample() | |
HISTORY.append(sensor_value) | |
#print(HISTORY) | |
# Make sure we keep only as many data points as we care to average | |
if len(HISTORY) > ROLLING_COUNT: | |
HISTORY = HISTORY[1:] | |
else: | |
continue | |
# Calculate the rolling average | |
rolling_average = statistics.mean(HISTORY) | |
print(f"Fan level: {FAN_LEVEL}") | |
print(f"Sensor rolling avg: {rolling_average}") | |
decision = None | |
# Decide if we need to change the fan level | |
for threshold in THRESHOLDS: | |
if decision == 1: | |
FAN_LEVEL = threshold["fan_level"] | |
set_fan_level(FAN_LEVEL) | |
break | |
if FAN_LEVEL > threshold["fan_level"] and rolling_average < threshold["sensor_low"]: | |
FAN_LEVEL = threshold["fan_level"] | |
set_fan_level(FAN_LEVEL) | |
break | |
elif FAN_LEVEL == threshold["fan_level"] and rolling_average > threshold["sensor_high"]: | |
decision = 1 | |
continue | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wrote this for my own setup. The pump and 4 fans are plugged into a Corsair Commander Core XT, which liquidctl can support on Linux. I have a water temperature sensor at the outlet of the pump. That is the sensor being measured to make fan/pump speed decisions.
If it's useful to someone else, cool. Otherwise, this is just out here to be archived in case I reinstall and forget to copy it off my system.