Difference between revisions of "Fan control scripts"

From ThinkWiki
Jump to: navigation, search
(<tt>bash</tt> script with fine control over fan speed)
(Variable speed control script without kernel patch)
Line 126: Line 126:
 
==Variable speed control scripts==
 
==Variable speed control scripts==
  
===<tt>bash</tt> script with fine control over fan speed===
+
===<tt>bash</tt> script with fine control over fan speed (requires kernel patch)===
 
While the above scripts only toggle the fan on and off, the following also sets the fan speed according to sytem temperatures. In addition, it includes a hack for preventing the annoying fan pulsing that occurs on some systems.  
 
While the above scripts only toggle the fan on and off, the following also sets the fan speed according to sytem temperatures. In addition, it includes a hack for preventing the annoying fan pulsing that occurs on some systems.  
  
Line 225: Line 225:
 
    else
 
    else
 
$DRY_RUN || echo level disengaged >> $FAN
 
$DRY_RUN || echo level disengaged >> $FAN
 +
sleep 0.5
 +
    fi
 +
else
 +
    SETTLE=6
 +
fi
 +
    fi
 +
 +
    IDX=$NEWIDX
 +
done
 +
</pre>
 +
 +
The [[User:Thinker|author]] disclaims all warranty for this script, and releases it to the public domain (meaning you may use it and further distribute it under any terms you wish, including incorporating it into other software).
 +
 +
===<tt>bash</tt> script with fine control over fan speed (for unpatched kernels)===
 +
The following is an alternative variable speed control script that doesn't need the [[patch for controlling fan speed]]. It requires only [[ibm-acpi]] 0.11 or higher (e.g., as found in kernel 2.6.14 and higher) with the <tt>experimental=1</tt> module parameter.
 +
 +
<pre>
 +
#!/bin/bash
 +
 +
# This script dynamically controls fan speed on some ThinkPad models
 +
# according to user-defined temperature thresholds.  It implements its
 +
# own decision algorithm, overriding the ThinkPad embedded
 +
# controller. It also implements a workaround for the fan noise pulse
 +
# experienced every few seconds on some ThinkPads.
 +
#
 +
# WARNING: This script relies on undocumented hardware features and
 +
# overrides nominal hardware behavior. It may thus cause arbitrary
 +
# damage to your laptop or data. Watch your temperatures!
 +
#
 +
# This file is placed in the public domain and may be freely distributed.
 +
 +
LEVELS=(    0      2      4      7)  # Fan speed levels
 +
UP_TEMPS=(      52    60    68  )  # Speed increase trip points
 +
DOWN_TEMPS=(  48    56    64    )  # Speed decrease trip points
 +
 +
ANTIPULSE=( 0      1      0      0)  # Prevent fan pulsing noise at this level
 +
                                    #  (this also prevents fan speed updates)
 +
 +
IBM_ACPI=/proc/acpi/ibm
 +
INTERVAL=3
 +
VERBOSE=true
 +
DRY_RUN=false
 +
 +
[[ "$1" == "-t" ]] && { DRY_RUN=true; echo "$0: Dry run, will not change fan state."; }
 +
 +
# Enable the fan in default mode if anything goes wrong:
 +
set -e -E -u
 +
$DRY_RUN || trap "echo enable > $IBM_ACPI/fan; exit 0" EXIT HUP INT ABRT QUIT SEGV TERM
 +
 +
 +
thermometer() { # output list of temperatures
 +
    read X Y < $IBM_ACPI/thermal
 +
    if ! [[ "$X" == "temperatures:" ]]; then
 +
echo "$0: Bad temperatures: $X $Y" >&2
 +
exit 1
 +
    fi
 +
    echo "$Y";
 +
}
 +
 +
speedometer() { # output fan speed RPM
 +
    cat $IBM_ACPI/fan | sed '/^speed/!d; s/speed:[ \t]*//'
 +
}
 +
 +
setlevel() { # set fan speed level
 +
    $DRY_RUN || echo 0x2F $1 > $IBM_ACPI/ecdump
 +
}
 +
 +
IDX=0
 +
MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
 +
SETTLE=0
 +
 +
