Controlling a CPU/case fan with GPIO PWM based on CPU temperature

An alternative approach (which I find cleaner and also dynamically adjusts the fan), is to set an automation to adjust the fan speed every X seconds and calculate the relative fan speed based on a temperature range. For my use, I want the fan to spin at a minimum of 65% (which seems to be my fans minimum - NASPI), if the temperature goes over 40c (I’m in the UK and the pi stays below 40 usually), dynamically ramp the temps up to 100 at a max temp of 70 and scale down under this temp…

service: light.turn_on
data:
  brightness_pct: >
    {% set cpu_temp = states('sensor.processor_temperature')|float %} {% if
    cpu_temp <= 40 %}
      65
    {% elif cpu_temp > 50 %}
      100
    {% else %}
      {{ ((cpu_temp - 40) * (100 - 65) / (50 - 40) + 65)|round(0) }}
    {% endif %}
target:
  entity_id: light.rpi_cooling_fan

Hope this helps someone.

2 Likes

Hi every one,

I try to install and use rpi_gpio_pwm on my home assistant wich is on a raspberry Pi 4 on a container.
This what I see on logs :slight_smile:

Logger: homeassistant.components.light
Source: custom_components/rpi_gpio_pwm/light.py:71
Integration: Lumière (documentation, issues)
First occurred: 14:54:12 (1 occurrences)
Last logged: 14:54:12

Error while setting up rpi_gpio_pwm platform for light
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/data.py", line 1194, in from_revision
    ) = PI_REVISIONS[revision]
