#!/usr/bin/python # MyRPi.ca script - ocs11@hotmail.com # 2018-10-01 # Python script that uses multiple sources to determine the CPU and enclosure temperature # and adjust the fan speed automatically # Load system libraries import RPi.GPIO as GPIO # Required to access the GPIO pin for PWM control. import time # Required for the loop delay. [time.sleep] import datetime # Required for date and time strings import sys # Required for CTRL-C clean exit. [sys.exit] import os # Required for secondary CPU temperature extraction. [os.open] import smbus # Required to read the sensor temperature # User Configuration Settings FAN_PIN = 12 # BCM pin used to drive the fan control. WAIT_TIME = 30 # Time, in seconds, to wait between each loop cycle. fanSpeedMin = 36 # Minimum fan speed. Must be greater than 0. Use the calibration utility to get this value. fanSpeedMax = 100 # Maximum fan speed. Set to limit noise otherwise leave at 100. Maximum 100. cpuTempMin = 45 # Temperature at which to start the fan. Minimum 1. cpuTempMax = 60 # Temperature at which fan should run at [fanSpeedMax] speed. Maximum 70 (for safety). diagCon = 0 # Set to [1] to enable diagnostic output to the console. [0] to diable. diagLog = 1 # Set to [1] to enable diagnostic output to a log file. [0] to diable. fanStatusLog = "/tmp/fanStatus" # Diagnostic Log file path and filename. # I2C Temperature LM75 sensor onboard Fan/power hat # i2cdetect -y 1 # 0 1 2 3 4 5 6 7 8 9 a b c d e f #00: -- -- -- -- -- -- -- -- -- -- -- -- -- #10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- <- 0x48 LM75 Temperature Sensor #50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- #60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- -- <- 0x68 DS3231 RTC Module #70: -- -- -- -- -- -- -- -- checkLM75 = 1 # Enable (1) or disable (0) the LM75 sensor check sensorAddress = 0x48 # I2C Address of the temperature sensor tempRegister = 0 # In progress I2Cbus = smbus.SMBus(1) # 0 or 1 depending on the RPi. 0=/dev/i2c-0 1=/dev/i2c-1 # End of typical user configuration. Changing the code below may result in unexpected behaviour. # Initialize runtime variables PWM_FREQ = 50 # [Hz] Do not change. See documentation. cpuTemp = 0 # CPU Temperatue cpuTempOld = 0 # CPU Temperature on the last cycle fanSpeed = 0 # Fan Speed fanSpeedOld = 0 # Fan Speed on the last cycle fanRunning = 1 # Boolean to store if the fan is running or not def regdata2float (regdata): return (regdata / 32.0) / 8.0 def getCPUtemp1(): if (diagCon): print "Trying to acquire CPU Temperature (Primary)." if (diagLog): fileLog.write ( "Trying to acquire CPU Temperature (Primary).\n" ) cpuTempFile = open("/sys/class/thermal/thermal_zone0/temp","r") # Open system file Read-Only cpuTemp1 = int(float(cpuTempFile.read())/1000) # Retrieve the system temperature value cpuTempFile.close() # Close the system file return cpuTemp1 def getCPUtemp2(): if (diagCon): print "Trying to acquire CPU Temperature (Secondary)." if (diagLog): fileLog.write ( "Trying to acquire CPU Temperature (Secondary).\n" ) cpuTemp2 = os.popen('vcgencmd measure_temp').readline() return float(cpuTemp2.replace("temp=","").replace("'C\n","")) def getSensorTemp(self): # Read the temperature from the I2C LM75 sensor if (diagCon): print "Trying to acquire LM75 Sensor Temperature." if (diagLog): fileLog.write ( "Trying to acquire LM75 Sensor Temperature.\n" ) raw = I2Cbus.read_word_data(sensorAddress, tempRegister) & 0xFFFF raw = ((raw << 8) & 0xFF00) + (raw >> 8) ret = regdata2float(raw) if (diagCon): print "LM75 Sensor Temperature is ", ret, "C" if (diagLog): fileLog.write ( "LM75 Sensor Temperature is " + str(ret) + " C\n" ) return ret # Verify the configuration values if ((cpuTempMin >= cpuTempMax) or (cpuTempMin < 1) or (cpuTempMax > 70)): print "Invalid cpuTempMin or cpuTempMax value has been configured. Please read the documentation. Aborting." exit(0) if ((fanSpeedMin >= fanSpeedMax) or (fanSpeedMin < 0) or (fanSpeedMax > 100)): print "Invalid fanSpeedMin or fanSpeedMax value has been configured. Please read the documentation. Aborting." exit(0) # Initialize the GPIO subsystem GPIO.setmode(GPIO.BCM) GPIO.setup(FAN_PIN, GPIO.OUT, initial=GPIO.LOW) GPIO.setwarnings(False) fan = GPIO.PWM(FAN_PIN,PWM_FREQ) fan.start(0); # Main runtime loop try: while (1): # Repeat until CTRL-C is pressed. # Reset variables at beginning of loop cpuTemp = 0 now = datetime.datetime.now() timeNow = now.strftime("%Y-%m-%d %H:%M:%S") if (diagCon): print timeNow if (diagLog): fileLog = open(fanStatusLog,"w") fileLog.write ( timeNow + "\n" ) # Try method 1 to acquire CPU Temperature cpuTemp = getCPUtemp1() # If method 1 failed, try method 2 to get CPU temperature if ((cpuTemp <= 0) or (cpuTemp > 120)): cpuTemp = getCPUtemp2() # Check to make sure we have a reasonable CPU temperature to work with if ((cpuTemp <= 0) or (cpuTemp > 120)): print "Unable to read the CPU Temperature [cpuTemp]. Incompatible system architecture or Linux version. Aborting." exit(0) if (diagCon): print "CPU Temperature: ", cpuTemp if (diagLog): fileLog.write ( "CPU Temperature: " + repr(cpuTemp) + "\n" ) # Calculate the appropriate fan speed based on the current CPU temperature if (cpuTemp > cpuTempMin): cpuTempPerc = (((cpuTemp - cpuTempMin) * 100) / (cpuTempMax - cpuTempMin)) fanSpeed = (((fanSpeedMax - fanSpeedMin) * cpuTempPerc ) / 100 ) + fanSpeedMin if (diagCon): print "Calculated cpuTempPerc:", cpuTempPerc, "% fanSpeed:", fanSpeed if (diagLog): fileLog.write ( "Calculated cpuTempPerc: " + str(cpuTempPerc) + "% fanSpeed: " + str(fanSpeed) + "\n" ) # Enforce maximum fan speed if ((fanSpeed > fanSpeedMax) or (cpuTemp >= cpuTempMax)): if (diagCon): print "Setting fanSpeed [", fanSpeed, "] -> fanSpeedMax [", fanSpeedMax, "] based on fanSpeedMax or cpuTempMax" if (diagLog): fileLog.write ( "Setting fanSpeed [" + str(fanSpeed) + "] -> fanSpeedMax [" + str(fanSpeedMax) + "] based on fanSpeedMax or cpuTempMax\n" ) fanSpeed = fanSpeedMax # If fanSpeed is at or below the minimum, stop the fan if (((fanSpeed < fanSpeedMin) or (cpuTemp <= cpuTempMin)) and (fanRunning != 0)): if (diagCon): print "Changing Duty Cycle [", fanSpeedOld, "] -> [Off] Cooling not required." if (diagLog): fileLog.write ( "Changing Duty Cycle [" + str(fanSpeedOld) + "] -> [Off] Cooling not required\n" ) fanSpeed = fanSpeedMin fanSpeedOld = fanSpeedMin fanRunning = 0 fan.ChangeDutyCycle(100) # If the calculated speed is different from the last cycle, set the fan speed if ((fanSpeed != fanSpeedOld)): if (diagCon): print "Changing Duty Cycle [", fanSpeedOld, "] -> [", fanSpeed, "]" if (diagLog): fileLog.write ( "Changing Duty Cycle [" + str(fanSpeedOld) + "] -> [" + str(fanSpeed) + "]\n" ) fanRunning = 1 fan.ChangeDutyCycle(100-fanSpeed) fanSpeedOld = fanSpeed if (checkLM75): getSensorTemp(I2Cbus) # Wait until the next loop cycle if (diagCon): print if (diagLog): fileLog.close() time.sleep(WAIT_TIME) # If a keyboard interrupt occurs (ctrl + c), set the GPIO to 0 and exit the program. except(KeyboardInterrupt): print "Fan speed control script interrupted by the user. CTRL-C was pressed." GPIO.cleanup() if not fileLog.closed: fileLog.close() sys.exit() # End of Script