#!/bin/bash # Fan speed control / low-battery autosuspend script for Thinkpad # T410, by teknohog # requires: # thinkpad-acpi module with the option fan_control=1 # awk (was: bc, but awk should be universally available) # acpitool # A wider range makes the curve flatter == more stable; Currently I am # maxing out around 56 C (undervolted), so it should never actually go near # 80 C. # 2016-05-13 see new format below MAXTEMP=78000 MINTEMP=48000 # The mapping from the fan level (0 to 7) to the actual # speed/noise/cooling effect is horribly nonlinear and ill-defined. I # think a stepwise function could be much better, but it would take a # little more work... # suspend when battery level is below this percentage AUTOSUSP_BAT=2 # cycle time in seconds WAIT=5 #THERMALFILE=/proc/acpi/ibm/thermal # 2016-05-13 this also works for X220i, with somewhat different format THERMALFILE=/sys/devices/virtual/thermal/thermal_zone0/temp FANFILE=/proc/acpi/ibm/fan MAXFAN=7 MINFAN=0 # Something like this can be used to find the actual maximum, but it # might not be safe/general enough #MAXFAN=$(cat $FANFILE | grep commands | grep level | sed -E 's/.* 0-([0-9]+).*/\1/') # 2016-05-27 passive cooling devices. One should be enough, choose # from something like Processor or intel_powerclamp. Use a higher # minimum temperature to avoid triggering this in normal use. # 2016-05-30 intel_powerclamp seems slightly more effective, so use it # if available for PASS_TYPE in intel_powerclamp Processor; do PASS_DIR=$(for FILE in /sys/class/thermal/cooling_device*/type; do if [ $(cat $FILE) == "$PASS_TYPE" ]; then dirname $FILE; break; fi; done) if [ -d "$PASS_DIR" ]; then break; fi done if [ ! -d "$PASS_DIR" ]; then echo No passive cooling device found, exiting. exit fi PASS_FILE=$PASS_DIR/cur_state PASS_MAX=$(cat $PASS_DIR/max_state) PASS_MIN=0 PASS_MINTEMP=72000 # Now needed more generally. Proper rounding: round(x) = int(x + 0.5) # since int = floor or ceil(x) = int(x + 0.999999) almost everywhere lin_adjust() { # input order: $TEMP $MINTEMP $MAXTEMP $MINLEVEL $MAXLEVEL echo $@ | awk '{level = ($1-$2)/($3-$2)*($5-$4) + $4 if (level > $5) level = $5 if (level < $4) level = $4 print int(level + 0.999999)}' } safequit () { setfan auto exit } fancontrol () { TEMP=$(cat $THERMALFILE) # This should be made more general for multiple coolers, but keep # it simple and light lin_adjust $TEMP $PASS_MINTEMP $MAXTEMP $PASS_MIN $PASS_MAX > $PASS_FILE # Also, the "level $LEVEL" setting complicates this a bit FANLEVEL=$(lin_adjust $TEMP $MINTEMP $MAXTEMP $MINFAN $MAXFAN) echo level $FANLEVEL > $FANFILE } autosuspend () { # we need to read 2 things from one command, better call it only once ACPI=`acpitool` #BAT=`readbattery` BAT=`echo $ACPI | sed -e 's/.* \([0-9]\{1,3\}\.[0-9]\{1,2\}\)%.*/\1/'` # round downwards to be on the safe side BAT=${BAT//\.*/} # Low battery is OK if we are on-line if [ $BAT -le $AUTOSUSP_BAT ] && [ -z "`echo $ACPI | egrep on-?line`" ]; then /root/bin/suspend.sh fi } # don't run multiple instances; if one is running already, two's a crowd #SCRIPT=`basename $0` #if [ "`pgrep $SCRIPT | wc -l`" -gt 2 ]; then exit; fi NRUN=$(ps ax | grep $(basename $0) | egrep -v "grep|basename" | wc -l) if [ $NRUN -gt 2 ]; then exit; fi trap 'safequit' TERM # main loop, different functions should be factored out while true; do fancontrol autosuspend sleep $WAIT done