2026-06-30 12:10:29 +08:00
|
|
|
using System.Diagnostics;
|
|
|
|
|
using SSPCTester.Devices.Interfaces;
|
|
|
|
|
|
|
|
|
|
namespace SSPCTester.Logic.Testing;
|
|
|
|
|
|
2026-07-06 17:26:09 +08:00
|
|
|
/// <summary>Shared pass/fail rules for the basic on/off test.</summary>
|
2026-06-30 12:10:29 +08:00
|
|
|
public static class BasicTestCriteria
|
|
|
|
|
{
|
|
|
|
|
public const double OnVoltageThreshold = 1.0;
|
|
|
|
|
public const double OffVoltageThreshold = 0.5;
|
2026-07-03 14:16:15 +08:00
|
|
|
public const double CurrentPresentThreshold = 0.001;
|
2026-06-30 12:10:29 +08:00
|
|
|
public const int SwitchSettleDelayMs = 1_000;
|
|
|
|
|
public const int OffSettleTimeoutMs = 2_000;
|
|
|
|
|
public const int OffInitialDelayMs = 100;
|
|
|
|
|
public const int OffSampleIntervalMs = 100;
|
|
|
|
|
public const int RequiredStableOffSamples = 2;
|
|
|
|
|
|
|
|
|
|
public static bool IsOn(double voltage) => voltage > OnVoltageThreshold;
|
|
|
|
|
|
|
|
|
|
public static bool IsOff(double voltage) => Math.Abs(voltage) < OffVoltageThreshold;
|
|
|
|
|
|
2026-07-03 14:16:15 +08:00
|
|
|
public static bool HasVoltageData(double voltage) => voltage > OnVoltageThreshold;
|
|
|
|
|
|
|
|
|
|
public static bool HasCurrentData(double current) => Math.Abs(current) > CurrentPresentThreshold;
|
|
|
|
|
|
|
|
|
|
public static bool IsOnState(double voltage, double current) =>
|
|
|
|
|
HasVoltageData(voltage) && HasCurrentData(current);
|
|
|
|
|
|
|
|
|
|
public static bool IsOffState(double voltage, double current) =>
|
2026-07-06 17:26:09 +08:00
|
|
|
IsOff(voltage) && !HasCurrentData(current);
|
2026-07-03 14:16:15 +08:00
|
|
|
|
2026-06-30 12:10:29 +08:00
|
|
|
/// <summary>
|
2026-07-06 17:26:09 +08:00
|
|
|
/// Waits until the channel is stably off: no voltage and no current for consecutive samples.
|
2026-06-30 12:10:29 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public static async Task<OffStateResult> WaitForStableOffAsync(
|
|
|
|
|
ISspc sspc,
|
|
|
|
|
int channel,
|
2026-07-06 17:26:09 +08:00
|
|
|
CancellationToken ct = default,
|
|
|
|
|
Action<ChannelMeasurement>? onSample = null,
|
|
|
|
|
Func<int, CancellationToken, Task<ChannelMeasurement>>? readSampleAsync = null)
|
2026-06-30 12:10:29 +08:00
|
|
|
{
|
|
|
|
|
await Task.Delay(OffInitialDelayMs, ct).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
|
int stableSamples = 0;
|
|
|
|
|
ChannelMeasurement? latest = null;
|
|
|
|
|
|
|
|
|
|
while (stopwatch.ElapsedMilliseconds < OffSettleTimeoutMs)
|
|
|
|
|
{
|
2026-07-06 17:26:09 +08:00
|
|
|
latest = await ReadSampleAsync(sspc, channel, ct, readSampleAsync).ConfigureAwait(false);
|
|
|
|
|
onSample?.Invoke(latest);
|
2026-07-03 14:16:15 +08:00
|
|
|
stableSamples = IsOffState(latest.Voltage, latest.Current) ? stableSamples + 1 : 0;
|
2026-06-30 12:10:29 +08:00
|
|
|
|
|
|
|
|
if (stableSamples >= RequiredStableOffSamples)
|
|
|
|
|
return new OffStateResult(true, latest, stopwatch.Elapsed);
|
|
|
|
|
|
|
|
|
|
await Task.Delay(OffSampleIntervalMs, ct).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 17:26:09 +08:00
|
|
|
latest ??= await ReadSampleAsync(sspc, channel, ct, readSampleAsync).ConfigureAwait(false);
|
|
|
|
|
onSample?.Invoke(latest);
|
2026-06-30 12:10:29 +08:00
|
|
|
return new OffStateResult(false, latest, stopwatch.Elapsed);
|
|
|
|
|
}
|
2026-07-06 17:26:09 +08:00
|
|
|
|
|
|
|
|
private static Task<ChannelMeasurement> ReadSampleAsync(
|
|
|
|
|
ISspc sspc,
|
|
|
|
|
int channel,
|
|
|
|
|
CancellationToken ct,
|
|
|
|
|
Func<int, CancellationToken, Task<ChannelMeasurement>>? readSampleAsync) =>
|
|
|
|
|
readSampleAsync?.Invoke(channel, ct) ?? sspc.ReadMeasurementAsync(channel, ct);
|
2026-06-30 12:10:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public sealed record OffStateResult(
|
|
|
|
|
bool IsStableOff,
|
|
|
|
|
ChannelMeasurement Measurement,
|
|
|
|
|
TimeSpan Elapsed);
|