while true; do
 +
    TEMPS=`thermometer`
 +
    $VERBOSE && SPEED=`speedometer`
 +
 +
    # Calculate new level
 +
    NEWIDX=$IDX
 +
    DOWN=$(( IDX > 0 ))
 +
    for TEMP in $TEMPS; do
 +
        # Increase speed as much as needed
 +
        while [[ $NEWIDX -lt $MAX_IDX ]] &&
 +
              [[ $TEMP -ge ${UP_TEMPS[$NEWIDX]} ]]; do
 +
            (( NEWIDX ++ ))
 +
            DOWN=0
 +
        done
 +
        # Allow decrease (by one index)?
 +
        if [[ $DOWN == 1 ]] &&
 +
          [[ $TEMP -gt ${DOWN_TEMPS[$(( IDX - 1 ))]} ]]; then
 +
            DOWN=0
 +
        fi
 +
    done
 +
    if [[ $DOWN == 1 ]]; then
 +
        NEWIDX=$(( IDX - 1 ))
 +
    fi
 +
 +
    # Transition
 +
    OLDLEVEL=${LEVELS[$IDX]}
 +
    NEWLEVEL=${LEVELS[$NEWIDX]}
 +
    $VERBOSE && echo "tpfan: Temps: $TEMPS  Fan: $SPEED  Level: $OLDLEVEL->$NEWLEVEL"
 +
    setlevel $NEWLEVEL
 +
 +
    sleep $INTERVAL
 +
 +
    # If needed, apply anti-pulsing hack after a settle-down period:
 +
    if [[ ${ANTIPULSE[${NEWIDX}]} == 1 ]]; then
 +
if [[ $NEWLEVEL == $OLDLEVEL ]]; then
 +
    if [[ $SETTLE -ge 0 ]]; then
 +
(( SETTLE -= INTERVAL ))
 +
    else
 +
setlevel 0x40 # disengaged
 
sleep 0.5
 
sleep 0.5
 
    fi
 
    fi

Revision as of 12:55, 5 November 2005

Fan enable/disable scripts

sh script example

#!/bin/sh

MAXTEMP=50

while [ 1 ];
do
       fan=no

       for temp in `sed s/temperatures:// < /proc/acpi/ibm/thermal`
       do
               test $temp -gt $MAXTEMP && fan=yes
       done

       command='disable'
       test "$fan" = "yes" && command='enable'
       echo $command > /proc/acpi/ibm/fan

       sleep 20
done

sh script with more features

#!/bin/sh

# fan control-script
#
# based upon ibm-acpi 0.11 (experimental=1 !)
#
# eliminates anoying "fan always on" in battery mode
# works with hysteresis (DELTA) so that always-turn-on/turn-off is avoided
# fan acivates at MAXTEMP and cools down CPU, GPU etc. to MAXTEMP-DELTA than the fan is turned off
# furthermore detects if AC is on and gives back fan control to default behaviour than
#
# one can change MAXTEMP and DELTA to individual values
# but take care of your THINKPAD don`t melt it!
#
# have fun!
# mk 05.05.05

MAXTEMP=51
DELTA=4

SWITCHTEMP=$MAXTEMP

#make sure the script doesn't leave the fan off on error
trap "echo enable > /proc/acpi/ibm/fan" EXIT

while [ 1 ];
do
  for ac in `sed s/state:// < /proc/acpi/ac_adapter/AC/state`
    do
     if [ "$ac" = "off-line" ]; then
         fan=no
         for temp in `sed s/temperatures:// < /proc/acpi/ibm/thermal`
           do
             test $temp -gt $SWITCHTEMP && fan=yes
           done

         if [ "$fan" = "yes" ]; then
           command='enable'
           SWITCHTEMP=`expr $MAXTEMP - $DELTA`
         else
           SWITCHTEMP=$MAXTEMP
           command='disable'
         fi

       else # ac-adapter on -> set fan control to standard behaviour
         command='enable'
       fi

       echo $command > /proc/acpi/ibm/fan
       sleep 15
     done 
  done

sh script with extra safety functionality

