309 lines
10 KiB
C#
309 lines
10 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Windows;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using SSPCTester.Devices.Interfaces;
|
||
using SSPCTester.Logic.Models;
|
||
using SSPCTester.Logic.Testing;
|
||
using SSPCTester.UI.Services;
|
||
|
||
namespace SSPCTester.UI.ViewModels;
|
||
|
||
/// <summary>基础测试页 VM:24 通道 + 行内开/关按钮 + 统计条 + 一键测试全部。</summary>
|
||
public partial class BasicTestViewModel : ObservableObject
|
||
{
|
||
private const int SwitchSettleDelayMs = BasicTestCriteria.SwitchSettleDelayMs;
|
||
private readonly ISspc _sspc;
|
||
private readonly UiLogSink _log;
|
||
private readonly object _pollLock = new();
|
||
private readonly HashSet<int> _pollingChannels = new();
|
||
private CancellationTokenSource? _pollCts;
|
||
private Task? _pollTask;
|
||
private CancellationTokenSource? _runAllCts;
|
||
|
||
[ObservableProperty] private bool _busy;
|
||
[ObservableProperty] private bool _isBatchRunning;
|
||
[ObservableProperty] private Project? _project;
|
||
[ObservableProperty] private int _selectedRefreshRateHz = 5;
|
||
|
||
public ObservableCollection<ChannelResult> Rows { get; } = new();
|
||
public IReadOnlyList<int> RefreshRatesHz { get; } = new[] { 2, 5 };
|
||
|
||
public BasicTestViewModel(ISspc sspc, UiLogSink log)
|
||
{
|
||
_sspc = sspc; _log = log;
|
||
}
|
||
|
||
public int Total => Rows.Count;
|
||
public int PassCount => Rows.Count(r => r.Status == TestStatus.Pass);
|
||
public int FailCount => Rows.Count(r => r.Status == TestStatus.Fail);
|
||
public int UntestedCount => Rows.Count(r => r.Status == TestStatus.Untested);
|
||
public bool CanRunAll => !Busy && !IsBatchRunning;
|
||
|
||
partial void OnBusyChanged(bool value) => OnPropertyChanged(nameof(CanRunAll));
|
||
|
||
partial void OnIsBatchRunningChanged(bool value) => OnPropertyChanged(nameof(CanRunAll));
|
||
|
||
public void SetProject(Project p)
|
||
{
|
||
StopAllPolling();
|
||
Project = p;
|
||
Rows.Clear();
|
||
if (p.BasicResults.Count == 0)
|
||
{
|
||
for (int i = 1; i <= _sspc.ChannelCount; i++)
|
||
p.BasicResults.Add(new ChannelResult { Channel = i });
|
||
}
|
||
foreach (var r in p.BasicResults)
|
||
{
|
||
r.PropertyChanged += (_, _) => RaiseStats();
|
||
Rows.Add(r);
|
||
}
|
||
RaiseStats();
|
||
}
|
||
|
||
private void RaiseStats()
|
||
{
|
||
Application.Current?.Dispatcher.Invoke(() =>
|
||
{
|
||
OnPropertyChanged(nameof(PassCount));
|
||
OnPropertyChanged(nameof(FailCount));
|
||
OnPropertyChanged(nameof(UntestedCount));
|
||
OnPropertyChanged(nameof(Total));
|
||
});
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task TurnOn(ChannelResult? row)
|
||
{
|
||
if (row == null) return;
|
||
Busy = true;
|
||
try
|
||
{
|
||
if (!_sspc.IsConnected) await _sspc.ConnectAsync();
|
||
row.Status = TestStatus.Running;
|
||
await _sspc.TurnOnAsync(row.Channel);
|
||
await Task.Delay(SwitchSettleDelayMs);
|
||
var measurement = await _sspc.ReadMeasurementAsync(row.Channel);
|
||
row.VoltageOn = measurement.Voltage;
|
||
row.CurrentOn = Math.Abs(measurement.Current);
|
||
StartPolling(row.Channel);
|
||
_log.Add(UiLogLevel.Info, $"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await StopPollingAsync(row.Channel);
|
||
row.Status = TestStatus.Fail;
|
||
_log.Add(UiLogLevel.Error, $"CH{row.Channel} 开启或读取失败:{ex.Message}");
|
||
try { await _sspc.TurnOffAsync(row.Channel); } catch { }
|
||
}
|
||
finally { Busy = false; }
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task TurnOff(ChannelResult? row)
|
||
{
|
||
if (row == null) return;
|
||
Busy = true;
|
||
try
|
||
{
|
||
await StopPollingAsync(row.Channel);
|
||
await _sspc.TurnOffAsync(row.Channel);
|
||
await Task.Delay(SwitchSettleDelayMs);
|
||
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel);
|
||
row.VoltageOff = offResult.Measurement.Voltage;
|
||
bool onOk = BasicTestCriteria.IsOn(row.VoltageOn);
|
||
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
||
string verdict = row.Status == TestStatus.Pass ? "合格" : "不合格";
|
||
string offState = offResult.IsStableOff ? "已稳定关断" : "关断超时";
|
||
_log.Add(row.Status == TestStatus.Pass ? UiLogLevel.Success : UiLogLevel.Error,
|
||
$"CH{row.Channel} 关 -> V={row.VoltageOff:F2}V {offState} {verdict}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await StopPollingAsync(row.Channel);
|
||
row.Status = TestStatus.Fail;
|
||
_log.Add(UiLogLevel.Error, $"CH{row.Channel} 关断或读取失败:{ex.Message}");
|
||
try { await _sspc.TurnOffAsync(row.Channel); } catch { }
|
||
}
|
||
finally { Busy = false; }
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task RunAll()
|
||
{
|
||
if (IsBatchRunning) return;
|
||
|
||
_runAllCts?.Dispose();
|
||
_runAllCts = new CancellationTokenSource();
|
||
var ct = _runAllCts.Token;
|
||
|
||
Busy = true;
|
||
IsBatchRunning = true;
|
||
try
|
||
{
|
||
foreach (var row in Rows)
|
||
{
|
||
ct.ThrowIfCancellationRequested();
|
||
await RunChannelAsync(row, ct);
|
||
}
|
||
_log.Add(UiLogLevel.Success, "基础测试全部完成。");
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
foreach (var row in Rows)
|
||
{
|
||
if (row.Status == TestStatus.Running)
|
||
row.Status = TestStatus.Untested;
|
||
}
|
||
_log.Add(UiLogLevel.Warning, "基础测试批量测试已中断。");
|
||
}
|
||
finally
|
||
{
|
||
_runAllCts?.Dispose();
|
||
_runAllCts = null;
|
||
IsBatchRunning = false;
|
||
Busy = false;
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task CancelRunAll()
|
||
{
|
||
if (!IsBatchRunning) return;
|
||
|
||
_runAllCts?.Cancel();
|
||
foreach (var row in Rows)
|
||
{
|
||
if (row.Status != TestStatus.Running) continue;
|
||
|
||
try
|
||
{
|
||
await StopPollingAsync(row.Channel);
|
||
await _sspc.TurnOffAsync(row.Channel);
|
||
}
|
||
catch { }
|
||
}
|
||
}
|
||
|
||
private async Task RunChannelAsync(ChannelResult row, CancellationToken ct)
|
||
{
|
||
if (!_sspc.IsConnected) await _sspc.ConnectAsync(ct);
|
||
|
||
row.Status = TestStatus.Running;
|
||
await _sspc.TurnOnAsync(row.Channel, ct);
|
||
await Task.Delay(SwitchSettleDelayMs, ct);
|
||
|
||
var measurement = await _sspc.ReadMeasurementAsync(row.Channel, ct);
|
||
row.VoltageOn = measurement.Voltage;
|
||
row.CurrentOn = Math.Abs(measurement.Current);
|
||
StartPolling(row.Channel);
|
||
|
||
ct.ThrowIfCancellationRequested();
|
||
|
||
await StopPollingAsync(row.Channel);
|
||
await _sspc.TurnOffAsync(row.Channel, ct);
|
||
await Task.Delay(SwitchSettleDelayMs, ct);
|
||
|
||
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel, ct);
|
||
row.VoltageOff = offResult.Measurement.Voltage;
|
||
bool onOk = BasicTestCriteria.IsOn(row.VoltageOn);
|
||
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void Clear()
|
||
{
|
||
foreach (var r in Rows)
|
||
{
|
||
r.VoltageOn = 0; r.CurrentOn = 0; r.VoltageOff = 0;
|
||
r.Status = TestStatus.Untested;
|
||
}
|
||
}
|
||
|
||
private void StartPolling(int channel)
|
||
{
|
||
lock (_pollLock)
|
||
{
|
||
_pollingChannels.Add(channel);
|
||
if (_pollTask is { IsCompleted: false }) return;
|
||
|
||
_pollCts = new CancellationTokenSource();
|
||
_pollTask = PollMeasurementsAsync(_pollCts.Token);
|
||
}
|
||
}
|
||
|
||
private async Task StopPollingAsync(int channel)
|
||
{
|
||
Task? taskToWait = null;
|
||
CancellationTokenSource? ctsToDispose = null;
|
||
lock (_pollLock)
|
||
{
|
||
_pollingChannels.Remove(channel);
|
||
if (_pollingChannels.Count != 0 || _pollCts == null) return;
|
||
|
||
_pollCts.Cancel();
|
||
taskToWait = _pollTask;
|
||
ctsToDispose = _pollCts;
|
||
_pollTask = null;
|
||
_pollCts = null;
|
||
}
|
||
|
||
if (taskToWait != null)
|
||
{
|
||
try { await taskToWait; }
|
||
catch (OperationCanceledException) { }
|
||
}
|
||
ctsToDispose?.Dispose();
|
||
}
|
||
|
||
private void StopAllPolling()
|
||
{
|
||
lock (_pollLock)
|
||
{
|
||
_pollingChannels.Clear();
|
||
_pollCts?.Cancel();
|
||
_pollCts = null;
|
||
_pollTask = null;
|
||
}
|
||
}
|
||
|
||
private async Task PollMeasurementsAsync(CancellationToken ct)
|
||
{
|
||
while (!ct.IsCancellationRequested)
|
||
{
|
||
int refreshRate = Math.Max(1, SelectedRefreshRateHz);
|
||
await Task.Delay(TimeSpan.FromSeconds(1.0 / refreshRate), ct);
|
||
|
||
IReadOnlyList<ChannelMeasurement> measurements;
|
||
try
|
||
{
|
||
measurements = await _sspc.ReadMeasurementsAsync(ct);
|
||
}
|
||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||
{
|
||
break;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_log.Add(UiLogLevel.Error, $"基础测试刷新失败:{ex.Message}");
|
||
continue;
|
||
}
|
||
|
||
HashSet<int> activeChannels;
|
||
lock (_pollLock)
|
||
activeChannels = _pollingChannels.ToHashSet();
|
||
|
||
foreach (var measurement in measurements)
|
||
{
|
||
if (!activeChannels.Contains(measurement.PhysicalSlot)) continue;
|
||
var row = Rows.FirstOrDefault(x => x.Channel == measurement.PhysicalSlot);
|
||
if (row == null) continue;
|
||
|
||
row.VoltageOn = measurement.Voltage;
|
||
row.CurrentOn = Math.Abs(measurement.Current);
|
||
}
|
||
}
|
||
}
|
||
}
|