using System.Diagnostics; using System.Text.RegularExpressions; namespace FanControl.ChromeboxEC; /// ectool 命令封装,参见 docs/ectool-commands-zh.md internal static class EctoolRunner { private static readonly Regex NumberRegex = new(@"\d+", RegexOptions.Compiled); /// 执行 ectool 命令,返回 (成功, stdout, stderr) public static (bool ok, string stdout, string stderr) Run( string exePath, IReadOnlyList 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); } /// 解析 pwmgetfanrpm 输出中的转速 (500–50000 RPM) 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; } /// 解析 temps 输出为摄氏温度。支持 "329 K (= 56 C)" 等格式,多传感器时取最高值。排除 ratio 行中的 (313 K and 333 K) 等阈值。 public static float? TryParseTemp(string stdout) { if (string.IsNullOrWhiteSpace(stdout)) return null; float? best = null; // 匹配 "= 56 C":最可靠,且不会被 ratio 行的 (313 K and 333 K) 干扰 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; } } if (best != null) return best; // 若无 "= X C",再匹配 "329 K",排除 ratio 中的 "313 K and" 与 "and 333 K" foreach (Match m in Regex.Matches(stdout, @"(?= 250 and <= 400) { var c = k - 273.15f; if (best == null || c > best) best = c; } } return best; } }