日常更新

This commit is contained in:
jack
2026-04-02 18:32:43 +08:00
parent 111eb22824
commit bc1e86d4ea
32 changed files with 2745 additions and 2742 deletions

View File

@@ -1,115 +1,115 @@
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;
}
}
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;
}
}