Skip to content

Instantly share code, notes, and snippets.

@johndavisnz
Last active August 30, 2022 20:44
Show Gist options
  • Save johndavisnz/6f21205fa77414bfccd8a44b43c72afb to your computer and use it in GitHub Desktop.
Save johndavisnz/6f21205fa77414bfccd8a44b43c72afb to your computer and use it in GitHub Desktop.
lockerstor as6604t fan control script - beta version
#!/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
@johndavisnz
Copy link
Author

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

@OrcD3vil
Copy link

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.

@johndavisnz
Copy link
Author

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

@johndavisnz
Copy link
Author

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

@OrcD3vil
Copy link

OrcD3vil commented Aug 30, 2022 via email

@johndavisnz
Copy link
Author

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

@OrcD3vil
Copy link

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

@johndavisnz
Copy link
Author

on earlier models the LCD is on a serial port - I'd start here and see what works : https://github.com/mafredri/lcm

@johndavisnz
Copy link
Author

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 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment