-
-
Save johndavisnz/6f21205fa77414bfccd8a44b43c72afb to your computer and use it in GitHub Desktop.
#!/bin/bash | |
# quick and dirty fan control script | |
# | |
# updated version that handles different hardware dynamically | |
# | |
# v2.1 30-8-2022 ( initial test version ) | |
# v2.2 31-8-2022 ( abstracted hwmon paths and core/drive counts - should now handle any intel cpu and number of drives ) | |
# | |
# | |
# depends on: | |
# | |
# lm_sensors (to query system sensors) | |
# | |
# smartmontools + hddtemp (to query hdd temp sensors) | |
# | |
# custom it87 kmod to read/set fan speeds | |
# needs to be compiled from source ( https://github.com/a1wong/it87 ) as the debian supplied | |
# it87 doesn't support the IT8625E used in the asustor | |
# uses sqr(temp above threshold)+base_pwm for hdd response curve | |
# uses sqr(temp above threshold/2)+base_pwm for sys response curve | |
# | |
# this gives a slow initial ramp up and a rapid final ramp up across the desired temp range (hdd range 35-50 celsius sys range 50-75 celsius ) | |
# | |
# global variables to tune behaviour | |
# | |
# debug output level : 0 = disabled 1 = minimal 2 = verbose 3 = extremely verbose | |
debug=0 | |
# enable email fan change alets | |
mailalerts=0 | |
# how often we check temps / set speed ( in seconds ) | |
frequency=10 | |
# ratio of how often we update system sensors vs hdd sensors | |
# sampling the sys sensors is lightweight, wheras querying the hdd sensors via SMART disrupts disk i/o - and hdd temp doesn't change that fast | |
ratio_sys_to_hdd=12 | |
# the hdd temperature above which we start to increase fan speed | |
hdd_threshold=35 | |
# the system temperatures above which we start to increase fan speed | |
sys_threshold=50 | |
# minimum pwm value we ever want to set the fan to ( 70 == 1600 rpm, 60 == 1400 ) | |
min_pwm=60 | |
# | |
# determine the /sys/class/hwmon mappings | |
# | |
# which /sys/class/hwmon symlink points to the it87 ( fan speed ) | |
hwmon_it87="/sys/class/hwmon/"`ls -lQ /sys/class/hwmon | grep -i it87 | cut -d "\"" -f 2` | |
if [ $debug -gt 1 ]; then | |
echo "hwmon_it87 is " $hwmon_it87 | |
fi | |
# which /sys/class/hwmon symlink points to the intel coretemp sensors | |
hwmon_coretemp="/sys/class/hwmon/"`ls -lQ /sys/class/hwmon | grep -i coretemp | cut -d "\"" -f 2` | |
if [ $debug -gt 1 ]; then | |
echo "hwmon_coretemp is " $hwmon_coretemp | |
fi | |
# which /sys/class/hwmon symlink points to the acpi sensors ( board temperature sensor ) | |
hwmon_acpi="/sys/class/hwmon/"`ls -lQ /sys/class/hwmon | grep -i thermal_zone0 | cut -d "\"" -f 2` | |
if [ $debug -gt 1 ]; then | |
echo "hwmon_acpi is " $hwmon_acpi | |
fi | |
# set fan speed to desired_pwm | |
function set_fan_speed() { | |
# with the it87 module loaded fan speed is readable via /sys/class/hwmon/hwmonX - the fan speed is on pwm1 | |
# 0 == full speed 255 = stopped | |
# note that this is inverted compared to ADM fanctrl which has 0 = stopped 255 = max | |
local real_pwm | |
let real_pwm=255-$desired_pwm | |
echo $real_pwm >$hwmon_it87/pwm1 | |
} | |
# query fan speed and set the global fan_rpm | |
function get_fan_speed() { | |
fan_rpm=`cat $hwmon_it87/fan1_input` | |
} | |
# query all drive temperatures and set the global hdd_temp to the highest | |
function get_hdd_temp() { | |
# presume we have up to 8 drives - find the highest temperature on them | |
hdd_temp=`hddtemp -n /dev/sd[a,b,c,d,e,f,g] | sort -rn | head -1` | |
if [ $debug -gt 1 ]; then | |
echo "hdd_temp: " $hdd_temp | |
fi | |
} | |
# query system temperatures and set the global sys_temp with the highest | |
function get_sys_temp() { | |
# read the system board temp sensor via acpi | |
local acpi_temp=`cat $hwmon_acpi/temp1_input` | |
acpi_temp=$(expr $acpi_temp / 1000) | |
# read all the temps available via coretemp ( pkg + core1..N ) and return the highest | |
local cpu_temp=`cat $hwmon_coretemp/temp?_input | sort -nr | head -1` | |
cpu_temp=$(expr $cpu_temp / 1000) | |
# choose the greatest of the core and system temps | |
sys_temp=$(( $acpi_temp > $cpu_temp ? $acpi_temp : $cpu_temp )) | |
if [[ $debug -gt 1 ]] ; then | |
echo "acpi temp:" $acpi_temp " cpu_temp:" $cpu_temp " sys_temp: " $sys_temp | |
fi | |
} | |
# map the current hdd_temp to a desired pwm value | |
# | |
# we use base_pwm_value+sqr(hdd_temp-hdd_threshold) to get a nice curve | |
# | |
function map_hdd_temp() { | |
if [[ $hdd_temp -le $hdd_threshold ]] ; then | |
if [[ $debug -gt 1 ]]; then | |
echo "hdd temp (" $hdd_temp ") is under threshold" | |
fi | |
hdd_desired_pwm=$min_pwm | |
else | |
if [[ $debug -gt 1 ]] ; then | |
echo "hdd temp (" $hdd_temp ") is over threshold" | |
fi | |
# get the difference above threshold | |
let hdd_desired_pwm=$hdd_temp-$hdd_threshold | |
# square it | |
let hdd_desired_pwm=$hdd_desired_pwm*$hdd_desired_pwm | |
# add it to the base_pwm value | |
let hdd_desired_pwm=$min_pwm+$hdd_desired_pwm | |
fi | |
if [[ $hdd_desired_pwm -gt 255 ]] ; then | |
# over max - truncate to max | |
hdd_desired_pwm=255 | |
fi | |
if [[ $debug -gt 2 ]] ; then | |
echo "+++ map_hdd_temp - hdd_desired_pwm : " $hdd_desired_pwm | |
fi | |
} | |
# map the current sys_temp to a desired pwm value | |
# | |
# we use base_pwm_value+sqr((hdd_temp-hdd_threshold)/2) to get a nice curve | |
# | |
function map_sys_temp() { | |
if [[ $sys_temp -le $sys_threshold ]] ; then | |
if [[ $debug -gt 1 ]] ; then | |
echo "sys temp (" $sys_temp ") is under threshold" | |
fi | |
sys_desired_pwm=$min_pwm | |
else | |
if [[ $debug -gt 1 ]] ; then | |
echo "sys temp (" $sys_temp ") is over threshold" | |
fi | |
# get the difference above threshold | |
let sys_desired_pwm=$sys_temp-$sys_threshold | |
# halve the difference | |
let sys_desired_pwm=$sys_desired_pwm/2 | |
# then square it | |
let sys_desired_pwm=$sys_desired_pwm*$sys_desired_pwm | |
# add it to the base pwm value | |
let sys_desired_pwm=$min_pwm+$sys_desired_pwm | |
fi | |
if [[ $sys_desired_pwm -gt 255 ]] ; then | |
# over max - truncate to max | |
hdd_desired_pwm=255 | |
fi | |
if [[ $debug -gt 2 ]] ; then | |
echo "+++ map_sys_temp - sys_desired_temp : " $sys_desired_pwm | |
fi | |
} | |
# determine desired zone based on current temp | |
function get_desired_pwm() { | |
map_sys_temp | |
map_hdd_temp | |
if [[ $hdd_desired_pwm -gt $sys_desired_pwm ]] ; then | |
desired_pwm=$hdd_desired_pwm | |
if [[ $debug -gt 2 ]] ; then | |
echo ">>> choosing hdd pwm value - desired pwm " $desired_pwm | |
fi | |
else | |
desired_pwm=$sys_desired_pwm | |
if [[ $debug -gt 2 ]] ; then | |
echo ">>> choosing sys pwm value - desired pwm " $desired_pwm | |
fi | |
fi | |
} | |
## MAIN ################################################################################# | |
# get initial temperatures | |
get_sys_temp | |
get_hdd_temp | |
get_fan_speed | |
last_sys_temp=$sys_temp | |
last_hdd_temp=$hdd_temp | |
# we use the variables 'last_pwm' 'last_sys_temp' and 'last_hdd_temp' to track what the pwm/temps values were last time | |
# thru the loop - so we only change the fan speeds when there's a state change as opposed to every iteration | |
# get initial pwm value | |
get_desired_pwm | |
last_pwm=$desired_pwm | |
# set initial fan speed | |
if [[ $debug -gt 1 ]] ; then | |
echo "initial fan pwm " $desired_pwm | |
fi | |
set_fan_speed | |
# now loop forever monitoring and reacting | |
cycles=$ratio_sys_to_hdd | |
while true; do | |
# update sensor readings | |
get_fan_speed | |
get_sys_temp | |
if [[ $cycles -eq 1 ]] ; then | |
if [[ $debug -gt 1 ]] ; then | |
echo "sampling hdd sensor" | |
fi | |
get_hdd_temp | |
cycles=$ratio_sys_to_hdd | |
else | |
if [[ $debug -gt 1 ]] ; then | |
echo "skipping hdd sensor update" | |
fi | |
let cycles=$cycles-1 | |
fi | |
# update target pwm value based on readings | |
get_desired_pwm | |
if [[ $debug -gt 1 ]] ; then | |
echo "desired pwm " $desired_pwm " last_pwm " $last_pwm | |
echo "sys_temp " $sys_temp " last_sys_temp " $last_sys_temp | |
echo "hdd_temp " $hdd_temp " last_hdd_temp " $last_hdd_temp | |
fi | |
if [[ $desired_pwm -gt $last_pwm ]] ; then | |
# fan speed increase desired - react immediately | |
if [[ $debug -ge 1 ]] ; then | |
echo "!!!! fan speed INCREASE : hdd_temp " $hdd_temp " sys_temp " $sys_temp " fan_rpm " $fan_rpm " - changing fan speed to " $desired_pwm | |
fi | |
if [[ $mailalerts -ge 1 ]] ; then | |
echo "!!!! fan speed INCREASE : hdd_temp " $hdd_temp " sys_temp " $sys_temp " fan_rpm " $fan_rpm " - changing fan speed to" $desired_pwm | mail root@as5202t.home -s "AS5202T - temperature alert" | |
fi | |
# set the fan speed | |
set_fan_speed | |
# update state tracking variables ONLY when there's a change in the target fan speed | |
last_pwm=$desired_pwm | |
last_sys_temp=$sys_temp | |
last_hdd_temp=$hdd_temp | |
fi | |
if [[ $desired_pwm -lt $last_pwm ]] ; then | |
# fan speed decrease desired | |
# calculate deltas from last reading for each sensor | |
let hdd_delta=$last_hdd_temp-$hdd_temp | |
let sys_delta=$last_sys_temp-$sys_temp | |
if [[ $debug -gt 1 ]] ; then | |
echo "current sys_delta " $sys_delta " current hdd_delta " $hdd_delta | |
fi | |
# we need to apply 2 degrees of hysteresis on hdd_temp and 4 degrees on sys_temp to prevent fan speed hunting | |
if [[ $hdd_delta -gt 2 ]] || [[ $sys_delta -gt 4 ]] ; then | |
# we've got sufficient downward temp delta - actually change the fan speed | |
if [[ $debug -ge 1 ]] ; then | |
echo "!!!! fan speed DECREASE : hdd_temp " $hdd_temp " sys_temp " $sys_temp " fan_rpm " $fan_rpm " - changing fan speed to " $desired_pwm | |
fi | |
if [[ $mailalerts -ge 1 ]] ; then | |
echo "!!!! fan speed DECREASE : hdd_temp " $hdd_temp " sys_temp " $sys_temp " fan_rpm " $fan_rpm " - changing fan speed to" $desired_pwm | mail root@as5202t.home -s "AS5202T - temperature alert" | |
fi | |
# set the fan speed | |
set_fan_speed | |
# update state tracking variables ONLY when there's a change in the target fan speed | |
last_pwm=$desired_pwm | |
last_sys_temp=$sys_temp | |
last_hdd_temp=$hdd_temp | |
else | |
# not enough downward delta to trigger an actual change yet | |
if [[ $debug -ge 1 ]] ; then | |
echo "!!!! fan speed DECREASE : hdd_temp " $hdd_temp " sys_temp " $sys_temp " - but not enough delta (" $hdd_delta " " $sys_delta ") yet!" | |
fi | |
fi | |
fi | |
sleep $frequency | |
done |
I wanted to Thank You for modifying your script for my box, I wanted to see if it was ok if I posted on the TrueNAS Forums to share with others that might be using the same box as me. I don't want to post without your permission.
absolutely no problem - I'll go link the '6604t' version from the main gist though as the background stuff on installng lm_sensors etc is in there, so just share the main ( public ) link https://gist.github.com/johndavisnz/bae122274fc6f0e006fdf0bc92fe6237
I've now got a alpha version running which dynamically determines the hwmon mappings and deals with variable numbers of drives and cores, which means it should work for pretty much any intel based NAS model - but I'll do some more testing on it before posting it
system independent version works on the as5202t - if you can confirm it works on the 6604t I'll update the main gist page with this version
It is working for me I changed the debug and mail and it seems to ramp up and down based on heat. Thank you so much! Only thing is to finish setting it up and see if someone figured out the LCD display hehe
on earlier models the LCD is on a serial port - I'd start here and see what works : https://github.com/mafredri/lcm
I've updated the version on the main page and removed the link to this beta version ( any future updates will be to the main version )
if you don't want email alerts change the mailalerts variable ( line 33 ) to 0 - similarly once you're sure the temperature monitoring and fan response is working you can change debug ( line 30 ) to 0 so you don't get piles of output cluttering up the console/system logs