Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 70 additions & 29 deletions poweredge_fan/poweredge_fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
import time
from subprocess import TimeoutExpired

from hardware import IPMIControl, get_all_sensors
from poweredge_fan.hardware import IPMIControl, get_all_sensors

arg_parser = argparse.ArgumentParser(description='PowerEdge Fan Controller')
arg_parser.add_argument('-H', '--host', type=str, help='IP address of the iDRAC')
arg_parser.add_argument('-U', '--username', type=str, help='Username for the iDRAC')
arg_parser.add_argument('-P', '--password', type=str, help='Password for the iDRAC')
arg_parser.add_argument('-H', '--host', type=str, help='IP address of the iDRAC', required=True)
arg_parser.add_argument('-U', '--username', type=str, help='Username for the iDRAC', required=True)
arg_parser.add_argument('-P', '--password', type=str, help='Password for the iDRAC', required=True)
arg_parser.add_argument('-I', '--high', type=float, help='Highest fan output', required=True)
arg_parser.add_argument('-L', '--low', type=float, help='Lowest fan output', required=True)
arg_parser.add_argument('-T', '--target', type=float, help='Target temperature', required=True)

arg_parser.add_argument('-T', '--target', type=float, help='Target temperature for the fan controller', required=True)

ipmi: IPMIControl

Expand All @@ -21,14 +20,13 @@ def __init__(self, Kp, Ki, Kd, setpoint, high, low):
self.Ki = Ki
self.Kd = Kd
self.setpoint = setpoint
self.high = high
self.low = low

self.integral = -46
self.last_error = 0
self.last_time = time.time()

self.high = high
self.low = low

def update(self, current_temperature):
# Calculate the error and time difference
error = self.setpoint - current_temperature
Expand All @@ -50,23 +48,48 @@ def update(self, current_temperature):
self.last_error = error
self.last_time = current_time

# Return the PID output for fan speed adjustment
return proportional + self.integral + derivative
# Compute the PID output
pid_output = proportional + self.integral + derivative

# Constrain PID output to fan speed range
return max(min(pid_output, self.high), self.low)


def get_cpu_temperature():
temp_dict = ipmi.get_temperatures()
return max(temp_dict["cpu_temps"])


# Global variable for last fan speed
last_fan_speed = None

def set_fan_speed(speed):
global last_fan_speed
# keep speed within 0-100:
speed = -speed
speed = max(min(speed, 100), 5)

# Smooth transition to the target speed
if last_fan_speed is None:
last_fan_speed = speed

step = 5 # Max RPM increment per adjustment
if abs(speed - last_fan_speed) > step:
speed = last_fan_speed + step if speed > last_fan_speed else last_fan_speed - step

# Update the fan speed and the last_fan_speed
ipmi.set_fan_speed(int(speed))
last_fan_speed = speed
pass

last_sensor_values = []
last_sensor_values_count = 20
spike_tolerance = 20
tolerance_increment = 5
tolerance_reset_time = 30
last_spike_time = None

def loop(args):
def get_cpu_temperature():
temp_dict = ipmi.get_temperatures()
return max(temp_dict["cpu_temps"])

def set_fan_speed(speed):
# keep speed within 0-100:
speed = -speed
speed = max(min(speed, args.high), args.low)
ipmi.set_fan_speed(int(speed))
pass
# PID parameters and target temperature
Kp = 3
Ki = 0.3
Expand All @@ -78,16 +101,38 @@ def set_fan_speed(speed):
# Main loop to update the fan speed based on PID output
while True:
try:
global spike_tolerance, last_spike_time
sensor_values = get_all_sensors()
avg_sensor_value = sum(sensor_values) / len(sensor_values)
if max(sensor_values) - avg_sensor_value > 20:
current_time = time.time()

# Check for inconsistencies
if max(sensor_values) - avg_sensor_value > spike_tolerance:
print("Sensor values are not consistent, skipping update")

# Adjust the spike tolerance incrementally
if last_spike_time and current_time - last_spike_time < 10:
spike_tolerance += tolerance_increment
print(f"Increasing spike tolerance to {spike_tolerance}")
else:
spike_tolerance = 20 # Reset tolerance
print("Resetting spike tolerance to 20")

last_spike_time = current_time

ipmi.set_fan_automatic()
time.sleep(1)
continue

# Reset spike_tolerance after the reset time
if last_spike_time and current_time - last_spike_time > tolerance_reset_time:
spike_tolerance = 20

last_sensor_values.append(avg_sensor_value)
if len(last_sensor_values) > last_sensor_values_count:
last_sensor_values.pop(0)

# Smooth current temperature using moving average
current_temperature = sum(last_sensor_values) / len(last_sensor_values)
pid_output = pid_controller.update(current_temperature)
print(f"CPU temperature: {current_temperature:.2f} PID output: {pid_output:.2f}")
Expand All @@ -96,7 +141,7 @@ def set_fan_speed(speed):
# Adjust the fan speed using the PID output
set_fan_speed(pid_output)

# # Sleep for some time to avoid excessive adjustments
# Sleep for some time to avoid excessive adjustments
time.sleep(0.1)
except TimeoutExpired as e:
print("Timeout expired, skipping update")
Expand All @@ -107,15 +152,11 @@ def set_fan_speed(speed):
def main():
global ipmi
args = arg_parser.parse_args()
ipmi = IPMIControl(
None if args.host == "" else args.host,
None if args.username == "" else args.username,
None if args.password =="" else args.password
)
ipmi = IPMIControl(args.host, args.username, args.password)
try:
loop(args)
finally:
ipmi.set_fan_automatic()

if __name__ == "__main__":
main()
main()