ibm_acpi usually works well. But to rely on it completely, this script provides some extra safety functionality:

  1. It catches various signals and turns the fan on before it quits.
  2. It turns off the fan under very strict conditions, leaving it on when unexpected errors occur.
#!/bin/sh

# july 2005 Erik Groeneveld, erik@cq2.nl
# It makes sure the fan is on in case of errors
# and only turns it off when all temps are ok.

IBM_ACPI=/proc/acpi/ibm
THERMOMETER=$IBM_ACPI/thermal
FAN=$IBM_ACPI/fan
MAXTRIPPOINT=65
MINTRIPPOINT=60
TRIPPOINT=$MINTRIPPOINT

echo fancontrol: Thermometer: $THERMOMETER, Fan: $FAN
echo fancontrol: Current `cat $THERMOMETER`
echo fancontrol: Controlling temperatures between $MINTRIPPOINT and $MAXTRIPPOINT degrees.

# Make sure the fan is turned on when the script crashes or is killed
trap "echo enable > $FAN; exit 0" HUP KILL INT ABRT STOP QUIT SEGV TERM

while [ 1 ];
do
       command=enable
       temperatures=`sed s/temperatures:// < $THERMOMETER`
       result=
       for temp in $temperatures
       do
               test $temp -le $TRIPPOINT && result=$result.Ok
       done
       if [ "$result" = ".Ok.Ok.Ok.Ok.Ok.Ok.Ok.Ok" ]; then
               command=disable
               TRIPPOINT=$MAXTRIPPOINT
       else
               command=enable
               TRIPPOINT=$MINTRIPPOINT
       fi
       echo $command > $FAN
       # Temperature ramps up quickly, so pick this not too large:
       sleep 5
done

Variable speed control scripts

bash script with fine control over fan speed (requires kernel patch)

While the above scripts only toggle the fan on and off, the following also sets the fan speed according to sytem temperatures. In addition, it includes a hack for preventing the annoying fan pulsing that occurs on some systems.

Prerequisite: the patch for controlling fan speed.

#!/bin/bash

# This script dynamically controls fan speed on some ThinkPad models
# according to user-defined temperature thresholds.  It implements its
# own decision algorithm, overriding the ThinkPad embedded
# controller. It also implements a workaround for the fan noise pulse
# experienced every few seconds on some ThinkPads.
#
# The script requires the ibm_acpi patch at 
# http://thinkwiki.org/wiki/Patch_for_controlling_fan_speed
#
# WARNING: This script relies on undocumented hardware features and
# overrides nominal hardware behavior. It may thus cause arbitrary
# damage to your laptop or data. Watch your temperatures!
#
# This file is placed in the public domain and may be freely distributed.

LEVELS=(    0      2      4      7)  # Fan speed levels
UP_TEMPS=(      52     60     68  )  # Speed increase trip points
DOWN_TEMPS=(  48     56     64    )  # Speed decrease trip points

ANTIPULSE=( 0      1      0      0)  # Prevent fan pulsing noise at this level
                                     #   (this also prevents fan speed updates)

IBM_ACPI=/proc/acpi/ibm
FAN=$IBM_ACPI/fan
INTERVAL=3
VERBOSE=true
DRY_RUN=false

[[ "$1" == "-t" ]] && { DRY_RUN=true; echo "$0: Dry run, will not change fan state."; }

# Enable the fan in default mode if anything goes wrong:
set -e -E -u
$DRY_RUN || trap "echo enable > $FAN; exit 0" EXIT HUP INT ABRT QUIT SEGV TERM


thermometer() { # output list of temperatures
    read X Y < $IBM_ACPI/thermal
    [[ "$X" == "temperatures:" ]] || { 
	echo "$0: Bad temperatures: $X $Y" >&2 
	exit 1
    }
    echo "$Y"; 
}

speedometer() { # output fan speed
    cat $FAN | sed '/^speed/!d; s/speed:[ \t]*//'
}

IDX=0
MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
SETTLE=0

