feat: ramp-up/down delay fan control
- Add RampUpSteps and RampUpMinDeltaPercent config - Gradual ramp in both directions (up and down) to reduce fan oscillation - Fix Advanced tab layout with RowStyles and label alignment Made-with: Cursor
This commit is contained in:
@@ -14,6 +14,10 @@ public sealed class FanController : IDisposable
|
||||
private int _consecutiveTempErrors;
|
||||
private int _cycleIndex;
|
||||
private bool _failSafeActive;
|
||||
private byte? _lastCommandedDuty;
|
||||
private byte _rampStartDuty;
|
||||
private byte _rampTargetDuty;
|
||||
private int _rampStepIndex;
|
||||
|
||||
public FanController(AppConfig initialConfig)
|
||||
{
|
||||
@@ -132,12 +136,13 @@ public sealed class FanController : IDisposable
|
||||
|
||||
if (_paused)
|
||||
{
|
||||
// Do not send fanduty while paused; EC keeps last hardware state.
|
||||
ClearRampState();
|
||||
targetDuty = null;
|
||||
ectoolMsg = "paused";
|
||||
}
|
||||
else if (_failSafeActive)
|
||||
{
|
||||
ClearRampState();
|
||||
if (cfg.FailSafeRestoreAutoFan)
|
||||
{
|
||||
var (okA, _, eA) = await EctoolRunner.RunAsync(cfg.EctoolPath, cfg.AutoFanCtrlArgs, ct)
|
||||
@@ -149,6 +154,7 @@ public sealed class FanController : IDisposable
|
||||
{
|
||||
var d = (byte)Math.Clamp(cfg.FailSafeFanPercent, 0, 100);
|
||||
targetDuty = d;
|
||||
_lastCommandedDuty = d;
|
||||
var (okF, _, eF) = await EctoolRunner
|
||||
.RunAsync(cfg.EctoolPath, ["fanduty", d.ToString()], ct)
|
||||
.ConfigureAwait(false);
|
||||
@@ -157,17 +163,20 @@ public sealed class FanController : IDisposable
|
||||
}
|
||||
else if (tempC == null)
|
||||
{
|
||||
ClearRampState();
|
||||
targetDuty = null;
|
||||
ectoolMsg = "no CPU temperature (holding EC state)";
|
||||
}
|
||||
else
|
||||
{
|
||||
var d = FanCurve.CalculateFanPercent(tempC.Value, cfg.CurvePoints);
|
||||
targetDuty = d;
|
||||
var curveTarget = FanCurve.CalculateFanPercent(tempC.Value, cfg.CurvePoints);
|
||||
var output = ComputeRampOutput(curveTarget, cfg);
|
||||
targetDuty = output;
|
||||
_lastCommandedDuty = output;
|
||||
var (okD, _, eD) = await EctoolRunner
|
||||
.RunAsync(cfg.EctoolPath, ["fanduty", d.ToString()], ct)
|
||||
.RunAsync(cfg.EctoolPath, ["fanduty", output.ToString()], ct)
|
||||
.ConfigureAwait(false);
|
||||
ectoolMsg = okD ? $"fanduty {d}%" : eD;
|
||||
ectoolMsg = okD ? $"fanduty {output}%" : eD;
|
||||
}
|
||||
|
||||
_cycleIndex++;
|
||||
@@ -233,6 +242,53 @@ public sealed class FanController : IDisposable
|
||||
System.Diagnostics.Debug.WriteLine($"[ChromeboxFanControl] {msg}");
|
||||
}
|
||||
|
||||
private void ClearRampState()
|
||||
{
|
||||
_rampStepIndex = 0;
|
||||
}
|
||||
|
||||
private byte ComputeRampOutput(byte targetDuty, AppConfig cfg)
|
||||
{
|
||||
var n = Math.Clamp(cfg.RampUpSteps, 1, 10);
|
||||
var minDelta = Math.Clamp(cfg.RampUpMinDeltaPercent, 0, 50);
|
||||
|
||||
if (_lastCommandedDuty == null)
|
||||
return targetDuty;
|
||||
|
||||
var curr = _lastCommandedDuty.Value;
|
||||
var delta = Math.Abs(targetDuty - curr);
|
||||
|
||||
if (targetDuty == curr)
|
||||
{
|
||||
ClearRampState();
|
||||
return targetDuty;
|
||||
}
|
||||
|
||||
if (delta < minDelta)
|
||||
{
|
||||
ClearRampState();
|
||||
return targetDuty;
|
||||
}
|
||||
|
||||
if (_rampStepIndex == 0 || targetDuty != _rampTargetDuty)
|
||||
{
|
||||
_rampStartDuty = curr;
|
||||
_rampTargetDuty = targetDuty;
|
||||
_rampStepIndex = 1;
|
||||
}
|
||||
|
||||
if (_rampStepIndex >= n)
|
||||
{
|
||||
ClearRampState();
|
||||
return _rampTargetDuty;
|
||||
}
|
||||
|
||||
var step = (int)Math.Round((_rampTargetDuty - _rampStartDuty) * (double)_rampStepIndex / n);
|
||||
var output = (byte)Math.Clamp(_rampStartDuty + step, 0, 100);
|
||||
_rampStepIndex++;
|
||||
return output;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopInternal(waitForLoop: true);
|
||||
|
||||
Reference in New Issue
Block a user