Skip to content

Instantly share code, notes, and snippets.

@Allesanddro
Created February 28, 2025 17:31
Show Gist options
  • Save Allesanddro/417063843939695719e4f46f19d2c5b4 to your computer and use it in GitHub Desktop.
Save Allesanddro/417063843939695719e4f46f19d2c5b4 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import subprocess
import re
import time
import logging
FAN_CURVE = {
25: 0,
28: 3,
30: 5,
32: 7,
34: 9,
36: 11,
38: 13,
40: 15,
42: 20,
44: 25,
46: 30,
48: 35,
50: 40,
52: 45,
54: 50,
56: 55,
58: 60,
60: 65,
62: 70,
64: 75,
66: 78,
68: 80,
70: 80,
75: 80,
}
FAN_SPEED_FAILSAFE = 50
IPMI_USER = "root"
HOST_IP = "192.168.178.220"
IPMI_PASSWORD = "YourPassWord"
IPMITOOL_PATH = "/usr/bin/ipmitool"
SLEEP_INTERVAL = 10
LOG_FILE = "/var/log/fanspeed.log"
logging.basicConfig(filename=LOG_FILE, level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
def get_ipmitool_base_command_list():
return [IPMITOOL_PATH, "-I", "lanplus", "-H", HOST_IP, "-U", IPMI_USER, "-P", IPMI_PASSWORD]
def set_fan_speed_python(ipmitool_base_command, fan_percentage):
hex_value = "{:02X}".format(int(fan_percentage * 255 / 100))
ipmitool_corrected_fan_set_command_list = ipmitool_base_command + ["raw", "0x30", "0x30", "0x02", "0xff", f"0x{hex_value}"]
try:
subprocess.run(ipmitool_corrected_fan_set_command_list, check=True, capture_output=True)
output_message = f"Fan speed set to {fan_percentage}% (Python) - Remote Host {HOST_IP} - Corrected Command - HEX: 0x{hex_value}"
echo_output = subprocess.run(['echo', output_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.info(f"Fan speed set to {fan_percentage}% - HEX: 0x{hex_value}")
return True
except subprocess.CalledProcessError as e:
error_message = f"ERROR (Python): Failed to set fan speed to {fan_percentage}% (Python) - Remote Host {HOST_IP} - Corrected Command. Error: {e}"
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return False
except FileNotFoundError:
error_message = "ERROR (Python): ipmitool executable not found."
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return False
def get_cpu_temp_python(ipmitool_base_command):
try:
command_list = ipmitool_base_command + ["sdr", "get", "Temp"]
process = subprocess.run(command_list, capture_output=True, text=True, check=True)
raw_temp_detail = process.stdout
except subprocess.CalledProcessError as e:
error_message = f"ERROR: Could not get CPU Temperature from ipmitool (sdr get Temp) - Remote Host {HOST_IP}"
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return None
except FileNotFoundError:
error_message = "ERROR (Python): ipmitool executable not found."
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return None
temp_reading_line = ""
for line in raw_temp_detail.splitlines():
if "Sensor Reading" in line:
temp_reading_line = line.strip()
break
if not temp_reading_line:
error_message = f"ERROR: Failed to parse temperature from ipmitool output (sdr get Temp) - 'Sensor Reading' line missing - Remote Host {HOST_IP}"
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return None
match = re.search(r":\s*(\d+)", temp_reading_line)
if match:
temp_value_str = match.group(1)
return temp_value_str
else:
error_message = f"ERROR: Failed to parse numeric temperature value from ipmitool output (sdr get Temp) - Regex failed - Remote Host {HOST_IP}"
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
return None
def calculate_fan_speed_curve(temp_celsius, fan_curve):
temp_points = sorted(fan_curve.keys())
if temp_celsius <= temp_points[0]:
return fan_curve[temp_points[0]]
if temp_celsius >= temp_points[-1]:
return fan_curve[temp_points[-1]]
for i in range(len(temp_points) - 1):
temp_lower = temp_points[i]
temp_upper = temp_points[i+1]
if temp_lower <= temp_celsius <= temp_upper:
speed_lower = fan_curve[temp_lower]
speed_upper = fan_curve[temp_upper]
fan_speed = speed_lower + (speed_upper - speed_lower) * (temp_celsius - temp_lower) / (temp_upper - temp_lower)
return max(min(int(fan_speed), 100), 0)
def set_failsafe_fan_python(ipmitool_base_command, failsafe_fan_speed):
failsafe_message = f"Setting failsafe fan speed: {failsafe_fan_speed}% (Python)"
echo_output = subprocess.run(['echo', failsafe_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.warning(failsafe_message)
return set_fan_speed_python(ipmitool_base_command, failsafe_fan_speed)
if __name__ == "__main__":
ipmitool_base_command_list = get_ipmitool_base_command_list()
while True:
cpu_temp_str = get_cpu_temp_python(ipmitool_base_command_list)
if cpu_temp_str is None:
set_failsafe_fan_python(ipmitool_base_command_list, FAN_SPEED_FAILSAFE)
continue
try:
cpu_temp = int(cpu_temp_str)
except ValueError:
error_message = f"ERROR (Python): Could not convert temperature value '{cpu_temp_str}' to integer."
echo_output = subprocess.run(['echo', error_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.error(error_message)
set_failsafe_fan_python(ipmitool_base_command_list, FAN_SPEED_FAILSAFE)
continue
target_fan_speed = calculate_fan_speed_curve(cpu_temp, FAN_CURVE)
output_message = f"Current CPU Temp (Python): {cpu_temp}°C, Target Fan Speed: {target_fan_speed}%"
echo_output = subprocess.run(['echo', output_message], capture_output=True, text=True)
print(echo_output.stdout.strip())
logging.info(f"CPU Temp: {cpu_temp}°C, Target Fan Speed: {target_fan_speed}%")
if not set_fan_speed_python(ipmitool_base_command_list, target_fan_speed):
set_failsafe_fan_python(ipmitool_base_command_list, FAN_SPEED_FAILSAFE)
time.sleep(SLEEP_INTERVAL)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment