Skip to content

Instantly share code, notes, and snippets.

@johndavisnz
Last active February 19, 2024 01:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johndavisnz/06a5e1aabaf878add0ad95669b3a0b3d to your computer and use it in GitHub Desktop.
Save johndavisnz/06a5e1aabaf878add0ad95669b3a0b3d to your computer and use it in GitHub Desktop.
installiang debian11 on Nimbustor 2/4 - step 4 : custom fancontrol script

the stock fancontrol package that is part of lm_sensors works ok for a lot of systems, but the lockerstors are unusual in that they only have ONE fan to cool both the CPU and the HDDs

this presents a problem as we need additional logic to handle this - so I have created my own custom fan control script - place it in /home/root

first install smartmontools and hddtemp

apt install smartmontools
apt install hddtemp

next edit /etc/rc.local

#!/bin/sh -e
#launch custom fan control script
nohup /root/temp_monitor.sh &

and make it executable

chmod +x /etc/rc.local

and also make /root/temp_monitor.sh executable

chmod +x /root/temp_monitor.sh

reboot - you should hear the fan go to full speed briefly, then drop back down once the script runs

[ the script below is an updated version that configures itself on startup for different sensors/drives/cores - it has been tested on the as5202t ( 2 core / 2 bay ) and as6604t ( quad core / 4 bay ) ]

#!/bin/bash
# Asustor fan control script
#
# updated version that handles different models 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=1
# address we send email alerts to
mail_address=root@locahost
# hostname we use to identify ourselves in email alerts
mail_hostname=AS5202T.home
if [ $debug -gt 1 ]; then
echo "STARTUP: mail_address=" $mail_address " mail_hostname=" $mail_hostname
fi
# 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 "STARTUP: hwmon_it87=" $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 "STARUP: hwmon_coretemp=" $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 "STARTUP: hwmon_acpi=" $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 "GET_HDD_TEMP: 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 "GET_SYS_TEMP: 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 "MAP_HDD_TEMP: hdd temp=" $hdd_temp " and is under threshold"
fi
hdd_desired_pwm=$min_pwm
else
if [[ $debug -gt 1 ]] ; then
echo "MAP_HDD_TEMP: hdd temp=" $hdd_temp " and 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 1 ]] ; 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 "MAP_SYS_TEMP: sys_temp=" $sys_temp " and is under threshold"
fi
sys_desired_pwm=$min_pwm
else
if [[ $debug -gt 1 ]] ; then
echo "MAP_SYS_TEMP: sys_temp=" $sys_temp " and 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 1 ]] ; then
echo "MAP_SYS_TEMP: sys_desired_pwm=" $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 "GET_DESIRED_PWM: choosing hdd_pwm value - desired_pwm=" $desired_pwm " hdd_desired_pwm=" $hdd_desired_pwm " sys_desired_pwm=" $sys_desired_pwm
fi
else
desired_pwm=$sys_desired_pwm
if [[ $debug -gt 2 ]] ; then
echo "GET_DESIRED_PWM: choosing sys_pwm value - desired_pwm=" $desired_pwm " hdd_desired_pwm=" $hdd_desired_pwm " sys_desired_pwm=" $sys_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 "MAIN: 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 "MAIN: sampling hdd sensor"
fi
get_hdd_temp
cycles=$ratio_sys_to_hdd
else
if [[ $debug -gt 1 ]] ; then
echo "MAIN: 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 "MAIN: desired_pwm=" $desired_pwm " last_pwm=" $last_pwm
echo "MAIN: sys_temp=" $sys_temp " last_sys_temp=" $last_sys_temp
echo "MAIN: hdd_temp=" $hdd_temp " last_hdd_temp=" $last_hdd_temp
echo "MAIN: fan_rpm=" $fan_rpm
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 $mail_address -s "$mail_name - 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 "MAIN: 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 $mail_address -s "$mail_name - 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
if [[ $debug -ge 1 ]] ; then
echo "MAIN: sleeping for " $frequency " seconds"
fi
sleep $frequency
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment