<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.thinkwiki.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jeidsath</id>
	<title>ThinkWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.thinkwiki.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jeidsath"/>
	<link rel="alternate" type="text/html" href="https://www.thinkwiki.org/wiki/Special:Contributions/Jeidsath"/>
	<updated>2026-05-09T20:47:00Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.31.12</generator>
	<entry>
		<id>https://www.thinkwiki.org/w/index.php?title=Code/tp-fancontrol&amp;diff=49476</id>
		<title>Code/tp-fancontrol</title>
		<link rel="alternate" type="text/html" href="https://www.thinkwiki.org/w/index.php?title=Code/tp-fancontrol&amp;diff=49476"/>
		<updated>2010-08-29T04:24:05Z</updated>

		<summary type="html">&lt;p&gt;Jeidsath: Fix for arithmetic evaluation returns error val in bash 4.1.5 on Ubuntu 10.04&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#!/bin/bash&lt;br /&gt;
&lt;br /&gt;
# tp-fancontrol 0.3.02 (http://thinkwiki.org/wiki/ACPI_fan_control_script)&lt;br /&gt;
# Provided under the GNU General Public License version 2 or later or&lt;br /&gt;
# the GNU Free Documentation License version 1.2 or later, at your option.&lt;br /&gt;
# See http://www.gnu.org/copyleft/gpl.html for the Warranty Disclaimer.&lt;br /&gt;
&lt;br /&gt;
# This script dynamically controls fan speed on some ThinkPad models&lt;br /&gt;
# according to user-defined temperature thresholds.  It implements its&lt;br /&gt;
# own decision algorithm, overriding the ThinkPad embedded&lt;br /&gt;
# controller. It also implements a workaround for the fan noise pulse&lt;br /&gt;
# experienced every few seconds on some ThinkPads.&lt;br /&gt;
#&lt;br /&gt;
# Run 'tp-fancontrol --help' for options.&lt;br /&gt;
#&lt;br /&gt;
# For optimal fan behavior during suspend and resume, invoke &lt;br /&gt;
# &amp;quot;tp-fancontrol -u&amp;quot; during the suspend process.&lt;br /&gt;
# &lt;br /&gt;
# WARNING: This script relies on undocumented hardware features and&lt;br /&gt;
# overrides nominal hardware behavior. It may thus cause arbitrary&lt;br /&gt;
# damage to your laptop or data. Watch your temperatures!&lt;br /&gt;
#&lt;br /&gt;
# WARNING: The list of temperature ranges used below is much more liberal&lt;br /&gt;
# than the rules used by the embedded controller firmware, and is&lt;br /&gt;
# derived mostly from anecdotal evidence, hunches and wishful thinking.&lt;br /&gt;
# It is also model-specific (see http://thinkwiki.org/wiki/Thermal_sensors).&lt;br /&gt;
&lt;br /&gt;
# Temperature ranges, per sensor:&lt;br /&gt;
# (min temperature: when to step up from 0-th fan level,&lt;br /&gt;
#  max temperature: when to step up to maximum fan level)&lt;br /&gt;
THRESHOLDS=( #  Sensor     ThinkPad model&lt;br /&gt;
             #             R51     T41/2  Z60t   T43-26xx&lt;br /&gt;
# min  max   #  ---------- ------- -----  -----  ---------------------------&lt;br /&gt;
  50   70    #  EC 0x78    CPU     CPU    ?      CPU&lt;br /&gt;
  47   60    #  EC 0x79    miniPCI ?      ?      Between CPU and PCMCIA slot&lt;br /&gt;
  43   55    #  EC 0x7A    HDD     ?      ?      PCMCIA slot&lt;br /&gt;
  49   68    #  EC 0x7B    GPU     GPU    ?      GPU&lt;br /&gt;
  40   50    #  EC 0x7C    BAT     BAT    BAT    Sys BAT (front left of battery)&lt;br /&gt;
  40   50    #  EC 0x7D    n/a     n/a    n/a    UltraBay BAT&lt;br /&gt;
  37   47    #  EC 0x7E    BAT     BAT    BAT    Sys BAT (rear right of battery)&lt;br /&gt;
  37   47    #  EC 0x7F    n/a     n/a    n/a    UltraBay BAT&lt;br /&gt;
&lt;br /&gt;
  45   60    #  EC 0xC0    ?       n/a    ?      Between northbridge and DRAM&lt;br /&gt;
  48   62    #  EC 0xC1    ?       n/a    ?      Southbridge (under miniPCI)&lt;br /&gt;
  50   65    #  EC 0xC2    ?       n/a    ?      Power circuitry (under CDC)&lt;br /&gt;
&lt;br /&gt;
  47   58    #  HDD        -&amp;gt;      -&amp;gt;     -&amp;gt;     Hard disk internal sensor&lt;br /&gt;
  47   60    #  HDAPS      -&amp;gt;      -&amp;gt;     -&amp;gt;     HDAPS readout (same as EC 0x79)&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
LEVELS=(    0      2      4      7)  # Fan speed levels&lt;br /&gt;
ANTIPULSE=( 0      1      1      0)  # Prevent fan pulsing noise at this level&lt;br /&gt;
                                     # (reduces frequency of fan RPM updates)&lt;br /&gt;
&lt;br /&gt;
OFF_THRESH_DELTA=3 # when gets this much cooler than 'min' above, may turn off fan&lt;br /&gt;
MIN_THRESH_SHIFT=0 # increase min thresholds by this much&lt;br /&gt;
MAX_THRESH_SHIFT=0 # increase max thresholds by this much&lt;br /&gt;
MIN_WAIT=180 # minimum time (seconds) to spend in a given level before stepping down&lt;br /&gt;
&lt;br /&gt;
IBM_ACPI=/proc/acpi/ibm&lt;br /&gt;
HDAPS_TEMP=/sys/bus/platform/drivers/hdaps/hdaps/temp1&lt;br /&gt;
PID_FILE=/var/run/tp-fancontrol.pid&lt;br /&gt;
LOGGER=/usr/bin/logger&lt;br /&gt;
INTERVAL=3        # sample+refresh interval&lt;br /&gt;
SETTLE_TIME=6     # wait this many seconds long before applying anti-pulsing&lt;br /&gt;
RESETTLE_TIME=600 # briefly disable anti-pulsing at every N seconds&lt;br /&gt;
SUSPEND_TIME=5    # seconds to sleep when receiving SIGUSR1&lt;br /&gt;
DISK_POLL_PERIOD=15 # poll period in seconds for disk sensors (it changes slowly and is expensive to read)&lt;br /&gt;
HITACHI_MODELS=&amp;quot;^(HTS4212..H9AT00|HTS726060M9AT00|HTS5410..G9AT00|IC25[NT]0..ATCS0[45]|HTE541040G9AT00|HTS5416..J9(AT|SA)00)&amp;quot;&lt;br /&gt;
SEP=','           # Separator char for display&lt;br /&gt;
&lt;br /&gt;
WATCHDOG_DELAY=$(( 3 * INTERVAL ))&lt;br /&gt;
HAVE_WATCHDOG=`grep -q watchdog $IBM_ACPI/fan &amp;amp;&amp;amp; echo true || echo false`&lt;br /&gt;
HAVE_LEVELCMD=`grep -q disengaged $IBM_ACPI/fan &amp;amp;&amp;amp; echo true || echo false`&lt;br /&gt;
&lt;br /&gt;
QUIET=false&lt;br /&gt;
DRY_RUN=false&lt;br /&gt;
DAEMONIZE=false&lt;br /&gt;
AM_DAEMON=false&lt;br /&gt;
KILL_DAEMON=false&lt;br /&gt;
SUSPEND_DAEMON=false&lt;br /&gt;
SYSLOG=false&lt;br /&gt;
DISK_POLL_TIME=-$DISK_POLL_PERIOD&lt;br /&gt;
&lt;br /&gt;
usage() {&lt;br /&gt;
    echo &amp;quot;&lt;br /&gt;
Usage: $0 [OPTION]...&lt;br /&gt;
&lt;br /&gt;
Available options:&lt;br /&gt;
   -s N   Shift up the min temperature thresholds by N degrees&lt;br /&gt;
          (positive for quieter, negative for cooler).&lt;br /&gt;
          Max temperature thresholds are not affected.&lt;br /&gt;
   -S N   Shift up the max temperature thresholds by N degrees&lt;br /&gt;
          (positive for quieter, negative for cooler). DANGEROUS.&lt;br /&gt;
   -t     Test mode&lt;br /&gt;
   -q     Quiet mode&lt;br /&gt;
   -d     Daemon mode, go into background (implies -q)&lt;br /&gt;
   -l     Log to syslog&lt;br /&gt;
   -k     Kill already-running daemon&lt;br /&gt;
   -u     Tell already-running daemon that the system is being suspended&lt;br /&gt;
   -p     Pid file location for daemon mode, default: $PID_FILE&lt;br /&gt;
&amp;quot;&lt;br /&gt;
    exit 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
while getopts 's:S:qtdlp:kuh' OPT; do&lt;br /&gt;
    case &amp;quot;$OPT&amp;quot; in&lt;br /&gt;
        s) # shift thresholds&lt;br /&gt;
            MIN_THRESH_SHIFT=&amp;quot;$OPTARG&amp;quot;&lt;br /&gt;
            ;;&lt;br /&gt;
        S) # shift thresholds&lt;br /&gt;
            MAX_THRESH_SHIFT=&amp;quot;$OPTARG&amp;quot;&lt;br /&gt;
            ;;&lt;br /&gt;
        t) # test mode&lt;br /&gt;
            DRY_RUN=true&lt;br /&gt;
            ;;&lt;br /&gt;
        q) # quiet mode&lt;br /&gt;
            QUIET=true&lt;br /&gt;
            ;;&lt;br /&gt;
        d) # go into background and daemonize&lt;br /&gt;
            DAEMONIZE=true&lt;br /&gt;
            ;;&lt;br /&gt;
        l) # log to syslog&lt;br /&gt;
            SYSLOG=true&lt;br /&gt;
            ;;&lt;br /&gt;
        p) # different pidfile&lt;br /&gt;
            PID_FILE=&amp;quot;$OPTARG&amp;quot;&lt;br /&gt;
            ;;&lt;br /&gt;
        k) # kill daemon&lt;br /&gt;
            KILL_DAEMON=true&lt;br /&gt;
            ;;&lt;br /&gt;
        u) # suspend daemon&lt;br /&gt;
            SUSPEND_DAEMON=true&lt;br /&gt;
            ;;&lt;br /&gt;
        h) # short help&lt;br /&gt;
            usage&lt;br /&gt;
            ;;&lt;br /&gt;
        \?) # error&lt;br /&gt;
            usage&lt;br /&gt;
            ;;&lt;br /&gt;
    esac&lt;br /&gt;
