#!/usr/bin/env python import argparse import datetime import json import os import pathlib import sys import typing class ThinkfanConfig(typing.TypedDict): sensors: list[dict[str, str]] fans: list[dict[str, str]] levels: list[dict[str, int | list[int]]] def main(sysargs=sys.argv[:]) -> int: parser = argparse.ArgumentParser() parser.add_argument( "-l", "--lower-bound", default=int(os.environ.get("THINKFAN_LOWER_BOUND", "30")), type=int, help="lower bound of sensor value for min fan speed (THINKFAN_LOWER_BOUND)", ) parser.add_argument( "-u", "--upper-bound", default=int(os.environ.get("THINKFAN_UPPER_BOUND", "32767")), type=int, help="upper bound of sensor value for max fan speed (THINKFAN_UPPER_BOUND)", ) parser.add_argument( "-s", "--step", default=int(os.environ.get("THINKFAN_STEP", "4")), type=int, help="step size between fan speed levels (THINKFAN_STEP)", ) args = parser.parse_args(sysargs[1:]) cfg: ThinkfanConfig = { "sensors": [], "fans": [], "levels": [], } acpi_fan = pathlib.Path("/proc/acpi/ibm/fan") if acpi_fan.exists(): cfg["fans"].append({"tpacpi": str(acpi_fan)}) acpi_thermal = pathlib.Path("/proc/acpi/ibm/thermal") n_sensors: int = 0 if acpi_thermal.exists(): cfg["sensors"].append({"tpacpi": str(acpi_thermal)}) n_sensors = len( [ l for l in acpi_thermal.read_text().splitlines() if l.startswith("temperatures:") ][0] .split(":")[1] .strip() .split() ) else: for dirpath, dirnames, filenames in pathlib.Path("/sys/devices").walk( on_error=print, follow_symlinks=False ): if "thinkpad_hwmon" in dirnames: dirnames.remove("thinkpad_hwmon") for filename in filenames: if filename.startswith("temp") and filename.endswith("_input"): cfg["sensors"].append({"hwmon": str(dirpath.joinpath(filename))}) n_sensors += 1 cur: int = args.lower_bound step: int = args.step halfstep = step // 2 for level in range(8): if level == 0: cfg["levels"].append( { "speed": 0, "lower_limit": [0] * n_sensors, "upper_limit": [cur] * n_sensors, } ) continue if level == 7: cfg["levels"].append( { "speed": 7, "upper_limit": [args.upper_bound] * n_sensors, "lower_limit": [cur - halfstep] * n_sensors, } ) continue cfg["levels"].append( { "speed": level, "lower_limit": [cur - halfstep] * n_sensors, "upper_limit": [cur + step] * n_sensors, }, ) cur += step print(f"# thinkfan_confgen.py created {datetime.datetime.now(datetime.UTC)}") print(json.dumps(cfg)) return 0 if __name__ == "__main__": sys.exit(main())