638 lines
30 KiB
C#
638 lines
30 KiB
C#
using DeviceCommand.Devices;
|
||
using OxyPlot;
|
||
using OxyPlot.Axes;
|
||
using OxyPlot.Legends;
|
||
using OxyPlot.Series;
|
||
using Prism.Commands;
|
||
using Prism.Ioc;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Input;
|
||
using UIShare.GlobalVariable;
|
||
using UIShare.ViewModelBase;
|
||
|
||
namespace PowerAdaptabilityModule.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// 电源适应性测试 ViewModel(Chroma61800 交流源一拖三):
|
||
/// 左侧为设备命令面板(三相输出设置 + LIST 模式),右侧为 A/B/C 三相实时波形图表。
|
||
/// </summary>
|
||
public class PowerAdaptabilityViewModel : NavigateViewModelBase, IDisposable
|
||
{
|
||
#region 常量
|
||
/// <summary>采样周期(毫秒)</summary>
|
||
private const int SampleIntervalMs = 1000;
|
||
|
||
/// <summary>合成正弦波的分段数(0~360°,每 2° 一个点,共 181 点)</summary>
|
||
private const int SinePoints = 180;
|
||
|
||
/// <summary>三相相位偏移(°):A=0,B=-120,C=+120</summary>
|
||
private static readonly double[] PhaseOffsetDeg = { 0, -120, 120 };
|
||
|
||
/// <summary>三相曲线配色:A 红、B 绿、C 蓝(三张图统一,便于对照)</summary>
|
||
private static readonly OxyColor[] PhaseColors =
|
||
{ OxyColor.FromRgb(0xE5, 0x39, 0x35), OxyColor.FromRgb(0x43, 0xA0, 0x47), OxyColor.FromRgb(0x1E, 0x88, 0xE5) };
|
||
#endregion
|
||
|
||
#region 私有字段
|
||
private readonly DeviceManager _dm;
|
||
private readonly GlobalInfo _globalInfo;
|
||
private Chroma61800? _dev;
|
||
private CancellationTokenSource? _cmdCts;
|
||
private CancellationTokenSource? _sampleCts;
|
||
|
||
// 三相正弦曲线:按物理量分组(电压 / 电流 / 功率),每组含 A/B/C 三相
|
||
private readonly LineSeries _voltA, _voltB, _voltC;
|
||
private readonly LineSeries _currA, _currB, _currC;
|
||
private readonly LineSeries _powA, _powB, _powC;
|
||
#endregion
|
||
|
||
#region 设备状态属性
|
||
private string _deviceName = "Chroma61800";
|
||
public string DeviceName { get => _deviceName; set => SetProperty(ref _deviceName, value); }
|
||
|
||
private bool _isConnected;
|
||
public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); }
|
||
|
||
private bool _isBusy;
|
||
public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); }
|
||
|
||
private bool _isSampling;
|
||
/// <summary>是否正在采集波形数据</summary>
|
||
public bool IsSampling { get => _isSampling; set => SetProperty(ref _isSampling, value); }
|
||
#endregion
|
||
|
||
#region 三相图表(按物理量分图:电压 / 电流 / 功率,每图叠加 A/B/C 三相正弦)
|
||
/// <summary>三相电压波形图表</summary>
|
||
public PlotModel VoltagePlot { get; }
|
||
|
||
/// <summary>三相电流波形图表</summary>
|
||
public PlotModel CurrentPlot { get; }
|
||
|
||
/// <summary>三相功率波形图表</summary>
|
||
public PlotModel PowerPlot { get; }
|
||
#endregion
|
||
|
||
#region 实时测量值
|
||
private double _voltageA; public double VoltageA { get => _voltageA; set => SetProperty(ref _voltageA, value); }
|
||
private double _currentA; public double CurrentA { get => _currentA; set => SetProperty(ref _currentA, value); }
|
||
private double _voltageB; public double VoltageB { get => _voltageB; set => SetProperty(ref _voltageB, value); }
|
||
private double _currentB; public double CurrentB { get => _currentB; set => SetProperty(ref _currentB, value); }
|
||
private double _voltageC; public double VoltageC { get => _voltageC; set => SetProperty(ref _voltageC, value); }
|
||
private double _currentC; public double CurrentC { get => _currentC; set => SetProperty(ref _currentC, value); }
|
||
|
||
private double _frequencyNow;
|
||
/// <summary>当前输出频率 (Hz)</summary>
|
||
public double FrequencyNow { get => _frequencyNow; set => SetProperty(ref _frequencyNow, value); }
|
||
|
||
private double _powerNow;
|
||
/// <summary>当前真实输出功率 (W)</summary>
|
||
public double PowerNow { get => _powerNow; set => SetProperty(ref _powerNow, value); }
|
||
#endregion
|
||
|
||
#region 输入参数(三相输出设置)
|
||
private double _voltage = 220;
|
||
/// <summary>待设置的交流电压 (V)</summary>
|
||
public double Voltage { get => _voltage; set => SetProperty(ref _voltage, value); }
|
||
|
||
private double _frequency = 50;
|
||
/// <summary>待设置的输出频率 (Hz)</summary>
|
||
public double Frequency { get => _frequency; set => SetProperty(ref _frequency, value); }
|
||
|
||
private string _waveform = "SINE";
|
||
/// <summary>波形选择</summary>
|
||
public string Waveform { get => _waveform; set => SetProperty(ref _waveform, value); }
|
||
|
||
private string _couplingMode = "AC";
|
||
/// <summary>输出耦合模式 (AC / DC / ACDC)</summary>
|
||
public string CouplingMode { get => _couplingMode; set => SetProperty(ref _couplingMode, value); }
|
||
|
||
private string _threePhaseRelation = "BALANCE";
|
||
/// <summary>三相关系 (INDEPEND / SAMEFREQ / BALANCE)</summary>
|
||
public string ThreePhaseRelation { get => _threePhaseRelation; set => SetProperty(ref _threePhaseRelation, value); }
|
||
#endregion
|
||
|
||
#region 输入参数(LIST 模式)
|
||
private string _listCoupling = "NONE";
|
||
/// <summary>列表耦合模态 (ALL / NONE)</summary>
|
||
public string ListCoupling { get => _listCoupling; set => SetProperty(ref _listCoupling, value); }
|
||
|
||
private string _listTrigger = "AUTO";
|
||
/// <summary>列表触发形态 (AUTO / MANUAL / EXCITE)</summary>
|
||
public string ListTrigger { get => _listTrigger; set => SetProperty(ref _listTrigger, value); }
|
||
|
||
private int _listCount = 1;
|
||
/// <summary>列表执行次数 (0 ~ 65535)</summary>
|
||
public int ListCount { get => _listCount; set => SetProperty(ref _listCount, value); }
|
||
|
||
private string _listTimeBase = "TIME";
|
||
/// <summary>列表时间基础 (TIME / CYCLE)</summary>
|
||
public string ListTimeBase { get => _listTimeBase; set => SetProperty(ref _listTimeBase, value); }
|
||
|
||
private string _listVoltAcStartText = "";
|
||
/// <summary>AC 起始电压序列(逗号分隔,如 0,50,100,150)</summary>
|
||
public string ListVoltAcStartText { get => _listVoltAcStartText; set => SetProperty(ref _listVoltAcStartText, value); }
|
||
|
||
private string _listVoltAcEndText = "";
|
||
/// <summary>AC 结束电压序列(逗号分隔)</summary>
|
||
public string ListVoltAcEndText { get => _listVoltAcEndText; set => SetProperty(ref _listVoltAcEndText, value); }
|
||
|
||
private string _listFreqStartText = "";
|
||
/// <summary>起始频率序列(逗号分隔,如 50,60,50)</summary>
|
||
public string ListFreqStartText { get => _listFreqStartText; set => SetProperty(ref _listFreqStartText, value); }
|
||
|
||
private string _listFreqEndText = "";
|
||
/// <summary>结束频率序列(逗号分隔)</summary>
|
||
public string ListFreqEndText { get => _listFreqEndText; set => SetProperty(ref _listFreqEndText, value); }
|
||
|
||
private string _listDwellText = "";
|
||
/// <summary>静止时间序列(ms,逗号分隔,如 500,500,500)</summary>
|
||
public string ListDwellText { get => _listDwellText; set => SetProperty(ref _listDwellText, value); }
|
||
|
||
private string _listDegrText = "";
|
||
/// <summary>相位角度序列(°,逗号分隔)</summary>
|
||
public string ListDegrText { get => _listDegrText; set => SetProperty(ref _listDegrText, value); }
|
||
|
||
private string _listShapeText = "";
|
||
/// <summary>波形缓冲顺序(A/B,逗号分隔,如 A,B,A)</summary>
|
||
public string ListShapeText { get => _listShapeText; set => SetProperty(ref _listShapeText, value); }
|
||
#endregion
|
||
|
||
#region 下拉选项
|
||
public IReadOnlyList<string> WaveformOptions { get; } = new[] { "SINE", "SQUA", "CSIN" };
|
||
public IReadOnlyList<string> CouplingOptions { get; } = new[] { "AC", "DC", "ACDC" };
|
||
public IReadOnlyList<string> RelationOptions { get; } = new[] { "INDEPEND", "SAMEFREQ", "BALANCE" };
|
||
public IReadOnlyList<string> ListCouplingOptions { get; } = new[] { "ALL", "NONE" };
|
||
public IReadOnlyList<string> ListTriggerOptions { get; } = new[] { "AUTO", "MANUAL", "EXCITE" };
|
||
public IReadOnlyList<string> ListTimeBaseOptions { get; } = new[] { "TIME", "CYCLE" };
|
||
#endregion
|
||
|
||
#region 日志
|
||
private string _responseLog = "";
|
||
/// <summary>操作与采样日志</summary>
|
||
public string ResponseLog { get => _responseLog; set => SetProperty(ref _responseLog, value); }
|
||
#endregion
|
||
|
||
#region 命令
|
||
public ICommand QueryIdnCommand { get; }
|
||
public ICommand ResetCommand { get; }
|
||
public ICommand OutputOnCommand { get; }
|
||
public ICommand OutputOffCommand { get; }
|
||
public ICommand RelayOnCommand { get; }
|
||
public ICommand RelayOffCommand { get; }
|
||
public ICommand ApplyCouplingCommand { get; }
|
||
public ICommand SetThreePhaseModeCommand { get; }
|
||
public ICommand SetSinglePhaseModeCommand { get; }
|
||
public ICommand ApplyRelationCommand { get; }
|
||
public ICommand SetVoltageCommand { get; }
|
||
public ICommand SetFrequencyCommand { get; }
|
||
public ICommand SetWaveformCommand { get; }
|
||
public ICommand SetFixedModeCommand { get; }
|
||
public ICommand EnterListModeCommand { get; }
|
||
public ICommand ApplyListBasicCommand { get; }
|
||
public ICommand ApplyListSequencesCommand { get; }
|
||
public ICommand ListStartCommand { get; }
|
||
public ICommand ListStopCommand { get; }
|
||
public ICommand StartSamplingCommand { get; }
|
||
public ICommand StopSamplingCommand { get; }
|
||
public ICommand ClearChartsCommand { get; }
|
||
#endregion
|
||
|
||
public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||
{
|
||
_dm = containerProvider.Resolve<DeviceManager>();
|
||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||
|
||
// 初始化三相图表(按物理量分图,每图叠加 A/B/C 三相正弦)
|
||
VoltagePlot = BuildSinePlot("三相电压", "V");
|
||
CurrentPlot = BuildSinePlot("三相电流", "A");
|
||
PowerPlot = BuildSinePlot("三相功率", "W");
|
||
(_voltA, _voltB, _voltC) = AddThreePhaseSeries(VoltagePlot);
|
||
(_currA, _currB, _currC) = AddThreePhaseSeries(CurrentPlot);
|
||
(_powA, _powB, _powC) = AddThreePhaseSeries(PowerPlot);
|
||
|
||
#region 命令初始化
|
||
QueryIdnCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => Log("IDN: " + await _dev!.查询设备信息Async(Ct()))));
|
||
|
||
ResetCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.复位Async(Ct()); Log("设备已复位"); }));
|
||
|
||
OutputOnCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置输出开关Async(true, Ct()); Log("输出已开启"); }));
|
||
|
||
OutputOffCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置输出开关Async(false, Ct()); Log("输出已关闭"); }));
|
||
|
||
RelayOnCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置输出继电器Async(true, Ct()); Log("输出继电器已开启"); }));
|
||
|
||
RelayOffCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置输出继电器Async(false, Ct()); Log("输出继电器已关闭"); }));
|
||
|
||
ApplyCouplingCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置耦合模式Async(CouplingMode, Ct()); Log($"耦合模式={CouplingMode}"); }));
|
||
|
||
SetThreePhaseModeCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置单三相模式Async(true, Ct()); Log("已切换为三相模式"); }));
|
||
|
||
SetSinglePhaseModeCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置单三相模式Async(false, Ct()); Log("已切换为单相模式"); }));
|
||
|
||
ApplyRelationCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置三相关系Async(ThreePhaseRelation, Ct()); Log($"三相关系={ThreePhaseRelation}"); }));
|
||
|
||
SetVoltageCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置交流电压Async(Voltage, Ct()); Log($"交流电压={Voltage} V"); }));
|
||
|
||
SetFrequencyCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置频率Async(Frequency, Ct()); Log($"输出频率={Frequency} Hz"); }));
|
||
|
||
SetWaveformCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置波形Async(Waveform, Ct()); Log($"波形={Waveform}"); }));
|
||
|
||
SetFixedModeCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置操作模态Async("FIXED", Ct()); Log("已切换为 FIXED 固定模式"); }));
|
||
|
||
EnterListModeCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.设置操作模态Async("LIST", Ct()); Log("已切换为 LIST 模式"); }));
|
||
|
||
ApplyListBasicCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
await _dev!.设置列表耦合模态Async(ListCoupling, Ct());
|
||
await _dev!.设置列表触发形态Async(ListTrigger, Ct());
|
||
await _dev!.设置列表执行次数Async(ListCount, Ct());
|
||
await _dev!.设置列表时间基础Async(ListTimeBase, Ct());
|
||
Log($"LIST 基础配置:耦合={ListCoupling} 触发={ListTrigger} 次数={ListCount} 时基={ListTimeBase}");
|
||
}));
|
||
|
||
ApplyListSequencesCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
int applied = 0;
|
||
var vAcStart = ParseDoubles(ListVoltAcStartText);
|
||
if (vAcStart.Length > 0) { await _dev!.设置列表AC起始电压顺序Async(vAcStart, Ct()); applied++; }
|
||
var vAcEnd = ParseDoubles(ListVoltAcEndText);
|
||
if (vAcEnd.Length > 0) { await _dev!.设置列表AC结束电压顺序Async(vAcEnd, Ct()); applied++; }
|
||
var fStart = ParseDoubles(ListFreqStartText);
|
||
if (fStart.Length > 0) { await _dev!.设置列表起始频率顺序Async(fStart, Ct()); applied++; }
|
||
var fEnd = ParseDoubles(ListFreqEndText);
|
||
if (fEnd.Length > 0) { await _dev!.设置列表结束频率顺序Async(fEnd, Ct()); applied++; }
|
||
var dwell = ParseDoubles(ListDwellText);
|
||
if (dwell.Length > 0) { await _dev!.设置列表静止时间顺序Async(dwell, Ct()); applied++; }
|
||
var degr = ParseDoubles(ListDegrText);
|
||
if (degr.Length > 0) { await _dev!.设置列表相位角度顺序Async(degr, Ct()); applied++; }
|
||
var shapes = ParseShapes(ListShapeText);
|
||
if (shapes.Length > 0) { await _dev!.设置列表波形顺序Async(shapes, Ct()); applied++; }
|
||
Log(applied > 0 ? $"已下发 {applied} 组列表序列" : "没有可下发的列表序列(请先填写序列文本框)");
|
||
}));
|
||
|
||
ListStartCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
await _dev!.设置操作模态Async("LIST", Ct());
|
||
await _dev!.触发列表执行Async(true, Ct());
|
||
Log("LIST 已开始执行");
|
||
}));
|
||
|
||
ListStopCommand = new DelegateCommand(async () =>
|
||
await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("LIST 已停止执行"); }));
|
||
|
||
StartSamplingCommand = new DelegateCommand(StartSampling);
|
||
StopSamplingCommand = new DelegateCommand(StopSampling);
|
||
ClearChartsCommand = new DelegateCommand(ClearCharts);
|
||
#endregion
|
||
|
||
Initialize();
|
||
}
|
||
|
||
#region 初始化 / 导航
|
||
/// <summary>
|
||
/// 查找 Chroma61800 设备实例:优先当前作用域 DeviceManager,回退全局 HardwarePool(跨作用域共享实例)。
|
||
/// </summary>
|
||
private void Initialize()
|
||
{
|
||
Chroma61800? found = null;
|
||
string? fn = null;
|
||
|
||
foreach (var kv in _dm.DeviceMap)
|
||
{
|
||
if (kv.Value is Chroma61800 it) { found = it; fn = kv.Key; break; }
|
||
}
|
||
|
||
if (found == null)
|
||
{
|
||
foreach (var kv in _globalInfo.HardwarePool)
|
||
{
|
||
if (kv.Value.IsValueCreated && kv.Value.Value is Chroma61800 c) { found = c; fn = kv.Key; break; }
|
||
}
|
||
}
|
||
|
||
_dev = found;
|
||
DeviceName = fn ?? "Chroma61800 (未找到)";
|
||
IsConnected = _dev?.IsConnected ?? false;
|
||
|
||
Log(found != null
|
||
? $"已关联设备 [{DeviceName}],连接:{(IsConnected ? "已连接" : "未连接")}"
|
||
: "未找到 Chroma61800 设备,请先在设置界面添加并启用该设备");
|
||
}
|
||
|
||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||
{
|
||
Initialize();
|
||
}
|
||
|
||
public override void OnNavigatedFrom(NavigationContext navigationContext)
|
||
{
|
||
// 离开页面时停止采样,避免后台持续占用设备通信
|
||
StopSampling();
|
||
}
|
||
#endregion
|
||
|
||
#region 数据采集
|
||
private void StartSampling()
|
||
{
|
||
if (_dev == null) { Log("未关联 Chroma61800 设备,无法采集"); return; }
|
||
if (IsSampling) return;
|
||
|
||
IsSampling = true;
|
||
_sampleCts = new CancellationTokenSource();
|
||
var token = _sampleCts.Token;
|
||
_ = Task.Run(() => SamplingLoopAsync(token), token);
|
||
Log($"开始采集(周期 {SampleIntervalMs} ms)");
|
||
}
|
||
|
||
private void StopSampling()
|
||
{
|
||
if (!IsSampling) return;
|
||
IsSampling = false;
|
||
try { _sampleCts?.Cancel(); } catch { }
|
||
_sampleCts?.Dispose();
|
||
_sampleCts = null;
|
||
Log("停止采集");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 采样循环:逐相读取交流电压/电流,合成波形;并刷新频率与功率实时值。
|
||
/// <para>说明:Chroma61800 类现有测量方法面向"活动相位",三相逐一调用读取;
|
||
/// 如设备需要按相切换后读取,可在每相读取前补充相位选择命令。</para>
|
||
/// </summary>
|
||
private async Task SamplingLoopAsync(CancellationToken token)
|
||
{
|
||
while (!token.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
if (_dev != null && _dev.IsConnected)
|
||
{
|
||
// A 相
|
||
double vA = await _dev.读取交流电压Async(token);
|
||
double iA = await _dev.读取交流电流Async(token);
|
||
|
||
// B 相
|
||
double vB = await _dev.读取交流电压Async(token);
|
||
double iB = await _dev.读取交流电流Async(token);
|
||
|
||
// C 相
|
||
double vC = await _dev.读取交流电压Async(token);
|
||
double iC = await _dev.读取交流电流Async(token);
|
||
|
||
// 设备级:频率与功率
|
||
double freq = await _dev.读取频率Async(token);
|
||
double power = await _dev.读取真实功率Async(token);
|
||
|
||
var dispatcher = Application.Current?.Dispatcher;
|
||
if (dispatcher != null)
|
||
{
|
||
_ = dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
RegenerateWaveforms(vA, iA, vB, iB, vC, iC, freq, power);
|
||
IsConnected = _dev.IsConnected;
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
catch (OperationCanceledException) { break; }
|
||
catch (Exception ex)
|
||
{
|
||
Log($"采样异常:{ex.Message}");
|
||
}
|
||
|
||
try { await Task.Delay(SampleIntervalMs, token); }
|
||
catch (OperationCanceledException) { break; }
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据实测 RMS 值合成三相正弦波并刷新三张图表(UI 线程调用)。
|
||
/// <para>横轴为相位角 0~360°,纵轴对称显示正负半周;电压/电流峰值 = √2 × RMS,
|
||
/// 三相相位互差 120°(A=0,B=-120,C=+120)。设备仅提供 RMS 标量,故为示意波形。</para>
|
||
/// </summary>
|
||
private void RegenerateWaveforms(double vA, double iA, double vB, double iB,
|
||
double vC, double iC, double freq, double power)
|
||
{
|
||
// 电压三相
|
||
RegenerateSine(_voltA, vA, PhaseOffsetDeg[0]);
|
||
RegenerateSine(_voltB, vB, PhaseOffsetDeg[1]);
|
||
RegenerateSine(_voltC, vC, PhaseOffsetDeg[2]);
|
||
// 电流三相
|
||
RegenerateSine(_currA, iA, PhaseOffsetDeg[0]);
|
||
RegenerateSine(_currB, iB, PhaseOffsetDeg[1]);
|
||
RegenerateSine(_currC, iC, PhaseOffsetDeg[2]);
|
||
// 功率三相(设备仅提供总有功,平衡三相每相 ≈ 总功率 / 3,按正弦示意呈现正负半周)
|
||
double pPhase = power / 3.0;
|
||
RegenerateSine(_powA, pPhase, PhaseOffsetDeg[0]);
|
||
RegenerateSine(_powB, pPhase, PhaseOffsetDeg[1]);
|
||
RegenerateSine(_powC, pPhase, PhaseOffsetDeg[2]);
|
||
|
||
// 实时数值
|
||
VoltageA = vA; CurrentA = iA;
|
||
VoltageB = vB; CurrentB = iB;
|
||
VoltageC = vC; CurrentC = iC;
|
||
FrequencyNow = freq; PowerNow = power;
|
||
|
||
// 纵轴对称范围(取该图三相峰值最大值并留 15% 余量,确保正负半周完整可见)
|
||
SetSymmetricAxis(VoltagePlot, vA, vB, vC);
|
||
SetSymmetricAxis(CurrentPlot, iA, iB, iC);
|
||
SetSymmetricAxis(PowerPlot, pPhase);
|
||
|
||
VoltagePlot.InvalidatePlot(true);
|
||
CurrentPlot.InvalidatePlot(true);
|
||
PowerPlot.InvalidatePlot(true);
|
||
}
|
||
|
||
/// <summary>按 RMS 幅值与相位偏移,生成 0~360° 一个完整周期的正弦曲线点(峰值 = √2 × RMS)。</summary>
|
||
private static void RegenerateSine(LineSeries series, double rms, double phaseDeg)
|
||
{
|
||
series.Points.Clear();
|
||
double peak = Math.Sqrt(2) * rms;
|
||
double phaseRad = phaseDeg * Math.PI / 180.0;
|
||
for (int i = 0; i <= SinePoints; i++)
|
||
{
|
||
double deg = 360.0 * i / SinePoints;
|
||
double rad = deg * Math.PI / 180.0 + phaseRad;
|
||
series.Points.Add(new DataPoint(deg, peak * Math.Sin(rad)));
|
||
}
|
||
}
|
||
|
||
/// <summary>设置纵轴对称范围(-peak ~ +peak);无输出时回退到 ±1 避免坐标轴退化。</summary>
|
||
private static void SetSymmetricAxis(PlotModel plot, params double[] rmsValues)
|
||
{
|
||
double maxRms = rmsValues.Max();
|
||
double peak = Math.Sqrt(2) * maxRms * 1.15;
|
||
if (peak <= 0) peak = 1;
|
||
if (plot.Axes.OfType<LinearAxis>().FirstOrDefault() is { } axis)
|
||
{
|
||
axis.Minimum = -peak;
|
||
axis.Maximum = peak;
|
||
}
|
||
}
|
||
|
||
private void ClearCharts()
|
||
{
|
||
foreach (var s in new[] { _voltA, _voltB, _voltC, _currA, _currB, _currC, _powA, _powB, _powC })
|
||
{
|
||
s.Points.Clear();
|
||
}
|
||
VoltagePlot.InvalidatePlot(true);
|
||
CurrentPlot.InvalidatePlot(true);
|
||
PowerPlot.InvalidatePlot(true);
|
||
Log("已清空波形数据");
|
||
}
|
||
#endregion
|
||
|
||
#region 图表构建
|
||
/// <summary>
|
||
/// 构建正弦波图表:横轴为相位角 0~360°,纵轴为对应物理量(对称显示正负半周),含图例。
|
||
/// </summary>
|
||
/// <param name="title">图表标题(如“三相电压”)</param>
|
||
/// <param name="unit">纵轴单位(V / A / W)</param>
|
||
private static PlotModel BuildSinePlot(string title, string unit)
|
||
{
|
||
var pm = new PlotModel
|
||
{
|
||
Title = $"{title} ({unit})",
|
||
PlotAreaBorderColor = OxyColors.LightGray,
|
||
Background = OxyColors.White
|
||
};
|
||
pm.Axes.Add(new LinearAxis
|
||
{
|
||
Position = AxisPosition.Bottom,
|
||
Title = "相位角 (°)",
|
||
Minimum = 0,
|
||
Maximum = 360,
|
||
MajorStep = 45,
|
||
MinorStep = 15,
|
||
StringFormat = "0",
|
||
MajorGridlineStyle = LineStyle.Dot,
|
||
MinorGridlineStyle = LineStyle.None
|
||
});
|
||
pm.Axes.Add(new LinearAxis
|
||
{
|
||
Position = AxisPosition.Left,
|
||
Title = unit,
|
||
Minimum = -1,
|
||
Maximum = 1,
|
||
MajorGridlineStyle = LineStyle.Dot,
|
||
MinorGridlineStyle = LineStyle.None
|
||
});
|
||
pm.Legends.Add(new Legend
|
||
{
|
||
LegendPosition = LegendPosition.RightTop,
|
||
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
|
||
LegendBorder = OxyColors.LightGray
|
||
});
|
||
return pm;
|
||
}
|
||
|
||
/// <summary>向图表添加 A/B/C 三相正弦曲线(红/绿/蓝,与三张图配色一致)。</summary>
|
||
private static (LineSeries A, LineSeries B, LineSeries C) AddThreePhaseSeries(PlotModel plot)
|
||
{
|
||
var a = NewPhaseSeries("A 相", PhaseColors[0]);
|
||
var b = NewPhaseSeries("B 相", PhaseColors[1]);
|
||
var c = NewPhaseSeries("C 相", PhaseColors[2]);
|
||
plot.Series.Add(a);
|
||
plot.Series.Add(b);
|
||
plot.Series.Add(c);
|
||
return (a, b, c);
|
||
}
|
||
|
||
/// <summary>创建一条相曲线(无标记点、平滑线条)。</summary>
|
||
private static LineSeries NewPhaseSeries(string title, OxyColor color) => new LineSeries
|
||
{
|
||
Title = title,
|
||
Color = color,
|
||
StrokeThickness = 1.8,
|
||
MarkerType = MarkerType.None
|
||
};
|
||
#endregion
|
||
|
||
#region 辅助
|
||
/// <summary>创建 10 秒超时的取消令牌(命令用)。</summary>
|
||
private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||
|
||
/// <summary>统一的设备命令执行包装:防重入、异常捕获与日志。</summary>
|
||
private async Task Exec(Func<Task> action)
|
||
{
|
||
if (_dev == null) { Log("错误:未找到 Chroma61800 设备实例,请检查设备配置"); return; }
|
||
if (IsBusy) return;
|
||
|
||
IsBusy = true;
|
||
try
|
||
{
|
||
await action();
|
||
IsConnected = _dev.IsConnected;
|
||
}
|
||
catch (OperationCanceledException) { Log("命令超时或已取消"); }
|
||
catch (Exception ex) { Log($"错误:{ex.Message}"); }
|
||
finally { IsBusy = false; }
|
||
}
|
||
|
||
/// <summary>线程安全的日志输出(自动切回 UI 线程,保留最近内容)。</summary>
|
||
private void Log(string message)
|
||
{
|
||
var dispatcher = Application.Current?.Dispatcher;
|
||
if (dispatcher != null && !dispatcher.CheckAccess())
|
||
{
|
||
_ = dispatcher.BeginInvoke(new Action(() => Log(message)));
|
||
return;
|
||
}
|
||
|
||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||
ResponseLog = ResponseLog.Length > 6000
|
||
? line + "\n" + ResponseLog[..4000]
|
||
: line + "\n" + ResponseLog;
|
||
}
|
||
|
||
/// <summary>解析逗号/空格/分号分隔的数值序列。</summary>
|
||
private static double[] ParseDoubles(string text)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text)) return Array.Empty<double>();
|
||
return text.Split(new[] { ',', ',', ' ', '\t', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||
.Select(s => double.TryParse(s.Trim(), out double v) ? (double?)v : null)
|
||
.Where(v => v.HasValue)
|
||
.Select(v => v!.Value)
|
||
.ToArray();
|
||
}
|
||
|
||
/// <summary>解析波形缓冲顺序(A/B)序列。</summary>
|
||
private static string[] ParseShapes(string text)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text)) return Array.Empty<string>();
|
||
return text.Split(new[] { ',', ',', ' ', '\t', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||
.Select(s => s.Trim().ToUpperInvariant())
|
||
.ToArray();
|
||
}
|
||
#endregion
|
||
|
||
public void Dispose()
|
||
{
|
||
StopSampling();
|
||
try { _cmdCts?.Cancel(); } catch { }
|
||
_cmdCts?.Dispose();
|
||
}
|
||
}
|
||
}
|