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

204 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.PowerLossW:F3} W效率 {row.EfficiencyPct: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;
}