while true; do
    TEMPS=`thermometer`
    $VERBOSE && SPEED=`speedometer`

    # Calculate new level
    NEWIDX=$IDX
    DOWN=$(( IDX > 0 ))
    for TEMP in $TEMPS; do
        # Increase speed as much as needed
        while [[ $NEWIDX -lt $MAX_IDX ]] && 
              [[ $TEMP -ge ${UP_TEMPS[$NEWIDX]} ]]; do
            (( NEWIDX ++ ))
            DOWN=0
        done
        # Allow decrease (by one index)?
        if [[ $DOWN == 1 ]] && 
           [[ $TEMP -gt ${DOWN_TEMPS[$(( IDX - 1 ))]} ]]; then
            DOWN=0
        fi
    done
    if [[ $DOWN == 1 ]]; then
        NEWIDX=$(( IDX - 1 ))
    fi

    # Transition
    OLDLEVEL=${LEVELS[$IDX]}
    NEWLEVEL=${LEVELS[$NEWIDX]}
    $VERBOSE && echo "tpfan: Temps: $TEMPS   Fan: $SPEED   Level: $OLDLEVEL->$NEWLEVEL"
    $DRY_RUN || echo level $NEWLEVEL > $FAN

    sleep $INTERVAL

    # If needed, apply anti-pulsing hack after a settle-down period:
    if [[ ${ANTIPULSE[${NEWIDX}]} == 1 ]]; then
	if [[ $NEWLEVEL == $OLDLEVEL ]]; then
	    if [[ $SETTLE -ge 0 ]]; then
		(( SETTLE -= INTERVAL ))
	    else
		$DRY_RUN || echo level disengaged >> $FAN
		sleep 0.5
	    fi
	else
	    SETTLE=6
	fi
    fi

    IDX=$NEWIDX
done

The author disclaims all warranty for this script, and releases it to the public domain (meaning you may use it and further distribute it under any terms you wish, including incorporating it into other software).

bash script with fine control over fan speed (for unpatched kernels)

The following is an alternative variable speed control script that doesn't need the patch for controlling fan speed. It requires only ibm-acpi 0.11 or higher (e.g., as found in kernel 2.6.14 and higher) with the experimental=1 module parameter.

#!/bin/bash

# This script dynamically controls fan speed on some ThinkPad models
# according to user-defined temperature thresholds.  It implements its
# own decision algorithm, overriding the ThinkPad embedded
# controller. It also implements a workaround for the fan noise pulse
# experienced every few seconds on some ThinkPads.
#
# WARNING: This script relies on undocumented hardware features and
# overrides nominal hardware behavior. It may thus cause arbitrary
# damage to your laptop or data. Watch your temperatures!
#
# This file is placed in the public domain and may be freely distributed.

LEVELS=(    0      2      4      7)  # Fan speed levels
UP_TEMPS=(      52     60     68  )  # Speed increase trip points
DOWN_TEMPS=(  48     56     64    )  # Speed decrease trip points

ANTIPULSE=( 0      1      0      0)  # Prevent fan pulsing noise at this level
                                     #   (this also prevents fan speed updates)

IBM_ACPI=/proc/acpi/ibm
INTERVAL=3
VERBOSE=true
DRY_RUN=false

[[ "$1" == "-t" ]] && { DRY_RUN=true; echo "$0: Dry run, will not change fan state."; }

# Enable the fan in default mode if anything goes wrong:
set -e -E -u
$DRY_RUN || trap "echo enable > $IBM_ACPI/fan; exit 0" EXIT HUP INT ABRT QUIT SEGV TERM


thermometer() { # output list of temperatures
    read X Y < $IBM_ACPI/thermal
    if ! [[ "$X" == "temperatures:" ]]; then
	echo "$0: Bad temperatures: $X $Y" >&2 
	exit 1
    fi
    echo "$Y"; 
}

speedometer() { # output fan speed RPM
    cat $IBM_ACPI/fan | sed '/^speed/!d; s/speed:[ \t]*//'
}

setlevel() { # set fan speed level
    $DRY_RUN || echo 0x2F $1 > $IBM_ACPI/ecdump
}

