Files
2026-04-02 18:32:43 +08:00

116 lines
3.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace ChromeboxFanControl;
public sealed class EctoolRunner
{
private static readonly Regex NumberRegex = new(@"\d+", RegexOptions.Compiled);
public static (bool ok, string stdout, string stderr) RunSync(
string exePath,
IReadOnlyList<string> args,
int timeoutMs = 15_000) =>
RunAsync(exePath, args, CancellationToken.None, timeoutMs).GetAwaiter().GetResult();
public static async Task<(bool ok, string stdout, string stderr)> RunAsync(
string exePath,
IReadOnlyList<string> args,
CancellationToken ct = default,
int timeoutMs = 15_000)
{
if (string.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
return (false, "", "ectool executable not found.");
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 stdoutTask = proc.StandardOutput.ReadToEndAsync(ct);
var stderrTask = proc.StandardError.ReadToEndAsync(ct);
var waitTask = proc.WaitForExitAsync(ct);
var completed = await Task.WhenAny(waitTask, Task.Delay(timeoutMs, ct)).ConfigureAwait(false);
if (completed != waitTask)
{
try
{
proc.Kill(entireProcessTree: true);
}
catch
{
/* ignore */
}
return (false, "", "ectool timed out.");
}
await waitTask.ConfigureAwait(false);
var stdout = await stdoutTask.ConfigureAwait(false);
var stderr = await stderrTask.ConfigureAwait(false);
var ok = proc.ExitCode == 0;
return (ok, stdout, stderr);
}
/// <summary>Pick the most plausible RPM (typically 50020000) from ectool text output.</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;
// Prefer larger numbers in typical fan range (avoid fan index "0")
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 >= 0 and <= 50_000)
return n;
}
return null;
}
/// <summary>Pick duty percent (0-100) from ectool text output.</summary>
public static int? TryParseFanDuty(string stdout)
{
if (string.IsNullOrWhiteSpace(stdout))
return null;
foreach (Match m in NumberRegex.Matches(stdout))
{
if (int.TryParse(m.Value, out var n) && n is >= 0 and <= 100)
return n;
}
return null;
}
}