Difference between revisions of "Talk:Fan control scripts"

From ThinkWiki
Jump to: navigation, search
(<tt>bash</tt> script with fine control over fan speed (for unpatched kernels))
(updated script for unpatched kernels)
Line 32: Line 32:
  
 
--[[User:Thinker|Thinker]] 12:58, 5 Nov 2005 (CET)
 
--[[User:Thinker|Thinker]] 12:58, 5 Nov 2005 (CET)
 +
----
 +
 +
===updated bash script for unpatched kernels===
 +
 +
Feedback welcome.
 +
 +
<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
 +
PID_FILE=/var/run/tp-fancontrol.pid
 +
INTERVAL=3
 +
VERBOSE=true
 +
DRY_RUN=false
 +
DAEMONIZE=false
 +
DAEMONOPT=
 +
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 if in daemon mode"
 +
    echo "  -p    pid file location for daemon mode, default: $PID_FILE"
 +
    exit 1
 +
}
 +
 +
while getopts 'qtdlp:h' 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
 +
            DAEMONOPT="$DAEMONOPT -l"
 +
            ;;
 +
        p) # different pidfile
 +
            PID_FILE="$OPTARG"
 +
            DAEMONOPT="$DAEMONOPT -p $PID_FILE"
 +
            ;;
 +
        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
 +
 +
if $DAEMONIZE ; then
 +
    if [[ -e "$PID_FILE" ]]; then
 +
        echo "$0: File $PID_FILE already exists, refusing to run."
 +
        exit 1
 +
    else
 +
        exec $0 -q $DAEMONOPT 0<&- 1>&- 2>&- &
 +
        echo $! > "$PID_FILE"
 +
        exit 0
 +
    fi
 +
fi
 +
 +
 +
# Enable the fan in default mode if anything goes wrong:
 +
set -e -E -u
 +
$DRY_RUN || trap "cleanup" EXIT HUP INT ABRT QUIT SEGV TERM
 +
 +
cleanup() { # clean up after work
 +
    rm -f $PID_FILE 2> /dev/null
 +
    echo enable > $IBM_ACPI/fan
 +
    exit 0
 +
}
 +
 +
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
 +
    sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan
 +
}
 +
 +
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"
 +
    $SYSLOG && [[ $OLDLEVEL != $NEWLEVEL ]] && $LOGGER -t "`basename $0`[$$]" "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
 +
</pre>
 +
 +
BTW, Thinker, I couldn't get the backgrounding of a function to work, and at the moment I'm not quiet sure that's even possible without it exiting once the main script exits.
 +
 +
If you think it's fine otherwise (after all your the author of the original script) we might put it into the article.
 +
 +
--[[User:Spiney|spiney]] 19:01, 8 Nov 2005 (CET)
 
----
 
----

Revision as of 19:01, 8 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)

Here's 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.

Alas, at the moment I don't have a suitable to test it on. Can someone give it a try and report?

(fixed and moved to article)

--Thinker 15:07, 3 Nov 2005 (CET)


IBM_ACPI was defined twice by accident I guess (changed above), but otherwise it seems to work fine, went from level 0 (cold machine) to 7 while compiling the kernel and now back to level 2 now at about 53 degrees Celsius.

--spiney 10:31, 5 Nov 2005 (CET)


Oops, yes. The IBM_ACPI=/tmp was just for testing (in the temporary absense of a ThinkPad, I wrote stuff to /tmp/thermal manually to test the script...). Fixed and moved to the article. Thanks!

--Thinker 12:58, 5 Nov 2005 (CET)


updated bash script for unpatched kernels

Feedback welcome.

#!/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
PID_FILE=/var/run/tp-fancontrol.pid
INTERVAL=3
VERBOSE=true
DRY_RUN=false
DAEMONIZE=false
DAEMONOPT=
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 if in daemon mode"
    echo "   -p     pid file location for daemon mode, default: $PID_FILE"
    exit 1
}

while getopts 'qtdlp:h' 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
            DAEMONOPT="$DAEMONOPT -l"
            ;;
        p) # different pidfile
            PID_FILE="$OPTARG"
            DAEMONOPT="$DAEMONOPT -p $PID_FILE"
            ;;
        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

if $DAEMONIZE ; then
    if [[ -e "$PID_FILE" ]]; then
        echo "$0: File $PID_FILE already exists, refusing to run."
        exit 1
    else
        exec $0 -q $DAEMONOPT 0<&- 1>&- 2>&- &
        echo $! > "$PID_FILE"
        exit 0
    fi
fi


# Enable the fan in default mode if anything goes wrong:
set -e -E -u
$DRY_RUN || trap "cleanup" EXIT HUP INT ABRT QUIT SEGV TERM

cleanup() { # clean up after work
    rm -f $PID_FILE 2> /dev/null
    echo enable > $IBM_ACPI/fan
    exit 0
}

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
    sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan
}

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"
    $SYSLOG && [[ $OLDLEVEL != $NEWLEVEL ]] && $LOGGER -t "`basename $0`[$$]" "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

BTW, Thinker, I couldn't get the backgrounding of a function to work, and at the moment I'm not quiet sure that's even possible without it exiting once the main script exits.

If you think it's fine otherwise (after all your the author of the original script) we might put it into the article.

--spiney 19:01, 8 Nov 2005 (CET)