Skip to content
kvmd-fan not functional with PiKVM A3 | Geekworm

kvmd-fan not functional with PiKVM A3


  • I installed the KVM-A3 and so far it works fine.

    One thing that does not work is the fan control. The fan is always spinning on high velocity, creating a lot of noise.

    I enabled the fan control like follows:

    [root@pikvm ~]# rw
    [root@pikvm ~]# systemctl enable --now kvmd-fan
    [root@pikvm ~]# ro

    Also I adjusted the temperatures like follows (and alos tried multiple other values):

    $ rw
    $ touch /etc/conf.d/kvmd-fan
    KVMD_FAN_ARGS="--speed-idle=0 --speed-low=35 --temp-low=55"
    $ ro
    $ systemctl restart kvmd-service

    This does not change anything, the fan is always spinning at full speed even though kvmd-fan is running.

    I checked the logs as follows:

    $ journalctl -u kvmd-fan

    [root@pikvm-a3 kvmd-webterm]# journalctl | grep fan
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[412]: -- INFO ?[174861.099 ? ?signal] -- ===== Stopping by SIGTERM =====
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[412]: -- INFO ?[174861.099 ? ? ?loop] -- Bye-bye
    Nov 09 21:34:37 pikvm-a3 systemd[1]: Stopping PiKVM - A small fan controller daemon...
    Nov 09 21:34:37 pikvm-a3 systemd[1]: kvmd-fan.service: Deactivated successfully.
    Nov 09 21:34:37 pikvm-a3 systemd[1]: Stopped PiKVM - A small fan controller daemon.
    Nov 09 21:34:37 pikvm-a3 systemd[1]: kvmd-fan.service: Consumed 1min 44.998s CPU time.
    Nov 09 21:34:37 pikvm-a3 systemd[1]: Started PiKVM - A small fan controller daemon.
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[3575]: -- INFO ?[174861.193 ? ?config] -- Reading config "/etc/kvmd/fan.ini" ...
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[3575]: -- INFO ?[174861.193 ? fan.pwm] -- Using pin=12 for PWM range 0...1024
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[3575]: -- INFO ?[174861.195 ? ?server] -- Listening HTTP on UNIX socket "/run/kvmd/fan.sock"
    Nov 09 21:34:37 pikvm-a3 kvmd-fan[3575]: -- INFO ?[174861.195 ? ? ?loop] -- Starting the loop ...
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3575]: -- INFO ?[175152.921 ? ?signal] -- ===== Stopping by SIGTERM =====
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3575]: -- INFO ?[175152.921 ? ? ?loop] -- Bye-bye
    Nov 09 21:39:29 pikvm-a3 systemd[1]: Stopping PiKVM - A small fan controller daemon...
    Nov 09 21:39:29 pikvm-a3 systemd[1]: kvmd-fan.service: Deactivated successfully.
    Nov 09 21:39:29 pikvm-a3 systemd[1]: Stopped PiKVM - A small fan controller daemon.
    Nov 09 21:39:29 pikvm-a3 systemd[1]: Started PiKVM - A small fan controller daemon.
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- INFO ?[175153.026 ? ?config] -- Reading config "/etc/kvmd/fan.ini" ...
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- INFO ?[175153.026 ? fan.pwm] -- Using pin=12 for PWM range 0...1024
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- INFO ?[175153.027 ? ?server] -- Listening HTTP on UNIX socket "/run/kvmd/fan.sock"
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- INFO ?[175153.027 ? ? ?loop] -- Starting the loop ...
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- VERB ?[175153.028 ? ? ?loop] -- Significant temperature change: 0.00°C -> 39.43°C
    Nov 09 21:39:29 pikvm-a3 kvmd-fan[3884]: -- VERB ?[175153.028 ? ? ?loop] -- Changed: [--- IDLE ---] temp=39.43°C, speed=0.00% (pwm=0), rpm=0

    As you can see, the last line indicates the fan was supposedly halted at 0 rpm, yet the fan is still spinning at full speed.

    Therefore I conclude that the fan control for my PiKVM A3 is non-functional. Ist this a general issue?

    See also a similar problem description here: https://geekworm.com/community/forum/topic/14811/fan-noise

    Please advise



  • @Peter Schneider  Did you ever solve this issue? I'm fairly picky with noise and this device seems well priced but I don't need to have a whine coming out of the study, my wife will kill me.


  • The 30mm fan that comes with the A3 kit is only a two wire fan, and is connected directly to 5V power.  It runs at full speed all the time.  However, with a little mod, you can get a PWM control of the fan, and a lot quieter experience.  It takes a little soldering.

    Changes required:

    * Get a fan controller that will allow the RPi PWM signal to drive the 5V 30mm fan.  I used the following,  https://www.tindie.com/products/jeremycook/ez-fan2-tiny-raspberry-pi-fan-controller/

    You can't run the fan directly from the RPi GPIO pin, because it draws too much current.

    * Re-wire the fan to go through the controller.  I cut and modified the cable connecting the fan to the Hat, and wired in the fan controller between them.  You can tuck it all in right next to the fan on the hat.

    * Wire the GPIO pin of the fan controller to GPIO 12 of the RPi.  I used a 100 ohm resistor in series to limit the current draw from the RPi GPIO pin, but that's not really necessary unless you plan on using other GPIO pins on your PiKVM (there is a limit to the total current available to GPIO pins).

    * Modify /boot/config.txt to enable PWM on the correct GPIO pins to not interfear with other functions of the PiKVM

    # PWM on GPIO-12
    dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4

    * Get/modify a version of kvmd-fan to "do the right thing".  The stock version does not properly configure the RPi PWM signal to be 25KHz, or "mark-space" mode.  It does not work well as a result.  There is a forked version on github here - https://github.com/agspoon/kvmd-fan.

    * Configure kvmd-fan with the following content in /etc/conf.h/kvmd-fan (tweak as needed).

    KVMD_FAN_ARGS=" --speed-idle=40 --speed-low=50 --speed-high=90 --pwm-high=135 --temp-low=30 --temp-high=60"

    * Enable the kvmd-fan service at boot

    systemctl enable kvmd-fan.service

    My A3 based PiKVM now sits next to me, and is nearly silent.  It will spin up if it has to work hard, but almost never runs at full speed.  I went as far as modifying the kvmd-oled utility to add the current PWM duty cycle to the display (next to the temperature).


  • Note, the kvmd-fan code to fix the PWM mode is on the "pwm_mode" branch of the forked repository linked above.


  • Thank you for the excellent solution, I just ordered this from Tindie. Unfortunately, the shipping for Tindie products to Europe costs more than the fan controller itself.

    In Germany, I only found this at Reichelt: https://www.reichelt.de/gb/de/raspberry-pi-lueftersteuerung-rpi-fan-control-p307000.html?&trstct=pol_15&nbc=1

    But this Reichelt "RPI fan control" does not seem to support PWM (only toggles the fan at a certain temperature point).

    Thanks again.


  • Based on the schematics shown, I think the fan contoller you found would work.  It's a very similar design to the one I used (perhaps better).

    Connect the fan to JP2 pins 1 & 2 (minding the polarity), JP3 to the original fan's +5/GND, and finally JP1 pin 7 to the RPi GPIO 12 (not GPIO 4).

    It's inexpensive, so probably worth a try. :)

    The PWM signal on GPIO 12 controlled by kvmd-fan, will be passed on with more current via Q1 to the fan.


  • @cragen Loch Today I had the time to try out your suggestions.

    I used your suggested https://www.tindie.com/products/jeremycook/ez-fan2-tiny-raspberry-pi-fan-controller/

    When using the default GPIO 12, I get a whirring sound from the fan that came with ez-fan2. I then tried to map GPIO14 to PWN as follows:

    /etc/conf.d/kvmd-fan (EnvironmentFile for $KVMD_FAN_ARGS / by the way there is a tiny typo in the path of your comment):

    KVMD_FAN_ARGS=" --pwm-pin=14 --speed-idle=40 --speed-low=50 --speed-high=90 --pwm-high=135 --temp-low=30 --temp-high=60"

    /boot/config.txt

    dtoverlay=pwm-2chan,pin=14,func=4,pin2=15,func2=4

    Logs  also shows this is picked up
    $ journalctl -u kvmd-fan:
    -- INFO  [10.689   fan.pwm] -- Using pin=14 for PWM range 0...135

    With this GPIO 14, I do not have the whirring sound, but honestly I am unsure if it works at all. The fan spins but I have to see it goes up or down yet.

    You wrote that you "modifed the kvmd-oled utility to add the current PWM duty cycle to the display (next to the temperature)". Could you briefly mention how you achieved this?


  • Peter, you didn't say, but the below assumes you are using an RPi4b.

    With the 4b, you only really have two choices as to which set of PWM outputs to use; GPIO 12&13 or  GPIO 18&19 (pins 32&33, pins 12&35).  Don't confuse GPIO numbers with pin numbers.  https://www.electrorules.com/raspberry-pi-4-gpio-pnout

    For the Geekworm hat in the pikvm design, GPIO 18 is used (for audio I belive), so that leaves only 12&13.  I chose pin 12 (pin #32 PWM0), as that's the default for kvmd-fan.

    Using GPIO 12 should work with the configuration I posted above.  However, you need to use my modified version of kvmd-fan (pwm_mode branch), as this will properly set the frequency and PWM mode.  I've added detailed comments to the changes in fan.c

    The first steps are to make sure that the PWM controller is working correctly.  If you connect the wire that would normally go to GPIO 12 to GND (or leave floating) the fan should stop.  If connected to 3.3v (pin 1) it should spin at full speed.

    If that's working, then you can try manually controlling the PWM signal.  Connect the controller to GPIO 12, and stop the kvmd-fan service (systemctl stop kvmd-fan).  Then use the sysfs interface to change the signal as follows, (https://www.kernel.org/doc/Documentation/pwm.txt

    [root@pikvm ~]# cd /sys/class/pwm/pwmchip0

    [root@pikvm pwmchip0]# ls -l
    total 0
    lrwxrwxrwx 1 root root    0 Jan 28 07:39 device -> ../../../fe20c000.pwm
    drwxr-xr-x 2 root root    0 Jan 28 07:39 power
    lrwxrwxrwx 1 root root    0 Jan 28 07:39 subsystem -> ../../../../../../class/pwm
    --w------- 1 root root 4096 Jan 28 07:39 export
    -r--r--r-- 1 root root 4096 Jan 28 07:39 npwm
    -rw-r--r-- 1 root root 4096 Jan 28 07:39 uevent
    --w------- 1 root root 4096 Jan 28 07:39 unexport

    [root@pikvm pwmchip0]# echo 0 > export 

    [root@pikvm pwmchip0]# ls -l
    total 0
    lrwxrwxrwx 1 root root    0 Jan 28 07:39 device -> ../../../fe20c000.pwm
    drwxr-xr-x 2 root root    0 Jan 28 07:39 power
    drwxr-xr-x 3 root root    0 Jan 28 07:40 pwm0
    lrwxrwxrwx 1 root root    0 Jan 28 07:39 subsystem -> ../../../../../../class/pwm
    --w------- 1 root root 4096 Jan 28 07:40 export
    -r--r--r-- 1 root root 4096 Jan 28 07:39 npwm
    -rw-r--r-- 1 root root 4096 Jan 28 07:39 uevent
    --w------- 1 root root 4096 Jan 28 07:39 unexport

    [root@pikvm pwmchip0]# cd pwm0

    [root@pikvm pwm0]# ls -l
    total 0
    drwxr-xr-x 2 root root    0 Jan 28 07:40 power
    -r--r--r-- 1 root root 4096 Jan 28 07:40 capture
    -rw-r--r-- 1 root root 4096 Jan 28 07:40 duty_cycle
    -rw-r--r-- 1 root root 4096 Jan 28 07:40 enable
    -rw-r--r-- 1 root root 4096 Jan 28 07:40 period
    -rw-r--r-- 1 root root 4096 Jan 28 07:40 polarity
    -rw-r--r-- 1 root root 4096 Jan 28 07:40 uevent

    [root@pikvm pwm0]# echo 40000 > period             (this is 25KHz)

    [root@pikvm pwm0]# echo 40000 > duty_cycle     (100% duty cycle)

    [root@pikvm pwm0]# echo 20000 > duty_cycle     (50% duty cycle)
    [root@pikvm pwm0]# echo 0 > duty_cycle                (stop)

    [root@pikvm pwm0]# echo 15000 > duty_cycle     (minimum my fan will spin)

    [root@pikvm pwm0]# cd ..

    [root@pikvm pwm0]# echo 0 > unexport                  (removes pwm0)

    If everything above works as expected, then the modified kvmd-fan service should control things as expected.  Hope that helps you get to the bottom of it.

    As for the kvmd-oled mod, it's pretty much a hack.  The kvmd-fan process creates a UNIX socket at "/run/kvmd/fan.sock".  This is used by the main kvmd process to read and publish values like temperature, fan speed, fan PWM, etc.  You can see this in the UI under "System->about->hardware".

    You can also read the socket with this curl command,

    curl -s --unix-socket /run/kvmd/fan.sock http://localhost/state

    Here is a diff of my slow and inefficient hack with the original,

    [cragen@pikvm src]$ diff /usr/bin/kvmd-oled /usr/bin/kvmd-oled.orig          
    24d23
    < import subprocess
    92,99d90
    < def _get_fan_speed() -> str:
    <     try:
    <         fan_cmd = "curl -s --unix-socket /run/kvmd/fan.sock http://localhost/state | jshon -e result -e fan -e speed"
    <         fan_speed = int(round(float(subprocess.check_output(fan_cmd,shell=True))))
    <         return f"{fan_speed}%"
    <     except Exception:
    <         # _logger.exception("Can't read fan speed")
    <         return "none"
    253,254c244
    <                     text = f"{socket.getfqdn()}\n{ip}\niface: {iface}\nT: {_get_temp(options.fahrenheit)}"
    <                     text += f"  F: {_get_fan_speed()}"
    ---
    >                     text = f"{socket.getfqdn()}\n{ip}\niface: {iface}\ntemp: {_get_temp(options.fahrenheit)}"
    261,262c251
    <                         text = f"{socket.getfqdn()}\n(__hb__) {_get_uptime()}\nT: {_get_temp(options.fahrenheit)}"
    <                         text += f"  F: {_get_fan_speed()}"
    ---
    >                         text = f"{socket.getfqdn()}\n(__hb__) {_get_uptime()}\ntemp: {_get_temp(options.fahrenheit)}"
    

  • Realized a unified diff might be a bit nicer... ;)

    --- /usr/bin/kvmd-oled.orig	2023-08-26 07:02:20.000000000 +0300
    +++ /usr/bin/kvmd-oled	2024-01-01 03:45:02.217732020 +0300
    @@ -21,6 +21,7 @@
     # ========================================================================== #
     
     
    +import subprocess
     import sys
     import socket
     import signal
    @@ -88,6 +89,14 @@
             # _logger.exception("Can't read temp")
             return "<no-temp>"
     
    +def _get_fan_speed() -> str:
    +    try:
    +        fan_cmd = "curl -s --unix-socket /run/kvmd/fan.sock http://localhost/state | jshon -e result -e fan -e speed"
    +        fan_speed = int(round(float(subprocess.check_output(fan_cmd,shell=True))))
    +        return f"{fan_speed}%"
    +    except Exception:
    +        # _logger.exception("Can't read fan speed")
    +        return "none"
     
     def _get_cpu() -> str:
         st = psutil.cpu_times_percent()
    @@ -241,14 +250,16 @@
                 if device.height >= 64:
                     while stop_reason is None:
                         (iface, ip) = _get_ip()
    -                    text = f"{socket.getfqdn()}\n{ip}\niface: {iface}\ntemp: {_get_temp(options.fahrenheit)}"
    +                    text = f"{socket.getfqdn()}\n{ip}\niface: {iface}\nT: {_get_temp(options.fahrenheit)}"
    +                    text += f"  F: {_get_fan_speed()}"
                         text += f"\ncpu: {_get_cpu()} mem: {_get_mem()}\n(__hb__) {_get_uptime()}"
                         draw(text)
                 else:
                     summary = True
                     while stop_reason is None:
                         if summary:
    -                        text = f"{socket.getfqdn()}\n(__hb__) {_get_uptime()}\ntemp: {_get_temp(options.fahrenheit)}"
    +                        text = f"{socket.getfqdn()}\n(__hb__) {_get_uptime()}\nT: {_get_temp(options.fahrenheit)}"
    +                        text += f"  F: {_get_fan_speed()}"
                         else:
                             (iface, ip) = _get_ip()
                             text = "%s\n(__hb__) iface: %s\ncpu: %s mem: %s" % (ip, iface, _get_cpu(), _get_mem())
    

  • Rereading the manual pwm control commands, I see that I omitted the command to enable it once you have set the period.

    [root@pikvm pwm0]# echo 1 > enable

     


Please login to reply this topic!