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 2 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
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
188 changes: 188 additions & 0 deletions nixos/modules/services/hardware/pid-fan-controller.nix
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 {
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 = ''
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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ExecStart = [ "systemctl stop pid-fan-controller.service" ];
ExecStop = [ "systemctl restart pid-fan-controller.service" ];
ExecStart = [ "/run/current-system/systemd/bin/systemctl stop pid-fan-controller.service" ];
ExecStop = [ "/run/current-system/systemd/bin/systemctl restart pid-fan-controller.service" ];

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 initialy had a absolute path (via systemd.package), SuperSandro pointed out that systemd is already in PATH, so i don't really see the benefit of pointing to the symlink.

Copy link
Member

Choose a reason for hiding this comment

The 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 ];
}
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";
};
}
Loading