2026-06-30 12:10:29 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 14:16:15 +08:00
|
|
|
|
await _power.OutputAsync(true);
|
|
|
|
|
|
await _load.InputAsync(true);
|
|
|
|
|
|
|
2026-06-30 12:10:29 +08:00
|
|
|
|
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;
|
2026-07-03 14:16:15 +08:00
|
|
|
|
_log.Add(UiLogLevel.Success, $"CH{ch} 功率损耗 {row.PowerLossW:F3} W,效率 {row.EfficiencyPct:F1}%");
|
2026-06-30 12:10:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
row.Status = TestStatus.Fail;
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, $"CH{ch} 功率损耗读取失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Busy = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-10 18:32:20 +08:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void Clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var row in Rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
row.Vin = 0;
|
|
|
|
|
|
row.Iin = 0;
|
|
|
|
|
|
row.Vout = 0;
|
|
|
|
|
|
row.Iout = 0;
|
|
|
|
|
|
row.MeasuredPowerOut = null;
|
|
|
|
|
|
row.Status = TestStatus.Untested;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Current = Rows.FirstOrDefault();
|
|
|
|
|
|
_log.Add(UiLogLevel.Info, "功率损耗结果已清空。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-30 12:10:29 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|