日常更新
This commit is contained in:
@@ -1,136 +1,136 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ChromeboxFanControl;
|
||||
|
||||
public sealed class AppConfig
|
||||
{
|
||||
/// <summary>UI language: "auto" (system), "en", "zh-Hans", "zh-Hant". Restart to apply.</summary>
|
||||
public string Language { get; set; } = "auto";
|
||||
public string EctoolPath { get; set; } = @"C:\Program Files\crosec\ectool.exe";
|
||||
public int PollIntervalMs { get; set; } = 1500;
|
||||
/// <summary>Call ectool for RPM every N control cycles (1 = every cycle).</summary>
|
||||
public int FanRpmPollEveryNCycles { get; set; } = 1;
|
||||
public string[] FanRpmArgs { get; set; } = ["pwmgetfanrpm", "0"];
|
||||
/// <summary>Ectool args to read fan duty (0-100). Empty = skip, use commanded duty.</summary>
|
||||
public string[]? FanDutyArgs { get; set; }
|
||||
public string[] AutoFanCtrlArgs { get; set; } = ["autofanctrl"];
|
||||
public string TempSource { get; set; } = "AverageCore";
|
||||
public int RampUpSteps { get; set; } = 3;
|
||||
public int RampUpMinDeltaPercent { get; set; } = 20;
|
||||
public int FailSafeAfterConsecutiveErrors { get; set; } = 5;
|
||||
public int FailSafeFanPercent { get; set; } = 100;
|
||||
public bool FailSafeRestoreAutoFan { get; set; }
|
||||
public int ChartHistoryMinutes { get; set; } = 15;
|
||||
public int ChartMaxPoints { get; set; } = 900;
|
||||
public int[] CurvePoints { get; set; } =
|
||||
[
|
||||
0, 0, 10, 25, 40, 55, 70, 80, 90, 95, 97, 98, 99, 100
|
||||
];
|
||||
|
||||
public static JsonSerializerOptions JsonOptions { get; } = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters = { new JsonStringEnumConverter() }
|
||||
};
|
||||
|
||||
public static string UserConfigPath =>
|
||||
Path.Combine(
|
||||
Environment.UserInteractive
|
||||
? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
|
||||
: Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"ChromeboxFanControl",
|
||||
"config.json");
|
||||
|
||||
public static AppConfig LoadMerged()
|
||||
{
|
||||
var cfg = new AppConfig();
|
||||
var baseDir = AppContext.BaseDirectory;
|
||||
var appSettings = Path.Combine(baseDir, "appsettings.json");
|
||||
if (File.Exists(appSettings))
|
||||
ApplyJson(File.ReadAllText(appSettings), cfg);
|
||||
|
||||
if (File.Exists(UserConfigPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyJson(File.ReadAllText(UserConfigPath), cfg);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* keep base */
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
private static void ApplyJson(string json, AppConfig dst)
|
||||
{
|
||||
var src = JsonSerializer.Deserialize<AppConfig>(json, JsonOptions);
|
||||
if (src == null)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(src.Language))
|
||||
dst.Language = src.Language;
|
||||
dst.EctoolPath = src.EctoolPath;
|
||||
dst.PollIntervalMs = Math.Clamp(src.PollIntervalMs, 250, 60_000);
|
||||
dst.FanRpmPollEveryNCycles = Math.Max(1, src.FanRpmPollEveryNCycles);
|
||||
if (src.FanRpmArgs is { Length: > 0 })
|
||||
dst.FanRpmArgs = src.FanRpmArgs;
|
||||
dst.FanDutyArgs = src.FanDutyArgs;
|
||||
if (src.AutoFanCtrlArgs is { Length: > 0 })
|
||||
dst.AutoFanCtrlArgs = src.AutoFanCtrlArgs;
|
||||
dst.TempSource = string.Equals(src.TempSource, "MaxCore", StringComparison.OrdinalIgnoreCase) ? "MaxCore" : "AverageCore";
|
||||
dst.RampUpSteps = Math.Clamp(src.RampUpSteps, 1, 10);
|
||||
dst.RampUpMinDeltaPercent = Math.Clamp(src.RampUpMinDeltaPercent, 0, 50);
|
||||
dst.FailSafeAfterConsecutiveErrors = Math.Max(1, src.FailSafeAfterConsecutiveErrors);
|
||||
dst.FailSafeFanPercent = Math.Clamp(src.FailSafeFanPercent, 0, 100);
|
||||
dst.FailSafeRestoreAutoFan = src.FailSafeRestoreAutoFan;
|
||||
dst.ChartHistoryMinutes = Math.Clamp(src.ChartHistoryMinutes, 1, 120);
|
||||
dst.ChartMaxPoints = src.ChartMaxPoints > 0 ? src.ChartMaxPoints : 900;
|
||||
if (src.CurvePoints is { Length: 14 })
|
||||
dst.CurvePoints = (int[])src.CurvePoints.Clone();
|
||||
else if (src.CurvePoints is { Length: 13 } old13)
|
||||
dst.CurvePoints = MigrateCurveTo14(old13);
|
||||
else if (src.CurvePoints is { Length: 20 } old20)
|
||||
dst.CurvePoints = MigrateCurveTo14(old20, new[] { 0, 5, 11, 16, 21, 26, 32, 37, 42, 47, 53, 58, 63, 68, 74, 79, 84, 90, 95, 100 });
|
||||
}
|
||||
|
||||
private static int[] MigrateCurveTo14(int[] oldCurve, int[]? oldTemps = null)
|
||||
{
|
||||
var temps = oldTemps ?? (oldCurve.Length == 13 ? new[] { 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100 } : new[] { 0, 5, 11, 16, 21, 26, 32, 37, 42, 47, 53, 58, 63, 68, 74, 79, 84, 90, 95, 100 });
|
||||
var newTemps = FanCurve.TempBreakpoints;
|
||||
var result = new int[14];
|
||||
for (var i = 0; i < 14; i++)
|
||||
{
|
||||
var t = newTemps[i];
|
||||
if (t <= temps[0])
|
||||
result[i] = Math.Clamp(oldCurve[0], 0, 100);
|
||||
else if (t >= temps[^1])
|
||||
result[i] = Math.Clamp(oldCurve[^1], 0, 100);
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < temps.Length - 1; j++)
|
||||
{
|
||||
if (t < temps[j + 1])
|
||||
{
|
||||
var frac = (t - temps[j]) / (double)(temps[j + 1] - temps[j]);
|
||||
result[i] = (int)Math.Round(Math.Clamp(oldCurve[j], 0, 100) + (Math.Clamp(oldCurve[j + 1], 0, 100) - Math.Clamp(oldCurve[j], 0, 100)) * frac);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SaveUser()
|
||||
{
|
||||
var dir = Path.GetDirectoryName(UserConfigPath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(UserConfigPath, JsonSerializer.Serialize(this, JsonOptions));
|
||||
}
|
||||
}
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ChromeboxFanControl;
|
||||
|
||||
public sealed class AppConfig
|
||||
{
|
||||
/// <summary>UI language: "auto" (system), "en", "zh-Hans", "zh-Hant". Restart to apply.</summary>
|
||||
public string Language { get; set; } = "auto";
|
||||
public string EctoolPath { get; set; } = @"C:\Program Files\crosec\ectool.exe";
|
||||
public int PollIntervalMs { get; set; } = 1500;
|
||||
/// <summary>Call ectool for RPM every N control cycles (1 = every cycle).</summary>
|
||||
public int FanRpmPollEveryNCycles { get; set; } = 1;
|
||||
public string[] FanRpmArgs { get; set; } = ["pwmgetfanrpm", "0"];
|
||||
/// <summary>Ectool args to read fan duty (0-100). Empty = skip, use commanded duty.</summary>
|
||||
public string[]? FanDutyArgs { get; set; }
|
||||
public string[] AutoFanCtrlArgs { get; set; } = ["autofanctrl"];
|
||||
public string TempSource { get; set; } = "AverageCore";
|
||||
public int RampUpSteps { get; set; } = 3;
|
||||
public int RampUpMinDeltaPercent { get; set; } = 20;
|
||||
public int FailSafeAfterConsecutiveErrors { get; set; } = 5;
|
||||
public int FailSafeFanPercent { get; set; } = 100;
|
||||
public bool FailSafeRestoreAutoFan { get; set; }
|
||||
public int ChartHistoryMinutes { get; set; } = 15;
|
||||
public int ChartMaxPoints { get; set; } = 900;
|
||||
public int[] CurvePoints { get; set; } =
|
||||
[
|
||||
0, 0, 10, 25, 40, 55, 70, 80, 90, 95, 97, 98, 99, 100
|
||||
];
|
||||
|
||||
public static JsonSerializerOptions JsonOptions { get; } = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters = { new JsonStringEnumConverter() }
|
||||
};
|
||||
|
||||
public static string UserConfigPath =>
|
||||
Path.Combine(
|
||||
Environment.UserInteractive
|
||||
? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
|
||||
: Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"ChromeboxFanControl",
|
||||
"config.json");
|
||||
|
||||
public static AppConfig LoadMerged()
|
||||
{
|
||||
var cfg = new AppConfig();
|
||||
var baseDir = AppContext.BaseDirectory;
|
||||
var appSettings = Path.Combine(baseDir, "appsettings.json");
|
||||
if (File.Exists(appSettings))
|
||||
ApplyJson(File.ReadAllText(appSettings), cfg);
|
||||
|
||||
if (File.Exists(UserConfigPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyJson(File.ReadAllText(UserConfigPath), cfg);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* keep base */
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
private static void ApplyJson(string json, AppConfig dst)
|
||||
{
|
||||
var src = JsonSerializer.Deserialize<AppConfig>(json, JsonOptions);
|
||||
if (src == null)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(src.Language))
|
||||
dst.Language = src.Language;
|
||||
dst.EctoolPath = src.EctoolPath;
|
||||
dst.PollIntervalMs = Math.Clamp(src.PollIntervalMs, 250, 60_000);
|
||||
dst.FanRpmPollEveryNCycles = Math.Max(1, src.FanRpmPollEveryNCycles);
|
||||
if (src.FanRpmArgs is { Length: > 0 })
|
||||
dst.FanRpmArgs = src.FanRpmArgs;
|
||||
dst.FanDutyArgs = src.FanDutyArgs;
|
||||
if (src.AutoFanCtrlArgs is { Length: > 0 })
|
||||
dst.AutoFanCtrlArgs = src.AutoFanCtrlArgs;
|
||||
dst.TempSource = string.Equals(src.TempSource, "MaxCore", StringComparison.OrdinalIgnoreCase) ? "MaxCore" : "AverageCore";
|
||||
dst.RampUpSteps = Math.Clamp(src.RampUpSteps, 1, 10);
|
||||
dst.RampUpMinDeltaPercent = Math.Clamp(src.RampUpMinDeltaPercent, 0, 50);
|
||||
dst.FailSafeAfterConsecutiveErrors = Math.Max(1, src.FailSafeAfterConsecutiveErrors);
|
||||
dst.FailSafeFanPercent = Math.Clamp(src.FailSafeFanPercent, 0, 100);
|
||||
dst.FailSafeRestoreAutoFan = src.FailSafeRestoreAutoFan;
|
||||
dst.ChartHistoryMinutes = Math.Clamp(src.ChartHistoryMinutes, 1, 120);
|
||||
dst.ChartMaxPoints = src.ChartMaxPoints > 0 ? src.ChartMaxPoints : 900;
|
||||
if (src.CurvePoints is { Length: 14 })
|
||||
dst.CurvePoints = (int[])src.CurvePoints.Clone();
|
||||
else if (src.CurvePoints is { Length: 13 } old13)
|
||||
dst.CurvePoints = MigrateCurveTo14(old13);
|
||||
else if (src.CurvePoints is { Length: 20 } old20)
|
||||
dst.CurvePoints = MigrateCurveTo14(old20, new[] { 0, 5, 11, 16, 21, 26, 32, 37, 42, 47, 53, 58, 63, 68, 74, 79, 84, 90, 95, 100 });
|
||||
}
|
||||
|
||||
private static int[] MigrateCurveTo14(int[] oldCurve, int[]? oldTemps = null)
|
||||
{
|
||||
var temps = oldTemps ?? (oldCurve.Length == 13 ? new[] { 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100 } : new[] { 0, 5, 11, 16, 21, 26, 32, 37, 42, 47, 53, 58, 63, 68, 74, 79, 84, 90, 95, 100 });
|
||||
var newTemps = FanCurve.TempBreakpoints;
|
||||
var result = new int[14];
|
||||
for (var i = 0; i < 14; i++)
|
||||
{
|
||||
var t = newTemps[i];
|
||||
if (t <= temps[0])
|
||||
result[i] = Math.Clamp(oldCurve[0], 0, 100);
|
||||
else if (t >= temps[^1])
|
||||
result[i] = Math.Clamp(oldCurve[^1], 0, 100);
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < temps.Length - 1; j++)
|
||||
{
|
||||
if (t < temps[j + 1])
|
||||
{
|
||||
var frac = (t - temps[j]) / (double)(temps[j + 1] - temps[j]);
|
||||
result[i] = (int)Math.Round(Math.Clamp(oldCurve[j], 0, 100) + (Math.Clamp(oldCurve[j + 1], 0, 100) - Math.Clamp(oldCurve[j], 0, 100)) * frac);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SaveUser()
|
||||
{
|
||||
var dir = Path.GetDirectoryName(UserConfigPath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(UserConfigPath, JsonSerializer.Serialize(this, JsonOptions));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user