From 4851b74edfedafe403e9607a673a40ec8b292781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sat, 12 Nov 2022 11:41:24 +0100 Subject: [PATCH] Support sensor value calculation (Pid and Quadratic) --- FanControl/FanControl.cs | 253 -------------------------- FanControl/FanControlForm.Designer.cs | 66 +++++-- FanControl/FanControlForm.cs | 9 +- FanControl/FanController.cs | 129 +++++++++++++ FanControl/FanControllerSensors.cs | 175 ++++++++++++++++++ FanControl/FanSensor.cs | 223 +++++++++++++++++++++++ 6 files changed, 584 insertions(+), 271 deletions(-) delete mode 100644 FanControl/FanControl.cs create mode 100644 FanControl/FanController.cs create mode 100644 FanControl/FanControllerSensors.cs create mode 100644 FanControl/FanSensor.cs diff --git a/FanControl/FanControl.cs b/FanControl/FanControl.cs deleted file mode 100644 index f903c4a..0000000 --- a/FanControl/FanControl.cs +++ /dev/null @@ -1,253 +0,0 @@ -using LibreHardwareMonitor.Hardware; -using LibreHardwareMonitor.Hardware.CPU; -using Microsoft.VisualBasic.Devices; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.Metrics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FanControl -{ - [TypeConverter(typeof(ExpandableObjectConverter))] - internal class FanControl : IDisposable - { - public enum FanMode - { - Default, - MidWay, - Max - } - - [TypeConverter(typeof(ExpandableObjectConverter))] - internal class FanSensor - { - public string? Name { get; internal set; } - public float? Value { get; internal set; } - public ushort InducedRPM { get; internal set; } - - internal string HardwareName { get; set; } = ""; - internal HardwareType HardwareType { get; set; } - internal string SensorName { get; set; } = ""; - internal SensorType SensorType { get; set; } - - public void Reset() - { - Name = null; - Value = null; - InducedRPM = 0; - } - - public bool Matches(ISensor sensor) - { - return sensor != null && - sensor.Hardware.HardwareType == HardwareType && - sensor.Hardware.Name.StartsWith(HardwareName) && - sensor.SensorType == SensorType && - sensor.Name == SensorName; - } - - public bool Update(ISensor hwSensor) - { - if (!Matches(hwSensor)) - return false; - - System.Diagnostics.Trace.WriteLine(String.Format("{0}: {1} {2}, value: {3}, type: {4}", - hwSensor.Identifier, hwSensor.Hardware.Name, hwSensor.Name, hwSensor.Value, hwSensor.SensorType)); - - return Update( - String.Format("{0} {1}", hwSensor.Hardware.Name, hwSensor.Name), - hwSensor.Value); - } - - public bool Update(string name, float? value) - { - if (!value.HasValue || value <= 0.0) - return false; - - Name = name; - Value = value; - return true; - } - - public String FormattedValue() - { - if (!Value.HasValue) - return ""; - - switch (SensorType) - { - case SensorType.Temperature: - return Value.Value.ToString("F1") + "℃"; - case SensorType.Power: - return Value.Value.ToString("F1") + "W"; - default: - return Value.Value.ToString("F1"); - } - } - - public override string ToString() - { - return Name; - } - } - - [CategoryAttribute("Fan")] - public FanMode Mode { get; private set; } - - [CategoryAttribute("Fan")] - public ushort CurrentRPM { get; private set; } - - [CategoryAttribute("Fan")] - public ushort DesiredRPM { get; private set; } - - [CategoryAttribute("Sensor - APU"), DisplayName("Name")] - public String? APUName { get { return allSensors["APU"].Name; } } - [CategoryAttribute("Sensor - APU"), DisplayName("Power")] - public String? APUPower { get { return allSensors["APU"].FormattedValue(); } } - - [CategoryAttribute("Sensor - CPU"), DisplayName("Name")] - public String? CPUName { get { return allSensors["CPU"].Name; } } - [CategoryAttribute("Sensor - CPU"), DisplayName("Temperature")] - public String? CPUTemperature { get { return allSensors["CPU"].FormattedValue(); } } - - [CategoryAttribute("Sensor - GPU"), DisplayName("Name")] - public String? GPUName { get { return allSensors["GPU"].Name; } } - [CategoryAttribute("Sensor - GPU"), DisplayName("Temperature")] - public String? GPUTemperature { get { return allSensors["GPU"].FormattedValue(); } } - - [CategoryAttribute("Sensor - SSD"), DisplayName("Name")] - public String? SSDName { get { return allSensors["SSD"].Name; } } - [CategoryAttribute("Sensor - SSD"), DisplayName("Temperature")] - public String? SSDTemperature { get { return allSensors["SSD"].FormattedValue(); } } - [CategoryAttribute("Sensor - Battery"), DisplayName("Name")] - public String? BatteryName { get { return allSensors["Batt"].Name; } } - [CategoryAttribute("Sensor - Battery"), DisplayName("Temperature")] - public String? BatteryTemperature { get { return allSensors["Batt"].FormattedValue(); } } - - private Dictionary allSensors = new Dictionary - { - { "APU", - new FanSensor() - { - // TODO: Is this correct? - HardwareName = "AMD Custom APU 0405", - HardwareType = HardwareType.Cpu, - SensorName = "Package", - SensorType = SensorType.Power - } }, - { "CPU", - new FanSensor() - { - HardwareName = "AMD Custom APU 0405", - HardwareType = HardwareType.Cpu, - SensorName = "Core (Tctl/Tdie)", - SensorType = SensorType.Temperature - } }, - { "GPU", - new FanSensor() - { - HardwareName = "AMD Custom GPU 0405", - HardwareType = HardwareType.GpuAmd, - SensorName = "GPU Core", - SensorType = SensorType.Temperature - } }, - { "SSD", - new FanSensor() - { - HardwareType = HardwareType.Storage, - SensorName = "Temperature", - SensorType = SensorType.Temperature - } }, - { "Batt", - new FanSensor() - { - HardwareType = HardwareType.Battery, - SensorName = "Temperature", - SensorType = SensorType.Temperature - } } - }; - - LibreHardwareMonitor.Hardware.Computer libreHardwareComputer = new LibreHardwareMonitor.Hardware.Computer - { - IsCpuEnabled = true, - IsGpuEnabled = true, - IsStorageEnabled = true, - IsBatteryEnabled = true - }; - - public FanControl() - { - libreHardwareComputer.Open(); - } - - private void visitHardware(IHardware hardware) - { - Dictionary matched = new Dictionary(); - - foreach (ISensor hwSensor in hardware.Sensors) - { - foreach (var sensor in allSensors.Values) - { - if (sensor.Matches(hwSensor)) - matched[sensor] = hwSensor; - } - } - - if (matched.Any()) - { - hardware.Update(); - foreach (var sensor in matched) - sensor.Key.Update(sensor.Value); - } - - foreach (IHardware subhardware in hardware.SubHardware) - { - visitHardware(subhardware); - } - } - - public void Update() - { - CurrentRPM = Vlv0100.GetFanRPM(); - DesiredRPM = Vlv0100.GetFanDesiredRPM(); - - foreach (var sensor in allSensors.Values) - sensor.Reset(); - - foreach(var hardware in libreHardwareComputer.Hardware) - visitHardware(hardware); - - allSensors["Batt"].Update("VLV0100", Vlv0100.GetBattTemperature()); - } - - public void SetMode(FanMode mode) - { - switch (mode) - { - case FanMode.Default: - Vlv0100.SetFanControl(false); - break; - - case FanMode.MidWay: - Vlv0100.SetFanControl(true); - Vlv0100.SetFanDesiredRPM(Vlv0100.MAX_FAN_RPM/2); - break; - - case FanMode.Max: - Vlv0100.SetFanControl(true); - Vlv0100.SetFanDesiredRPM(Vlv0100.MAX_FAN_RPM); - break; - } - - this.Mode = mode; - } - - public void Dispose() - { - libreHardwareComputer.Close(); - } - } -} diff --git a/FanControl/FanControlForm.Designer.cs b/FanControl/FanControlForm.Designer.cs index 2627524..97746dc 100644 --- a/FanControl/FanControlForm.Designer.cs +++ b/FanControl/FanControlForm.Designer.cs @@ -36,14 +36,17 @@ this.fanModeSelectNotifyMenu = new System.Windows.Forms.ToolStripComboBox(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); - this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.fanModeSelectMenu = new System.Windows.Forms.ToolStripComboBox(); this.controlToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.propertyGridUpdateTimer = new System.Windows.Forms.Timer(this.components); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.sensorWarningLabel = new System.Windows.Forms.Label(); + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); this.contextMenu.SuspendLayout(); this.menuStrip1.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // // fanLoopTimer @@ -92,17 +95,6 @@ this.toolStripMenuItem1.Text = "Exit"; this.toolStripMenuItem1.Click += new System.EventHandler(this.formClose_Event); // - // propertyGrid1 - // - this.propertyGrid1.DisabledItemForeColor = System.Drawing.SystemColors.ControlText; - this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill; - this.propertyGrid1.HelpVisible = false; - this.propertyGrid1.Location = new System.Drawing.Point(0, 44); - this.propertyGrid1.Name = "propertyGrid1"; - this.propertyGrid1.Size = new System.Drawing.Size(712, 885); - this.propertyGrid1.TabIndex = 0; - this.propertyGrid1.ToolbarVisible = false; - // // menuStrip1 // this.menuStrip1.ImageScalingSize = new System.Drawing.Size(32, 32); @@ -145,12 +137,54 @@ this.propertyGridUpdateTimer.Interval = 1000; this.propertyGridUpdateTimer.Tick += new System.EventHandler(this.propertyGridUpdateTimer_Tick); // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.sensorWarningLabel, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.propertyGrid1, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 44); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(712, 885); + this.tableLayoutPanel1.TabIndex = 5; + // + // sensorWarningLabel + // + this.sensorWarningLabel.AutoSize = true; + this.sensorWarningLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.sensorWarningLabel.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.sensorWarningLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.sensorWarningLabel.ForeColor = System.Drawing.Color.Red; + this.sensorWarningLabel.Location = new System.Drawing.Point(3, 757); + this.sensorWarningLabel.Name = "sensorWarningLabel"; + this.sensorWarningLabel.Size = new System.Drawing.Size(706, 128); + this.sensorWarningLabel.TabIndex = 6; + this.sensorWarningLabel.Text = "Some sensors are missing.\r\nThe fan behavior might be incorrect.\r\nWhich might resu" + + "lt in device failure.\r\nUse at your own risk!"; + this.sensorWarningLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.sensorWarningLabel.Visible = false; + // + // propertyGrid1 + // + this.propertyGrid1.DisabledItemForeColor = System.Drawing.SystemColors.ControlText; + this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill; + this.propertyGrid1.HelpVisible = false; + this.propertyGrid1.Location = new System.Drawing.Point(3, 3); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.Size = new System.Drawing.Size(706, 751); + this.propertyGrid1.TabIndex = 1; + this.propertyGrid1.ToolbarVisible = false; + // // FanControlForm // this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(712, 929); - this.Controls.Add(this.propertyGrid1); + this.Controls.Add(this.tableLayoutPanel1); this.Controls.Add(this.menuStrip1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip1; @@ -162,6 +196,8 @@ this.contextMenu.ResumeLayout(false); this.menuStrip1.ResumeLayout(false); this.menuStrip1.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -171,7 +207,6 @@ private System.Windows.Forms.Timer fanLoopTimer; private NotifyIcon notifyIcon; - private PropertyGrid propertyGrid1; private ContextMenuStrip contextMenu; private MenuStrip menuStrip1; private ToolStripComboBox fanModeSelectNotifyMenu; @@ -181,5 +216,8 @@ private ToolStripMenuItem controlToolStripMenuItem; private ToolStripMenuItem exitToolStripMenuItem; private System.Windows.Forms.Timer propertyGridUpdateTimer; + private TableLayoutPanel tableLayoutPanel1; + private Label sensorWarningLabel; + private PropertyGrid propertyGrid1; } } \ No newline at end of file diff --git a/FanControl/FanControlForm.cs b/FanControl/FanControlForm.cs index 9de96be..42d565c 100644 --- a/FanControl/FanControlForm.cs +++ b/FanControl/FanControlForm.cs @@ -14,7 +14,7 @@ namespace FanControl { public partial class FanControlForm : Form { - private FanControl fanControl = new FanControl(); + private FanController fanControl = new FanController(); public FanControlForm() { @@ -23,7 +23,7 @@ namespace FanControl propertyGrid1.SelectedObject = fanControl; propertyGrid1.ExpandAllGridItems(); - foreach (var item in Enum.GetValues(typeof(FanControl.FanMode))) + foreach (var item in Enum.GetValues(typeof(FanController.FanMode))) { fanModeSelectMenu.Items.Add(item); fanModeSelectNotifyMenu.Items.Add(item); @@ -38,7 +38,7 @@ namespace FanControl private void fanModeSelect_SelectedValueChanged(object sender, EventArgs e) { var comboBox = (ToolStripComboBox)sender; - var selectedMode = (FanControl.FanMode)comboBox.SelectedItem; + var selectedMode = (FanController.FanMode)comboBox.SelectedItem; fanControl.SetMode(selectedMode); fanModeSelectMenu.SelectedItem = selectedMode; fanModeSelectNotifyMenu.SelectedItem = selectedMode; @@ -68,7 +68,7 @@ namespace FanControl private void FanControlForm_FormClosed(object sender, FormClosedEventArgs e) { // Always revert to default on closing - fanControl.SetMode(FanControl.FanMode.Default); + fanControl.SetMode(FanController.FanMode.Default); } private void fanLoopTimer_Tick(object sender, EventArgs e) @@ -84,6 +84,7 @@ namespace FanControl var item = propertyGrid1.SelectedGridItem; propertyGrid1.Refresh(); propertyGrid1.SelectedGridItem = item; + sensorWarningLabel.Visible = fanControl.IsAnyInvalid(); notifyIcon.Text = String.Format("Fan: {0} RPM Mode: {1}", fanControl.CurrentRPM, fanControl.Mode); } } diff --git a/FanControl/FanController.cs b/FanControl/FanController.cs new file mode 100644 index 0000000..a0cf6c4 --- /dev/null +++ b/FanControl/FanController.cs @@ -0,0 +1,129 @@ +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Hardware.CPU; +using Microsoft.VisualBasic.Devices; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FanControl +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + internal partial class FanController : IDisposable + { + public enum FanMode + { + Default, + MidWay, + SteamOS, + Max + } + + [CategoryAttribute("Fan")] + public FanMode Mode { get; private set; } + + [CategoryAttribute("Fan")] + public ushort CurrentRPM { get; private set; } + + [CategoryAttribute("Fan")] + public ushort DesiredRPM { get; private set; } + + private LibreHardwareMonitor.Hardware.Computer libreHardwareComputer = new LibreHardwareMonitor.Hardware.Computer + { + IsCpuEnabled = true, + IsGpuEnabled = true, + IsStorageEnabled = true, + IsBatteryEnabled = true + }; + + public FanController() + { + libreHardwareComputer.Open(); + } + + private void visitHardware(IHardware hardware) + { + Dictionary matched = new Dictionary(); + + foreach (ISensor hwSensor in hardware.Sensors) + { + foreach (var sensor in allSensors.Values) + { + if (sensor.Matches(hwSensor)) + matched[sensor] = hwSensor; + } + } + + if (matched.Any()) + { + hardware.Update(); + foreach (var sensor in matched) + sensor.Key.Update(sensor.Value, Mode); + } + + foreach (IHardware subhardware in hardware.SubHardware) + { + visitHardware(subhardware); + } + } + + private ushort getDesiredRPM() + { + ushort rpm = 0; + foreach (var sensor in allSensors.Values) + if (sensor.CalculatedRPM.HasValue) + rpm = Math.Max(rpm, sensor.CalculatedRPM.Value); + return rpm; + } + + public void Update() + { + foreach (var sensor in allSensors.Values) + sensor.Reset(); + + foreach (var hardware in libreHardwareComputer.Hardware) + visitHardware(hardware); + + allSensors["Batt"].Update("VLV0100", Vlv0100.GetBattTemperature(), Mode); + + Vlv0100.SetFanDesiredRPM(getDesiredRPM()); + + CurrentRPM = Vlv0100.GetFanRPM(); + DesiredRPM = Vlv0100.GetFanDesiredRPM(); + } + + public void SetMode(FanMode mode) + { + switch (mode) + { + case FanMode.Default: + Vlv0100.SetFanControl(false); + break; + + default: + Vlv0100.SetFanControl(true); + break; + } + + this.Mode = mode; + } + + public bool IsAnyInvalid() + { + foreach (var sensor in allSensors.Values) + { + if (!sensor.IsValid(Mode)) + return true; + } + return false; + } + + public void Dispose() + { + libreHardwareComputer.Close(); + } + } +} diff --git a/FanControl/FanControllerSensors.cs b/FanControl/FanControllerSensors.cs new file mode 100644 index 0000000..4e0db68 --- /dev/null +++ b/FanControl/FanControllerSensors.cs @@ -0,0 +1,175 @@ +using LibreHardwareMonitor.Hardware; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FanControl +{ + internal partial class FanController + { + private Dictionary allSensors = new Dictionary + { + { + "APU", new FanSensor() + { + // TODO: Is this correct? + HardwareName = "AMD Custom APU 0405", + HardwareType = HardwareType.Cpu, + SensorName = "Package", + SensorType = SensorType.Power, + ValueDeadZone = 0.1f, + Profiles = new Dictionary() + { + { + FanMode.MidWay, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Constant, + MinRPM = Vlv0100.MAX_FAN_RPM / 2 + } + }, + { + FanMode.Max, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Constant, + MinRPM = Vlv0100.MAX_FAN_RPM + } + }, + { + FanMode.SteamOS, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Constant, + MinRPM = 1500 + } + }, + } + } + }, + { + "CPU", new FanSensor() + { + HardwareName = "AMD Custom APU 0405", + HardwareType = HardwareType.Cpu, + SensorName = "Core (Tctl/Tdie)", + SensorType = SensorType.Temperature, + ValueDeadZone = 2.0f, + Profiles = new Dictionary() + { + { + FanMode.SteamOS, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Quadratic, + MinInput = 55, + MaxInput = 90, + A = 2.286f, + B = -188.6f, + C = 5457.0f + } + } + } + } + }, + { + "GPU", new FanSensor() + { + HardwareName = "AMD Custom GPU 0405", + HardwareType = HardwareType.GpuAmd, + SensorName = "GPU Core", + SensorType = SensorType.Temperature, + ValueDeadZone = 2.0f, + Profiles = new Dictionary() + { + { + FanMode.SteamOS, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Quadratic, + MinInput = 55, + MaxInput = 90, + A = 2.286f, + B = -188.6f, + C = 5457.0f + } + } + } + } + }, + { + "SSD", new FanSensor() + { + HardwareType = HardwareType.Storage, + SensorName = "Temperature", + SensorType = SensorType.Temperature, + ValueDeadZone = 1.0f, + Profiles = new Dictionary() + { + { + FanMode.SteamOS, new FanSensor.Profile() + { + Type = FanSensor.Profile.ProfileType.Pid, + MinInput = 30, + MaxInput = 70, + MaxRPM = 3000, + PidSetPoint = 70, + Kp = 0, + Ki = -20, + Kd = 0 + } + } + } + } + }, + { + "Batt", new FanSensor() + { + HardwareType = HardwareType.Battery, + SensorName = "Temperature", + SensorType = SensorType.Temperature, + ValueDeadZone = 1.0f, + Profiles = new Dictionary() + { + { + FanMode.SteamOS, new FanSensor.Profile() + { + // If battery goes over 40oC require 2kRPM + Type = FanSensor.Profile.ProfileType.Constant, + MinInput = 0, + MaxInput = 40, + MinRPM = 0, + MaxRPM = 2000, + } + } + } + } + } + }; + + #region Sensor Properties for Property Grid + [CategoryAttribute("Sensor - APU"), DisplayName("Name")] + public String? APUName { get { return allSensors["APU"].Name; } } + [CategoryAttribute("Sensor - APU"), DisplayName("Power")] + public String? APUPower { get { return allSensors["APU"].FormattedValue(); } } + + [CategoryAttribute("Sensor - CPU"), DisplayName("Name")] + public String? CPUName { get { return allSensors["CPU"].Name; } } + [CategoryAttribute("Sensor - CPU"), DisplayName("Temperature")] + public String? CPUTemperature { get { return allSensors["CPU"].FormattedValue(); } } + + [CategoryAttribute("Sensor - GPU"), DisplayName("Name")] + public String? GPUName { get { return allSensors["GPU"].Name; } } + [CategoryAttribute("Sensor - GPU"), DisplayName("Temperature")] + public String? GPUTemperature { get { return allSensors["GPU"].FormattedValue(); } } + + [CategoryAttribute("Sensor - SSD"), DisplayName("Name")] + public String? SSDName { get { return allSensors["SSD"].Name; } } + [CategoryAttribute("Sensor - SSD"), DisplayName("Temperature")] + public String? SSDTemperature { get { return allSensors["SSD"].FormattedValue(); } } + [CategoryAttribute("Sensor - Battery"), DisplayName("Name")] + public String? BatteryName { get { return allSensors["Batt"].Name; } } + [CategoryAttribute("Sensor - Battery"), DisplayName("Temperature")] + public String? BatteryTemperature { get { return allSensors["Batt"].FormattedValue(); } } + + #endregion Sensor Properties for Property Grid + } +} diff --git a/FanControl/FanSensor.cs b/FanControl/FanSensor.cs new file mode 100644 index 0000000..69e2e02 --- /dev/null +++ b/FanControl/FanSensor.cs @@ -0,0 +1,223 @@ +using LibreHardwareMonitor.Hardware; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Policy; +using System.Text; +using System.Threading.Tasks; + +namespace FanControl +{ + [TypeConverter(typeof(ExpandableObjectConverter))] + internal class FanSensor + { + public string? Name { get; internal set; } + public float? Value { get; internal set; } + public ushort? CalculatedRPM { get; internal set; } + + public float ValueDeadZone { get; set; } + public int AvgSamples { get; set; } = 5; + + internal string HardwareName { get; set; } = ""; + internal HardwareType HardwareType { get; set; } + internal string SensorName { get; set; } = ""; + internal SensorType SensorType { get; set; } + + private List AllSamples = new List(); + + internal Dictionary Profiles { get; set; } = new Dictionary(); + + internal class Profile + { + public enum ProfileType + { + Constant, + Quadratic, + Pid + } + + public ProfileType Type { get; set; } + public float MinInput { get; set; } + public float MaxInput { get; set; } = 90; + public ushort MinRPM { get; set; } + public ushort MaxRPM { get; set; } = ushort.MaxValue; + + public float A { get; set; } + public float B { get; set; } + public float C { get; set; } + + public float Kp { get; set; } + public float Ki { get; set; } + public float Kd { get; set; } + public float PidSetPoint { get; set; } + + private float? pidLastInput { get; set; } + private float pidLastError { get; set; } + private DateTime pidLastTime { get; set; } + private float pidP { get; set; } + private float pidI { get; set; } + private float pidD { get; set; } + + public ushort CalculateRPM(float input) + { + float rpm = 0; + + switch (Type) + { + case ProfileType.Constant: + rpm = MinRPM; + break; + + case ProfileType.Quadratic: + rpm = calculateQuadraticRPM(input); + break; + + case ProfileType.Pid: + rpm = calculatePidRPM(input); + break; + } + + if (input < MinInput) + rpm = MinRPM; + else if (input > MaxInput) + rpm = MaxRPM; + + rpm = Math.Clamp(rpm, (float)MinRPM, (float)MaxRPM); + + return (ushort)rpm; + } + + private float calculateQuadraticRPM(float input) + { + return A * input * input + B * input + C; + } + + private float calculatePidRPM(float input) + { + if (!pidLastInput.HasValue) + { + pidLastInput = input; + pidLastTime = DateTime.Now; + return 0; + } + + float error = PidSetPoint - input; + float dInput = input - pidLastInput.Value; + float dt = Math.Min((float)(DateTime.Now - pidLastTime).TotalSeconds, 1.0f); + + this.pidP = Kp * error; + this.pidI += Ki * error * dt; + this.pidI = Math.Min(this.pidI, this.MaxRPM); + this.pidD -= Kd * dInput / dt; + + pidLastInput = input; + pidLastError = error; + pidLastTime = DateTime.Now; + + return pidP + pidI + pidD; + } + } + + public void Reset() + { + Name = null; + Value = null; + CalculatedRPM = 0; + } + + public bool Matches(ISensor sensor) + { + return sensor != null && + sensor.Hardware.HardwareType == HardwareType && + sensor.Hardware.Name.StartsWith(HardwareName) && + sensor.SensorType == SensorType && + sensor.Name == SensorName; + } + + public bool Update(ISensor hwSensor, FanController.FanMode mode) + { + if (!Matches(hwSensor)) + return false; + + System.Diagnostics.Trace.WriteLine(String.Format("{0}: {1} {2}, value: {3}, type: {4}", + hwSensor.Identifier, hwSensor.Hardware.Name, hwSensor.Name, hwSensor.Value, hwSensor.SensorType)); + + return Update( + String.Format("{0} {1}", hwSensor.Hardware.Name, hwSensor.Name), + hwSensor.Value, mode); + } + + public bool Update(string name, float? newValue, FanController.FanMode mode) + { + if (!newValue.HasValue || newValue <= 0.0) + return false; + + if (AllSamples.Count == 0 || Math.Abs(AllSamples.Last() - newValue.Value) > ValueDeadZone) + { + AllSamples.Add(newValue.Value); + while (AllSamples.Count > AvgSamples) + AllSamples.RemoveAt(0); + } + + float avgValue = 0.0f; + foreach (var value in AllSamples) + avgValue += value; + + Name = name; + Value = avgValue / AllSamples.Count; + CalculatedRPM = CalculateRPM(mode); + return true; + } + + public bool IsValid(FanController.FanMode mode) + { + // If we have profile, but no sensor value to consume it + // it is invalid + if (Profiles.ContainsKey(mode) && !Value.HasValue) + return false; + + return true; + } + + public String FormattedValue() + { + if (!Value.HasValue) + return ""; + + String value = Value.Value.ToString("F1"); + + switch (SensorType) + { + case SensorType.Temperature: + value += "℃"; + break; + + case SensorType.Power: + value += "W"; + break; + } + + if (CalculatedRPM.HasValue) + { + value += " (min: " + CalculatedRPM.ToString() + "RPM)"; + } + + return value; + } + + public override string ToString() + { + return Name; + } + + public ushort? CalculateRPM(FanController.FanMode mode) + { + if (!Profiles.ContainsKey(mode) || !Value.HasValue) + return null; + + var profile = Profiles[mode]; + return profile.CalculateRPM(Value.Value); + } + } +}