443 lines
18 KiB
C#
443 lines
18 KiB
C#
using DeviceCommand.Devices;
|
||
using Prism.Commands;
|
||
using Prism.Ioc;
|
||
using System;
|
||
using System.IO;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Input;
|
||
using UIShare.GlobalVariable;
|
||
using UIShare.ViewModelBase;
|
||
using static DeviceCommand.Devices.SDS2000X_HD;
|
||
|
||
namespace DeviceEditModule.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// SDS2000X_HD 数字存储示波器控制面板 ViewModel。
|
||
/// </summary>
|
||
public class SDS2000X_HDViewModel : NavigateViewModelBase, IDisposable
|
||
{
|
||
#region 私有字段
|
||
|
||
private readonly DeviceManager _deviceManager;
|
||
private SDS2000X_HD? _device;
|
||
private CancellationTokenSource? _cts;
|
||
|
||
#endregion
|
||
|
||
#region 设备信息属性
|
||
|
||
private string _deviceName = "SDS2000X_HD";
|
||
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 int _channel = 1;
|
||
/// <summary>当前操作通道(1~4)。</summary>
|
||
public int Channel
|
||
{
|
||
get => _channel;
|
||
set => SetProperty(ref _channel, value);
|
||
}
|
||
|
||
private string _channelUnit = "V";
|
||
/// <summary>通道物理单位(V 电压 / A 电流)。</summary>
|
||
public string ChannelUnit
|
||
{
|
||
get => _channelUnit;
|
||
set => SetProperty(ref _channelUnit, value);
|
||
}
|
||
|
||
public string[] ChannelUnitOptions { get; } = { "V", "A" };
|
||
|
||
private double _voltsPerDiv = 1.0;
|
||
/// <summary>垂直档位(V/div 或 A/div)。</summary>
|
||
public double VoltsPerDiv
|
||
{
|
||
get => _voltsPerDiv;
|
||
set => SetProperty(ref _voltsPerDiv, value);
|
||
}
|
||
|
||
private ChannelCoupling _selectedCoupling = ChannelCoupling.DC;
|
||
/// <summary>通道耦合方式(DC/AC/GND,测纹波需 AC)。</summary>
|
||
public ChannelCoupling SelectedCoupling
|
||
{
|
||
get => _selectedCoupling;
|
||
set => SetProperty(ref _selectedCoupling, value);
|
||
}
|
||
|
||
public ChannelCoupling[] CouplingOptions { get; } =
|
||
(ChannelCoupling[])Enum.GetValues(typeof(ChannelCoupling));
|
||
|
||
private string _bandwidthLimit = "20M";
|
||
/// <summary>通道带宽限制(20M/200M/FULL,测纹波常用 20M)。</summary>
|
||
public string BandwidthLimit
|
||
{
|
||
get => _bandwidthLimit;
|
||
set => SetProperty(ref _bandwidthLimit, value);
|
||
}
|
||
|
||
public string[] BandwidthLimitOptions { get; } = { "20M", "200M", "FULL" };
|
||
|
||
private double _timeBase = 0.001;
|
||
/// <summary>水平时基档位(s/div)。</summary>
|
||
public double TimeBase
|
||
{
|
||
get => _timeBase;
|
||
set => SetProperty(ref _timeBase, value);
|
||
}
|
||
|
||
private TriggerMode _selectedTriggerMode = TriggerMode.AUTO;
|
||
/// <summary>触发模式(AUTO/NORMal/SINGle)。</summary>
|
||
public TriggerMode SelectedTriggerMode
|
||
{
|
||
get => _selectedTriggerMode;
|
||
set => SetProperty(ref _selectedTriggerMode, value);
|
||
}
|
||
|
||
public TriggerMode[] TriggerModeOptions { get; } =
|
||
(TriggerMode[])Enum.GetValues(typeof(TriggerMode));
|
||
|
||
private string _triggerSource = "C1";
|
||
/// <summary>边沿触发源(C1~C4/EX/LINE)。</summary>
|
||
public string TriggerSource
|
||
{
|
||
get => _triggerSource;
|
||
set => SetProperty(ref _triggerSource, value);
|
||
}
|
||
|
||
private double _triggerLevel = 0.0;
|
||
/// <summary>边沿触发电平(V)。</summary>
|
||
public double TriggerLevel
|
||
{
|
||
get => _triggerLevel;
|
||
set => SetProperty(ref _triggerLevel, value);
|
||
}
|
||
|
||
private TriggerSlope _selectedTriggerSlope = TriggerSlope.RISing;
|
||
/// <summary>边沿触发斜率(RISing/FALLing/ALTernate,测泄放时间用 FALLing)。</summary>
|
||
public TriggerSlope SelectedTriggerSlope
|
||
{
|
||
get => _selectedTriggerSlope;
|
||
set => SetProperty(ref _selectedTriggerSlope, value);
|
||
}
|
||
|
||
public TriggerSlope[] TriggerSlopeOptions { get; } =
|
||
(TriggerSlope[])Enum.GetValues(typeof(TriggerSlope));
|
||
|
||
private double _cursorX1 = 0.0;
|
||
/// <summary>X1 光标位置(s)。</summary>
|
||
public double CursorX1
|
||
{
|
||
get => _cursorX1;
|
||
set => SetProperty(ref _cursorX1, value);
|
||
}
|
||
|
||
private double _cursorX2 = 0.001;
|
||
/// <summary>X2 光标位置(s)。</summary>
|
||
public double CursorX2
|
||
{
|
||
get => _cursorX2;
|
||
set => SetProperty(ref _cursorX2, value);
|
||
}
|
||
|
||
private string _saveFilePath = Path.Combine(AppContext.BaseDirectory, "Screenshots", "SDS2000X_HD.png");
|
||
/// <summary>屏幕截图保存路径(保存时自动追加时间戳)。</summary>
|
||
public string SaveFilePath
|
||
{
|
||
get => _saveFilePath;
|
||
set => SetProperty(ref _saveFilePath, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测量结果属性
|
||
|
||
private double _measuredVpp;
|
||
public double MeasuredVpp
|
||
{
|
||
get => _measuredVpp;
|
||
set => SetProperty(ref _measuredVpp, value);
|
||
}
|
||
|
||
private double _measuredFrequency;
|
||
public double MeasuredFrequency
|
||
{
|
||
get => _measuredFrequency;
|
||
set => SetProperty(ref _measuredFrequency, value);
|
||
}
|
||
|
||
private double _measuredRms;
|
||
public double MeasuredRms
|
||
{
|
||
get => _measuredRms;
|
||
set => SetProperty(ref _measuredRms, value);
|
||
}
|
||
|
||
private double _cursorDeltaT;
|
||
/// <summary>X1/X2 光标时间差 ΔT(s)。</summary>
|
||
public double CursorDeltaT
|
||
{
|
||
get => _cursorDeltaT;
|
||
set => SetProperty(ref _cursorDeltaT, value);
|
||
}
|
||
|
||
private string _responseLog = string.Empty;
|
||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||
public string ResponseLog
|
||
{
|
||
get => _responseLog;
|
||
set => SetProperty(ref _responseLog, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 命令
|
||
|
||
// IEEE 488.2 公共命令
|
||
public ICommand QueryIdentityCommand { get; }
|
||
public ICommand ResetDeviceCommand { get; }
|
||
public ICommand ClearStatusCommand { get; }
|
||
public ICommand QueryErrorCommand { get; }
|
||
public ICommand QueryOpcCommand { get; }
|
||
|
||
// 运行与捕获控制
|
||
public ICommand RunCommand { get; }
|
||
public ICommand StopCommand { get; }
|
||
public ICommand SetTriggerModeCommand { get; }
|
||
|
||
// 通道垂直控制
|
||
public ICommand SetChannelOnCommand { get; }
|
||
public ICommand SetChannelOffCommand { get; }
|
||
public ICommand SetChannelUnitCommand { get; }
|
||
public ICommand SetVoltsDivCommand { get; }
|
||
public ICommand SetChannelCouplingCommand { get; }
|
||
public ICommand SetBandwidthLimitCommand { get; }
|
||
|
||
// 水平与触发控制
|
||
public ICommand SetTimeBaseCommand { get; }
|
||
public ICommand SetTriggerSourceCommand { get; }
|
||
public ICommand SetTriggerLevelCommand { get; }
|
||
public ICommand SetTriggerSlopeCommand { get; }
|
||
|
||
// 光标测量
|
||
public ICommand EnableManualXCursorCommand { get; }
|
||
public ICommand SetCursorX1Command { get; }
|
||
public ICommand SetCursorX2Command { get; }
|
||
public ICommand QueryCursorDeltaTCommand { get; }
|
||
|
||
// 基本测量
|
||
public ICommand QueryMeasurementsCommand { get; }
|
||
public ICommand QueryVppCommand { get; }
|
||
public ICommand QueryFrequencyCommand { get; }
|
||
public ICommand QueryRmsCommand { get; }
|
||
|
||
// 屏幕截图
|
||
public ICommand SaveScreenshotCommand { get; }
|
||
|
||
#endregion
|
||
|
||
public SDS2000X_HDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||
{
|
||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||
|
||
// IEEE 488.2 公共命令
|
||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置 (*RST)"); }));
|
||
ClearStatusCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除状态(Ct()); AppendLog("状态寄存器与错误队列已清除 (*CLS)"); }));
|
||
QueryErrorCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("SYST:ERR? → " + await _device!.查询错误信息(Ct()))));
|
||
QueryOpcCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("操作完成 (*OPC?): " + (await _device!.检查操作完成_OPC(Ct()) ? "是" : "否"))));
|
||
|
||
// 运行与捕获控制
|
||
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始连续捕获"); }));
|
||
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获,画面定格"); }));
|
||
SetTriggerModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发模式(SelectedTriggerMode, Ct()); AppendLog($"触发模式已设为 {SelectedTriggerMode}"); }));
|
||
|
||
// 通道垂直控制
|
||
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
||
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
||
SetChannelUnitCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道单位(Channel, ChannelUnit, Ct()); AppendLog($"C{Channel} 单位已设为 {ChannelUnit}"); }));
|
||
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 垂直档位已设为 {VoltsPerDiv} {ChannelUnit}/div"); }));
|
||
SetChannelCouplingCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道耦合(Channel, SelectedCoupling, Ct()); AppendLog($"C{Channel} 耦合方式已设为 {SelectedCoupling}"); }));
|
||
SetBandwidthLimitCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道带宽限制(Channel, BandwidthLimit, Ct()); AppendLog($"C{Channel} 带宽限制已设为 {BandwidthLimit}"); }));
|
||
|
||
// 水平与触发控制
|
||
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置边沿触发源(TriggerSource, Ct()); AppendLog($"边沿触发源已设为 {TriggerSource}"); }));
|
||
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置边沿触发电平(TriggerLevel, Ct()); AppendLog($"边沿触发电平已设为 {TriggerLevel} V"); }));
|
||
SetTriggerSlopeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置边沿触发斜率(SelectedTriggerSlope, Ct()); AppendLog($"边沿触发斜率已设为 {SelectedTriggerSlope}"); }));
|
||
|
||
// 光标测量
|
||
EnableManualXCursorCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.开启手动X光标(Ct()); AppendLog("已开启手动 X 轴光标"); }));
|
||
SetCursorX1Command = new DelegateCommand(async () => await Exec(async () => { await _device!.设置光标X1位置(CursorX1, Ct()); AppendLog($"X1 光标已设为 {CursorX1} s"); }));
|
||
SetCursorX2Command = new DelegateCommand(async () => await Exec(async () => { await _device!.设置光标X2位置(CursorX2, Ct()); AppendLog($"X2 光标已设为 {CursorX2} s"); }));
|
||
QueryCursorDeltaTCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
CursorDeltaT = await _device!.查询光标X时间差(Ct());
|
||
AppendLog($"光标时间差 ΔT = {CursorDeltaT:0.######E+0} s");
|
||
}));
|
||
|
||
// 基本测量(查询前先将测量源切换到当前通道)
|
||
QueryVppCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
MeasuredVpp = await QuerySimpleValueAsync("PKPK");
|
||
AppendLog($"C{Channel} Vpp: {MeasuredVpp:0.######} {ChannelUnit}");
|
||
}));
|
||
|
||
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
MeasuredFrequency = await QuerySimpleValueAsync("FREQ");
|
||
AppendLog($"C{Channel} Freq: {MeasuredFrequency:0.######} Hz");
|
||
}));
|
||
|
||
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
MeasuredRms = await QuerySimpleValueAsync("RMS");
|
||
AppendLog($"C{Channel} RMS: {MeasuredRms:0.######} {ChannelUnit}");
|
||
}));
|
||
|
||
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
await _device!.设置基本测量源(Channel, Ct());
|
||
MeasuredVpp = await _device.查询基本测量值("PKPK", Ct());
|
||
MeasuredFrequency = await _device.查询基本测量值("FREQ", Ct());
|
||
MeasuredRms = await _device.查询基本测量值("RMS", Ct());
|
||
|
||
AppendLog($"C{Channel} 测量结果 → Vpp:{MeasuredVpp:0.######}{ChannelUnit} Freq:{MeasuredFrequency:0.######}Hz RMS:{MeasuredRms:0.######}{ChannelUnit}");
|
||
}));
|
||
|
||
// 屏幕截图
|
||
SaveScreenshotCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
bool ok = await _device!.获取屏幕图像并保存(SaveFilePath, Ct());
|
||
AppendLog(ok ? $"截图已保存:{SaveFilePath}" : "截图获取或保存失败,请检查连接与保存路径。");
|
||
}));
|
||
|
||
Initialize();
|
||
}
|
||
|
||
#region 初始化 / Navigation
|
||
|
||
public void Initialize(string? deviceName = null)
|
||
{
|
||
SDS2000X_HD? found = null;
|
||
string? foundName = null;
|
||
|
||
if (deviceName != null &&
|
||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||
d is SDS2000X_HD s)
|
||
{
|
||
found = s;
|
||
foundName = deviceName;
|
||
}
|
||
else
|
||
{
|
||
foreach (var kv in _deviceManager.DeviceMap)
|
||
{
|
||
if (kv.Value is SDS2000X_HD sds)
|
||
{
|
||
found = sds;
|
||
foundName = kv.Key;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
_device = found;
|
||
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
||
IsConnected = _device?.IsConnected ?? false;
|
||
|
||
AppendLog(found != null
|
||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||
: "未在 DeviceManager 中找到 SDS2000X_HD 设备,请先初始化设备配置。");
|
||
}
|
||
|
||
public override void OnNavigatedTo(NavigationContext context)
|
||
{
|
||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||
Initialize(name);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助
|
||
|
||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||
|
||
/// <summary>
|
||
/// 将基本测量源切换到当前通道后,查询指定测量项的实时数值。
|
||
/// </summary>
|
||
/// <param name="type">测量参数名称(如: "PKPK"、"FREQ"、"RMS")</param>
|
||
/// <returns>测量项提取到的实时数值</returns>
|
||
private async Task<double> QuerySimpleValueAsync(string type)
|
||
{
|
||
await _device!.设置基本测量源(Channel, Ct());
|
||
return await _device.查询基本测量值(type, Ct());
|
||
}
|
||
|
||
private async Task Exec(Func<Task> action)
|
||
{
|
||
if (_device == null)
|
||
{
|
||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||
return;
|
||
}
|
||
if (IsBusy) return;
|
||
IsBusy = true;
|
||
try
|
||
{
|
||
await action();
|
||
IsConnected = _device.IsConnected;
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
AppendLog("命令超时或已取消。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AppendLog($"错误:{ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
IsBusy = false;
|
||
}
|
||
}
|
||
|
||
private void AppendLog(string message)
|
||
{
|
||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||
ResponseLog = ResponseLog.Length > 4000
|
||
? line + "\n" + ResponseLog[..3000]
|
||
: line + "\n" + ResponseLog;
|
||
}
|
||
|
||
#endregion
|
||
|
||
public void Dispose()
|
||
{
|
||
_cts?.Cancel();
|
||
_cts?.Dispose();
|
||
}
|
||
}
|
||
}
|