#!/usr/bin/env python import logging import sys 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): with open(build_pwm_path(path), 'w') as sysfs_f: sysfs_f.write(str(value)) 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.getLogger("pyfan").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) try: 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)) except OSError as err: logging.getLogger("pyfan").warning("Failed to set pwm, trying to reset it. (%s)" % err.strerror) self.setup_pwm(1) p, i, d = self.pid.components logging.getLogger("pyfan").debug( "[{name}] {val}% ({diff}C/{temp}C) ({p}|{i}|{d})".format(name=self.name, val=int(val / 255 * 100), diff=diff, temp=self.get_temp(), p=int(p), i=int(i), d=int(d))) 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"])) 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.getLogger("pyfan").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)