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

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