diff --git a/config-example.yaml b/config-example.yaml index 28a3bab..bea831e 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -6,8 +6,10 @@ # otherwise INFO is a good value. loglevel: DEBUG -# Set interval (seconds) in which PyFan is checking your temp sources and adjusts PWM values. -interval: 1 +# Frequency in seconds of feeding sensors values into PID. Higher frequency (lower value) means PID can react faster. +# Fans only get set every thermalzones->interval seconds, regardless of this value. +# Must be lower then your lowest thermalzone interval. +pid_interval: 0.2 thermalzones: - name: GPU+SYSTEM @@ -19,6 +21,11 @@ thermalzones: # If not, check what you sensor is outputting if you try to read from it. factor: 1000 + # Frequency in which fan speed should be adjusted in seconds. + # PyFan reads sensors values from hwmon every pid_interval, but only sets fan speeds in this interval. + # Can't be lower then pid_interval. + interval: 3 + # Define all fans that this thermal zone is going to control. # There is no limit to how many fans one thermal zone can control. # You can have different limitations on your fans, for example: @@ -50,6 +57,7 @@ thermalzones: - coretemp/temp1_input - amdgpu/temp1_input factor: 1000 + interval: 3 fan: - it8686/pwm1: [ 100, 200 ] target: 50 diff --git a/pyfan.py b/pyfan.py index f1351e5..577d32b 100644 --- a/pyfan.py +++ b/pyfan.py @@ -15,17 +15,25 @@ class ThermalZone: def __init__(self, config, pyfan_parent) -> None: self.fans = config["fan"] self.temp_source = config["source"] - self.pid = PID(config["pid"]["p"], config["pid"]["i"], config["pid"]["d"], setpoint=0) - self.pid.output_limits = (0, 255) self.factor = 1 / config["factor"] self.name = config["name"] self.target = config["target"] - self.hwmap = pyfan_parent.hwmap self.pyfan = pyfan_parent + self.hwmap = self.pyfan.hwmap self.alias_replace = re.compile("|".join(self.hwmap.keys())) + + if "interval" not in config: + config["interval"] = 3 + logging.getLogger("pyfan").warning( + "[%s] No interval specified, using default. This is deprecated since 1.6 and may be removed in future " + "versions. See example config for reference.", self.name) + + self.pid = PID(config["pid"]["p"], config["pid"]["i"], config["pid"]["d"], setpoint=0, + sample_time=config["interval"]) + self.pid.output_limits = (0, 255) self.setup_pwm() - logging.getLogger("pyfan").info("[%s] Source=%s Fans=%s Factor=%d PID=%s", self.name, self.temp_source, + logging.getLogger("pyfan").info("[%s] Source=%s Fans=%s Factor=%f %s", self.name, self.temp_source, self.fans, self.factor, self.pid) def eval(self): @@ -44,20 +52,21 @@ class ThermalZone: logging.getLogger("pyfan").warning( "[%s] max/min for %s was not set correctly (%s)", self.name, fan, fan_val) - self.write_sysfs(fan, min(fan_val[1], max(val, fan_val[0]))) - else: + if self.read_sysfs(fan) != min(fan_val[1], max(val, fan_val[0])): + self.write_sysfs(fan, min(fan_val[1], max(val, fan_val[0]))) + elif self.read_sysfs(fan) != min(val, fan_val): self.write_sysfs(fan, min(val, fan_val)) logging.getLogger("pyfan").debug("[%s] %s=%i%%", self.name, fan, int(int(self.read_sysfs(fan)) / 255 * 100)) - else: + elif self.read_sysfs(target_fan) != val: self.write_sysfs(target_fan, val) except OSError as err: logging.getLogger("pyfan").warning("[%s] Failed to set pwm, trying to reset it. (%s)", self.name, err.strerror) self.setup_pwm(1) - logging.getLogger("pyfan").debug("[%s] %i%% D:%iC T:%iC pid:%s", self.name, int(val / 255 * 100), diff, + logging.getLogger("pyfan").debug("[%s] %i%% D:%iC T:%iC %s", self.name, int(val / 255 * 100), diff, self.get_temp(), self.pid) def get_temp(self): @@ -118,18 +127,19 @@ class PyFan: self.config = self.__load_config(config) logging.basicConfig(level=logging.getLevelName(self.config["loglevel"])) self.zones = [] - self._hwmon_map = {} - - if "interval" in self.config: - self.interval = self.config["interval"] + if "pid_interval" not in self.config: + self.interval = 0.2 + logging.getLogger("pyfan").warning( + "No pid_interval specified, using default. This is deprecated since 1.6 and may be removed in future " + "versions. See example config for reference.") else: - self.interval = 1 + self.interval = self.config["pid_interval"] for zone in self.config["thermalzones"]: self.zones.append(ThermalZone(zone, self)) logging.getLogger("pyfan").info( - "Finished creating %d thermal zones, checking all %d seconds", len(self.zones), self.interval) + "Created %d thermal zones, pid_interval=%f.", len(self.zones), self.interval) def __enter__(self): return self @@ -144,16 +154,16 @@ class PyFan: @property def hwmap(self): - self._hwmon_map.clear() + hwmon_map = {} names = glob.glob(SYSFS_HWMON_BASE + "hwmon*/name") for name in names: hwmon = name.split("/")[-2] with open(name) as file: - hwname = file.read().strip() - self._hwmon_map[hwname] = hwmon - return self._hwmon_map + hw_name = file.read().strip() + hwmon_map[hw_name] = hwmon + return hwmon_map @staticmethod def __load_config(path):