Raspberry pi fan control and monitoring with bash

From Domoticz
Revision as of 18:52, 5 February 2017 by Lomax (talk | contribs) (→‎Hardware)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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:

A simple MOSFET PWM fan driver circuit

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:

Stripboard layout

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&param=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.

--Lomax (talk) 18:38, 5 February 2017 (CET)