#!/bin/bash # # Script implementing a temperature dependent fan speed control. # # Usage: fancontrol [CONFIGFILE] # # Version 1.0 # # Dependencies: # * awk (for the math expression evaluation) # * lm_sensors :) # * A correctly configured config file, default /etc/fancontrol, see pwmconfig # for creating an initial version. # # For configuration instructions and warnings please see fancontrol.txt, which # can be found in the lm_sensors package. # # Copyright 2003 Marius Reiner # http://www.hdev.de/fancontrol/ up to version 0.63 # # Copyright 2006 Volker Kuhlmann # http://volker.dnsalias.net/ from version 1.0 # # Changelog: # 1.0, 24 Jan 2006: # * Tidied up script, replacing sub-optimal shell constructs. # * Added hysteresis when turning fan on/off, preventing the fan from going # between off and minimum every $INTERVAL seconds. # This change is upwards compatible; the script's behaviour is unchanged # with older config files. # TODO: add hysteresis handling to pwmconfig # * Fixed the sleep constructs to be usable with system service scripts. # * Rudimentary provision for multiple (different) control curves # 0.63, 2003: # * by Marius Reiner # # 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., 675 Mass Ave, Cambridge, MA 02139, USA. # #DEBUG=1 MAX=255 PIDFILE=/var/run/fancontrol.pid SENSDIR=/proc/sys/dev/sensors SSENSDIR=/sys/bus/i2c/devices # $1: config file to load function LoadConfig { echo "Loading configuration from $1 ..." while read -r; do case "$REPLY" in INTERVAL=*) INTERVAL="${REPLY#*=}";; FCTEMPS=*) FCTEMPS="${REPLY#*=}";; MINTEMP=*) MINTEMP="${REPLY#*=}";; MAXTEMP=*) MAXTEMP="${REPLY#*=}";; MINSTART=*) MINSTART="${REPLY#*=}";; MINSTOP=*) MINSTOP="${REPLY#*=}";; # optional settings: FCFANS=*) FCFANS="${REPLY#*=}";; THRESHTEMP=*) THRTEMP="${REPLY#*=}";; esac done < "$1" # 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 # here the other settings should be verified # write settings to arrays for easier use and print them getpwmval() { local pwm for pwm in $1; do case "$pwm" in *$2=*) echo "${pwm#*=}"; break;; esac done } echo echo "Common settings:" echo " INTERVAL=$INTERVAL" let fcvcount=0 for fcv in $FCTEMPS; do AFCPWM[$fcvcount]="${fcv%%=*}" # field 1 AFCTEMP[$fcvcount]="${fcv#*=}" # field 2 AFCFAN[$fcvcount]=`getpwmval "$FCFANS" "${AFCPWM[$fcvcount]}"` AFCMINTEMP[$fcvcount]=`getpwmval "$MINTEMP" "${AFCPWM[$fcvcount]}"` AFCMAXTEMP[$fcvcount]=`getpwmval "$MAXTEMP" "${AFCPWM[$fcvcount]}"` AFCTHRTEMP[$fcvcount]=`getpwmval "$THRTEMP" "${AFCPWM[$fcvcount]}"` AFCMINSTART[$fcvcount]=`getpwmval "$MINSTART" "${AFCPWM[$fcvcount]}"` AFCMINSTOP[$fcvcount]=`getpwmval "$MINSTOP" "${AFCPWM[$fcvcount]}"` # Default threshold: test -z "${AFCTHRTEMP[$fcvcount]}" && AFCTHRTEMP[$fcvcount]=0 echo echo "Settings for ${AFCPWM[$fcvcount]}:" echo " Depends on ${AFCTEMP[$fcvcount]}" echo " Controls ${AFCFAN[$fcvcount]}" echo " MINTEMP=${AFCMINTEMP[$fcvcount]}" echo " MAXTEMP=${AFCMAXTEMP[$fcvcount]}" echo " THRTEMP=${AFCTHRTEMP[$fcvcount]}" echo " MINSTART=${AFCMINSTART[$fcvcount]}" echo " MINSTOP=${AFCMINSTOP[$fcvcount]}" let fcvcount=fcvcount+1 done echo } # $1 = full pwm file name function pwmdisable() { if [ "$SYSFS" = "1" ]; then echo $MAX > $1 ENABLE=${1}_enable if [ -f $ENABLE ]; then echo 0 > $ENABLE fi else echo $MAX 0 > $1 fi } # $1 = full pwm file name function pwmenable() { if [ "$SYSFS" = "1" ]; then ENABLE=${1}_enable if [ -f $ENABLE ]; then echo 1 > $ENABLE fi else echo $MAX 1 > $1 fi } # Set fans to maximum and exit. function restorefans() { echo "Aborting, restoring fans..." let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )); do # go through all pwm outputs pwmo=${AFCPWM[$fcvcount]} pwmdisable $SENSDIR/$pwmo let fcvcount=$fcvcount+1 done echo 'Verify fans have returned to full speed' rm "$PIDFILE" exit 0 } # Function evaluating the mathematical expression. function calc () { awk "BEGIN { print $@ }" # Alternatives: bc, or bc -l } # Calculate the new value from temperature and settings; quadratic function. # Sets variables pwmval and func! calc_quadratic() { func="((${tval}-${mint})/(${maxt}-${mint}))^2*(255-${minso})+${minso}" pwmval=`calc "$func"` pwmval="${pwmval%%.*}" } # Calculate the new value from temperature and settings; "quad" function? # Sets variables pwmval and func! calc_quad() { func="((10/(${maxt}-${mint})*(${temp}-${mint}))^2/1000*(${maxt}-${mint})*(255-${minspeed})+${minspeed})" pwmval=`calc "$func"` pwmval="${pwmval%%.*}" } # Calculate the new value from temperature and settings; exponential function. # Sets variables pwmval and func! calc_exponential() { func="..." pwmval=`calc "$func"` pwmval="${pwmval%%.*}" } # main function function UpdateFanSpeeds { local filler filler2 let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )); do # go through all pwm outputs #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]} thresht=${AFCTHRTEMP[$fcvcount]} read tval < $SENSDIR/${tsens} || { echo "Error reading temperature from $SENSDIR/$tsens" restorefans } if [ "$SYSFS" = "1" ]; then let tval="$tval / 1000" else tval=`echo ${tval} |cut -d' ' -f3 |cut -d'.' -f1` fi read pwmpval filler < $SENSDIR/${pwmo} || { echo "Error reading PWM value from $SENSDIR/$pwmo" restorefans } # If fanspeed-sensor output shall be used, do it if [[ -n ${fan} ]]; then read filler fanval filler2 < $SENSDIR/${fan} || { echo "Error reading Fan value from $SENSDIR/$fan" restorefans } test "$fanval" || fanval=$filler # field 2, or first field else fanval=1 # set it to a non zero value, so the rest of the script still works fi # debug info if [ -n "$DEBUG" ]; then echo "" echo "pwmo=$pwmo" echo "tsens=$tsens" echo "fan=$fan" echo "mint=$mint" echo "maxt=$maxt" echo "thresht=$thresht" echo "minsa=$minsa" echo "minso=$minso" echo "tval=$tval" echo "pwmpval=$pwmpval" echo "fanval=$fanval" fi if (( $tval <= $mint )) then pwmval=0 # at specified mintemp shut fan off elif (( $tval >= $maxt )) then pwmval=255 # at specified maxtemp switch to 100% else # Calculate the new value from temperature and settings. # (Sets variables pwmval and func.) calc_quadratic # If fan was stopped, start it using a safe value, # but only start it if temp is at least the threshold over minimum. if [ $pwmpval -eq 0 -o $fanval -eq 0 ]; then if (( $tval >= (( $mint + $thresht )) )); then if [ -n "$DEBUG" ]; then echo "starting fan with pwmval=$minsa" fi echo $minsa > $SENSDIR/$pwmo # ignore possible error #sleep 1 # not killable read < <(exec sleep 1) else pwmval=0 fi fi fi # write new value to pwm output echo $pwmval > $SENSDIR/$pwmo || { echo "Error writing PWM value to $SENSDIR/$pwmo" restorefans } if [ -n "$DEBUG" ]; then echo "func=$func" echo "new pwmval=$pwmval" fi let fcvcount=$fcvcount+1 done } ## MAIN LoadConfig "${1-/etc/fancontrol}" if [ -e "$PIDFILE" ]; then echo 1>&2 "$0: Error: PID file already exists: $PIDFILE" exit 1 fi if [ ! -d $SENSDIR ]; then if [ ! -d $SSENSDIR ]; then echo $0: 'No sensors found! (did you load the necessary modules?)' exit 1 else SYSFS=1 SENSDIR=$SSENSDIR fi fi # cd $SENSDIR # Let's not stand in procfs or sysfs! trap restorefans SIGHUP SIGINT SIGQUIT SIGTERM SIGKILL echo $$ > "$PIDFILE" echo 'Enabling PWM on fans...' let fcvcount=0 while (( $fcvcount < ${#AFCPWM[@]} )); do # go through all pwm outputs pwmo=${AFCPWM[$fcvcount]} pwmenable $SENSDIR/$pwmo if [ $? -ne 0 ]; then echo "Error enabling PWM on $SENSDIR/$pwmo" restorefans fi let fcvcount=$fcvcount+1 done echo 'Starting automatic fan control...' # main loop calling the main function at specified intervals while true; do UpdateFanSpeeds # The sleep in the foreground isn't interruptible by kill, which is # unsuitable for system service scripts. # Werner@suse doesn't like sleep $INTERVAL &; wait $! although it works. read < <(exec sleep $INTERVAL) done