- Add FanControl.ChromeboxEC plugin for Fan Control - Add project background/缘由 in README (EN/zh) - Add docs/ectool-commands-zh.md - Update sln, appsettings, gitignore Made-with: Cursor
108 lines
3.3 KiB
C#
108 lines
3.3 KiB
C#
using System.Diagnostics;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace FanControl.ChromeboxEC;
|
||
|
||
/// <summary>ectool 命令封装,参见 docs/ectool-commands-zh.md</summary>
|
||
internal static class EctoolRunner
|
||
{
|
||
private static readonly Regex NumberRegex = new(@"\d+", RegexOptions.Compiled);
|
||
|
||
/// <summary>执行 ectool 命令,返回 (成功, stdout, stderr)</summary>
|
||
public static (bool ok, string stdout, string stderr) Run(
|
||
string exePath,
|
||
IReadOnlyList<string> args,
|
||
int timeoutMs = 15_000)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
|
||
return (false, "", "ectool 未找到");
|
||
|
||
var psi = new ProcessStartInfo
|
||
{
|
||
FileName = exePath,
|
||
UseShellExecute = false,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
CreateNoWindow = true,
|
||
};
|
||
foreach (var a in args)
|
||
psi.ArgumentList.Add(a);
|
||
|
||
using var proc = new Process { StartInfo = psi };
|
||
try
|
||
{
|
||
proc.Start();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return (false, "", ex.Message);
|
||
}
|
||
|
||
var stdout = proc.StandardOutput.ReadToEnd();
|
||
var stderr = proc.StandardError.ReadToEnd();
|
||
if (!proc.WaitForExit(timeoutMs))
|
||
{
|
||
try { proc.Kill(entireProcessTree: true); } catch { /* ignore */ }
|
||
return (false, "", "ectool 超时");
|
||
}
|
||
|
||
return (proc.ExitCode == 0, stdout, stderr);
|
||
}
|
||
|
||
/// <summary>解析 pwmgetfanrpm 输出中的转速 (500–50000 RPM)</summary>
|
||
public static int? TryParseFanRpm(string stdout)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(stdout))
|
||
return null;
|
||
|
||
int? best = null;
|
||
foreach (Match m in NumberRegex.Matches(stdout))
|
||
{
|
||
if (!int.TryParse(m.Value, out var n))
|
||
continue;
|
||
if (n is < 400 or > 50_000)
|
||
continue;
|
||
if (n >= 800 && (best == null || n > best))
|
||
best = n;
|
||
}
|
||
if (best != null)
|
||
return best;
|
||
|
||
foreach (Match m in NumberRegex.Matches(stdout))
|
||
{
|
||
if (int.TryParse(m.Value, out var n) && n is >= 400 and <= 50_000)
|
||
return n;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>解析 temps 输出为摄氏温度。支持 "315 K (= 42 C)" 等格式,多传感器时取最高值。</summary>
|
||
public static float? TryParseTemp(string stdout)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(stdout))
|
||
return null;
|
||
|
||
float? best = null;
|
||
// 匹配 "315 K" 或 "315 K (= 42 C)"
|
||
foreach (Match m in Regex.Matches(stdout, @"(\d{2,3})\s*K\b", RegexOptions.IgnoreCase))
|
||
{
|
||
if (int.TryParse(m.Groups[1].Value, out var k) && k is >= 250 and <= 400)
|
||
{
|
||
var c = k - 273.15f;
|
||
if (best == null || c > best)
|
||
best = c;
|
||
}
|
||
}
|
||
// 匹配 "= 42 C" 或 "42 C"
|
||
foreach (Match m in Regex.Matches(stdout, @"(?:=\s*)?(\d{1,3})\s*[Cc]\b"))
|
||
{
|
||
if (int.TryParse(m.Groups[1].Value, out var c) && c is >= 0 and <= 120)
|
||
{
|
||
if (best == null || c > best)
|
||
best = c;
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
}
|