107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
#!/usr/bin/python3
|
|
import difflib
|
|
import tempfile
|
|
import shutil
|
|
|
|
from tui import TUI
|
|
|
|
class DtParam:
|
|
__slots__ = ('temp', 'hyst', 'speed', 'N')
|
|
def __init__(self, temp : int, hyst : int, speed : int, n: int):
|
|
self.temp = temp # Temperature in MilliCelsius
|
|
self.hyst = hyst # Temperature Hysteresis in MilliCelsius
|
|
self.speed = speed # Fan Speed in 0-255
|
|
self.N = n # Fan Threshold number
|
|
self._assert_valid()
|
|
|
|
def _assert_valid(self):
|
|
assert self.temp // 1000 > 0 and self.temp // 1000 < 100, f"Temperature threshold not in sensible range (0, 100) C, found {self.temp}"
|
|
assert self.hyst // 1000 > 0 and self.hyst // 1000 < 100, f"Temperature hysteresis threshold not in sensible range (0, 100) C, found {self.temp}"
|
|
assert self.speed >= 0 and self.speed <= 255, f"Fan speed not in value range (0, 255), found {self.speed}"
|
|
assert self.N >= 0 and self.N <= 3, f"Threshold number not in supported set (0, 1, 2, 3), found {self.N}"
|
|
|
|
def __str__(self):
|
|
return f"dtparam=fan_temp{self.N}={self.temp},fan_temp{self.N}_hyst={self.hyst},fan_temp{self.N}_speed={self.speed}"
|
|
|
|
|
|
class FanCurve:
|
|
def __init__(self):
|
|
self.config_path = "/tmp/boot/firmware/config.txt"
|
|
self.settings = self._get_current_settings()
|
|
self._assert_valid_cfg()
|
|
|
|
def update(self, settings: list[DtParam]):
|
|
self.settings = settings
|
|
self.settings.sort(key=lambda s: s.temp)
|
|
for i, setting in enumerate(self.settings):
|
|
setting.N = i
|
|
self._assert_valid_cfg()
|
|
self._write_cfg()
|
|
|
|
def _write_cfg(self):
|
|
with open(self.config_path, "r") as f:
|
|
lines = f.readlines()
|
|
old_cfg = [line for line in lines if not (line.startswith("dtparam=fan_temp") or line.startswith("# Auto-Generated Fan Curve"))]
|
|
|
|
insert_at = None
|
|
for i, line in enumerate(old_cfg):
|
|
if line.strip() == "[all]":
|
|
insert_at = i
|
|
|
|
new_lines = ["# Auto-Generated Fan Curve parameters by PiFanUI\n"]
|
|
new_lines.extend([str(setting)+"\n" for setting in self.settings])
|
|
|
|
new_cfg = old_cfg[:insert_at + 1] + new_lines + old_cfg[insert_at + 1:]
|
|
|
|
if new_cfg == lines:
|
|
print("No new changes")
|
|
return
|
|
|
|
with tempfile.NamedTemporaryFile("w", dir="/tmp", delete=False) as new_conf:
|
|
new_conf.writelines(new_cfg)
|
|
|
|
diff = difflib.unified_diff(lines, new_cfg, fromfile=self.config_path, tofile=new_conf.name)
|
|
print(f"Updated values:")
|
|
for line in diff:
|
|
print(line)
|
|
|
|
shutil.move(new_conf.name, self.config_path)
|
|
|
|
def _assert_valid_cfg(self):
|
|
assert len(self.settings) <= 4 and len(self.settings) >= 1, f"Only up to 4 thresholds supported, found {len(self.settings)}"
|
|
assert sorted(self.settings, key=lambda s:s.temp) == sorted(self.settings, key=lambda s:s.N), f"Thresholds are not ordered correctly:\n{[str(s) for s in self.settings]}"
|
|
|
|
def _get_default_settings(self) -> list[DtParam]:
|
|
settings = []
|
|
settings.append(DtParam(temp=55000, hyst=2000, speed=90, n=0))
|
|
settings.append(DtParam(temp=65000, hyst=3000, speed=160, n=1))
|
|
settings.append(DtParam(temp=70000, hyst=4000, speed=200, n=2))
|
|
settings.append(DtParam(temp=72000, hyst=5000, speed=255, n=3))
|
|
return settings
|
|
|
|
def _get_current_settings(self) -> list[DtParam]:
|
|
settings = []
|
|
with open(self.config_path, "r") as f:
|
|
lines = f.readlines()
|
|
config_lines = [line.replace(" ", "").lower().split("#")[0][8:] for line in lines if line.startswith("dtparam=fan_temp")]
|
|
config_lines = sorted([item[8:].replace("_", "=") for string in config_lines for item in string.split(',')])
|
|
cfg = [item[2:].rstrip('\n') for item in config_lines]
|
|
# TODO if not specified fill in with defaults
|
|
assert len(cfg) % 3 == 0, "Partially Specified Fan Behaviour found, make sure to specify _temp, _hyst & _speed"
|
|
for i in range(0, len(config_lines), 3):
|
|
settings.append(DtParam(temp=int(cfg[i]), hyst=int(cfg[i+1][5:]), speed=int(cfg[i+2][6:]), n=i//3))
|
|
return settings
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
sc = FanCurve()
|
|
tui = TUI(sc)
|
|
print("Bye from pifantui!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|