SSPC-Tester/SSPCTester.UI/ViewModels/PowerTestViewModel.cs

204 lines
7.2 KiB
C#
Raw Normal View History

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SSPCTester.Devices.Config;
using SSPCTester.Devices.Interfaces;
using SSPCTester.Logic.Models;
using SSPCTester.UI.Services;
namespace SSPCTester.UI.ViewModels;
/// <summary>功率损耗页 VM。</summary>
public partial class PowerTestViewModel : ObservableObject
{
private readonly ISspc _sspc;
private readonly IPowerSupply _power;
private readonly ILoad _load;
private readonly UiLogSink _log;
public ObservableCollection<PowerLossRow> Rows { get; } = new();
public ObservableCollection<int> Channels { get; } = new();
[ObservableProperty] private Project? _project;
[ObservableProperty] private int _selectedChannel;
[ObservableProperty] private int _samplesPerChannel = 5;
[ObservableProperty] private int _intervalMs = 100;
[ObservableProperty] private PowerLossRow? _current;
[ObservableProperty] private bool _busy;
public bool CanOperate => !Busy;
public PowerTestViewModel(ISspc sspc, IPowerSupply power, ILoad load, UiLogSink log)
{
_sspc = sspc;
_power = power;
_load = load;
_log = log;
for (int i = 1; i <= sspc.ChannelCount; i++) Channels.Add(i);
SelectedChannel = 1;
}
partial void OnBusyChanged(bool value) => OnPropertyChanged(nameof(CanOperate));
public void SetProject(Project p)
{
Project = p;
Rows.Clear();
if (p.PowerResults.Count == 0)
{
for (int i = 1; i <= Channels.Count; i++)
p.PowerResults.Add(new PowerLossRow { Channel = i });
}
foreach (var r in p.PowerResults) Rows.Add(r);
Current = Rows.FirstOrDefault();
}
[RelayCommand]
private async Task TurnOnCurrentChannel()
{
Busy = true;
int ch = SelectedChannel;
try
{
if (!_sspc.IsConnected) await _sspc.ConnectAsync();
await _sspc.TurnOnAsync(ch);
_log.Add(UiLogLevel.Info, $"CH{ch} 已开启。");
}
catch (Exception ex)
{
_log.Add(UiLogLevel.Error, $"CH{ch} 开启失败:{ex.Message}");
}
finally
{
Busy = false;
}
}
[RelayCommand]
private async Task TurnOffCurrentChannel()
{
Busy = true;
int ch = SelectedChannel;
try
{
if (!_sspc.IsConnected) await _sspc.ConnectAsync();
await _sspc.TurnOffAsync(ch);
_log.Add(UiLogLevel.Info, $"CH{ch} 已关断。");
}
catch (Exception ex)
{
_log.Add(UiLogLevel.Error, $"CH{ch} 关断失败:{ex.Message}");
}
finally
{
Busy = false;
}
}
[RelayCommand]
private async Task MeasureCurrent()
{
if (Project == null) return;
Busy = true;
int ch = SelectedChannel;
var row = Rows.First(r => r.Channel == ch);
try
{
// 功率损耗输入端来自 UDP5080-100 电源实时测量。
try
{
await Task.CompletedTask;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Modbus继电器端连接失败{ex.Message}。请在设置中检查串口与连接。", ex);
}
try
{
if (!_power.IsConnected) await _power.ConnectAsync();
}
catch (Exception ex)
{
throw new InvalidOperationException($"高速采集卡连接失败:{ex.Message}。请在设置中检查采集卡。", ex);
}
try
{
if (!_load.IsConnected) await _load.ConnectAsync();
}
catch (Exception ex)
{
throw new InvalidOperationException($"IT8702P connection failed: {ex.Message}", ex);
}
double sVin = 0, sIin = 0, sVout = 0, sIout = 0, sPout = 0;
for (int s = 0; s < SamplesPerChannel; s++)
{
/*
// 输出端Modbus 继电器端电压 + 通流电流SSPC 为串联开关Iin = Iout
var measurement = await _load.ReadPowerAsync();
// 输入端:高速采集卡电压。
var daqValues = await _daq.ReadInstantAsync(new[] { daqVoltageChannel });
*/
var input = await _power.ReadAsync();
var measurement = await _load.ReadPowerAsync();
double vin = input.volts;
double iin = Math.Abs(input.amps);
double vout = measurement.Volts;
double iout = Math.Abs(measurement.Amps);
double pout = measurement.Watts;
if (!double.IsFinite(vin) || !double.IsFinite(iin) || !double.IsFinite(vout) || !double.IsFinite(iout) || !double.IsFinite(pout))
throw new InvalidOperationException("功率损耗采样无效,请检查采集卡电压通道标定与 Modbus 连接。");
sVin += vin;
sIin += iin;
sVout += vout;
sIout += iout;
sPout += pout;
await Task.Delay(IntervalMs);
}
row.Vin = sVin / SamplesPerChannel;
row.Iin = sIin / SamplesPerChannel;
row.Vout = sVout / SamplesPerChannel;
row.Iout = sIout / SamplesPerChannel;
row.MeasuredPowerOut = sPout / SamplesPerChannel;
row.Status = TestStatus.Measured;
Current = row;
_log.Add(UiLogLevel.Success, $"CH{ch} 功率损耗 {row.PowerLossPct:F1}%,效率 {row.EfficiencyRatio:F3}");
}
catch (Exception ex)
{
row.Status = TestStatus.Fail;
_log.Add(UiLogLevel.Error, $"CH{ch} 功率损耗读取失败:{ex.Message}");
}
finally
{
Busy = false;
}
}
/// <summary>解析用于输入端电压的高速采集卡通道(取已标定的电压通道,优先 OutputVoltage。</summary>
private static int ResolveDaqVoltageChannel(DaqOptions options)
{
if (options.Channels == null || options.Channels.Count == 0)
throw new InvalidOperationException("未配置采集卡通道,无法执行功率损耗测试。");
var candidates = options.Channels
.Where(x => x.PhysicalChannel >= 0 && x.PhysicalChannel < options.ChannelCount)
.Where(x => x.IsCalibrated)
.Where(x => IsVoltageRole(x.Role))
.ToArray();
var pick = candidates.FirstOrDefault(x => string.Equals(x.Role, nameof(DaqChannelRole.OutputVoltage), StringComparison.OrdinalIgnoreCase))
?? candidates.FirstOrDefault();
if (pick == null)
throw new InvalidOperationException("功率损耗测试需要一个已标定的采集卡电压通道(输入端电压),请在设置中配置并标定。");
return pick.PhysicalChannel;
}
private static bool IsVoltageRole(string? role) =>
Enum.TryParse<DaqChannelRole>(role, true, out var parsed)
&& parsed is DaqChannelRole.OutputVoltage or DaqChannelRole.InputVoltage;
}