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; /// 功率损耗页 VM。 public partial class PowerTestViewModel : ObservableObject { private readonly ISspc _sspc; private readonly IPowerSupply _power; private readonly ILoad _load; private readonly UiLogSink _log; public ObservableCollection Rows { get; } = new(); public ObservableCollection 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; } } /// 解析用于输入端电压的高速采集卡通道(取已标定的电压通道,优先 OutputVoltage)。 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(role, true, out var parsed) && parsed is DaqChannelRole.OutputVoltage or DaqChannelRole.InputVoltage; }