From 8aac0de0e16a478f15f4613bc06a932e82afca67 Mon Sep 17 00:00:00 2001 From: Giovanni Harting <539@idlegandalf.com> Date: Tue, 3 Sep 2019 15:26:39 +0200 Subject: [PATCH] inital commit --- README.md | 21 ++++++++- pyfan.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ pyfan.service | 11 +++++ 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 pyfan.py create mode 100644 pyfan.service diff --git a/README.md b/README.md index 83cb864..3fffce5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ -# PyFan +## PyFAN -Fan control based on hwmon and pir. \ No newline at end of file +This simple python script utilizes PID as base for fan control. + +# Usage + +Put your config in /etc/pyfan (reference example config) and enable pyfan as a service (service file also available here). + +# Config + +To know which hwmon is what device and what pwm controls what fan, the following commands can help you: + +List all devices + names: +```tail /sys/class/hwmon/hwmon*/name``` + +Enable control for a specific pwm: ```echo 1 > /sys/class/hwmon/hwmonX/pwmX_enable``` + +Set fan speed: ```echo [0-255] > /sys/class/hwmon/hwmonX/pwmX``` + +After you have figured out which fan is controlled by what pwm, you can adjust your config. You can have as many thermal zones as you want, just repeate them like shown. diff --git a/pyfan.py b/pyfan.py new file mode 100644 index 0000000..fde0062 --- /dev/null +++ b/pyfan.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import sys +import logging +from time import sleep +import yaml +from simple_pid import PID + +SYSFS_HWMON_BASE = "/sys/class/hwmon/" + + +def build_pwm_path(specific): + return SYSFS_HWMON_BASE + specific + + +def write_sysfs(path, value): + try: + with open(build_pwm_path(path), 'w') as sysfs_f: + sysfs_f.write(str(value)) + except OSError as err: + print("WARN:", err.strerror) + + +def read_sysfs(path): + with open(build_pwm_path(path)) as sysfs_f: + return sysfs_f.readline() + + +def set_pwm_mode(path, value=1): + write_sysfs(path + "_enable", value) + + +class ThermalZone: + def __init__(self, temp_source, fans, p, i, d, target, factor, name) -> None: + self.fans = fans + self.temp_source = temp_source + self.pid = PID(p, i, d, setpoint=0) + self.pid.output_limits = (0, 255) + self.factor = 1 / factor + self.name = name + self.target = target + self.setup_pwm() + + logging.info("[{zone}] Source={source} Fans={fans} Factor={factor} PID={pid}".format(zone=name, + source=temp_source, + fans=fans, + factor=factor, + pid=( + p, i, d))) + + def eval(self): + diff = self.target - self.get_temp() + val = self.pid(diff) + + for target_fan in self.fans: + if type(target_fan) is dict: + write_sysfs(list(target_fan.keys())[0], min(int(val), list(target_fan.values())[0])) + else: + write_sysfs(target_fan, int(val)) + + logging.debug( + "[{name}] {val}% ({diff}C/{temp}C)".format(name=self.name, val=int(val / 255 * 100), diff=diff, + temp=self.get_temp())) + + def get_temp(self): + return float(read_sysfs(self.temp_source)) * self.factor + + def restore(self): + self.setup_pwm(2) + + def setup_pwm(self, value=1): + for target_fan in self.fans: + if type(target_fan) is dict: + set_pwm_mode(list(target_fan.keys())[0], value) + else: + set_pwm_mode(target_fan, value) + + +class PyFan: + def __init__(self, config="/etc/pyfan") -> None: + self.config = self.__load_config(config) + logging.basicConfig(level=logging.getLevelName(self.config["loglevel"]), style='{') + self.zones = [] + + for zone in self.config["thermalzones"]: + self.zones.append( + ThermalZone(zone["source"], zone["fan"], zone["pid"]["p"], zone["pid"]["i"], zone["pid"]["d"], + zone["target"], + zone["factor"], zone["name"])) + + logging.info("Finished creating %d thermal zones." % len(self.zones)) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for zone in self.zones: + zone.restore() + + def eval(self): + for zone in self.zones: + zone.eval() + + @staticmethod + def __load_config(path): + with open(path) as cfg_file: + return yaml.safe_load(cfg_file) + + +if __name__ == "__main__": + with PyFan() as pyfan: + while True: + try: + pyfan.eval() + sleep(1) + except KeyboardInterrupt: + sys.exit(0) diff --git a/pyfan.service b/pyfan.service new file mode 100644 index 0000000..616f1ac --- /dev/null +++ b/pyfan.service @@ -0,0 +1,11 @@ +[Unit] +Description=Start PyFan fan control +ConditionFileNotEmpty=/etc/pyfan +After=lm_sensors.service + +[Service] +Type=simple +ExecStart=/usr/sbin/pyfan.py + +[Install] +WantedBy=multi-user.target