#!/bin/bash # # Simple script implementing a temperature dependent fan speed control # # Version 0.67 # # Usage: fancontrol [CONFIGFILE] # # Dependencies: # bash, egrep, sed, cut, sleep, lm_sensors :) # # Please send any questions, comments or success stories to # marius.reiner@hdev.de # Thanks! # # For configuration instructions and warnings please see fancontrol.txt, which # can be found in the doc/ directory or at the website mentioned above. # # # Copyright 2003 Marius Reiner # Copyright (C) 2007-2008 Jean Delvare # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. # # PIDFILE="/var/run/fancontrol.pid" #DEBUG=1 MAX=255 if [ -f "$PIDFILE" ] then echo "File $PIDFILE exists, is fancontrol already running?" exit 1 fi echo $$ > "$PIDFILE" declare -i pwmval function LoadConfig { echo "Loading configuration from $1 ..." # grep configuration from file INTERVAL=`egrep '^INTERVAL=.*$' $1 | sed -e 's/INTERVAL=//g'` FCTEMPS=`egrep '^FCTEMPS=.*$' $1 | sed -e 's/FCTEMPS=//g'` MINTEMP=`egrep '^MINTEMP=.*$' $1 | sed -e 's/MINTEMP=//g'` MAXTEMP=`egrep '^MAXTEMP=.*$' $1 | sed -e 's/MAXTEMP=//g'` MINSTART=`egrep '^MINSTART=.*$' $1 | sed -e 's/MINSTART=//g'` MINSTOP=`egrep '^MINSTOP=.*$' $1 | sed -e 's/MINSTOP=//g'` # optional settings: FCFANS=`egrep '^FCFANS=.*$' $1 | sed -e 's/FCFANS=//g'` MINPWM=`egrep '^MINPWM=.*$' $1 | sed -e 's/MINPWM=//g'` MAXPWM=`egrep '^MAXPWM=.*$' $1 | sed -e 's/MAXPWM=//g'` # Check whether all mandatory settings are set if [[ -z ${INTERVAL} || -z ${FCTEMPS} || -z ${MINTEMP} || -z ${MAXTEMP} || -z ${MINSTART} || -z ${MINSTOP} ]] then echo "Some mandatory settings missing, please check your config file!" exit 1 fi if [ "$INTERVAL" -le 0 ] then echo "Error in configuration file:" echo "INTERVAL must be at least 1" exit 1 fi # write settings to arrays for easier use and print them echo echo "Common settings:" echo " INTERVAL=$INTERVAL" let fcvcount=0 for fcv in $FCTEMPS do if ! echo $fcv | egrep -q '=' then echo "Error in configuration file:" echo "FCTEMPS value is improperly formatted" exit 1 fi AFCPWM[$fcvcount]=`echo $fcv |cut -d'=' -f1` AFCTEMP[$fcvcount]=`echo $fcv |cut -d'=' -f2` AFCFAN[$fcvcount]=`echo $FCFANS |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` AFCMINTEMP[$fcvcount]=`echo $MINTEMP |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` AFCMAXTEMP[$fcvcount]=`echo $MAXTEMP |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` AFCMINSTART[$fcvcount]=`echo $MINSTART |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` AFCMINSTOP[$fcvcount]=`echo $MINSTOP |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` AFCMINPWM[$fcvcount]=`echo $MINPWM |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` [ -z "${AFCMINPWM[$fcvcount]}" ] && AFCMINPWM[$fcvcount]=0 AFCMAXPWM[$fcvcount]=`echo $MAXPWM |sed -e 's/ /\n/g' |egrep "${AFCPWM[$fcvcount]}" |cut -d'=' -f2` [ -z "${AFCMAXPWM[$fcvcount]}" ] && AFCMAXPWM[$fcvcount]=255 # verify the validity of the settings if [ "${AFCMINTEMP[$fcvcount]}" -ge "${AFCMAXTEMP[$fcvcount]}" ] then echo "Error in configuration file (${AFCPWM[$fcvcount]}):" echo "MINTEMP must be less than MAXTEMP" exit 1 fi if [ "${AFCMAXPWM[$fcvcount]}" -gt 255 ] then echo "Error in configuration file (${AFCPWM[$fcvcount]}):" echo "MAXPWM must be at most 255" exit 1 fi if [ "${AFCMINSTOP[$fcvcount]}" -ge "${AFCMAXPWM[$fcvcount]}" ] then echo "Error in configuration file (${AFCPWM[$fcvcount]}):" echo "MINSTOP must be less than MAXPWM" exit 1 fi if [ "${AFCMINSTOP[$fcvcount]}" -lt "${AFCMINPWM[$fcvcount]}" ] then echo "Error in configuration file (${AFCPWM[$fcvcount]}):" echo "MINSTOP must be greater than or equal to MINPWM" exit 1 fi if [ "${AFCMINPWM[$fcvcount]}" -lt 0 ] then echo "Error in configuration file (${AFCPWM[$fcvcount]}):" echo "MINPWM must be at least 0" exit 1 fi echo echo "Settings for ${AFCPWM[$fcvcount]}:" echo " Depends on ${AFCTEMP[$fcvcount]}" echo " Controls ${AFCFAN[$fcvcount]}" echo " MINTEMP=${AFCMINTEMP[$fcvcount]}" echo " MAXTEMP=${AFCMAXTEMP[$fcvcount]}" echo " MINSTART=${AFCMINSTART[$fcvcount]}" echo " MINSTOP=${AFCMINSTOP[$fcvcount]}" echo " MINPWM=${AFCMINPWM[$fcvcount]}" echo " MAXPWM=${AFCMAXPWM[$fcvcount]}" let fcvcount=fcvcount+1 done echo } if [ -f "$1" ] then LoadConfig $1 else LoadConfig /etc/fancontrol fi DIR=/proc/sys/dev/sensors if [ ! -d $DIR ] then # For Linux 2.6, detect if config file uses the hwmon class or not yet if echo "${AFCPWM[0]}" | egrep -q '^hwmon[0-9]' then SDIR=/sys/class/hwmon else SDIR=/sys/bus/i2c/devices fi if [ ! -d $SDIR ] then echo $0: 'No sensors found! (did you load the necessary modules?)' exit 1 else SYSFS=1 DIR=$SDIR fi fi cd $DIR # $1 = pwm file name function pwmdisable() { if [ -n "$SYSFS" ] then ENABLE=${1}_enable # No enable file? Just set to max if [ ! -f $ENABLE ] then echo $MAX > $1 return 0 fi # Try pwmN_enable=0 echo 0 > $ENABLE 2> /dev/null if [ `cat $ENABLE` -eq 0 ] then # Success return 0 fi # It didn't work, try pwmN_enable=1 pwmN=255 echo 1 > $ENABLE 2> /dev/null echo $MAX > $1 if [ `cat $ENABLE` -eq 1 -a `cat $1` -ge 190 ] then # Success return 0 fi # Nothing worked echo "$ENABLE stuck to" `cat $ENABLE` >&2 return 1 else echo $MAX 0 > $1 fi } # $1 = pwm file name function pwmenable() { if [ "$SYSFS" = "1" ] then ENABLE=${1}_enable if [ -f $ENABLE ] then echo 1 > $ENABLE 2> /dev/null if [ $? -ne 0 ] then return 1 fi fi echo $MAX > $1 else echo $MAX 1 > $1 fi } function restorefans() { local status=$1 echo 'Aborting, restoring fans...' let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )) # go through all pwm outputs do pwmo=${AFCPWM[$fcvcount]} pwmdisable $pwmo let fcvcount=$fcvcount+1 done echo 'Verify fans have returned to full speed' rm -f "$PIDFILE" exit $status } trap 'restorefans 0' SIGQUIT SIGTERM trap 'restorefans 1' SIGHUP SIGINT # main function function UpdateFanSpeeds { let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )) # go through all pwm outputs do #hopefully shorter vars will improve readability: pwmo=${AFCPWM[$fcvcount]} tsens=${AFCTEMP[$fcvcount]} fan=${AFCFAN[$fcvcount]} mint=${AFCMINTEMP[$fcvcount]} maxt=${AFCMAXTEMP[$fcvcount]} minsa=${AFCMINSTART[$fcvcount]} minso=${AFCMINSTOP[$fcvcount]} minpwm=${AFCMINPWM[$fcvcount]} maxpwm=${AFCMAXPWM[$fcvcount]} read tval < ${tsens} if [ $? -ne 0 ] then echo "Error reading temperature from $DIR/$tsens" restorefans 1 fi if [ "$SYSFS" = "1" ] then let tval="($tval+500)/1000" else tval=`echo ${tval} | cut -d' ' -f3 | cut -d'.' -f1` fi read pwmpval < ${pwmo} if [ $? -ne 0 ] then echo "Error reading PWM value from $DIR/$pwmo" restorefans 1 fi if [ "$SYSFS" != "1" ] then pwmpval=`echo ${pwmpval} | cut -d' ' -f1` fi # If fanspeed-sensor output shall be used, do it if [[ -n ${fan} ]] then read fanval < ${fan} if [ $? -ne 0 ] then echo "Error reading Fan value from $DIR/$fan" restorefans 1 fi if [ "$SYSFS" != "1" ] then fanval=`echo ${fanval} | cut -d' ' -f2` fi else fanval=1 # set it to a non zero value, so the rest of the script still works fi # debug info if [ "$DEBUG" != "" ] then echo "pwmo=$pwmo" echo "tsens=$tsens" echo "fan=$fan" echo "mint=$mint" echo "maxt=$maxt" echo "minsa=$minsa" echo "minso=$minso" echo "minpwm=$minpwm" echo "maxpwm=$maxpwm" echo "tval=$tval" echo "pwmpval=$pwmpval" echo "fanval=$fanval" fi if (( $tval <= $mint )) then pwmval=$minpwm # below min temp, use defined min pwm elif (( $tval >= $maxt )) then pwmval=$maxpwm # over max temp, use defined max pwm else # calculate the new value from temperature and settings pwmval="(${tval}-${mint})*(${maxpwm}-${minso})/(${maxt}-${mint})+${minso}" if [ $pwmpval -eq 0 -o $fanval -eq 0 ] then # if fan was stopped start it using a safe value echo $minsa > $pwmo # Sleep while still handling signals sleep 1 & wait $! fi fi echo $pwmval > $pwmo # write new value to pwm output if [ $? -ne 0 ] then echo "Error writing PWM value to $DIR/$pwmo" restorefans 1 fi if [ "$DEBUG" != "" ] then echo "new pwmval=$pwmval" fi let fcvcount=$fcvcount+1 done } echo 'Enabling PWM on fans...' let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )) # go through all pwm outputs do pwmo=${AFCPWM[$fcvcount]} pwmenable $pwmo if [ $? -ne 0 ] then echo "Error enabling PWM on $DIR/$pwmo" restorefans 1 fi let fcvcount=$fcvcount+1 done echo 'Starting automatic fan control...' # main loop calling the main function at specified intervals while true do UpdateFanSpeeds # Sleep while still handling signals sleep $INTERVAL & wait $! done