Difference between revisions of "Talk:Fan control scripts"
(Sensor-specific variable-speed script) |
|||
Line 24: | Line 24: | ||
--[[User:Micampe|Micampe]] 08:19, 12 Nov 2005 (CET) | --[[User:Micampe|Micampe]] 08:19, 12 Nov 2005 (CET) | ||
+ | |||
+ | == Sensor-specific variable-speed script == | ||
+ | |||
+ | Here's a new variable-speed control script that lets you define the temperature range separately for each sensor. To keep things simple, it auto-computes the trip points (unlike the current script). Works well on a {{T43}}, and (just barely) keeps the fan off most of the time with CPU undervolting and {{fglrx}} set to maximum power saving. Feedback on other machines would be appreciated. | ||
+ | |||
+ | Any idea what the funny sensors (2nd and 3rd) are? On the T43, the second sensor seems to be the same as the HDAPS sensor (it never deviates by more than one degree from what the HDAPS sensor tells directly), but I don't know where it's located. The 3rd sensor is uncorrelated with disk temperature and activity. | ||
+ | |||
+ | <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 | ||
+ | ANTIPULSE=( 0 1 0 0) # Prevent fan pulsing noise at this level | ||
+ | # (this also prevents fan speed updates) | ||
+ | |||
+ | # Temperature ranges, per sensor: | ||
+ | MIN_TEMPS=( 52 49 44 51 39 45 35 45 49 ) # step up from zero-th fan level at this temperature | ||
+ | MAX_TEMPS=( 70 59 54 68 52 55 45 55 59 ) # step up to maximum fan at this temperature | ||
+ | # CPU HDAPS ??? GPU BAT-SW n/a BAT-NE n/a HDAPS (ThinkPad T43) | ||
+ | # CPU m-PCI HDD GPU BAT-SW n/a BAT-NE n/a HDAPS (ThinkPad R51) | ||
+ | |||
+ | IBM_ACPI=/proc/acpi/ibm | ||
+ | HDAPS_TEMP=/sys/bus/platform/drivers/hdaps/hdaps/temp1 | ||
+ | PID_FILE=/var/run/tp-fancontrol.pid | ||
+ | INTERVAL=3 | ||
+ | VERBOSE=true | ||
+ | DRY_RUN=false | ||
+ | DAEMONIZE=false | ||
+ | AM_DAEMON=false | ||
+ | KILL_DAEMON=false | ||
+ | SYSLOG=false | ||
+ | LOGGER=/usr/bin/logger | ||
+ | |||
+ | usage() { | ||
+ | echo "Usage: $0 [OPTION]..." | ||
+ | echo | ||
+ | echo "Available options:" | ||
+ | echo " -t test mode" | ||
+ | echo " -q quiet mode" | ||
+ | echo " -d daemon mode, go into background (implies -q)" | ||
+ | echo " -l log to syslog" | ||
+ | echo " -p pid file location for daemon mode, default: $PID_FILE" | ||
+ | echo " -k kill daemon (ignores all but -p)" | ||
+ | exit 1 | ||
+ | } | ||
+ | |||
+ | while getopts 'qtdlp:kh' OPT; do | ||
+ | case "$OPT" in | ||
+ | t) # test mode | ||
+ | DRY_RUN=true | ||
+ | ;; | ||
+ | q) # quiet mode | ||
+ | VERBOSE=false | ||
+ | ;; | ||
+ | d) # go into background and daemonize | ||
+ | DAEMONIZE=true | ||
+ | ;; | ||
+ | l) # log to syslog | ||
+ | SYSLOG=true | ||
+ | ;; | ||
+ | p) # different pidfile | ||
+ | PID_FILE="$OPTARG" | ||
+ | ;; | ||
+ | k) # kill daemon | ||
+ | KILL_DAEMON=true | ||
+ | ;; | ||
+ | h) # short help | ||
+ | usage | ||
+ | ;; | ||
+ | \?) # error | ||
+ | usage | ||
+ | ;; | ||
+ | esac | ||
+ | done | ||
+ | [ $OPTIND -gt $# ] || usage # no non-option args | ||
+ | |||
+ | # no logger found, no syslog capabilities | ||
+ | $SYSLOG && [ ! -x $LOGGER ] && SYSLOG=false | ||
+ | |||
+ | if $DRY_RUN; then | ||
+ | echo "$0: Dry run, will not change fan state." | ||
+ | VERBOSE=true | ||
+ | DAEMONIZE=false | ||
+ | fi | ||
+ | |||
+ | 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 -n "$Y "; | ||
+ | [ -r $HDAPS_TEMP ] && echo -n "`cat $HDAPS_TEMP` " | ||
+ | return 0 | ||
+ | } | ||
+ | |||
+ | speedometer() { # output fan speed RPM | ||
+ | sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan | ||
+ | } | ||
+ | |||
+ | setlevel() { # set fan speed level | ||
+ | $DRY_RUN || echo 0x2F $1 > $IBM_ACPI/ecdump | ||
+ | } | ||
+ | |||
+ | cleanup() { # clean up after work | ||
+ | $AM_DAEMON && rm -f $PID_FILE 2> /dev/null | ||
+ | $SYSLOG && $LOGGER -t "`basename $0`[$$]" \ | ||
+ | "Shutting down, switching to automatic fan control" | ||
+ | $DRY_RUN || echo enable > $IBM_ACPI/fan | ||
+ | } | ||
+ | |||
+ | floor_div() { | ||
+ | echo $(( (($1)+1000*($2))/($2) - 1000 )) | ||
+ | } | ||
+ | |||
+ | control_fan() { | ||
+ | # Enable the fan in default mode if anything goes wrong: | ||
+ | set -e -E -u | ||
+ | trap "cleanup; exit 2" HUP INT ABRT QUIT SEGV TERM | ||
+ | trap "cleanup" EXIT | ||
+ | |||
+ | IDX=0 | ||
+ | MAX_IDX=$(( ${#LEVELS[@]} - 1 )) | ||
+ | SETTLE=0 | ||
+ | FIRST=true | ||
+ | $SYSLOG && $LOGGER -t "`basename $0`[$$]" "Starting dynamic fan control" | ||
+ | |||
+ | # Control loop: | ||
+ | while true; do | ||
+ | TEMPS=`thermometer` | ||
+ | $VERBOSE && SPEED=`speedometer` | ||
+ | # Calculate new level index by placing temperatures into Z-regions: | ||
+ | # Z >= 2*I means "must be at index I or higher" | ||
+ | # Z = 2*I+1 is hysteresis: "don't step down if currently at I+1" | ||
+ | # hence the Z-regions are, for d=(MAX-MIN)/(2*MAX_IDX-1) : | ||
+ | # Z=0:{-infty..MIN-d) Z=1:{MIN-d..MIN) Z=2:{MIN..MIN+d} Z=3:{MIN+d..MIN+2d} ... Z=2*MAX_IDX:{MAX-d, MAX} | ||
+ | |||
+ | MAX_Z=$(( IDX>0 ? 2*(IDX-1) : 0 )) | ||
+ | SENSOR=0 | ||
+ | Z_LIST='' | ||
+ | for TEMP in $TEMPS; do | ||
+ | [ $SENSOR -lt ${#MIN_TEMPS[@]} -a $SENSOR -lt ${#MAX_TEMPS[@]} ] || | ||
+ | { echo "Too many sensors, not enough values in MIN_TEMPS and MAX_TEMPS" 2>&1; exit 1; } | ||
+ | MIN=${MIN_TEMPS[$SENSOR]}; MAX=${MAX_TEMPS[$SENSOR]} | ||
+ | Z=$(( `floor_div $(( (TEMP-MIN)*(2*MAX_IDX-2) )) $((MAX-MIN))` + 2 )) | ||
+ | Z_LIST="$Z_LIST $Z" | ||
+ | [ $MAX_Z -gt $Z ] || MAX_Z=$Z | ||
+ | (( ++SENSOR )) | ||
+ | done | ||
+ | [ $SENSOR -gt 0 ] || { echo "No temperatures read" >&2; exit 1; } | ||
+ | |||
+ | (( (MAX_Z == 2*IDX-1) && ++MAX_Z )) # hysteresis | ||
+ | NEW_IDX=$(( MAX_Z/2 )) | ||
+ | [ $NEW_IDX -le $MAX_IDX ] || NEW_IDX=$MAX_IDX | ||
+ | |||
+ | # Transition | ||
+ | $FIRST && OLDLEVEL=unknown || OLDLEVEL=${LEVELS[$IDX]} | ||
+ | NEWLEVEL=${LEVELS[$NEW_IDX]} | ||
+ | $VERBOSE && echo "Level: $OLDLEVEL->$NEWLEVEL Fan: $SPEED Temps: $TEMPS" #" Z:$Z_LIST" | ||
+ | $SYSLOG && [ $OLDLEVEL != $NEWLEVEL ] && | ||
+ | $LOGGER -t "`basename $0`[$$]" "Changing fan level: $OLDLEVEL->$NEWLEVEL" | ||
+ | |||
+ | setlevel $NEWLEVEL | ||
+ | |||
+ | sleep $INTERVAL | ||
+ | |||
+ | # If needed, apply anti-pulsing hack after a settle-down period: | ||
+ | if [ ${ANTIPULSE[${NEW_IDX}]} == 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=$NEW_IDX | ||
+ | FIRST=false | ||
+ | done | ||
+ | } | ||
+ | |||
+ | if $KILL_DAEMON ; then | ||
+ | if [ -f $PID_FILE ]; then | ||
+ | set -e | ||
+ | DPID="`cat $PID_FILE`" | ||
+ | kill "$DPID" | ||
+ | rm "$PID_FILE" | ||
+ | $VERBOSE && echo "Killed process $DPID" | ||
+ | else | ||
+ | $VERBOSE && echo "Daemon not running." | ||
+ | exit 1 | ||
+ | fi | ||
+ | elif $DAEMONIZE ; then | ||
+ | if [ -e "$PID_FILE" ]; then | ||
+ | echo "$0: File $PID_FILE already exists, refusing to run." | ||
+ | exit 1 | ||
+ | else | ||
+ | AM_DAEMON=true VERBOSE=false control_fan 0<&- 1>&- 2>&- & | ||
+ | echo $! > "$PID_FILE" | ||
+ | exit 0 | ||
+ | fi | ||
+ | else | ||
+ | [ -e "$PID_FILE" ] && echo "WARNING: daemon already running" | ||
+ | control_fan | ||
+ | fi | ||
+ | </pre> |
Revision as of 04:51, 18 November 2005
Wyrfel, are you sure the recent (19:54, 27 Oct 2005) cosmetic change was a good idea? The extensive chunks of code make it hard to grok the structure of the article in the absense of separator lines (which "===" doesn't have). --Thinker 22:10, 27 Oct 2005 (CEST)
We can discuss this. From my point of view, the chunks of code distinguish themselves from each other quite well, because they are each in one code block.
I do not like the = section level - and so far we avoided them on all pages - because
- it generates H1 headings, which is the same as the page heading,
- having more than one level with the hbars is confusing/less readable, because they are not very well distinguishable. This way i.e. the "Other" section looked like a separate empty secion.
I think the way it's now, the separator lines make it possible to easily distinguish the different main sections, while when you have both levels with separator lines, an additional task of distinguishing H1 and H2 separators is necessary.
However, i see your point as well and would like to hear more opinions/arguments.
Wyrfel 22:31, 27 Oct 2005 (CEST)
bash script with fine control over fan speed (for unpatched kernels)
Moved to the article page, after joint development by Spiney and Thinker.
Note that the fan levels, thresholds and anti-pulsing hacks are system-specific, so you may need to adjust them.
I think it'd probably be nice to have a table of the suggested values here. Those in the "unpatched kernels" script seems to work fine on my R52, but the other scripts all have different values.
--Micampe 08:19, 12 Nov 2005 (CET)
Sensor-specific variable-speed script
Here's a new variable-speed control script that lets you define the temperature range separately for each sensor. To keep things simple, it auto-computes the trip points (unlike the current script). Works well on a T43, and (just barely) keeps the fan off most of the time with CPU undervolting and Template:Fglrx set to maximum power saving. Feedback on other machines would be appreciated.
Any idea what the funny sensors (2nd and 3rd) are? On the T43, the second sensor seems to be the same as the HDAPS sensor (it never deviates by more than one degree from what the HDAPS sensor tells directly), but I don't know where it's located. The 3rd sensor is uncorrelated with disk temperature and activity.
#!/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 ANTIPULSE=( 0 1 0 0) # Prevent fan pulsing noise at this level # (this also prevents fan speed updates) # Temperature ranges, per sensor: MIN_TEMPS=( 52 49 44 51 39 45 35 45 49 ) # step up from zero-th fan level at this temperature MAX_TEMPS=( 70 59 54 68 52 55 45 55 59 ) # step up to maximum fan at this temperature # CPU HDAPS ??? GPU BAT-SW n/a BAT-NE n/a HDAPS (ThinkPad T43) # CPU m-PCI HDD GPU BAT-SW n/a BAT-NE n/a HDAPS (ThinkPad R51) IBM_ACPI=/proc/acpi/ibm HDAPS_TEMP=/sys/bus/platform/drivers/hdaps/hdaps/temp1 PID_FILE=/var/run/tp-fancontrol.pid INTERVAL=3 VERBOSE=true DRY_RUN=false DAEMONIZE=false AM_DAEMON=false KILL_DAEMON=false SYSLOG=false LOGGER=/usr/bin/logger usage() { echo "Usage: $0 [OPTION]..." echo echo "Available options:" echo " -t test mode" echo " -q quiet mode" echo " -d daemon mode, go into background (implies -q)" echo " -l log to syslog" echo " -p pid file location for daemon mode, default: $PID_FILE" echo " -k kill daemon (ignores all but -p)" exit 1 } while getopts 'qtdlp:kh' OPT; do case "$OPT" in t) # test mode DRY_RUN=true ;; q) # quiet mode VERBOSE=false ;; d) # go into background and daemonize DAEMONIZE=true ;; l) # log to syslog SYSLOG=true ;; p) # different pidfile PID_FILE="$OPTARG" ;; k) # kill daemon KILL_DAEMON=true ;; h) # short help usage ;; \?) # error usage ;; esac done [ $OPTIND -gt $# ] || usage # no non-option args # no logger found, no syslog capabilities $SYSLOG && [ ! -x $LOGGER ] && SYSLOG=false if $DRY_RUN; then echo "$0: Dry run, will not change fan state." VERBOSE=true DAEMONIZE=false fi 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 -n "$Y "; [ -r $HDAPS_TEMP ] && echo -n "`cat $HDAPS_TEMP` " return 0 } speedometer() { # output fan speed RPM sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan } setlevel() { # set fan speed level $DRY_RUN || echo 0x2F $1 > $IBM_ACPI/ecdump } cleanup() { # clean up after work $AM_DAEMON && rm -f $PID_FILE 2> /dev/null $SYSLOG && $LOGGER -t "`basename $0`[$$]" \ "Shutting down, switching to automatic fan control" $DRY_RUN || echo enable > $IBM_ACPI/fan } floor_div() { echo $(( (($1)+1000*($2))/($2) - 1000 )) } control_fan() { # Enable the fan in default mode if anything goes wrong: set -e -E -u trap "cleanup; exit 2" HUP INT ABRT QUIT SEGV TERM trap "cleanup" EXIT IDX=0 MAX_IDX=$(( ${#LEVELS[@]} - 1 )) SETTLE=0 FIRST=true $SYSLOG && $LOGGER -t "`basename $0`[$$]" "Starting dynamic fan control" # Control loop: while true; do TEMPS=`thermometer` $VERBOSE && SPEED=`speedometer` # Calculate new level index by placing temperatures into Z-regions: # Z >= 2*I means "must be at index I or higher" # Z = 2*I+1 is hysteresis: "don't step down if currently at I+1" # hence the Z-regions are, for d=(MAX-MIN)/(2*MAX_IDX-1) : # Z=0:{-infty..MIN-d) Z=1:{MIN-d..MIN) Z=2:{MIN..MIN+d} Z=3:{MIN+d..MIN+2d} ... Z=2*MAX_IDX:{MAX-d, MAX} MAX_Z=$(( IDX>0 ? 2*(IDX-1) : 0 )) SENSOR=0 Z_LIST='' for TEMP in $TEMPS; do [ $SENSOR -lt ${#MIN_TEMPS[@]} -a $SENSOR -lt ${#MAX_TEMPS[@]} ] || { echo "Too many sensors, not enough values in MIN_TEMPS and MAX_TEMPS" 2>&1; exit 1; } MIN=${MIN_TEMPS[$SENSOR]}; MAX=${MAX_TEMPS[$SENSOR]} Z=$(( `floor_div $(( (TEMP-MIN)*(2*MAX_IDX-2) )) $((MAX-MIN))` + 2 )) Z_LIST="$Z_LIST $Z" [ $MAX_Z -gt $Z ] || MAX_Z=$Z (( ++SENSOR )) done [ $SENSOR -gt 0 ] || { echo "No temperatures read" >&2; exit 1; } (( (MAX_Z == 2*IDX-1) && ++MAX_Z )) # hysteresis NEW_IDX=$(( MAX_Z/2 )) [ $NEW_IDX -le $MAX_IDX ] || NEW_IDX=$MAX_IDX # Transition $FIRST && OLDLEVEL=unknown || OLDLEVEL=${LEVELS[$IDX]} NEWLEVEL=${LEVELS[$NEW_IDX]} $VERBOSE && echo "Level: $OLDLEVEL->$NEWLEVEL Fan: $SPEED Temps: $TEMPS" #" Z:$Z_LIST" $SYSLOG && [ $OLDLEVEL != $NEWLEVEL ] && $LOGGER -t "`basename $0`[$$]" "Changing fan level: $OLDLEVEL->$NEWLEVEL" setlevel $NEWLEVEL sleep $INTERVAL # If needed, apply anti-pulsing hack after a settle-down period: if [ ${ANTIPULSE[${NEW_IDX}]} == 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=$NEW_IDX FIRST=false done } if $KILL_DAEMON ; then if [ -f $PID_FILE ]; then set -e DPID="`cat $PID_FILE`" kill "$DPID" rm "$PID_FILE" $VERBOSE && echo "Killed process $DPID" else $VERBOSE && echo "Daemon not running." exit 1 fi elif $DAEMONIZE ; then if [ -e "$PID_FILE" ]; then echo "$0: File $PID_FILE already exists, refusing to run." exit 1 else AM_DAEMON=true VERBOSE=false control_fan 0<&- 1>&- 2>&- & echo $! > "$PID_FILE" exit 0 fi else [ -e "$PID_FILE" ] && echo "WARNING: daemon already running" control_fan fi