387 lines
15 KiB
C#
387 lines
15 KiB
C#
using DeviceCommand.Devices;
|
||
using Prism.Commands;
|
||
using Prism.Ioc;
|
||
using System;
|
||
using System.Globalization;
|
||
using System.Text.RegularExpressions;
|
||
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 double _voltsPerDiv = 1.0;
|
||
/// <summary>垂直电压档位(V/div)。</summary>
|
||
public double VoltsPerDiv
|
||
{
|
||
get => _voltsPerDiv;
|
||
set => SetProperty(ref _voltsPerDiv, value);
|
||
}
|
||
|
||
private double _offset = 0.0;
|
||
/// <summary>垂直偏移(V)。</summary>
|
||
public double Offset
|
||
{
|
||
get => _offset;
|
||
set => SetProperty(ref _offset, value);
|
||
}
|
||
|
||
private bool _is50Ohm;
|
||
/// <summary>是否使用 50Ω 输入阻抗,false 为 1MΩ。</summary>
|
||
public bool Is50Ohm
|
||
{
|
||
get => _is50Ohm;
|
||
set => SetProperty(ref _is50Ohm, value);
|
||
}
|
||
|
||
private double _timeBase = 0.001;
|
||
/// <summary>水平时基档位(s/div)。</summary>
|
||
public double TimeBase
|
||
{
|
||
get => _timeBase;
|
||
set => SetProperty(ref _timeBase, value);
|
||
}
|
||
|
||
private double _triggerLevel = 0.0;
|
||
/// <summary>触发电平(V)。</summary>
|
||
public double TriggerLevel
|
||
{
|
||
get => _triggerLevel;
|
||
set => SetProperty(ref _triggerLevel, value);
|
||
}
|
||
|
||
private string _triggerSource = "C1";
|
||
/// <summary>触发源(C1/C2/C3/C4/EX/LINE)。</summary>
|
||
public string TriggerSource
|
||
{
|
||
get => _triggerSource;
|
||
set => SetProperty(ref _triggerSource, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 测量结果属性
|
||
|
||
private string _measuredVpp = "—";
|
||
public string MeasuredVpp
|
||
{
|
||
get => _measuredVpp;
|
||
set => SetProperty(ref _measuredVpp, value);
|
||
}
|
||
|
||
private string _measuredFrequency = "—";
|
||
public string MeasuredFrequency
|
||
{
|
||
get => _measuredFrequency;
|
||
set => SetProperty(ref _measuredFrequency, value);
|
||
}
|
||
|
||
private string _measuredRms = "—";
|
||
public string MeasuredRms
|
||
{
|
||
get => _measuredRms;
|
||
set => SetProperty(ref _measuredRms, value);
|
||
}
|
||
|
||
private string _responseLog = string.Empty;
|
||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||
public string ResponseLog
|
||
{
|
||
get => _responseLog;
|
||
set => SetProperty(ref _responseLog, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 命令
|
||
|
||
public ICommand QueryIdentityCommand { get; }
|
||
public ICommand ResetDeviceCommand { get; }
|
||
public ICommand RunCommand { get; }
|
||
public ICommand StopCommand { get; }
|
||
public ICommand SingleCommand { get; }
|
||
public ICommand ForceTriggerCommand { get; }
|
||
public ICommand SetChannelOnCommand { get; }
|
||
public ICommand SetChannelOffCommand { get; }
|
||
public ICommand SetVoltsDivCommand { get; }
|
||
public ICommand SetOffsetCommand { get; }
|
||
|
||
// 🛠️ 补全:设置阻抗的 Command
|
||
public ICommand SetImpedance50Command { get; }
|
||
public ICommand SetImpedance1MCommand { get; }
|
||
|
||
public ICommand SetTimeBaseCommand { get; }
|
||
public ICommand SetTriggerLevelCommand { get; }
|
||
public ICommand SetTriggerSourceCommand { get; }
|
||
public ICommand QueryMeasurementsCommand { get; }
|
||
public ICommand QueryVppCommand { get; }
|
||
public ICommand QueryFrequencyCommand { get; }
|
||
public ICommand QueryRmsCommand { get; }
|
||
|
||
#endregion
|
||
|
||
public SDS2000X_HDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||
{
|
||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||
|
||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
||
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
||
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
||
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
||
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} 已关闭"); }));
|
||
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
||
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
||
|
||
// 🛠️ 绑定:设置 50Ω 和 1MΩ 阻抗控制
|
||
SetImpedance50Command = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
await _device!.设置通道阻抗(Channel, ImpedanceType.FIFty, Ct());
|
||
Is50Ohm = true;
|
||
AppendLog($"C{Channel} 输入阻抗已设为 50Ω");
|
||
}));
|
||
|
||
SetImpedance1MCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
await _device!.设置通道阻抗(Channel, ImpedanceType.ONEM, Ct());
|
||
Is50Ohm = false;
|
||
AppendLog($"C{Channel} 输入阻抗已设为 1MΩ");
|
||
}));
|
||
|
||
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
||
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
||
|
||
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
// 1. 保留设备原生吐出的完整原始字符串
|
||
string rawVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||
string rawFreq = await _device!.查询实际频率(Channel, Ct());
|
||
string rawRms = await _device!.查询实际电压均方根(Channel, Ct());
|
||
|
||
// 2. 清洗数据并将科学计数法转为常规小数后赋值给 UI 属性
|
||
MeasuredVpp = ParseMeasurement(rawVpp);
|
||
MeasuredFrequency = ParseMeasurement(rawFreq);
|
||
MeasuredRms = ParseMeasurement(rawRms);
|
||
|
||
// 3. 在日志中同时体现设备原始报文与解析呈现值,极方便联调
|
||
AppendLog($"C{Channel} 测量原始数据 → Vpp:{rawVpp.Trim()} Freq:{rawFreq.Trim()} RMS:{rawRms.Trim()}");
|
||
AppendLog($"C{Channel} 界面呈现数值 → Vpp:{MeasuredVpp}V Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
||
}));
|
||
|
||
QueryVppCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
string raw = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||
MeasuredVpp = ParseMeasurement(raw);
|
||
AppendLog($"C{Channel} Vpp: {MeasuredVpp} (Raw: {raw.Trim()})");
|
||
}));
|
||
|
||
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
string raw = await _device!.查询实际频率(Channel, Ct());
|
||
MeasuredFrequency = ParseMeasurement(raw);
|
||
AppendLog($"C{Channel} Freq: {MeasuredFrequency} (Raw: {raw.Trim()})");
|
||
}));
|
||
|
||
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||
{
|
||
string raw = await _device!.查询实际电压均方根(Channel, Ct());
|
||
MeasuredRms = ParseMeasurement(raw);
|
||
AppendLog($"C{Channel} RMS: {MeasuredRms} (Raw: {raw.Trim()})");
|
||
}));
|
||
|
||
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;
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清洗示波器原始返回的测量字符串(例如 "C1:PAVA RMS,5.57E-03V")并安全转化为无科学计数法的小数形式。
|
||
/// </summary>
|
||
/// <param name="rawResponse">设备原始应答数据</param>
|
||
/// <returns>可直接绑定到 UI 呈现的字符串数字</returns>
|
||
private string ParseMeasurement(string rawResponse)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(rawResponse)) return "—";
|
||
|
||
try
|
||
{
|
||
string cleanData = rawResponse.Trim();
|
||
|
||
// 1. 斩断报头,提取逗号后面的具体内容(如 "5.57E-03V" 或 "****")
|
||
int commaIndex = cleanData.IndexOf(',');
|
||
if (commaIndex == -1) return "—";
|
||
|
||
string valStr = cleanData.Substring(commaIndex + 1);
|
||
|
||
var match = Regex.Match(valStr, @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?");
|
||
if (match.Success)
|
||
{
|
||
valStr = match.Value;
|
||
}
|
||
|
||
// 3. 校验并拦截设备未测出时的无效星号 "****"
|
||
if (valStr.Contains("*") || string.IsNullOrWhiteSpace(valStr))
|
||
{
|
||
return "0"; // 回归为零或 "—",防止触发数据异常
|
||
}
|
||
|
||
// 4. 解析科学计数法,并重新以不带科学计数法的小数样式展开
|
||
if (double.TryParse(valStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double result))
|
||
{
|
||
// "0.######" 样式会自动消除末尾无用的冗余零,并显示为普通小数(如 0.00557)
|
||
return result.ToString("0.######", CultureInfo.InvariantCulture);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 捕获可能产生的边缘转换故障,保证轮询线程绝不崩溃
|
||
}
|
||
|
||
return "—";
|
||
}
|
||
|
||
#endregion
|
||
|
||
public void Dispose()
|
||
{
|
||
_cts?.Cancel();
|
||
_cts?.Dispose();
|
||
}
|
||
}
|
||
} |