-
-
Notifications
You must be signed in to change notification settings - Fork 14.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pid-fan-controller: init at 0.1.1 #336849
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,188 @@ | ||||||||||
{ | ||||||||||
lib, | ||||||||||
config, | ||||||||||
pkgs, | ||||||||||
... | ||||||||||
}: | ||||||||||
let | ||||||||||
cfg = config.services.pid-fan-controller; | ||||||||||
heatSource = { | ||||||||||
options = { | ||||||||||
name = lib.mkOption { | ||||||||||
type = lib.types.uniq lib.types.nonEmptyStr; | ||||||||||
description = "Name of the heat source."; | ||||||||||
}; | ||||||||||
wildcardPath = lib.mkOption { | ||||||||||
type = lib.types.nonEmptyStr; | ||||||||||
description = '' | ||||||||||
Path of the heat source's `hwmon` `temp_input` file. | ||||||||||
This path can contain multiple wildcards, but has to resolve to | ||||||||||
exactly one result. | ||||||||||
''; | ||||||||||
}; | ||||||||||
pidParams = { | ||||||||||
setPoint = lib.mkOption { | ||||||||||
type = lib.types.ints.unsigned; | ||||||||||
description = "Set point of the controller in °C."; | ||||||||||
}; | ||||||||||
P = lib.mkOption { | ||||||||||
description = "K_p of PID controller."; | ||||||||||
type = lib.types.float; | ||||||||||
}; | ||||||||||
I = lib.mkOption { | ||||||||||
description = "K_i of PID controller."; | ||||||||||
type = lib.types.float; | ||||||||||
}; | ||||||||||
D = lib.mkOption { | ||||||||||
description = "K_d of PID controller."; | ||||||||||
type = lib.types.float; | ||||||||||
}; | ||||||||||
}; | ||||||||||
}; | ||||||||||
}; | ||||||||||
|
||||||||||
fan = { | ||||||||||
options = { | ||||||||||
wildcardPath = lib.mkOption { | ||||||||||
type = lib.types.str; | ||||||||||
description = '' | ||||||||||
Wildcard path of the `hwmon` `pwm` file. | ||||||||||
If the fans are not to be found in `/sys/class/hwmon/hwmon*` the corresponding | ||||||||||
kernel module (like `nct6775`) needs to be added to `boot.kernelModules`. | ||||||||||
See the [`hwmon` Documentation](https://www.kernel.org/doc/html/latest/hwmon/index.html). | ||||||||||
''; | ||||||||||
}; | ||||||||||
minPwm = lib.mkOption { | ||||||||||
default = 0; | ||||||||||
type = lib.types.ints.u8; | ||||||||||
description = "Minimum PWM value."; | ||||||||||
}; | ||||||||||
maxPwm = lib.mkOption { | ||||||||||
default = 255; | ||||||||||
type = lib.types.ints.u8; | ||||||||||
description = "Maximum PWM value."; | ||||||||||
}; | ||||||||||
cutoff = lib.mkOption { | ||||||||||
default = false; | ||||||||||
type = lib.types.bool; | ||||||||||
description = "Whether to stop the fan when `minPwm` is reached."; | ||||||||||
}; | ||||||||||
heatPressureSrcs = lib.mkOption { | ||||||||||
type = lib.types.nonEmptyListOf lib.types.str; | ||||||||||
description = "Heat pressure sources affected by the fan."; | ||||||||||
}; | ||||||||||
}; | ||||||||||
}; | ||||||||||
in | ||||||||||
{ | ||||||||||
options.services.pid-fan-controller = { | ||||||||||
enable = lib.mkEnableOption "the PID fan controller, which controls the configured fans by running a closed-loop PID control loop"; | ||||||||||
package = lib.mkPackageOption pkgs "pid-fan-controller" { }; | ||||||||||
settings = { | ||||||||||
interval = lib.mkOption { | ||||||||||
default = 500; | ||||||||||
type = lib.types.int; | ||||||||||
description = "Interval between controller cycles in milliseconds."; | ||||||||||
}; | ||||||||||
heatSources = lib.mkOption { | ||||||||||
type = lib.types.listOf (lib.types.submodule heatSource); | ||||||||||
description = "List of heat sources to be monitored."; | ||||||||||
example = '' | ||||||||||
SuperSandro2000 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
[ | ||||||||||
{ | ||||||||||
name = "cpu"; | ||||||||||
wildcardPath = "/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon*/temp1_input"; | ||||||||||
pidParams = { | ||||||||||
setPoint = 60; | ||||||||||
P = -5.0e-3; | ||||||||||
I = -2.0e-3; | ||||||||||
D = -6.0e-3; | ||||||||||
}; | ||||||||||
} | ||||||||||
]; | ||||||||||
''; | ||||||||||
}; | ||||||||||
fans = lib.mkOption { | ||||||||||
type = lib.types.listOf (lib.types.submodule fan); | ||||||||||
description = "List of fans to be controlled."; | ||||||||||
example = '' | ||||||||||
[ | ||||||||||
{ | ||||||||||
wildcardPath = "/sys/devices/platform/nct6775.2592/hwmon/hwmon*/pwm1"; | ||||||||||
minPwm = 60; | ||||||||||
maxPwm = 255; | ||||||||||
heatPressureSrcs = [ | ||||||||||
"cpu" | ||||||||||
"gpu" | ||||||||||
]; | ||||||||||
} | ||||||||||
]; | ||||||||||
''; | ||||||||||
}; | ||||||||||
}; | ||||||||||
}; | ||||||||||
config = lib.mkIf cfg.enable { | ||||||||||
#map camel cased attrs into snake case for config | ||||||||||
environment.etc."pid-fan-settings.json".text = builtins.toJSON { | ||||||||||
interval = cfg.settings.interval; | ||||||||||
heat_srcs = map (heatSrc: { | ||||||||||
name = heatSrc.name; | ||||||||||
wildcard_path = heatSrc.wildcardPath; | ||||||||||
PID_params = { | ||||||||||
set_point = heatSrc.pidParams.setPoint; | ||||||||||
P = heatSrc.pidParams.P; | ||||||||||
I = heatSrc.pidParams.I; | ||||||||||
D = heatSrc.pidParams.D; | ||||||||||
}; | ||||||||||
}) cfg.settings.heatSources; | ||||||||||
fans = map (fan: { | ||||||||||
wildcard_path = fan.wildcardPath; | ||||||||||
min_pwm = fan.minPwm; | ||||||||||
max_pwm = fan.maxPwm; | ||||||||||
cutoff = fan.cutoff; | ||||||||||
heat_pressure_srcs = fan.heatPressureSrcs; | ||||||||||
}) cfg.settings.fans; | ||||||||||
}; | ||||||||||
|
||||||||||
systemd.services.pid-fan-controller = { | ||||||||||
wantedBy = [ "multi-user.target" ]; | ||||||||||
serviceConfig = { | ||||||||||
Type = "simple"; | ||||||||||
ExecStart = [ (lib.getExe cfg.package) ]; | ||||||||||
ExecStopPost = [ "${lib.getExe cfg.package} disable" ]; | ||||||||||
Restart = "always"; | ||||||||||
#This service needs to run as root to write to /sys. | ||||||||||
#therefore it should operate with the least amount of privileges needed | ||||||||||
ProtectHome = "yes"; | ||||||||||
#strict is not possible as it needs /sys | ||||||||||
ProtectSystem = "full"; | ||||||||||
ProtectProc = "invisible"; | ||||||||||
PrivateNetwork = "yes"; | ||||||||||
NoNewPrivileges = "yes"; | ||||||||||
MemoryDenyWriteExecute = "yes"; | ||||||||||
RestrictNamespaces = "~user pid net uts mnt"; | ||||||||||
ProtectKernelModules = "yes"; | ||||||||||
RestrictRealtime = "yes"; | ||||||||||
SystemCallFilter = "@system-service"; | ||||||||||
CapabilityBoundingSet = "~CAP_KILL CAP_WAKE_ALARM CAP_IPC_LOC CAP_BPF CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_MKNOD"; | ||||||||||
}; | ||||||||||
# restart unit if config changed | ||||||||||
restartTriggers = [ config.environment.etc."pid-fan-settings.json".source ]; | ||||||||||
}; | ||||||||||
#sleep hook to restart the service as it breaks otherwise | ||||||||||
systemd.services.pid-fan-controller-sleep = { | ||||||||||
before = [ "sleep.target" ]; | ||||||||||
wantedBy = [ "sleep.target" ]; | ||||||||||
unitConfig = { | ||||||||||
StopWhenUnneeded = "yes"; | ||||||||||
}; | ||||||||||
serviceConfig = { | ||||||||||
Type = "oneshot"; | ||||||||||
RemainAfterExit = true; | ||||||||||
ExecStart = [ "systemctl stop pid-fan-controller.service" ]; | ||||||||||
ExecStop = [ "systemctl restart pid-fan-controller.service" ]; | ||||||||||
Comment on lines
+182
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initialy had a absolute path (via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay then. I've just seen some other modules use the full path. |
||||||||||
}; | ||||||||||
}; | ||||||||||
}; | ||||||||||
zimward marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
meta.maintainers = with lib.maintainers; [ zimward ]; | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
rustPlatform, | ||
fetchFromGitHub, | ||
lib, | ||
}: | ||
let | ||
version = "0.1.1"; | ||
in | ||
rustPlatform.buildRustPackage { | ||
pname = "pid-fan-controller"; | ||
inherit version; | ||
|
||
src = fetchFromGitHub { | ||
owner = "zimward"; | ||
repo = "pid-fan-controller"; | ||
rev = version; | ||
hash = "sha256-ALR9Qa0AhcGyc3+7x5CEG/72+bJzhaEoIvQNL+QjldY="; | ||
}; | ||
cargoHash = "sha256-u1Y1k1I5gRzpDHhRJZCCtMTwAvtCaIy3uXQTvmtEx5w="; | ||
|
||
meta = { | ||
description = "Service to provide closed-loop PID fan control"; | ||
homepage = "https://github.com/zimward/pid-fan-controller"; | ||
license = lib.licenses.gpl3Only; | ||
maintainers = with lib.maintainers; [ zimward ]; | ||
platforms = lib.platforms.linux; | ||
mainProgram = "pid-fan-controller"; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(non blocking nit)