Raspberry pi fan control and monitoring with bash
Purpose
If you're not using the on-board audio, you can take advantage of the hardware Pulse Width Modulation capabilities of the Raspberry Pi to control the speed of a small PC fan, based on the system temperature. The fan's status is monitored and logged in Domoticz (and in the syslog). Note that this script is not dependent on Domoticz in any way other than for monitoring. It is launched and runs independently, so that if Domoticz should stop running, for whatever reason, the script will continue to quietly do its thing, keeping your system cool.
Dependencies
First you'll need to get the PWM activated, which unfortunately isn't as straightforward as one might think; the hardware PWM clock is not initialised at boot, and by default only starts up when the on-board soundcard is in use. There is however an alternative Device Tree Overlay, which activates the hardware PWM clock for general use, see the following guide for how to install it: Using the Raspberry Pi hardware PWM timers. You could of course use a "soft" PWM instead, but these eat up precious CPU cycles, which seems unnecessary when you just want to change the speed of a fan - so this guide assumes the hardware PWM route.
Once you have installed and activated the pwm-with-clk.dtbo overlay linked to above, you should be able to test the PWM by doing (in a root shell):
# cd to the device location
cd /sys/class/pwm/pwmchip0
# Export PWM channel 0
echo 0 > export
# Set the period in nanoseconds
echo 1000000 > pwm0/period
# Set the duty cycle <= period
echo 200000 > pwm0/duty_cycle
# Enable the PWM
echo 1 > pwm0/enable
# Duty cycle can be changed while running
echo 999999 > pwm0/duty_cycle
Domoticz configuration
To be able to monitor the fan status in Domoticz, you need to create a "virtual sensor" of the type "Alert". Call it "Fan Status" for example, and make note of its index number (shown on the "Devices" page in Domoticz).
Hardware
If you have access to an oscilloscope, correct functioning of the hardware PWM can be verified by hooking it up to the pin you chose when enabling the overlay (pwm0 defaults to GPIO_18). Otherwise, you can hook up an LED with a small series resistor (270 Ω) between the PWM pin and a GND pin; by modifying the "duty_cycle" value above you should see the light intensity change. But an output pin on a Raspberry Pi is not powerful enough to directly drive a fan, nor is the voltage sufficient for most PC type fans, which tend to run on 12 V DC (though 5 V varieties also exist). So we will need some external cicuitry to give it a bit more brawn:
Suggested components:
- Q1: any small N-channel MOSFET (e.g. 2N7000/BS170)
- R1: 10 kΩ resistor
- C1: 0.1 uF electrolytic capacitor
- D1: Any small rectifier diode
Nothing too complicated going on there; Q1 will be switched on/off by the PWM signal from the Pi, C1 & D1 smooth out transients, while R1 keeps the MOSFET gate from floating. You can build this on an 8x4 piece of stripboard:
The "RPM" line is just a straight through connection, in case you want to use a 3-pin fan with tachometer output. You can connect this to an input pin on the RasPi if you want to read it, but you'll need to enable the internal pull-up resistor for it to work (the tachometer output on PC fans is an open collector).
To connect it up, run a wire from the PWM output pin on the Pi (the default is GPIO_18 for pwm0, though you can change this in /boot/config.txt) to the PWM input connector on the stripboard. If your fan is a 12 V model you'll need a separate 12 V power supply, which you connect to the GND and VDC inputs. If it's a 5 V model, you can probably get away with powering it from the 5 V pin on the Pi, provided it has a sufficiently powerful PSU - in this case connect the GND input to one of the ground pins on the Pi, and VDC to the Pi's 5 V pin.
Optionally, if you want to monitor the fan's rotational speed, connect a wire from an input pin on the Pi to the RPM pin on the stripboard, and activate its internal pull-up resistor. Since it's not possible to do this from a Bash script, this functionality is not currently supported by the script featured here. If you want to include RPM monitoring you'll need to install a GPIO library, such as WiringPi, and modify the script to count RPM pulses, and report these to Domoticz in a separate virtual sensor.
Installation instructions
Assuming you have followed the instructions for activating the PWM hardware clock at boot, all you need to do is copy the script below into a new file on your Pi, change the "fan_idx" to the index of the Domoticz alert sensor you want to use for monitoring, and make it executable:
sudo nano /usr/local/bin/fancontrol.sh
# Paste the script into this file and save it
# Make it executable with
sudo chmod u+x /usr/local/bin/fancontrol.sh
You can now test the script by running:
sudo /usr/local/bin/fancontrol.sh
Leave the script running, and in another terminal run (for example) "stress" to load the CPU, causing it to heat up:
stress --cpu 4
You should see the fan start spinning, and after stopping "stress", eventually stop. You should also see the status of the "alert" sensor you set up in Domoticz change when the fan status changes. Open the script and edit the temperature breakpoints, and low/high fan-speeds to suit. Note that the fan-speed should never exceed the "period" value ("period" is the PWM frequency in nanoseconds, so 100000 is 1 kHz), and if you set it too low it may not provide enough energy for the fan to start.
Finally, you probably want to launch this script at boot, and leave it running in the background. On a Systemd Linux distribution (like Raspbian), this is very simple; create a new file in /etc/systemd/system/ called fancontrol.service:
sudo nano /etc/systemd/system/fancontrol.service
And paste the following into it:
[Unit]
After=multi-user.target
[Service]
ExecStart=/usr/local/bin/fancontrol.sh
[Install]
WantedBy=default.target
Again, make this executable with:
sudo chmod u+x /etc/systemd/system/fancontrol.service
You then need to tell Systemd about the new service, and enable it to run at boot:
sudo systemctl daemon-reload
sudo systemctl enable fancontrol
That's all there is to it - you can now try rebooting your system and checking if the fancontrol service is running with:
sudo systemctl status fancontrol
Script with comments
#!/bin/bash
# Device references
dev_temp=/sys/class/thermal/thermal_zone0/temp
dev_pwm=/sys/class/pwm/pwmchip0/pwm0
dev_enable=$dev_pwm/enable
dev_duty=$dev_pwm/duty_cycle
dev_period=$dev_pwm/period
# Export pwm0 if it's not available
if [ ! -e $dev_pwm ]; then
echo 0 > /sys/class/pwm/pwmchip0/export
sleep 2
fi
# PWM frequency (nanoseconds)
period=1000000
# temperature breakpoints (millidegrees)
off_low=34000
low_off=30000
low_high=40000
high_low=36000
# fan-speed (nanoseconds)
low=300000
high=999999
# on/off values
off=0
on=1
# domoticz configuration
domoticz="https://127.0.0.1"
fan_idx=31
# update interval (seconds)
interval = 10
# initialise the fan
next=($off $low)
echo $period > $dev_period
echo ${next[0]} > $dev_enable
echo ${next[1]} > $dev_duty
update_status() {
if [[ $(cat $dev_enable) == 1 ]]; then
if [[ $(cat $dev_duty) == $high ]]; then
nvalue=3
svalue="High"
else
nvalue=2
svalue="Low"
fi
else
nvalue=1
svalue="Off"
fi
logger "Fan $svalue"
curl -s -k ""$domoticz"/json.htm?type=command¶m=udevice&idx="$fan_idx"&nvalue="$nvalue"&svalue="$svalue"" > /dev/null
}
while [ : ]
do
temp=$(cat $dev_temp)
current=($(cat $dev_enable) $(cat $dev_duty))
if [ $temp -gt $off_low ]; then
next[0]=$on
if [ $temp -gt $low_high ]; then
next[1]=$high
elif [ $temp -lt $high_low ]; then
next[1]=$low
fi
elif [ $temp -lt $low_off ]; then
next[0]=$off
fi
if [ "${next[*]}" != "${current[*]}" ]; then
echo ${next[1]} > $dev_duty
echo ${next[0]} > $dev_enable
update_status
fi
sleep $interval
done
Link to forum post
There is a topic in the forum if you wish to discuss this script, or if you need help getting it to work: Raspberry Pi hardware PWM fan control and monitoring.