KeyError: 1684095520

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 304, in _async_setup_platform
    await asyncio.shield(task)
  File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/rpi_gpio_pwm/light.py", line 71, in setup_platform
    led = PwmSimpleLed(PWMLED(pin, **opt_args), led_conf[CONF_NAME])
  File "/usr/local/lib/python3.10/site-packages/gpiozero/devices.py", line 108, in __call__
    self = super(GPIOMeta, cls).__call__(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/gpiozero/output_devices.py", line 403, in __init__
    super(PWMOutputDevice, self).__init__(
  File "/usr/local/lib/python3.10/site-packages/gpiozero/output_devices.py", line 83, in __init__
    super(OutputDevice, self).__init__(pin, pin_factory=pin_factory)
  File "/usr/local/lib/python3.10/site-packages/gpiozero/mixins.py", line 85, in __init__
    super(SourceMixin, self).__init__(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/gpiozero/devices.py", line 548, in __init__
    self.pin_factory.reserve_pins(self, pin)
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/pi.py", line 91, in reserve_pins
    super(PiFactory, self).reserve_pins(
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/pi.py", line 92, in <genexpr>
    requester, *(self.pi_info.to_gpio(pin) for pin in pins))
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/__init__.py", line 169, in <lambda>
    lambda self: self._get_pi_info(),
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/pi.py", line 116, in _get_pi_info
    self._info = PiBoardInfo.from_revision(self._get_revision())
  File "/usr/local/lib/python3.10/site-packages/gpiozero/pins/data.py", line 1198, in from_revision
    raise PinUnknownPi('unknown old-style revision "%x"' % revision)
gpiozero.exc.PinUnknownPi: unknown old-style revision "64614220"

Some help please ? :woozy_face:

Hello, total noob here - this page has really helped me in getting my PWM cooling fan functional on my HA RPi 4. I managed to follow the instructions including the automations and it seems to be holding my CPU temp steady at ~43C! For reference if anyone’s setting it up for the first time I have:
-Noctua NF-F12 PMW 5V 120mm fan
-3d-printed Raspberry Pi Case with FULL 120mm FAN (Raspberry Pi 4 Case with FULL 120MM FAN by jesperklang - Thingiverse)
-fan pins plugged into GPIO pins 4(5v) / 5(GND) / 10(GPIO18) (for hardware PWM)

I set the PWM frequency to 25000 based on Noctua’s documentation for the fan. It starts up at 10% and runs totally silent. I have it set to ramp up above 40C but run constantly at 15% below.

I ran into an issue with the configuration.yaml formatting. The #RPI fan control needs to go under command_line as seen here:

light:
  - platform: rpi_gpio_pwm
    leds:
      - name: RPI Cooling Fan
        frequency: 25000
        pin: 18

command_line:
# RPi fan control
  sensor:
#  - platform: command_line (original text)
    name: CPU Temperature
    command: "cat /sys/class/thermal/thermal_zone0/temp"
    unit_of_measurement: "°C"
    value_template: '{{ value | multiply(0.001) | round(1) }}'
    scan_interval: 10

Hope this is helpful for someone, y’all have helped me immensely and I look forward to future projects.

1 Like

Hi,
also for me this thread was very helpful sorting my fan control. Thanks to all who contributed.
As my setup is working now I wanted to share my settings.
RPI 4B 8GB with SSD in an Geekworm NASPI 2.0 case.
HA 2023.10.0 Home Assistant Operating System.
To get the fan running I followed these steps:

  1. Install PIGPIO and Pi GPIO PWM as described above.
  2. in configuration.yaml:
command_line:
# NASPI fan control
  sensor:
    name: CPU Temperature
    command: "cat /sys/class/thermal/thermal_zone0/temp"
    unit_of_measurement: "°C"
    value_template: '{{ value | multiply(0.001) | round(1) }}'
    scan_interval: 10

light:
  - platform: rpi_gpio_pwm
    leds:
      - name: RPI Cooling Fan
        pin: 18
        frequency: 25000
  1. in automations.yaml
- id: '1112223334445'
  alias: CPU_Fan_Control
  description: CPU_Fan_Control
  trigger:
  - platform: state
    entity_id:
    - sensor.cpu_temperature
  condition: []
  action:
  - service: light.turn_on
    data:
      brightness: "{% set cpu_temp = states('sensor.cpu_temperature')|float %}
        {% if cpu_temp < 40 %} 130 {% elif cpu_temp > 45 %} 220 {% else %} 150
        {% endif %}"
    target:
      entity_id: light.rpi_cooling_fan
  mode: single

That’s it.
My CPU fan is running now on a minimal speed to have some airflow in the case. When CPU temperature increases due to load or external conditions the fan runs faster to get back to the normal temperature window.

3 Likes

HACS isn’t supported for HA on a container.

@von-zinzer
GPIO18 is on pin 12 and not 10, which is GPIO 15

As @gorbirad wrote:
I would also like to know how I can measure the speed of the fan.

Why can’t I find the RPI GPIO PWM add-on in HACS? I can only find “Raspberry Pi GPIO” and “Raspberry Pi RF.” It seems like I’m the only one with this issue and I feel like I’m losing my mind. Searching “pwm” only returns “No repositories matching search.”

Not just you, it seems to have been removed judging by other posts like this one. No idea why it was removed, but I imagine you could still install it manually.

Using this post as a guide I made a dumber version if it (it simply turns the fan on and off, no support for different speeds). I’m no HA expert but if you want something similar, here’s what I did:

  1. Install pigpio add-on as described
  2. Skip installing the GPIO PWM add on and install “Raspberry Pi GPIO” from HACS instead
  3. Add the following to your configuration.yaml:
# CPU Temp sensor works fine as is
command_line:
  - sensor:
      name: CPU Temperature
      command: "cat /sys/class/thermal/thermal_zone0/temp"
      unit_of_measurement: "°C"
      value_template: "{{ value | multiply(0.001) | round(1) }}"
      scan_interval: 10

# add rpi_gpio switch for fan, be sure to check the GPIO pin number
switch:
  - platform: rpi_gpio
    switches:
      - port: 14
        name: "CPU Fan"

  1. Add simple automation to control the fan switch based on fan sensor (I did this in the UI, but here’s the resulting YAML)
- id: '1699310657518'
  alias: CPU Fan Control
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.cpu_temperature
  condition:
  - condition: numeric_state
    entity_id: sensor.cpu_temperature
    above: 45
  action:
  - service: switch.turn_on
    data: {}
    target:
      entity_id: switch.cpu_fan
  - wait_for_trigger:
    - platform: numeric_state
      entity_id:
      - sensor.cpu_temperature
      for:
        hours: 0
        minutes: 1
        seconds: 0
      below: 37
  - service: switch.turn_off
    data: {}
    target:
      entity_id: switch.cpu_fan
  mode: single

I’m sure there are better ways to do this, like I imagine you use some command line to set the fan speed with pigpio, but it is working well enough for now.

4 Likes

Thanks for this- I installed Raspberry PI GPIO from HACS (no pigpio needed)
I copied the configuration.yaml as above and (instead of the automation) I configured a generic thermostat to pick up the sensor.cpu_temperature and switch.cpu_fan and it seems to do the trick. It is still on/off control, but a bit handier

Thanks for posting the correct code for the CPU, other guides have the code below which does not work, very frustrating!

Now I have GPIO control and CPU temp working, I have realised my fan is 2 cable, rather than 3. There is no way to control a 2 cable fan right?

sensor:
- platform: command_line
name: CPU Temperature
command: “cat /sys/class/thermal/thermal_zone0/temp”
unit_of_measurement: “°C”
value_template: “{{ value | multiply(0.001) | round(1) }}”
scan_interval: 10

Not an expert but I believe you are correct - two pins would just be power (constant voltage) and ground, the third pin is required for control.

Hi there!
Thanks for sharing your steps which i followed until part 3. Unfortunately, i am still not able to switch on/off the fan with the button (switch) that i have added on my overview page. I m missing something i guess but can’t figure it out.
I am using a RPI4, fan is 3 wires, red wire connected to pin 4 and black to pin 6 and yellow connected to gpio 14 (pin 8). I have installed both add-ons (pigpio and Raspberry Pi GPIO).
My config file is as below:

command_line:
  - sensor:
      name: RPI CPU Temperature
      command: "cat /sys/class/thermal/thermal_zone0/temp"
      unit_of_measurement: "°C"
      value_template: "{{ value | multiply(0.001) | round(1) }}"
      scan_interval: 10

switch:
  - platform: rpi_gpio
    switches:
     - port: 14 
       name: "RPI_cooling_fan"
       unique_id: "rpi_cooling_fan"

The fan won’t stop even if i have switch if off with the button that i have added
image

I am now thinking about a permission issue with gpio pins… anyone has an idea?
Thx for any help!

Hi there, just a question, how do you run your HA? on a RPI? which kind of installation ? HAos? thx

RPi 4 with external SSD and plain vanilla HAos

1 Like

Hello

I followed the steps but now I get the following error code.

Logger: homeassistant.components.light
Source: helpers/entity_platform.py:361
Integration: Licht (documentation, issues)
First occurred: 21:10:09 (1 occurrences)
Last logged: 21:10:09

Error while setting up rpi_gpio_pwm platform for light
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 361, in _async_setup_platform
    await asyncio.shield(task)
  File "/usr/local/lib/python3.11/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/rpi_gpio_pwm/light.py", line 70, in setup_platform
    opt_args["pin_factory"] = PiGPIOFactory(host=led_conf[CONF_HOST], port= led_conf[CONF_PORT])
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/gpiozero/pins/pigpio.py", line 101, in __init__
    raise IOError('failed to connect to %s:%s' % (host, port))
OSError: failed to connect to localhost:8888

Can someone help me with this?

My fan control suddenly stopped working. Two things helped me:

  1. Changed the entity_id in all automations from
entity_id: sensor.cpu_temperature

to

entity_id: sensor.processor_temperature
  1. Added a unique Id for the “RPI Cooling Fan” in the configuration.yaml:
light:
  - platform: rpi_gpio_pwm
    leds:
      - name: RPI Cooling Fan
        pin: xy
        unique_id: whateveridyouwant

Hope that helps one or the other.
Best

1 Like

Hi, I solved the error

OSError: failed to connect to localhost:8888

of the rpi_gpio_pwm add-on, simply by installing the Poeschl pigpio add-on (as written in the rpi_gpio_pwm readme) and then restarting HA.
Installing pigpio is done by adding https://github.com/Poeschl/Hassio-Addons into your HA add-ons custom repositories.
I hope it can be useful to someone.

I would like to use your automation. How do I use your code to create one? I tried copy and paste but get an error “Message malformed: extra keys not allowed @ data[‘service’]”

did you ever find the answer?

Just in case anyone wants it, I made a better python script that has a threshold limit and better status returning:

try:
    fan_speed = data.get("fan_speed")
    threshold = 25
    above_th = fan_speed > threshold
    set_speed = (fan_speed if above_th else threshold+1)
    service_data = {"entity_id": "fan.rpi_cooling_fan", "percentage": set_speed}
    
    if (fan_speed >= 0 and fan_speed <= 100):
        if fan_speed == 0:
            service_data = {"entity_id": "fan.rpi_cooling_fan"}
            hass.services.call("fan", "turn_off", service_data)
            output["result"] = {"level":"success", "message":f"CPU fan off"}
        else:
            hass.services.call("fan", "turn_on", service_data)
            if above_th:
                output["result"] = {"level":"success", "message":f"CPU fan set to {set_speed}%"}
            else:
                output["result"] = {"level":"warn", "message":f"{fan_speed}% is too low, CPU fan set to {set_speed}%"}
    else:
        output["result"] = {"level":"error", "message":f"Got invalid value, fan_speed: {fan_speed}"}
except Exception as e:
    output["result"] = {"level":"error", "message":f"Unknown error: {e}"}

The reason I did this is because I’m using a non-PWM fan, and I built a 0-5v DC control circuit that converts PWM voltage into 0-5v DC to run the fan. Setting the percentage too low causes my fan to make noise and not spin, and having no control ciruit causes the PWM noise to be very audible <100%

What I used:
https://forums.raspberrypi.com/viewtopic.php?t=305481

Hey y’all,

First of all: super impressed by all the comments and efforts here! I would like to ask for your help as well :slight_smile:

My setup is a Raspberry Pi 4 in a Naspi V2 case, which comes with a custom Python script - found here: x-c1/fan-rpi.py at main · geekworm-com/x-c1 · GitHub - that is currently incompatible with Home Assistant. From both the website documentation and the script, I deducted that pin 18 is where the PWM control for the included fan should be at.

  1. I installed the pigpio add-on (not from the original link, but the new repository linked by Poeschl). From what I can see, the add-on is running as planned:
s6-rc: info: service pigpio: starting
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service pigpio successfully started
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
[12:13:23] INFO: Started pigpio
  1. I installed both the originally linked “GPIO-PWM” and the later linked “Rasperry Pi GPIO” integrations. From what I can tell, both are running as well. However, I figured out one issue as I am typing this - I cannot set entities via the configuration.yaml
    What I needed to do instead was to navigate to the “Integrations” menu and click on the RPI GPIO PWM entry. With this, I can manually create an entry for my CPU fan (as a light).

  2. As @drewzh mentioned, the NASPI case fan only kickstarts at a setting of 65%. Delving into the original Python scripts for the NASPI, I was able to find the following entry in the file “fan.py” - found here: x-c1/fan.py at main · geekworm-com/x-c1 · GitHub :

pwm.set_PWM_frequency( servo, 25000 )
pwm.set_PWM_range(servo, 100)

I am no coder or micro-electrician, but if RPI GPIO PWM features a default frequency of 100 and other people here have reported fan frequencies of ~500 max, then 25000 seems a tad high. I took an (un-)educated guess and divided this by a 100, and entered 250 into the frequency. And voila, the fan now operates over the full spectrum of 0 - 100%.

  1. For the CPU temperature, I had to rely on the “System Monitor” add-on rather than configuring manually again, which did not do anything. Here, I enabled the entity “Processor temperature”.

Now where I am a bit stuck: is there a way that I can set an automation to kick in at a processor temperature of ~45°C, starting at 20% “brightness”, and dynamically scale up to 100% brightness by a temperature of e.g., 70°C?
I have had no luck getting python scripts to work on HA to-date, and know too little about the syntax to make more (un-)educated guesses here :smiley:

Thanks in advance!