Skip to content
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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23517,6 +23517,13 @@
githubId = 1108325;
name = "Théo Zimmermann";
};
zimward = {
name = "zimward";
github = "zimward";
githubId = 96021122;
matrix = "@memoryfragmentation:matrix.org";
keys = [ { fingerprint = "CBF7 FA5E F4B5 8B68 5977 3E3E 4CAC 61D6 A482 FCD9"; } ];
};
zlepper = {
name = "Rasmus Hansen";
github = "zlepper";
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@
./services/hardware/nvidia-optimus.nix
./services/hardware/openrgb.nix
./services/hardware/pcscd.nix
./services/hardware/pid-fan-controller.nix
./services/hardware/pommed.nix
./services/hardware/power-profiles-daemon.nix
./services/hardware/rasdaemon.nix
Expand Down
189 changes: 189 additions & 0 deletions nixos/modules/services/hardware/pid-fan-controller.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
{
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 {
Copy link
Contributor

@SigmaSquadron SigmaSquadron Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
heatPressureSrcs = lib.mkOption {
heatPressureSources = lib.mkOption {

(non blocking nit)

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 = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is likely a big to big to properly render on search.nixos.org.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just halved the length of the example. Is there some example of the limits search.nixos.org is able to display?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym? Yeah, it's a bit big, but nixos-search and the manual should be fine with it.

[
{
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".text ];
};
#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 = [ "${config.systemd.package}/bin/systemctl stop pid-fan-controller.service" ];
ExecStop = [ "${config.systemd.package}/bin/systemctl restart pid-fan-controller.service" ];
};
};
};
zimward marked this conversation as resolved.
Show resolved Hide resolved
meta.maintainers = with lib.maintainers; [ zimward ];
}
29 changes: 29 additions & 0 deletions pkgs/by-name/pi/pid-fan-controller/package.nix
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";
};
}