inital commit
This commit is contained in:
21
README.md
21
README.md
@@ -1,3 +1,20 @@
|
||||
# PyFan
|
||||
## PyFAN
|
||||
|
||||
Fan control based on hwmon and pir.
|
||||
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.
|
||||
|
117
pyfan.py
Normal file
117
pyfan.py
Normal file
@@ -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)
|
11
pyfan.service
Normal file
11
pyfan.service
Normal file
@@ -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
|
Reference in New Issue
Block a user