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;
/// 基础测试页 VM:24 通道 + 行内开/关按钮 + 统计条 + 一键测试全部。
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 _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 Rows { get; } = new();
public IReadOnlyList 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);
row.DisplayVoltage = row.VoltageOn;
row.DisplayCurrent = row.CurrentOn;
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
row.Status = onOk ? TestStatus.Running : TestStatus.Fail;
StartPolling(row.Channel);
_log.Add(onOk ? UiLogLevel.Info : UiLogLevel.Error,
$"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A {(onOk ? "开状态合格" : "开状态不合格")}");
}
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;
row.CurrentOff = Math.Abs(offResult.Measurement.Current);
row.DisplayVoltage = row.VoltageOff;
row.DisplayCurrent = row.CurrentOff;
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
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 I={row.CurrentOff:F3}A {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;
_log.Add(UiLogLevel.Info, $"CH{row.Channel} 基础测试开始:执行开状态测试。");
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);
row.DisplayVoltage = row.VoltageOn;
row.DisplayCurrent = row.CurrentOn;
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
_log.Add(onOk ? UiLogLevel.Info : UiLogLevel.Error,
$"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A {(onOk ? "开状态合格" : "开状态不合格")}");
StartPolling(row.Channel);
ct.ThrowIfCancellationRequested();
await StopPollingAsync(row.Channel);
_log.Add(UiLogLevel.Info, $"CH{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;
row.CurrentOff = Math.Abs(offResult.Measurement.Current);
row.DisplayVoltage = row.VoltageOff;
row.DisplayCurrent = row.CurrentOff;
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 I={row.CurrentOff:F3}A {offState} {verdict}");
}
[RelayCommand]
private void Clear()
{
foreach (var r in Rows)
{
r.VoltageOn = 0; r.CurrentOn = 0; r.VoltageOff = 0; r.CurrentOff = 0;
r.DisplayVoltage = 0; r.DisplayCurrent = 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 measurements;
try
{
measurements = await _sspc.ReadMeasurementsAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
_log.Add(UiLogLevel.Error, $"基础测试刷新失败:{ex.Message}");
continue;
}
HashSet 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);
row.DisplayVoltage = row.VoltageOn;
row.DisplayCurrent = row.CurrentOn;
}
}
}
}