Files
ACP/PowerAdaptabilityModule/ViewModels/PowerAdaptabilityViewModel.cs
T
2026-10-12 08:49:04 +08:00

1241 lines
62 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 DeviceCommand.Devices;
using Newtonsoft.Json;
using Prism.Commands;
using Prism.Ioc;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
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 模式 + SYNTHESIS 自定义波形输出)。
/// </summary>
public class PowerAdaptabilityViewModel : NavigateViewModelBase, IDisposable
{
#region 私有字段
private readonly DeviceManager _dm;
private readonly GlobalInfo _globalInfo;
private Chroma61800? _dev;
private CancellationTokenSource? _cmdCts;
#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); }
#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); }
/// <summary>相位选择列表: 0=ALL(三相同时), 1=A相, 2=B相, 3=C相</summary>
public List<string> PhaseOptions { get; } = new() { "ALL (三相)", "A相 (Φ1)", "B相 (Φ2)", "C相 (Φ3)" };
private int _selectedPhaseIndex;
/// <summary>当前选中的相位索引 (0=ALL, 1=A, 2=B, 3=C)</summary>
public int SelectedPhaseIndex { get => _selectedPhaseIndex; set => SetProperty(ref _selectedPhaseIndex, value); }
#endregion
#region 输入参数(LIST 模式 · 三相独立编辑)
/// <summary>下发完成到启动之间的延时(ms),对应 *D 命令,等待仪器就绪</summary>
private const int StartDelayMs = 200;
private string _listTrigger = "AUTO";
/// <summary>列表触发形态 (AUTO / MANUAL / EXCITE)</summary>
public string ListTrigger { get => _listTrigger; set => SetProperty(ref _listTrigger, value); }
private int _listCount;
/// <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); }
/// <summary>三相各自的 LIST 序列配置(索引 0=A/Φ1, 1=B/Φ2, 2=C/Φ3),INST:EDIT EACH 逐相下发</summary>
private readonly ListPhaseConfig[] _phaseConfigs = { new(), new(), new() };
/// <summary>相位编辑选项(对应 INST:NSEL 1/2/3)</summary>
public List<string> ListPhaseOptions { get; } = new() { "A相 (Φ1)", "B相 (Φ2)", "C相 (Φ3)" };
private int _listEditPhaseIndex;
/// <summary>当前正在编辑的相位索引 (0=A, 1=B, 2=C),切换时下方序列文本框自动刷新为该相内容</summary>
public int ListEditPhaseIndex
{
get => _listEditPhaseIndex;
set
{
if (SetProperty(ref _listEditPhaseIndex, value))
RaiseListSequenceChanged();
}
}
/// <summary>当前选中相位的配置对象</summary>
private ListPhaseConfig CurCfg => _phaseConfigs[_listEditPhaseIndex];
// —— 以下文本属性均代理到"当前编辑相位"的配置,切换相位时统一刷新 ——
/// <summary>当前相波形 (FUNC:SHAP:A)</summary>
public string ListWaveform { get => CurCfg.Waveform; set { if (CurCfg.Waveform != value) { CurCfg.Waveform = value; RaisePropertyChanged(); } } }
/// <summary>相位角度序列 (LIST:DEGR, °)</summary>
public string ListDegrText { get => CurCfg.Degr; set { if (CurCfg.Degr != value) { CurCfg.Degr = value; RaisePropertyChanged(); } } }
/// <summary>AC 起始电压序列 (LIST:VOLT:AC:STAR, V)</summary>
public string ListVoltAcStartText { get => CurCfg.VoltAcStart; set { if (CurCfg.VoltAcStart != value) { CurCfg.VoltAcStart = value; RaisePropertyChanged(); } } }
/// <summary>AC 结束电压序列 (LIST:VOLT:AC:END, V)</summary>
public string ListVoltAcEndText { get => CurCfg.VoltAcEnd; set { if (CurCfg.VoltAcEnd != value) { CurCfg.VoltAcEnd = value; RaisePropertyChanged(); } } }
/// <summary>DC 起始电压序列 (LIST:VOLT:DC:STAR, V)</summary>
public string ListVoltDcStartText { get => CurCfg.VoltDcStart; set { if (CurCfg.VoltDcStart != value) { CurCfg.VoltDcStart = value; RaisePropertyChanged(); } } }
/// <summary>DC 结束电压序列 (LIST:VOLT:DC:END, V)</summary>
public string ListVoltDcEndText { get => CurCfg.VoltDcEnd; set { if (CurCfg.VoltDcEnd != value) { CurCfg.VoltDcEnd = value; RaisePropertyChanged(); } } }
/// <summary>起始频率序列 (LIST:FREQ:STAR, Hz)</summary>
public string ListFreqStartText { get => CurCfg.FreqStart; set { if (CurCfg.FreqStart != value) { CurCfg.FreqStart = value; RaisePropertyChanged(); } } }
/// <summary>结束频率序列 (LIST:FREQ:END, Hz)</summary>
public string ListFreqEndText { get => CurCfg.FreqEnd; set { if (CurCfg.FreqEnd != value) { CurCfg.FreqEnd = value; RaisePropertyChanged(); } } }
/// <summary>静止时间序列 (LIST:DWEL, ms)</summary>
public string ListDwellText { get => CurCfg.Dwell; set { if (CurCfg.Dwell != value) { CurCfg.Dwell = value; RaisePropertyChanged(); } } }
/// <summary>波形缓冲顺序 (LIST:SHAP, A/B)</summary>
public string ListShapeText { get => CurCfg.ShapeSeq; set { if (CurCfg.ShapeSeq != value) { CurCfg.ShapeSeq = value; RaisePropertyChanged(); } } }
/// <summary>切换编辑相位后,通知所有序列文本框刷新为新相位内容</summary>
private void RaiseListSequenceChanged()
{
RaisePropertyChanged(nameof(ListWaveform));
RaisePropertyChanged(nameof(ListDegrText));
RaisePropertyChanged(nameof(ListVoltAcStartText));
RaisePropertyChanged(nameof(ListVoltAcEndText));
RaisePropertyChanged(nameof(ListVoltDcStartText));
RaisePropertyChanged(nameof(ListVoltDcEndText));
RaisePropertyChanged(nameof(ListFreqStartText));
RaisePropertyChanged(nameof(ListFreqEndText));
RaisePropertyChanged(nameof(ListDwellText));
RaisePropertyChanged(nameof(ListShapeText));
}
#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> ListTriggerOptions { get; } = new[] { "AUTO", "MANUAL", "EXCITE" };
public IReadOnlyList<string> ListTimeBaseOptions { get; } = new[] { "TIME", "CYCLE" };
#endregion
#region 输入参数(SYNTHESIS 自定义谐波波形输出)
/// <summary>谐波阶数据格式选项 (SYNT:COMP):VALUE=绝对值,PERCENT=基频百分率,后缀 1/2/3 为三段波形</summary>
public IReadOnlyList<string> SynthComposeOptions { get; } = new[]
{ "VALUE1", "VALUE2", "VALUE3", "PERCENT1", "PERCENT2", "PERCENT3" };
/// <summary>基频选项 (SYNT:FREQ):仅支持 50 / 60 Hz</summary>
public IReadOnlyList<int> SynthFrequencyOptions { get; } = new[] { 50, 60 };
private string _synthCompose = "VALUE1";
/// <summary>谐波阶数据格式 (SYNT:COMP)</summary>
public string SynthCompose { get => _synthCompose; set => SetProperty(ref _synthCompose, value); }
private int _synthFrequency = 50;
/// <summary>基频 (SYNT:FREQ, 50 | 60 Hz)</summary>
public int SynthFrequency { get => _synthFrequency; set => SetProperty(ref _synthFrequency, value); }
private double _synthFundamental = 220;
/// <summary>基波交流电压 (SYNT:FUND, 0.0 ~ 300.0 V)</summary>
public double SynthFundamental { get => _synthFundamental; set => SetProperty(ref _synthFundamental, value); }
private double _synthDc;
/// <summary>叠加直流电压 (SYNT:DC, -424.2 ~ 424.2 V)</summary>
public double SynthDc { get => _synthDc; set => SetProperty(ref _synthDc, value); }
private double _synthStartPhase;
/// <summary>起始相位角 (SYNT:SPH, 0.0 ~ 359.9 °)</summary>
public double SynthStartPhase { get => _synthStartPhase; set => SetProperty(ref _synthStartPhase, value); }
private string _synthAmplitudeText = "";
/// <summary>各次谐波振幅序列文本 (SYNT:AMPL, 英文逗号分隔, 最多 50 阶)</summary>
public string SynthAmplitudeText { get => _synthAmplitudeText; set => SetProperty(ref _synthAmplitudeText, value); }
private string _synthPhaseText = "";
/// <summary>各次谐波相位角序列文本 (SYNT:PHAS, 英文逗号分隔, 0~359.9°)</summary>
public string SynthPhaseText { get => _synthPhaseText; set => SetProperty(ref _synthPhaseText, value); }
#endregion
#region SYNTHESIS 波形预设(文件:D:\acp\源测试\合成波形,一键载入/保存自定义谐波波形)
/// <summary>合成波形预设文件目录(LIST 测试项目录下的子目录,避免与 LIST 预设混淆)</summary>
private static readonly string SynthStorageDir = Path.Combine(PresetStorageDir, "合成波形");
/// <summary>合成波形预设文件目录提示(供界面显示)</summary>
public string SynthStorageHint => $"波形文件目录:{SynthStorageDir}";
// 从磁盘加载的合成波形预设集合
private List<SynthPreset> _synthPresets = new();
/// <summary>合成波形预设集合</summary>
public IReadOnlyList<SynthPreset> SynthPresets => _synthPresets;
private List<string> _synthPresetOptions = new();
/// <summary>合成波形预设名称列表(供 ComboBox 显示)</summary>
public IReadOnlyList<string> SynthPresetOptions => _synthPresetOptions;
private string _synthPresetName = "";
/// <summary>当前波形名称(保存时作为文件名;载入后自动填充)</summary>
public string SynthPresetName { get => _synthPresetName; set => SetProperty(ref _synthPresetName, value); }
private string _synthPresetNote = "";
/// <summary>当前波形说明(随文件保存,载入后自动填充)</summary>
public string SynthPresetNote { get => _synthPresetNote; set => SetProperty(ref _synthPresetNote, value); }
private int _selectedSynthPresetIndex = -1;
/// <summary>当前选中的合成波形预设索引(-1 表示未选择)</summary>
public int SelectedSynthPresetIndex
{
get => _selectedSynthPresetIndex;
set
{
if (SetProperty(ref _selectedSynthPresetIndex, value))
{
// 选中后把名称/说明填入编辑框,便于修改后另存或覆盖保存
if (value >= 0 && value < _synthPresets.Count)
{
SynthPresetName = _synthPresets[value].Name;
SynthPresetNote = _synthPresets[value].Note;
}
}
}
}
#endregion
#region 试验预设(测试项文件:D:\acp\源测试,一键载入大纲典型波形)
/// <summary>测试项文件默认存储目录(保存与查询均在此目录)</summary>
private const string PresetStorageDir = @"D:\acp\源测试";
/// <summary>测试项文件扩展名</summary>
private const string PresetFileExt = ".json";
/// <summary>测试项文件目录提示(供界面显示)</summary>
public string PresetStorageHint => $"测试项文件目录:{PresetStorageDir}";
// 从磁盘加载的测试项集合(源真相为 PresetStorageDir 下的 .json 文件)
private List<ListPreset> _presets = new();
/// <summary>测试项集合(由 PresetStorageDir 下的文件加载而来)</summary>
public IReadOnlyList<ListPreset> Presets => _presets;
// 供 ComboBox 显示的名称列表,随磁盘刷新
private List<string> _presetOptions = new();
/// <summary>测试项名称列表(供 ComboBox 显示)</summary>
public IReadOnlyList<string> PresetOptions => _presetOptions;
private string _presetName = "";
/// <summary>当前测试项名称(保存时作为文件名;载入后自动填充,便于覆盖保存)</summary>
public string PresetName { get => _presetName; set => SetProperty(ref _presetName, value); }
private string _presetNote = "";
/// <summary>当前测试项说明(随文件保存,载入后自动填充)</summary>
public string PresetNote { get => _presetNote; set => SetProperty(ref _presetNote, value); }
private int _selectedPresetIndex = -1;
/// <summary>当前选中的测试项索引(-1 表示未选择)</summary>
public int SelectedPresetIndex
{
get => _selectedPresetIndex;
set
{
if (SetProperty(ref _selectedPresetIndex, value))
{
// 选中后把名称/说明填入编辑框,便于修改后另存或覆盖保存
if (value >= 0 && value < _presets.Count)
{
PresetName = _presets[value].Name;
PresetNote = _presets[value].Note;
}
}
}
}
#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 ExitRemoteCommand { 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 LoadPresetCommand { get; }
public ICommand SavePresetCommand { get; }
public ICommand DeletePresetCommand { get; }
public ICommand CopyPhaseToThreeCommand { get; }
public ICommand DownloadThreePhaseListCommand { get; }
public ICommand ListStopCommand { get; }
public ICommand DownloadSynthesisCommand { get; }
public ICommand SynthStopCommand { get; }
public ICommand ResetSynthHarmonicsCommand { get; }
public ICommand LoadSynthPresetCommand { get; }
public ICommand SaveSynthPresetCommand { get; }
public ICommand DeleteSynthPresetCommand { get; }
#endregion
public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_dm = containerProvider.Resolve<DeviceManager>();
_globalInfo = containerProvider.Resolve<GlobalInfo>();
// 初始化测试项文件目录(首次运行迁移内置预设),再从磁盘加载测试项
EnsurePresetStorage();
RefreshPresets();
// 初始化合成波形文件目录(首次运行迁移内置示例),再从磁盘加载波形
EnsureSynthStorage();
RefreshSynthPresets();
#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("设备已复位"); }));
ExitRemoteCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.退出远程Async(Ct()); Log("已发送退出远程 SYST:LOC,前面板应恢复可用(手册注明僅 RS-232 下有效)"); }));
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 ApplyPhaseAsync();
await _dev!.设置交流电压Async(Voltage, Ct());
Log($"交流电压={Voltage} V → {PhaseLabel}");
}));
SetFrequencyCommand = new DelegateCommand(async () =>
await Exec(async () =>
{
await ApplyPhaseAsync();
await _dev!.设置频率Async(Frequency, Ct());
Log($"输出频率={Frequency} Hz → {PhaseLabel}");
}));
SetWaveformCommand = new DelegateCommand(async () =>
await Exec(async () =>
{
await ApplyPhaseAsync();
await _dev!.设置波形Async(Waveform, Ct());
Log($"波形={Waveform} → {PhaseLabel}");
}));
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 模式"); }));
// 载入测试项:把选中测试项的三相序列与基础参数一次性填充到界面
LoadPresetCommand = new DelegateCommand(() =>
{
if (SelectedPresetIndex < 0 || SelectedPresetIndex >= Presets.Count)
{
Log("请先在下拉框选择一项测试项");
return;
}
var p = Presets[SelectedPresetIndex];
ListCount = p.Count;
ListTrigger = p.Trigger;
ListTimeBase = p.TimeBase;
for (int i = 0; i < 3 && i < p.Phases.Length; i++)
_phaseConfigs[i] = p.Phases[i].Clone();
ListEditPhaseIndex = 0; // 切到 A 相显示(若原本就是 0,setter 不触发刷新,故下方手动刷新)
RaiseListSequenceChanged();
PresetName = p.Name; // 填充名称/说明,便于修改后覆盖保存
PresetNote = p.Note;
Log($"已载入测试项:{p.Name}(次数={(p.Count == 0 ? "无限" : p.Count.ToString())} 触发={p.Trigger} 时基={p.TimeBase})");
});
// 保存当前界面的 LIST 配置为测试项文件(写入 D:\acp\源测试\{名称}.json)
SavePresetCommand = new DelegateCommand(SaveCurrentPreset);
// 删除当前选中的测试项文件(带二次确认)
DeletePresetCommand = new DelegateCommand(DeleteCurrentPreset);
// 以当前编辑相位为模板复制到三相,B/C 相相位角按 ±120° 自动偏移,生成对称三相序列
CopyPhaseToThreeCommand = new DelegateCommand(() =>
{
var src = _phaseConfigs[_listEditPhaseIndex];
for (int p = 0; p < 3; p++)
{
if (p == _listEditPhaseIndex) continue;
var clone = src.Clone();
clone.Degr = OffsetDegr(src.Degr, (p - _listEditPhaseIndex) * 120.0);
_phaseConfigs[p] = clone;
}
RaiseListSequenceChanged();
Log($"已将 [{ListPhaseOptions[_listEditPhaseIndex]}] 序列复制到三相(B/C 相相位角自动 ±120° 偏移)");
});
// 一次性下发三相独立 LIST 并启动,严格遵循仪器要求的命令顺序
DownloadThreePhaseListCommand = new DelegateCommand(async () => await Exec(async () =>
{
// 若上一次 LIST 仍在执行,先等待其结束(TRIG:STATE? = OFF)再改写参数
await 等待List空闲Async();
await _dev!.清除错误队列Async(Ct()); // *CLS
await _dev.设置单三相模式Async(true, Ct()); // INST:PHAS THREE
await _dev.设置编辑方式Async("EACH", Ct()); // INST:EDIT EACH(逐相独立编辑)
for (int p = 0; p < 3; p++)
{
await _dev.选择相位Async(p + 1, Ct()); // INST:NSEL 1|2|3
// COUN/TRIG/BASE 在 EACH 逐相编辑模式下随相位上下文分相:必须在每相 NSEL 之后各发一次,
// 否则未选中的相 COUN 保持默认 0(无限循环),表现为“设了次数仍一直执行”
await _dev.设置列表执行次数Async(ListCount, Ct()); // LIST:COUN(0=无限循环)
await _dev.设置列表触发形态Async(ListTrigger, Ct()); // LIST:TRIG
await _dev.设置列表时间基础Async(ListTimeBase, Ct()); // LIST:BASE
await 下发单相序列Async(_phaseConfigs[p]); // FUNC:SHAP:A + LIST:xxx 序列
}
await _dev.延时Async(StartDelayMs, Ct()); // *D 200(等待仪器就绪)
await _dev.设置操作模态Async("LIST", Ct()); // OUTP:MODE LIST
// 回读校验:确认 COUN 真的写入(当前相位上下文=相3);读回 0 而设>0 即未生效
int counBack = await _dev.查询列表执行次数Async(Ct());
await _dev.触发列表执行Async(true, Ct()); // TRIG ON
Log($"三相独立 LIST 已下发并启动(触发={ListTrigger} 次数={(ListCount == 0 ? "无限" : ListCount.ToString())} 时基={ListTimeBase};回读COUN={counBack})");
}));
ListStopCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("LIST 已停止执行(TRIG OFF)"); }));
// 下发 SYNTHESIS 自定义谐波波形并启动:手册要求进入 SYNTH 前先离开输出,参数下发后 OUTP:MODE SYNTH → TRIG ON
DownloadSynthesisCommand = new DelegateCommand(async () => await Exec(async () =>
{
// 先强制 TRIG OFF 停止连续执行模态并等状态回 OFF,再改写参数(执行中改写会被仪器忽略)
await 等待List空闲Async();
await _dev!.清除错误队列Async(Ct()); // *CLS
await _dev.设置输出开关Async(false, Ct()); // OUTP OFF(进入 SYNTH 前先离开输出)
await _dev.设置操作模态Async("FIXED", Ct()); // OUTP:MODE FIXED(清模态)
await _dev.设置合成数据格式Async(SynthCompose, Ct()); // SYNT:COMP
await _dev.设置合成基频Async(SynthFrequency, Ct()); // SYNT:FREQ
await _dev.设置合成基波电压Async(SynthFundamental, Ct()); // SYNT:FUND
await _dev.设置合成直流电压Async(SynthDc, Ct()); // SYNT:DC
await _dev.设置合成起始相位Async(SynthStartPhase, Ct()); // SYNT:SPH
var ampl = ParseDoubles(SynthAmplitudeText);
if (ampl.Length > 0) await _dev.设置合成谐波振幅Async(ampl, Ct()); // SYNT:AMPL
var phas = ParseDoubles(SynthPhaseText);
if (phas.Length > 0) await _dev.设置合成谐波相位Async(phas, Ct()); // SYNT:PHAS
// 回读校验:用仪器自己的回答确认参数是否真的写入(设定与回读不符=被拒绝或落到别的槽位)
string compBack = await _dev.查询合成数据格式Async(Ct());
double fundBack = await _dev.查询合成基波电压Async(Ct());
double[] amplBack = await _dev.查询合成谐波振幅Async(Ct());
Log($"回读校验:COMP 设={SynthCompose} 读={compBack};FUND 设={SynthFundamental} 读={fundBack};AMPL 读前6阶={string.Join(",", amplBack.Take(6))}");
await _dev.延时Async(StartDelayMs, Ct()); // *D 200(等待仪器就绪)
await _dev.设置操作模态Async("SYNTH", Ct()); // OUTP:MODE SYNTH
await _dev.触发列表执行Async(true, Ct()); // TRIG ON
double vrms = await _dev.读取交流电压Async(Ct()); // MEAS:VOLT:AC? 实测输出
Log($"SYNTHESIS 波形已下发并启动(格式={SynthCompose} 基频={SynthFrequency}Hz 基波={SynthFundamental}V 谐波阶数={ampl.Length})实测Vrms={vrms}");
}));
SynthStopCommand = new DelegateCommand(async () =>
await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("SYNTHESIS 已停止执行(TRIG OFF)"); }));
// 重置谐波:把振幅/相位两个序列框填成 50 个 0(仅改界面文本,需再点「下发并启动 SYNTH」才写入仪器)
ResetSynthHarmonicsCommand = new DelegateCommand(() =>
{
string zeros = string.Join(",", Enumerable.Repeat("0", 50));
SynthAmplitudeText = zeros;
SynthPhaseText = zeros;
Log("SYNTHESIS 谐波已重置:振幅/相位两框均填入 50 个 0;点「下发并启动 SYNTH」写入仪器");
});
// 合成波形的载入 / 保存 / 删除(与 LIST 测试项同机制,文件存于 D:\acp\源测试\合成波形)
LoadSynthPresetCommand = new DelegateCommand(LoadCurrentSynthPreset);
SaveSynthPresetCommand = new DelegateCommand(SaveCurrentSynthPreset);
DeleteSynthPresetCommand = new DelegateCommand(DeleteCurrentSynthPreset);
#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)
{
}
#endregion
#region 测试项文件读写(D:\acp\源测试)
/// <summary>
/// 确保测试项目录存在;若目录中尚无任何测试项文件,则把内置大纲预设迁移写入为 .json 文件。
/// </summary>
private void EnsurePresetStorage()
{
try
{
if (!Directory.Exists(PresetStorageDir))
Directory.CreateDirectory(PresetStorageDir);
var existing = Directory.GetFiles(PresetStorageDir, "*" + PresetFileExt);
if (existing.Length > 0) return; // 已有测试项文件,无需迁移
// 首次运行:把内置大纲预设逐项迁移为文件
var seeds = BuildPresets();
foreach (var p in seeds)
WritePresetFile(p);
Log($"已将 {seeds.Count} 项内置测试项迁移到 {PresetStorageDir}");
}
catch (Exception ex)
{
Log($"初始化测试项目录失败:{ex.Message}");
}
}
/// <summary>从磁盘重新加载测试项列表,刷新下拉框并保持选中项有效。</summary>
private void RefreshPresets()
{
var list = new List<ListPreset>();
try
{
if (Directory.Exists(PresetStorageDir))
{
foreach (var file in Directory.GetFiles(PresetStorageDir, "*" + PresetFileExt).OrderBy(f => f))
{
try
{
var json = File.ReadAllText(file);
var item = JsonConvert.DeserializeObject<ListPreset>(json);
if (item != null) list.Add(item);
}
catch (Exception ex)
{
Log($"测试项文件解析失败 [{Path.GetFileName(file)}]:{ex.Message}");
}
}
}
}
catch (Exception ex)
{
Log($"读取测试项目录失败:{ex.Message}");
}
_presets = list;
_presetOptions = list.Select(p => p.Name).ToList();
RaisePropertyChanged(nameof(PresetOptions));
RaisePropertyChanged(nameof(PresetStorageHint));
if (_selectedPresetIndex >= _presets.Count)
SelectedPresetIndex = -1;
}
/// <summary>把一个测试项写入 D:\acp\源测试\{清洗后名称}.json(缩进格式化,便于人工查看)。</summary>
private void WritePresetFile(ListPreset preset)
{
string path = Path.Combine(PresetStorageDir, SanitizeFileName(preset.Name) + PresetFileExt);
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
File.WriteAllText(path, json);
}
/// <summary>
/// 保存当前界面的 LIST 配置为测试项文件(名称取自“测试项名称”输入框)。
/// <para>快照当前三相序列配置 + 执行次数/触发/时基 + 说明,写入后刷新列表并选中该项。</para>
/// </summary>
private void SaveCurrentPreset()
{
string name = (PresetName ?? "").Trim();
if (string.IsNullOrEmpty(name))
{
Log("请先在“测试项名称”填写要保存的名称");
return;
}
try
{
if (!Directory.Exists(PresetStorageDir))
Directory.CreateDirectory(PresetStorageDir);
// 用当前界面参数 + 三相配置快照构建测试项
var phases = new[]
{
_phaseConfigs[0].Clone(),
_phaseConfigs[1].Clone(),
_phaseConfigs[2].Clone(),
};
var preset = new ListPreset(name, PresetNote ?? "", ListCount, phases, ListTrigger, ListTimeBase);
WritePresetFile(preset);
RefreshPresets();
int idx = _presets.FindIndex(p => p.Name == name);
if (idx >= 0) SelectedPresetIndex = idx;
Log($"测试项已保存:{Path.Combine(PresetStorageDir, SanitizeFileName(name) + PresetFileExt)}");
}
catch (Exception ex)
{
Log($"保存测试项失败:{ex.Message}");
}
}
/// <summary>
/// 删除当前选中的测试项文件(带二次确认),删除后刷新列表。
/// </summary>
private void DeleteCurrentPreset()
{
if (SelectedPresetIndex < 0 || SelectedPresetIndex >= _presets.Count)
{
Log("请先在下拉框选择要删除的测试项");
return;
}
var p = _presets[SelectedPresetIndex];
string path = Path.Combine(PresetStorageDir, SanitizeFileName(p.Name) + PresetFileExt);
var confirm = MessageBox.Show(
$"确定删除测试项「{p.Name}」?\n文件:{path}\n此操作不可撤销。",
"删除确认", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (confirm != MessageBoxResult.Yes) return;
try
{
if (File.Exists(path))
File.Delete(path);
SelectedPresetIndex = -1;
RefreshPresets();
Log($"已删除测试项:{path}");
}
catch (Exception ex)
{
Log($"删除测试项失败:{ex.Message}");
}
}
/// <summary>清洗文件名中的非法字符(Windows),空白名回退为“未命名”。</summary>
private static string SanitizeFileName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return "未命名";
foreach (char c in Path.GetInvalidFileNameChars())
name = name.Replace(c, '_');
return name.Trim();
}
#endregion
#region SYNTHESIS 波形文件读写(D:\acp\源测试\合成波形)
/// <summary>
/// 确保合成波形目录存在(波形 .json 一律由用户自行编写或界面保存,不再自动生成内置示例)。
/// </summary>
private void EnsureSynthStorage()
{
try
{
if (!Directory.Exists(SynthStorageDir))
Directory.CreateDirectory(SynthStorageDir);
}
catch (Exception ex)
{
Log($"初始化合成波形目录失败:{ex.Message}");
}
}
/// <summary>从磁盘重新加载合成波形预设列表,刷新下拉框并保持选中项有效。</summary>
private void RefreshSynthPresets()
{
var list = new List<SynthPreset>();
try
{
if (Directory.Exists(SynthStorageDir))
{
foreach (var file in Directory.GetFiles(SynthStorageDir, "*" + PresetFileExt).OrderBy(f => f))
{
try
{
var json = File.ReadAllText(file);
var item = JsonConvert.DeserializeObject<SynthPreset>(json);
if (item != null) list.Add(item);
}
catch (Exception ex)
{
Log($"波形文件解析失败 [{Path.GetFileName(file)}]:{ex.Message}");
}
}
}
}
catch (Exception ex)
{
Log($"读取波形目录失败:{ex.Message}");
}
_synthPresets = list;
_synthPresetOptions = list.Select(p => p.Name).ToList();
RaisePropertyChanged(nameof(SynthPresetOptions));
RaisePropertyChanged(nameof(SynthStorageHint));
if (_selectedSynthPresetIndex >= _synthPresets.Count)
SelectedSynthPresetIndex = -1;
}
/// <summary>把一个合成波形预设写入 {SynthStorageDir}\{清洗后名称}.json(缩进格式化)。</summary>
private void WriteSynthPresetFile(SynthPreset preset)
{
string path = Path.Combine(SynthStorageDir, SanitizeFileName(preset.Name) + PresetFileExt);
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
File.WriteAllText(path, json);
}
/// <summary>载入选中波形:把文件参数一次性回填到界面各 SYNTHESIS 输入框。</summary>
private void LoadCurrentSynthPreset()
{
if (SelectedSynthPresetIndex < 0 || SelectedSynthPresetIndex >= _synthPresets.Count)
{
Log("请先在下拉框选择一项波形");
return;
}
var p = _synthPresets[SelectedSynthPresetIndex];
SynthCompose = p.Compose;
SynthFrequency = p.Frequency;
SynthFundamental = p.Fundamental;
SynthDc = p.Dc;
SynthStartPhase = p.StartPhase;
SynthAmplitudeText = p.AmplitudeText;
SynthPhaseText = p.PhaseText;
SynthPresetName = p.Name;
SynthPresetNote = p.Note;
Log($"已载入波形:{p.Name}(格式={p.Compose} 基频={p.Frequency}Hz 基波={p.Fundamental}V)");
}
/// <summary>
/// 保存当前界面的 SYNTHESIS 参数快照为波形文件(名称取自“波形名称”输入框)。
/// </summary>
private void SaveCurrentSynthPreset()
{
string name = (SynthPresetName ?? "").Trim();
if (string.IsNullOrEmpty(name))
{
Log("请先在“波形名称”填写要保存的名称");
return;
}
try
{
if (!Directory.Exists(SynthStorageDir))
Directory.CreateDirectory(SynthStorageDir);
var preset = new SynthPreset(name, SynthPresetNote ?? "", SynthCompose, SynthFrequency,
SynthFundamental, SynthDc, SynthStartPhase, SynthAmplitudeText, SynthPhaseText);
WriteSynthPresetFile(preset);
RefreshSynthPresets();
int idx = _synthPresets.FindIndex(p => p.Name == name);
if (idx >= 0) SelectedSynthPresetIndex = idx;
Log($"波形已保存:{Path.Combine(SynthStorageDir, SanitizeFileName(name) + PresetFileExt)}");
}
catch (Exception ex)
{
Log($"保存波形失败:{ex.Message}");
}
}
/// <summary>删除当前选中的波形文件(带二次确认),删除后刷新列表。</summary>
private void DeleteCurrentSynthPreset()
{
if (SelectedSynthPresetIndex < 0 || SelectedSynthPresetIndex >= _synthPresets.Count)
{
Log("请先在下拉框选择要删除的波形");
return;
}
var p = _synthPresets[SelectedSynthPresetIndex];
string path = Path.Combine(SynthStorageDir, SanitizeFileName(p.Name) + PresetFileExt);
var confirm = MessageBox.Show(
$"确定删除波形「{p.Name}」?\n文件:{path}\n此操作不可撤销。",
"删除确认", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (confirm != MessageBoxResult.Yes) return;
try
{
if (File.Exists(path))
File.Delete(path);
SelectedSynthPresetIndex = -1;
RefreshSynthPresets();
Log($"已删除波形:{path}");
}
catch (Exception ex)
{
Log($"删除波形失败:{ex.Message}");
}
}
#endregion
#region 辅助
/// <summary>创建 10 秒超时的取消令牌(命令用)。</summary>
private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
/// <summary>当前选中相位的显示标签</summary>
private string PhaseLabel => SelectedPhaseIndex switch
{
1 => "A相",
2 => "B相",
3 => "C相",
_ => "ALL"
};
/// <summary>
/// 根据 SelectedPhaseIndex 发送 INST:COUP / INST:NSEL 命令切换相位上下文。
/// <para>索引 0 → INST:COUP ALL (三相同时);索引 1~3 → INST:COUP NONE + INST:NSEL n</para>
/// </summary>
private async Task ApplyPhaseAsync()
{
if (SelectedPhaseIndex == 0)
{
await _dev!.设置相位耦合Async("ALL", Ct());
}
else
{
await _dev!.设置相位耦合Async("NONE", Ct());
await _dev!.选择相位Async(SelectedPhaseIndex, Ct());
}
}
/// <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();
}
/// <summary>
/// 下发单个相位的 LIST 序列(调用前须已通过 INST:NSEL 选定目标相、并已 INST:EDIT EACH)。
/// <para>依次下发 FUNC:SHAP:A 波形,以及 DEGR / VOLT:AC / VOLT:DC / FREQ / DWEL / SHAP 各序列,留空的序列跳过。</para>
/// </summary>
private async Task 下发单相序列Async(ListPhaseConfig cfg)
{
if (!string.IsNullOrWhiteSpace(cfg.Waveform))
await _dev!.设置波形Async(cfg.Waveform.Trim(), Ct()); // FUNC:SHAP:A
var degr = ParseDoubles(cfg.Degr);
if (degr.Length > 0) await _dev!.设置列表相位角度顺序Async(degr, Ct()); // LIST:DEGR
var vAcS = ParseDoubles(cfg.VoltAcStart);
if (vAcS.Length > 0) await _dev!.设置列表AC起始电压顺序Async(vAcS, Ct()); // LIST:VOLT:AC:STAR
var vAcE = ParseDoubles(cfg.VoltAcEnd);
if (vAcE.Length > 0) await _dev!.设置列表AC结束电压顺序Async(vAcE, Ct()); // LIST:VOLT:AC:END
var vDcS = ParseDoubles(cfg.VoltDcStart);
if (vDcS.Length > 0) await _dev!.设置列表DC起始电压顺序Async(vDcS, Ct()); // LIST:VOLT:DC:STAR
var vDcE = ParseDoubles(cfg.VoltDcEnd);
if (vDcE.Length > 0) await _dev!.设置列表DC结束电压顺序Async(vDcE, Ct()); // LIST:VOLT:DC:END
var fS = ParseDoubles(cfg.FreqStart);
if (fS.Length > 0) await _dev!.设置列表起始频率顺序Async(fS, Ct()); // LIST:FREQ:STAR
var fE = ParseDoubles(cfg.FreqEnd);
if (fE.Length > 0) await _dev!.设置列表结束频率顺序Async(fE, Ct()); // LIST:FREQ:END
var dwell = ParseDoubles(cfg.Dwell);
if (dwell.Length > 0) await _dev!.设置列表静止时间顺序Async(dwell, Ct()); // LIST:DWEL
var shapes = ParseShapes(cfg.ShapeSeq);
if (shapes.Length > 0) await _dev!.设置列表波形顺序Async(shapes, Ct()); // LIST:SHAP
}
/// <summary>
/// 轮询 TRIG:STATE? 等待当前 LIST 执行结束(返回 OFF),避免在执行过程中改写参数导致仪器报错。
/// </summary>
/// <param name="timeoutMs">最长等待时间(ms),超时后仍继续下发并给出提示</param>
private async Task 等待List空闲Async(int timeoutMs = 8000)
{
if (_dev == null) return;
// SYNTH / 无限 LIST 属连续执行模态,不会自行回到 OFF;必须先强制 TRIG OFF 停止,
// 否则执行中改写参数会被仪器忽略(输出沿用本地已存波形)。
try { await _dev.触发列表执行Async(false, Ct()); }
catch { /* 停止失败不阻塞,继续靠轮询兜底 */ }
var sw = System.Diagnostics.Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < timeoutMs)
{
try
{
if (!await _dev.查询触发状态Async(Ct())) return; // OFF:已空闲
}
catch { return; } // 查询失败不阻塞下发流程
await Task.Delay(200);
}
Log("等待空闲超时(可能仍在执行),将继续下发,请留意仪器状态");
}
/// <summary>
/// 将相位角序列整体偏移指定角度(结果对 360 取模,保持 0~359.9),用于生成对称三相的 DEGR。
/// </summary>
private static string OffsetDegr(string degrText, double offset)
{
var vals = ParseDoubles(degrText);
if (vals.Length == 0) return degrText;
return string.Join(",", vals.Select(v =>
{
double d = (v + offset) % 360.0;
if (d < 0) d += 360.0;
return d.ToString("0.#", CultureInfo.InvariantCulture);
}));
}
/// <summary>
/// 构建三相对称的 LIST 相位配置:以给定参数为 A 相,B/C 相复制后 DEGR 自动 +120°/+240°。
/// 适用于三相平衡试验(电压/频率范围、骤变、瞬时跌落等)。DWEL 单位 ms。
/// </summary>
private static ListPhaseConfig[] Sym(string waveform, string degr, string vacS, string vacE,
string vdcS, string vdcE, string fS, string fE, string dwell, string shape = "")
{
var a = new ListPhaseConfig
{
Waveform = waveform,
Degr = degr,
VoltAcStart = vacS,
VoltAcEnd = vacE,
VoltDcStart = vdcS,
VoltDcEnd = vdcE,
FreqStart = fS,
FreqEnd = fE,
Dwell = dwell,
ShapeSeq = shape
};
var b = a.Clone(); b.Degr = OffsetDegr(degr, 120);
var c = a.Clone(); c.Degr = OffsetDegr(degr, 240);
return new[] { a, b, c };
}
/// <summary>
/// 构建大纲典型试验预设集合(仅含 LIST 模式可实现的项)。
/// <para>名称含大纲章节号;对称试验用 <see cref="Sym"/> 生成三相,不对称试验逐相显式定义。</para>
/// </summary>
private static IReadOnlyList<ListPreset> BuildPresets() => new List<ListPreset>
{
// ===== 5.1 交流电压范围(三相平衡渐变,2 循环,DWEL 单位 ms)=====
new ListPreset("5.1 上限电压范围 220→265V",
"三相平衡:额定 220V 线性升至上限 265V 再回落,各段 90s,2 循环。判定 A。",
2, Sym("SINE", "0,0", "220,265", "265,220", "", "", "50,50", "50,50", "90000,90000")),
new ListPreset("5.1 下限电压范围 220→85V",
"三相平衡:220V 降至下限 85V 再回升,各段 90s,2 循环。判定 B。",
2, Sym("SINE", "0,0", "220,85", "85,220", "", "", "50,50", "50,50", "90000,90000")),
new ListPreset("5.1 上限保护电压 220→275V",
"验证过压保护点与恢复点:220V 升至 275V 再回落,2 循环。判定 C。",
2, Sym("SINE", "0,0", "220,275", "275,220", "", "", "50,50", "50,50", "90000,90000")),
new ListPreset("5.1 下限保护电压 220→80V",
"验证欠压保护点与恢复点:220V 降至 80V 再回升,2 循环。判定 C。",
2, Sym("SINE", "0,0", "220,80", "80,220", "", "", "50,50", "50,50", "90000,90000")),
// ===== 5.3 交流频率范围(220V 恒定,频率渐变,2 循环)=====
new ListPreset("5.3 上限频率范围 50→65Hz",
"220V 恒定,频率 50Hz 升至 65Hz 再回落,各段 60s,2 循环。判定 B。",
2, Sym("SINE", "0,0", "220,220", "220,220", "", "", "50,65", "65,50", "60000,60000")),
new ListPreset("5.3 下限频率范围 50→40Hz",
"220V 恒定,频率 50Hz 降至 40Hz 再回升,各段 60s,2 循环。判定 B。",
2, Sym("SINE", "0,0", "220,220", "220,220", "", "", "50,40", "40,50", "60000,60000")),
new ListPreset("5.3 上限保护频率 50→75Hz",
"验证过频保护点与恢复点:50Hz 升至 75Hz 再回落,2 循环。判定 C。",
2, Sym("SINE", "0,0", "220,220", "220,220", "", "", "50,75", "75,50", "60000,60000")),
// ===== 5.4 交流电压骤变(指定相位角阶跃,2 循环)=====
new ListPreset("5.4 上限电压骤变 220→265V@90°",
"稳态 220V(5s)→在 90° 相位瞬间跳到 265V(0.5s)→跳回。改 DEGR 中间值可切 0/180/270°。判定 B。",
2, Sym("SINE", "0,90,0", "220,265,220", "220,265,220", "", "", "50,50,50", "50,50,50", "5000,500,5000")),
new ListPreset("5.4 下限电压骤变 220→90V@90°",
"稳态 220V→在 90° 瞬间跌到 90V(0.5s)→恢复。判定 B。",
2, Sym("SINE", "0,90,0", "220,90,220", "220,90,220", "", "", "50,50,50", "50,50,50", "5000,500,5000")),
// ===== 5.5 交流频率骤变(2 循环)=====
new ListPreset("5.5 上限频率骤变 50→75Hz",
"220V 恒定,频率 50Hz 瞬间跳到 75Hz(1s)再回落。判定 B。",
2, Sym("SINE", "0,90,0", "220,220,220", "220,220,220", "", "", "50,75,50", "50,75,50", "5000,1000,5000")),
new ListPreset("5.5 下限频率骤变 50→40Hz",
"220V 恒定,频率 50Hz 瞬间跌到 40Hz(1s)再回升。判定 B。",
2, Sym("SINE", "0,90,0", "220,220,220", "220,220,220", "", "", "50,40,50", "50,40,50", "5000,1000,5000")),
// ===== 5.12 瞬时电压跌落/冲击(1ms 突变,2 循环)=====
new ListPreset("5.12 0°冲击 310V/1ms",
"稳态 220V→0° 相位冲击到 310V 峰值持续 1ms→恢复。1ms 接近 LIST 最小 dwell,若不精确请改 BASE CYCLE。判定不低于 C。",
2, Sym("SINE", "0,0,0", "220,310,220", "220,310,220", "", "", "50,50,50", "50,50,50", "5000,1,5000")),
new ListPreset("5.12 90°跌落 0V/1ms",
"稳态 220V→90° 相位跌到 0V 持续 1ms→恢复。判定不低于 C。",
2, Sym("SINE", "0,90,0", "220,0,220", "220,0,220", "", "", "50,50,50", "50,50,50", "5000,1,5000")),
// ===== 单相深跌(不对称,A 相深跌 / B·C 相浅跌,无限循环)=====
new ListPreset("5.x 单相深跌 A相11.5V(不对称示例)",
"A 相跌到 11.5V、B/C 相跌到 200.1V,跌落 280ms,前后各稳态 20s;三相 DEGR 基准 0/120/240、中间点各异。无限循环,需手动停止。",
0, new[]
{
new ListPhaseConfig { Waveform = "SINE", Degr = "0,0,0", VoltAcStart = "230,11.5,230", VoltAcEnd = "230,11.5,230", VoltDcStart = "10,10,10", VoltDcEnd = "10,10,10", FreqStart = "50,50,50", FreqEnd = "50,50,50", Dwell = "20000,280,20000" },
new ListPhaseConfig { Waveform = "SINE", Degr = "120,92,120", VoltAcStart = "230,200.1,230", VoltAcEnd = "230,200.1,230", VoltDcStart = "10,10,10", VoltDcEnd = "10,10,10", FreqStart = "50,50,50", FreqEnd = "50,50,50", Dwell = "20000,280,20000" },
new ListPhaseConfig { Waveform = "SINE", Degr = "240,268,240", VoltAcStart = "230,200.1,230", VoltAcEnd = "230,200.1,230", VoltDcStart = "10,10,10", VoltDcEnd = "10,10,10", FreqStart = "50,50,50", FreqEnd = "50,50,50", Dwell = "20000,280,20000" },
}),
// ===== 6.3 直流电压骤变(注意量程与耦合模式)=====
new ListPreset("6.3 直流骤变 680→860→450V【注意量程】",
"直流侧瞬时骤变。⚠ 需先在“耦合模式”设为 DC;且 680~860V 已超 Chroma61800.cs 标注的 DC 量程(-424.2~414.2V),须确认设备为高压选配档,否则命令会被拒绝或截顶。",
2, Sym("SINE", "0,0,0,0", "", "", "680,860,450,680", "680,860,450,680", "", "", "5000,5000,5000,5000")),
};
#endregion
public void Dispose()
{
try { _cmdCts?.Cancel(); } catch { }
_cmdCts?.Dispose();
}
}
/// <summary>
/// 单个相位的 LIST 序列配置(对应界面一组序列文本框)。
/// <para>三相独立下发(INST:EDIT EACH + INST:NSEL)时,每相持有一份本配置,
/// 各字段均为逗号分隔的原始文本,下发时再解析为数值序列。</para>
/// </summary>
public class ListPhaseConfig
{
/// <summary>该相波形 (FUNC:SHAP:A),如 SINE / SQUA</summary>
public string Waveform { get; set; } = "SINE";
/// <summary>相位角度序列 (LIST:DEGR, °)</summary>
public string Degr { get; set; } = "";
/// <summary>AC 起始电压序列 (LIST:VOLT:AC:STAR, V)</summary>
public string VoltAcStart { get; set; } = "";
/// <summary>AC 结束电压序列 (LIST:VOLT:AC:END, V)</summary>
public string VoltAcEnd { get; set; } = "";
/// <summary>DC 起始电压序列 (LIST:VOLT:DC:STAR, V)</summary>
public string VoltDcStart { get; set; } = "";
/// <summary>DC 结束电压序列 (LIST:VOLT:DC:END, V)</summary>
public string VoltDcEnd { get; set; } = "";
/// <summary>起始频率序列 (LIST:FREQ:STAR, Hz)</summary>
public string FreqStart { get; set; } = "";
/// <summary>结束频率序列 (LIST:FREQ:END, Hz)</summary>
public string FreqEnd { get; set; } = "";
/// <summary>静止时间序列 (LIST:DWEL, ms)</summary>
public string Dwell { get; set; } = "";
/// <summary>波形缓冲顺序 (LIST:SHAP, A/B)</summary>
public string ShapeSeq { get; set; } = "";
/// <summary>浅拷贝一份配置(用于"复制到三相")。</summary>
public ListPhaseConfig Clone() => (ListPhaseConfig)MemberwiseClone();
}
/// <summary>
/// 大纲典型试验预设:一次载入即可填充三相 LIST 序列与基础参数(次数/触发/时基)。
/// </summary>
public class ListPreset
{
/// <summary>预设名称(含大纲章节号,供下拉显示)</summary>
public string Name { get; }
/// <summary>试验说明与判定等级提示</summary>
public string Note { get; }
/// <summary>列表执行次数 (LIST:COUN,0 = 无限循环)</summary>
public int Count { get; }
/// <summary>触发形态 (LIST:TRIG)</summary>
public string Trigger { get; }
/// <summary>时间基础 (LIST:BASE)</summary>
public string TimeBase { get; }
/// <summary>三相序列配置(索引 0=A/Φ1, 1=B/Φ2, 2=C/Φ3)</summary>
public ListPhaseConfig[] Phases { get; }
[JsonConstructor]
public ListPreset(string name, string note, int count, ListPhaseConfig[] phases,
string trigger = "AUTO", string timeBase = "TIME")
{
Name = name;
Note = note;
Count = count;
Phases = phases;
Trigger = trigger;
TimeBase = timeBase;
}
}
/// <summary>
/// SYNTHESIS 自定义谐波波形预设:一次载入即可填充合成波形全部参数
/// (数据格式/基频/基波电压/直流/起始相位/谐波振幅与相位序列)。
/// </summary>
public class SynthPreset
{
/// <summary>预设名称(保存时作为文件名,供下拉显示)</summary>
public string Name { get; }
/// <summary>波形说明</summary>
public string Note { get; }
/// <summary>谐波阶数据格式 (SYNT:COMP)</summary>
public string Compose { get; }
/// <summary>基频 (SYNT:FREQ, 50 | 60)</summary>
public int Frequency { get; }
/// <summary>基波交流电压 (SYNT:FUND, V)</summary>
public double Fundamental { get; }
/// <summary>叠加直流电压 (SYNT:DC, V)</summary>
public double Dc { get; }
/// <summary>起始相位角 (SYNT:SPH, °)</summary>
public double StartPhase { get; }
/// <summary>各次谐波振幅序列文本 (SYNT:AMPL, 逗号分隔, 首值=2次谐波)</summary>
public string AmplitudeText { get; }
/// <summary>各次谐波相位角序列文本 (SYNT:PHAS, 逗号分隔, 首值=2次谐波)</summary>
public string PhaseText { get; }
[JsonConstructor]
public SynthPreset(string name, string note, string compose, int frequency,
double fundamental, double dc, double startPhase,
string amplitudeText, string phaseText)
{
Name = name;
Note = note;
Compose = compose;
Frequency = frequency;
Fundamental = fundamental;
Dc = dc;
StartPhase = startPhase;
AmplitudeText = amplitudeText;
PhaseText = phaseText;
}
}
}