2022-11-15 23:40:54 +01:00
|
|
|
|
using CommonHelpers;
|
|
|
|
|
|
using LibreHardwareMonitor.Hardware;
|
2022-11-12 11:41:24 +01:00
|
|
|
|
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;
|
2022-11-13 07:51:47 +01:00
|
|
|
|
public float? MaxValue { get; set; }
|
2022-11-12 11:41:24 +01:00
|
|
|
|
|
|
|
|
|
|
internal string HardwareName { get; set; } = "";
|
|
|
|
|
|
internal HardwareType HardwareType { get; set; }
|
|
|
|
|
|
internal string SensorName { get; set; } = "";
|
|
|
|
|
|
internal SensorType SensorType { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
private List<float> AllSamples = new List<float>();
|
|
|
|
|
|
|
2022-11-15 23:40:54 +01:00
|
|
|
|
internal Dictionary<FanMode, Profile> Profiles { get; set; } = new Dictionary<FanMode, Profile>();
|
2022-11-12 11:41:24 +01:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-15 23:40:54 +01:00
|
|
|
|
public bool Update(ISensor hwSensor, FanMode mode)
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-15 23:40:54 +01:00
|
|
|
|
public bool Update(string name, float? newValue, FanMode mode)
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (!newValue.HasValue || newValue <= 0.0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
2022-11-13 07:51:47 +01:00
|
|
|
|
if (MaxValue.HasValue)
|
|
|
|
|
|
newValue = Math.Min(newValue.Value, MaxValue.Value);
|
|
|
|
|
|
|
2022-11-12 16:22:02 +01:00
|
|
|
|
if (AllSamples.Count == 0 || Math.Abs(AllSamples.Last() - newValue.Value) >= ValueDeadZone)
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-15 23:40:54 +01:00
|
|
|
|
public bool IsValid(FanMode mode)
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
// If we have profile, but no sensor value to consume it
|
|
|
|
|
|
// it is invalid
|
|
|
|
|
|
if (Profiles.ContainsKey(mode) && !Value.HasValue)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-12 17:06:34 +01:00
|
|
|
|
private String Unit()
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
switch (SensorType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case SensorType.Temperature:
|
2022-11-12 17:06:34 +01:00
|
|
|
|
return "℃";
|
2022-11-12 11:41:24 +01:00
|
|
|
|
|
|
|
|
|
|
case SensorType.Power:
|
2022-11-12 17:06:34 +01:00
|
|
|
|
return "W";
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "";
|
2022-11-12 11:41:24 +01:00
|
|
|
|
}
|
2022-11-12 17:06:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public String FormattedValue()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Value.HasValue)
|
|
|
|
|
|
return "";
|
|
|
|
|
|
|
|
|
|
|
|
String value = "";
|
|
|
|
|
|
|
|
|
|
|
|
if (AllSamples.Count > 0)
|
|
|
|
|
|
value += AllSamples.Last().ToString("F1") + Unit();
|
|
|
|
|
|
|
|
|
|
|
|
value += " (avg: " + Value.Value.ToString("F1") + Unit() + ")";
|
2022-11-12 11:41:24 +01:00
|
|
|
|
|
|
|
|
|
|
if (CalculatedRPM.HasValue)
|
2022-11-12 17:06:34 +01:00
|
|
|
|
value += " (" + CalculatedRPM.ToString() + "RPM)";
|
2022-11-12 11:41:24 +01:00
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-15 23:40:54 +01:00
|
|
|
|
public ushort? CalculateRPM(FanMode mode)
|
2022-11-12 11:41:24 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (!Profiles.ContainsKey(mode) || !Value.HasValue)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var profile = Profiles[mode];
|
|
|
|
|
|
return profile.CalculateRPM(Value.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|