IDX=0
MAX_IDX=$(( ${#LEVELS[@]} - 1 ))
SETTLE=0

while true; do
    TEMPS=`thermometer`
    $VERBOSE && SPEED=`speedometer`

    # Calculate new level
    NEWIDX=$IDX
    DOWN=$(( IDX > 0 ))
    for TEMP in $TEMPS; do
        # Increase speed as much as needed
        while [[ $NEWIDX -lt $MAX_IDX ]] && 
              [[ $TEMP -ge ${UP_TEMPS[$NEWIDX]} ]]; do
            (( NEWIDX ++ ))
            DOWN=0
        done
        # Allow decrease (by one index)?
        if [[ $DOWN == 1 ]] && 
           [[ $TEMP -gt ${DOWN_TEMPS[$(( IDX - 1 ))]} ]]; then
            DOWN=0
        fi
    done
    if [[ $DOWN == 1 ]]; then
        NEWIDX=$(( IDX - 1 ))
    fi

    # Transition
    OLDLEVEL=${LEVELS[$IDX]}
    NEWLEVEL=${LEVELS[$NEWIDX]}
    $VERBOSE && echo "tpfan: Temps: $TEMPS   Fan: $SPEED   Level: $OLDLEVEL->$NEWLEVEL"
    setlevel $NEWLEVEL

    sleep $INTERVAL

    # If needed, apply anti-pulsing hack after a settle-down period:
    if [[ ${ANTIPULSE[${NEWIDX}]} == 1 ]]; then
	if [[ $NEWLEVEL == $OLDLEVEL ]]; then
	    if [[ $SETTLE -ge 0 ]]; then
		(( SETTLE -= INTERVAL ))
	    else
		setlevel 0x40 # disengaged
		sleep 0.5
	    fi
	else
	    SETTLE=6
	fi
    fi

    IDX=$NEWIDX
done

The author disclaims all warranty for this script, and releases it to the public domain (meaning you may use it and further distribute it under any terms you wish, including incorporating it into other software).

Init scripts

Init script example

#! /bin/sh

N=/etc/init.d/fan

set -e

case "$1" in
 start)
       # make sure privileges don't persist across reboots
       if [ -d /var/run/fan ] && [ "x`ls /var/run/fan`" != x ]
       then
               touch -t 198501010000 /var/run/fan/*
       fi
       fan.sh &    # Script from above
       ;;
 stop|reload|restart|force-reload)
       killall fan.sh
       echo enable > /proc/acpi/ibm/fan
       ;;
 *)
       echo "Usage: $N {start|stop|restart|force-reload}" >&2
       exit 1
       ;;
esac

exit 0


Init script example for gentoo

Assume one of the above control scripts is /usr/sbin/ibm-fancontrold, for gentoo use the following init script in /etc/init.d/ibm-fancontrol. Copy the script to /etc/init.d/ibm-fancontrol, then do

# rc-update add ibm-fancontrol default

This will add the init script to the default runlevel.


#!/sbin/runscript
# 2005 Gilbert Tiefengruber
# Distributed under the terms of the GNU General Public License v2 
# IBM Fancontrol init script for IBM Thinkpad laptops (tested with R50)
# This init script was written for gentoo 2005.1, kernel 2.6.12
# You need the ibm_acpi kernel module version 0.11 or greater
# load the module with experimental=1 to enable the fan controls

depend() {
        need localmount
}
checkconfig() {
        if [ ! -e /proc/acpi/ibm/fan ]; then
                eerror "The ibm_acpi module must be loaded with (experimental=1)"
                return 1
        fi
} 
start() {
        checkconfig || return 1
        ebegin "Starting ibm-fancontrold"
        start-stop-daemon --quiet -p /var/run/ibm-fancontrold.pid -m -b --start -a /usr/sbin/ibm-fancontrold
        eend ${?}
} 
stop() {
        ebegin "Stopping ibm-fancontrold"
        start-stop-daemon --stop --quiet -p /var/run/ibm-fancontrold.pid
        eend ${?}
}

Other

fanctrld

fanctrld is a daemon (written in C) that controls the Thinkpad's fan. The basic approach is to monitor both temperature and fan speed. The fan is enabled when a certain temperature is exceeded, and disabled when the BIOS slows down the fan below a certain speed.