Files
ADP/DeviceEditModule/ViewModels/SDS2000X_HDViewModel.cs
T
2026-09-16 14:36:28 +08:00

443 lines
18 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 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 光标时间差 ΔTs)。</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();
}
}
}