done&lt;br /&gt;
[ $OPTIND -gt $# ] || usage  # no non-option args&lt;br /&gt;
&lt;br /&gt;
# no logger found, no syslog capabilities&lt;br /&gt;
$SYSLOG &amp;amp;&amp;amp; [ ! -x $LOGGER ] &amp;amp;&amp;amp; SYSLOG=false || :&lt;br /&gt;
&lt;br /&gt;
if $DRY_RUN; then&lt;br /&gt;
    echo &amp;quot;$0: Dry run, will not change fan state.&amp;quot;&lt;br /&gt;
    QUIET=false&lt;br /&gt;
    DAEMONIZE=false&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
# Read the temperature sensor on new Hitachi drivers without spinning up the&lt;br /&gt;
# disk or unloading its head (this cannot be done using standard SMART).&lt;br /&gt;
# Works only with drivers/ide or new libata. Equivalent to hdparm -H in &amp;gt;=6.7.&lt;br /&gt;
read_hitachi_temp() { perl - &amp;quot;$@&amp;quot; &amp;lt;&amp;lt;'EOPERL'    # do it in Perl&lt;br /&gt;
    #!/usr/bin/perl&lt;br /&gt;
    $dev=&amp;quot;$ARGV[0]&amp;quot; or die &amp;quot;No device given.\n&amp;quot;;&lt;br /&gt;
    $HDIO_DRIVE_CMD=0x031f;&lt;br /&gt;
    $args=pack(&amp;quot;cccc&amp;quot;,0xf0,0,0x01,0);   # Sense Condition command&lt;br /&gt;
    open(DEV,&amp;quot;&amp;lt;&amp;quot;,$dev) or die &amp;quot;open(\&amp;quot;$dev\&amp;quot;): $!\n&amp;quot;;&lt;br /&gt;
    if (ioctl(DEV,$HDIO_DRIVE_CMD,$args)) {&lt;br /&gt;
       $nsect=(unpack(&amp;quot;cccc&amp;quot;,$args))[2];&lt;br /&gt;
       if ($nsect==0 || $nsect==0xff) {&lt;br /&gt;
           die &amp;quot;Temperature over/underflow.\n&amp;quot;;&lt;br /&gt;
       } elsif ($nsect==0x01) {  # Linux&amp;lt;=2.6.18 doesn't return ATA registers&lt;br /&gt;
           die &amp;quot;Old Linux kernel, readout not supported.\n&amp;quot;;&lt;br /&gt;
       } else {&lt;br /&gt;
           printf &amp;quot;%d\n&amp;quot;, $nsect/2-20;&lt;br /&gt;
       }&lt;br /&gt;
    } else {&lt;br /&gt;
        die &amp;quot;ioctl(\&amp;quot;$dev\&amp;quot;,HDIO_DRIVE_CMD,SENSE_CONDITION): $!\n&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
EOPERL&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
update_disk_temp() {&lt;br /&gt;
    if (( SECONDS &amp;gt;= DISK_POLL_TIME + DISK_POLL_PERIOD )); then&lt;br /&gt;
        LAST_DISK_TEMP=&amp;quot;-128&amp;quot;&lt;br /&gt;
        for DEV in {sda,hda}; do&lt;br /&gt;
            if [[ -b &amp;quot;/dev/$DEV&amp;quot; ]]; then&lt;br /&gt;
                local MODEL=`cat /sys/block/$DEV/device/model`&lt;br /&gt;
                if [[ &amp;quot;$MODEL&amp;quot; =~ &amp;quot;$HITACHI_MODELS&amp;quot; ]]; then&lt;br /&gt;
                    if HTEMP=`read_hitachi_temp &amp;quot;/dev/$DEV&amp;quot; 2&amp;gt;/dev/null`; then&lt;br /&gt;
                        LAST_DISK_TEMP=&amp;quot;$HTEMP&amp;quot;&lt;br /&gt;
                        break&lt;br /&gt;
                    fi&lt;br /&gt;
                fi&lt;br /&gt;
            fi&lt;br /&gt;
        done&lt;br /&gt;
        DISK_POLL_TIME=$SECONDS&lt;br /&gt;
    fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
thermometer() { # output list of temperatures&lt;br /&gt;
    # 8 basic temperatures from ibm-acpi:&lt;br /&gt;
    [[ -r $IBM_ACPI/thermal ]] || { echo &amp;quot;$0: Cannot read $IBM_ACPI/thermal&amp;quot; 2&amp;gt;&amp;amp;1 ; exit 1; }&lt;br /&gt;
    read THERMAL &amp;lt; $IBM_ACPI/thermal&lt;br /&gt;
    read X Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 Z1 Z2 Z3 JNK &amp;lt; &amp;lt;(echo &amp;quot;$THERMAL&amp;quot;) &lt;br /&gt;
    [[ &amp;quot;$X&amp;quot; == &amp;quot;temperatures:&amp;quot; ]] || { echo &amp;quot;$0: Bad readout: \&amp;quot;$THERMAL\&amp;quot;&amp;quot; &amp;gt;&amp;amp;2;  exit 1; }&lt;br /&gt;
    echo -n &amp;quot;$Y1 $Y2 $Y3 $Y4 $Y5 $Y6 $Y7 $Y8 &amp;quot;;&lt;br /&gt;
    # 3 extra temperatures from ibm_acpi:&lt;br /&gt;
    if [[ -n &amp;quot;$Z1&amp;quot; &amp;amp;&amp;amp; -n &amp;quot;$Z2&amp;quot; &amp;amp;&amp;amp; -n &amp;quot;$Z3&amp;quot; ]]; then &lt;br /&gt;
        # ibm_acpi provided extra sensors from at EC offsets 0xC0 to 0xC2?&lt;br /&gt;
        echo -n &amp;quot;$SEP $Z1 $Z2 $Z3 &amp;quot;&lt;br /&gt;
    else &lt;br /&gt;
        [ -r $IBM_ACPI/ecdump ] || { echo &amp;quot;$0: Cannot read $IBM_ACPI/ecdump&amp;quot; 2&amp;gt;&amp;amp;1; exit 1; }&lt;br /&gt;
        perl -e 'm/^EC 0xc0: .(..) .(..) .(..) / and print hex($1).&amp;quot; &amp;quot;.hex($2).&amp;quot; &amp;quot;.hex($3).&amp;quot; &amp;quot; and exit 0 while &amp;lt;&amp;gt;; exit 1' &amp;lt; $IBM_ACPI/ecdump&lt;br /&gt;
    fi&lt;br /&gt;
    # 1 Disk drive temperatures:&lt;br /&gt;
    echo -n &amp;quot;$SEP $LAST_DISK_TEMP &amp;quot;&lt;br /&gt;
    # 1 HDAPS temperature (optional):&lt;br /&gt;
    if [ -r $HDAPS_TEMP ]; then&lt;br /&gt;
        Y=&amp;quot;`cat $HDAPS_TEMP`&amp;quot;&lt;br /&gt;
        (( &amp;quot;$Y&amp;quot; &amp;gt; 100 )) || echo -n &amp;quot;$Y &amp;quot;  # the HDAPS readouts are nonsensical right after resume&lt;br /&gt;
    fi&lt;br /&gt;
    return 0&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
speedometer() { # output fan speed RPM&lt;br /&gt;
    sed -n 's/^speed:[ \t]*//p' $IBM_ACPI/fan&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
setlevel() { # set fan speed level&lt;br /&gt;
    local LEVEL=$1&lt;br /&gt;
    if ! $DRY_RUN; then&lt;br /&gt;
        if $HAVE_LEVELCMD; then&lt;br /&gt;
        	echo &amp;quot;level $LEVEL&amp;quot; &amp;gt; $IBM_ACPI/fan&lt;br /&gt;
	else&lt;br /&gt;
		case &amp;quot;$LEVEL&amp;quot; in&lt;br /&gt;
		(auto)        LEVEL=0x80 ;;&lt;br /&gt;
		(disengaged)  LEVEL=0x40 ;;&lt;br /&gt;
		esac&lt;br /&gt;
        	echo 0x2F $LEVEL &amp;gt; $IBM_ACPI/ecdump&lt;br /&gt;
	fi&lt;br /&gt;
    fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
getlevel() { # get fan speed level&lt;br /&gt;
    perl -e 'm/^EC 0x20: .* .(..)$/ and print $1 and exit 0 while &amp;lt;&amp;gt;; exit 1' &amp;lt; $IBM_ACPI/ecdump&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
log() {&lt;br /&gt;
	$QUIET || echo &amp;quot;&amp;gt; $*&amp;quot;&lt;br /&gt;
	! $SYSLOG || $LOGGER -t &amp;quot;`basename $0`[$$]&amp;quot; &amp;quot;$*&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
cleanup() { # clean up after work&lt;br /&gt;
    $AM_DAEMON &amp;amp;&amp;amp; rm -f &amp;quot;$PID_FILE&amp;quot; 2&amp;gt; /dev/null&lt;br /&gt;
    log &amp;quot;Shutting down, switching to automatic fan control&amp;quot;&lt;br /&gt;
    if ! $DRY_RUN; then&lt;br /&gt;
        if $HAVE_LEVELCMD; then&lt;br /&gt;
            echo enable &amp;gt; $IBM_ACPI/fan&lt;br /&gt;
        else&lt;br /&gt;
            echo 0x2F 0x80 &amp;gt; $IBM_ACPI/ecdump&lt;br /&gt;
        fi&lt;br /&gt;
        if $HAVE_WATCHDOG; then&lt;br /&gt;
            echo watchdog 0 &amp;gt; $IBM_ACPI/fan  # disable watchdog&lt;br /&gt;
        fi&lt;br /&gt;
    fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
floor_div() {&lt;br /&gt;
    echo $(( (($1)+1000*($2))/($2) - 1000 ))&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
set_priority() {&lt;br /&gt;
    ! $DRY_RUN &amp;amp;&amp;amp; renice -10 -p $$&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
init_state() {&lt;br /&gt;
    IDX=0&lt;br /&gt;
    NEW_IDX=0&lt;br /&gt;
    START_TIME=0&lt;br /&gt;
    MAX_IDX=$(( ${#LEVELS[@]} - 1 ))&lt;br /&gt;
    SETTLE_LEFT=0&lt;br /&gt;
    RESETTLE_LEFT=0&lt;br /&gt;
    FIRST=true&lt;br /&gt;
    RESTART=false&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
control_fan() {&lt;br /&gt;
    # Enable the fan in default mode if anything goes wrong:&lt;br /&gt;
    set -e -E -u&lt;br /&gt;
    trap &amp;quot;cleanup; exit 2&amp;quot; HUP INT ABRT QUIT SEGV TERM&lt;br /&gt;
    trap &amp;quot;cleanup&amp;quot; EXIT&lt;br /&gt;
    trap &amp;quot;log 'Got SIGUSR1'; setlevel 0; RESTART=true; sleep $SUSPEND_TIME&amp;quot; USR1&lt;br /&gt;
    if ! $DRY_RUN &amp;amp;&amp;amp; $HAVE_WATCHDOG; then&lt;br /&gt;
        log &amp;quot;Activating watchdog with delay $WATCHDOG_DELAY sec&amp;quot;&lt;br /&gt;
        echo &amp;quot;watchdog $WATCHDOG_DELAY&amp;quot; &amp;gt; $IBM_ACPI/fan&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    init_state&lt;br /&gt;
    log &amp;quot;Starting dynamic fan control&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    # Control loop:&lt;br /&gt;
    while true; do&lt;br /&gt;
        # Get readouts&lt;br /&gt;
        update_disk_temp  # don't do this in a subshell, it's stateful&lt;br /&gt;
        TEMPS=`thermometer`&lt;br /&gt;
        $QUIET || SPEED=`speedometer`&lt;br /&gt;
        $QUIET || ECLEVEL=`getlevel`&lt;br /&gt;
        NOW=`date +%s`&lt;br /&gt;
        if echo &amp;quot;$TEMPS&amp;quot; | grep -q &amp;quot;[^ 0-9$SEP\n-]&amp;quot;; then&lt;br /&gt;
            echo &amp;quot;Invalid character in temperatures: $TEMPS&amp;quot; &amp;gt;&amp;amp;2; exit 1;&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        # Calculate new level index by placing temperatures into regions of &amp;quot;Z&amp;quot; values:&lt;br /&gt;
        # Z &amp;gt;= 2*I means &amp;quot;must be at index I or higher&amp;quot;&lt;br /&gt;
        # Z  = 2*I+1 is hysteresis: &amp;quot;don't step down if currently at I+1&amp;quot;&lt;br /&gt;
        # The set of temperatures for each Z value are as follows, denoting d=(MAX-MIN)/(2*(MAX_IDX-1)) :&lt;br /&gt;
        #   Z=0:         {-infty..MIN-OFF_THRESH_DELTA)     Z=1:   {MIN-OFF_THRESH_DELTA..MIN}&lt;br /&gt;
        #   Z=2:         {MIN..MIN+d}         Z=3:   {MIN+d..MIN+2d}&lt;br /&gt;
        #   Z=4:         {MIN+2d..MIN+3d}     Z=5:   {MIN+3d..MIN+4d}   ...&lt;br /&gt;
        #   Z=2*MAX_IDX: {MAX..infty}&lt;br /&gt;
&lt;br /&gt;
        # Enforce minimum time in this level before stepping down:&lt;br /&gt;
        MAX_Z=$(( IDX&amp;gt;0 ? ( NOW&amp;gt;START_TIME+MIN_WAIT ? 2*(IDX-1) : 2*IDX ) : 0 ))&lt;br /&gt;
&lt;br /&gt;
        # Go over all sensors and compute the Z value; compute the maximum Z and a pretty-printed string:&lt;br /&gt;
        SENSOR=0&lt;br /&gt;
        Z_STR=&amp;quot;$MAX_Z+&amp;quot;&lt;br /&gt;
        TEMP_STR=&amp;quot;&amp;quot;;&lt;br /&gt;
        for TEMP in $TEMPS; do&lt;br /&gt;
            if [[ &amp;quot;$TEMP&amp;quot; == &amp;quot;$SEP&amp;quot; ]]; then   # ignore this (a separator for visual aid)&lt;br /&gt;
                Z_STR=&amp;quot;${Z_STR}$SEP&amp;quot;&lt;br /&gt;
                TEMP_STR=&amp;quot;${TEMP_STR}$SEP &amp;quot;&lt;br /&gt;
                continue&lt;br /&gt;
            fi&lt;br /&gt;
            [ $((2*SENSOR+2)) -le ${#THRESHOLDS[@]} ] ||&lt;br /&gt;
                { echo &amp;quot;Too many sensors, not enough values in THRESHOLDS&amp;quot; 2&amp;gt;&amp;amp;1; exit 1; }&lt;br /&gt;
            if [[ $TEMP == -128 || $TEMP == 128 ]]; then&lt;br /&gt;
                Z='_'; TEMP='_' # inactive sensor&lt;br /&gt;
            else&lt;br /&gt;
                MIN=$((THRESHOLDS[SENSOR*2] + MIN_THRESH_SHIFT))&lt;br /&gt;
                MAX=$((THRESHOLDS[SENSOR*2+1] + MAX_THRESH_SHIFT ))&lt;br /&gt;
                [[ $MAX -le $MIN ]] &amp;amp;&amp;amp; \&lt;br /&gt;
                    { echo 'Reversed temperature thresholds (shifted too much?)' 2&amp;gt;&amp;amp;1; exit 1; }&lt;br /&gt;
                if (( TEMP &amp;lt; MIN - OFF_THRESH_DELTA )); then&lt;br /&gt;
                    Z=0&lt;br /&gt;
                else  # compute Z value for this sensor (see above):&lt;br /&gt;
                    Z=$(( `floor_div $(( 2*(TEMP-MIN)*(MAX_IDX-1) )) $((MAX-MIN))` + 2 ))&lt;br /&gt;
                    [ $Z -ge 1 ] || Z=1&lt;br /&gt;
                    [ $Z -le $((2*MAX_IDX)) ] || Z=$((2*MAX_IDX))&lt;br /&gt;
                fi&lt;br /&gt;
                [ $MAX_Z -gt $Z ] || MAX_Z=$Z&lt;br /&gt;
            fi&lt;br /&gt;
            Z_STR=&amp;quot;${Z_STR}${Z}&amp;quot;&lt;br /&gt;
            TEMP_STR=&amp;quot;${TEMP_STR}${TEMP} &amp;quot;&lt;br /&gt;
            (( ++SENSOR ))&lt;br /&gt;
        done&lt;br /&gt;
        [ $SENSOR -gt 0 ] || { echo &amp;quot;No temperatures read&amp;quot; &amp;gt;&amp;amp;2; exit 1; }&lt;br /&gt;
&lt;br /&gt;
        HYS=$(( (MAX_Z == 2*IDX-1) &amp;amp;&amp;amp; ++MAX_Z )) # hysteresis&lt;br /&gt;
        NEW_IDX=$(( MAX_Z/2 ))&lt;br /&gt;
&lt;br /&gt;
        # Interrupted by a signal?&lt;br /&gt;
        if $RESTART; then&lt;br /&gt;
            init_state&lt;br /&gt;
            log &amp;quot;Resetting state&amp;quot;&lt;br /&gt;
            continue&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        # Transition&lt;br /&gt;
        $FIRST &amp;amp;&amp;amp; OLDLEVEL='?' || OLDLEVEL=${LEVELS[$IDX]}&lt;br /&gt;
        NEWLEVEL=${LEVELS[$NEW_IDX]}&lt;br /&gt;
        $QUIET || echo &amp;quot;L=$OLDLEVEL-&amp;gt;$NEWLEVEL EC=$ECLEVEL RPM=`printf %4s $SPEED` T=($TEMP_STR) Z=$Z_STR&amp;quot;&lt;br /&gt;
        if [ &amp;quot;$OLDLEVEL&amp;quot; != &amp;quot;$NEWLEVEL&amp;quot; ]; then&lt;br /&gt;
            START_TIME=$NOW&lt;br /&gt;
            log &amp;quot;Changing fan level: $OLDLEVEL-&amp;gt;$NEWLEVEL  (temps: $TEMP_STR)&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        setlevel $NEWLEVEL&lt;br /&gt;
&lt;br /&gt;
        sleep $INTERVAL&lt;br /&gt;
&lt;br /&gt;
        # If needed, apply anti-pulsing hack after a settle-down period (and occasionally re-settle):&lt;br /&gt;
        if [ ${ANTIPULSE[${NEW_IDX}]} == 1 ]; then &lt;br /&gt;
            if [ $NEWLEVEL != $OLDLEVEL -o $RESETTLE_LEFT -le 0 ]; then # start settling?&lt;br /&gt;
                SETTLE_LEFT=$SETTLE_TIME&lt;br /&gt;
                RESETTLE_LEFT=$RESETTLE_TIME&lt;br /&gt;
            fi&lt;br /&gt;
            if [ $SETTLE_LEFT -ge 0 ]; then&lt;br /&gt;
                SETTLE_LEFT=$((SETTLE_LEFT-INTERVAL))&lt;br /&gt;
            else&lt;br /&gt;
                setlevel disengaged # disengage briefly to fool embedded controller&lt;br /&gt;
                sleep 0.5&lt;br /&gt;
                RESETTLE_LEFT=$((RESETTLE_LEFT-INTERVAL))&lt;br /&gt;
            fi&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        IDX=$NEW_IDX&lt;br /&gt;
        FIRST=false&lt;br /&gt;
    done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if $KILL_DAEMON || $SUSPEND_DAEMON; then &lt;br /&gt;
    if [ -f &amp;quot;$PID_FILE&amp;quot; ]; then&lt;br /&gt;
	set -e&lt;br /&gt;
	DPID=&amp;quot;`cat \&amp;quot;$PID_FILE\&amp;quot;`&amp;quot; &lt;br /&gt;
	if $KILL_DAEMON; then&lt;br /&gt;
        	kill &amp;quot;$DPID&amp;quot;&lt;br /&gt;
		rm &amp;quot;$PID_FILE&amp;quot;&lt;br /&gt;
		$QUIET || echo &amp;quot;Killed process $DPID&amp;quot;&lt;br /&gt;
	else # SUSPEND_DAEMON&lt;br /&gt;
		kill -USR1 &amp;quot;$DPID&amp;quot;&lt;br /&gt;
		$QUIET || echo &amp;quot;Sent SIGUSR1 to $DPID&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
    else&lt;br /&gt;
        $QUIET || echo &amp;quot;Daemon not running.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
    fi&lt;br /&gt;
elif $DAEMONIZE ; then&lt;br /&gt;
    if [ -e &amp;quot;$PID_FILE&amp;quot; ]; then&lt;br /&gt;
        echo &amp;quot;$0: File $PID_FILE already exists, refusing to run.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
    else&lt;br /&gt;
        set_priority&lt;br /&gt;
        AM_DAEMON=true QUIET=true control_fan 0&amp;lt;&amp;amp;- 1&amp;gt;&amp;amp;- 2&amp;gt;&amp;amp;- &amp;amp;&lt;br /&gt;
        echo $! &amp;gt; &amp;quot;$PID_FILE&amp;quot;&lt;br /&gt;
        exit 0&lt;br /&gt;
    fi&lt;br /&gt;
else&lt;br /&gt;
    [ -e &amp;quot;$PID_FILE&amp;quot; ] &amp;amp;&amp;amp; echo &amp;quot;WARNING: daemon already running&amp;quot;&lt;br /&gt;
    set_priority&lt;br /&gt;
    control_fan&lt;br /&gt;
fi&lt;/div&gt;</summary>
		<author><name>Jeidsath</name></author>
		
	</entry>
</feed>