using Common.Attributes; using DeviceCommand.Base; using Model.Models; using System; using System.Globalization; using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Devices { [ADPCommand] public class SDS2000X_HD : Tcp { // 示波器底层 Socket 字符串命令以换行符 \n 结束 private const string ScpiDelimiter = "\n"; /// /// 构造函数:一次性初始化示波器通信参数。 /// /// TCP 通信配置对象,包含 IP 地址和端口号等信息 public SDS2000X_HD(TcpConfig config) : base(config) { } /// /// 从示波器返回的 SCPI 响应字符串中提取数值部分并转换为 double。 /// 响应格式示例: "PKPK,2.48E-03V" → 按逗号分割后提取 2.48E-03 /// /// 示波器返回的原始字符串 /// 提取到的数值,解析失败或字符串为空时返回 0 private static double ExtractDouble(string raw) { if (string.IsNullOrWhiteSpace(raw)) return 0; string dataPart = raw; // 按逗号分割,取最后一段(数值通常在逗号后面) int commaIdx = raw.LastIndexOf(','); if (commaIdx >= 0 && commaIdx < raw.Length - 1) dataPart = raw.Substring(commaIdx + 1); // 从数据段中提取数值(含正负号、小数点、科学计数法) var match = Regex.Match(dataPart, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?"); return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val) ? val : 0; } #region 1. IEEE 488.2 公共命令 /// /// 清除标准事件状态寄存器和错误队列 (*CLS)。 /// /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 清除状态(CancellationToken ct = default) { await SendAsync($"*CLS{ScpiDelimiter}", ct); } /// /// 查询错误信息。 /// /// 异步取消令牌 /// 示波器返回的错误代码和描述信息的字符串 public virtual async Task 查询错误信息(CancellationToken ct = default) { return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); } /// /// 读取示波器识别字符串(制造商、型号、序列号、固件版本)。 /// /// 异步取消令牌 /// 设备标识字符串(如 "Siglent Technologies,SDS2004X HD,...") public virtual async Task 查询设备标识(CancellationToken ct = default) { return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); } /// /// 复位命令 (*RST)。使示波器恢复到默认的出厂设置。 /// /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 重置设备(CancellationToken ct = default) { await SendAsync($"*RST{ScpiDelimiter}", ct); } /// /// 查询先前操作是否完成 (*OPC?)。 /// /// 异步取消令牌 /// 布尔值:true 表示所有挂起的操作已完成,false 表示操作仍在执行中 public virtual async Task 检查操作完成_OPC(CancellationToken ct = default) { string res = await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct); return res.Trim() == "1"; } #endregion #region 2. 运行与捕获控制 (Run / Stop / Trigger Mode) /// /// 控制示波器开始连续捕获波形。 /// /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 启动捕获_RUN(CancellationToken ct = default) { await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct); } /// /// 停止捕获波形,画面将定格在当前捕捉的帧。 /// /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 停止捕获_STOP(CancellationToken ct = default) { await SendAsync($":TRIGger:STOP{ScpiDelimiter}", ct); } public enum TriggerMode { /// 自动触发模式 (即使无触发信号也周期性刷屏) AUTO, /// 普通触发模式 (仅当满足触发条件时才刷新) NORMal, /// 单次触发模式 (捕捉到一次满足条件的信号后立刻 STOP) SINGle } /// /// 设置触发模式。 /// /// 触发模式枚举 (AUTO 自动, NORMal 普通, SINGle 单次) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置触发模式(TriggerMode mode, CancellationToken ct = default) { string cmd = $":TRIGger:MODE {mode}{ScpiDelimiter}"; await SendAsync(cmd, ct); } #endregion #region 3. Channel 垂直控制子系统 (电压/电流纹波测试依赖此项) /// /// 开启或关闭指定的模拟通道。 /// /// 通道号 (1 - 4) /// 布尔值:true 表示开启通道, false 表示关闭通道 /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default) { string state = enable ? "ON" : "OFF"; await SendAsync($":CHANnel{channel}:SWITch {state}{ScpiDelimiter}", ct); } /// /// 设置指定通道的物理单位,用于区分测电压还是测电流,影响测量结果读数。 /// /// 通道号 (1 - 4) /// 字符串:"V" 代表电压,"A" 代表电流 /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道单位(int channel, string unit, CancellationToken ct = default) { await SendAsync($":CHANnel{channel}:UNIT {unit.ToUpper()}{ScpiDelimiter}", ct); } /// /// 设置指定通道的垂直档位 (Volts/Div 或 Amps/Div)。 /// /// 通道号 (1 - 4) /// 档位数值 (例如 0.05 表示 50mV/div 或 50mA/div) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道档位(int channel, double value, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":CHANnel{0}:SCALe {1:E4}{2}", channel, value, ScpiDelimiter); await SendAsync(cmd, ct); } public enum ChannelCoupling { DC, AC, GND } /// /// 设置通道耦合方式 (测纹波时必须设置为 AC 交流耦合)。 /// /// 通道号 (1 - 4) /// 耦合方式枚举 (DC, AC, GND) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道耦合(int channel, ChannelCoupling coupling, CancellationToken ct = default) { await SendAsync($":CHANnel{channel}:COUPling {coupling}{ScpiDelimiter}", ct); } /// /// 设置通道带宽限制 (测纹波时通常设置为 20M 带宽限制以滤除高频噪声)。 /// /// 通道号 (1 - 4) /// 带宽限制参数的字符串 (可选: "20M", "200M", "FULL") /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道带宽限制(int channel, string limit, CancellationToken ct = default) { await SendAsync($":CHANnel{channel}:BWLimit {limit.ToUpper()}{ScpiDelimiter}", ct); } /// /// 设置通道的垂直偏移量,用于调整波形在屏幕上的垂直位置。 /// 泄放测试中需要设置合适的偏移使衰减波形完整显示在屏幕内。 /// /// 通道号 (1 - 4) /// 垂直偏移值 (单位:V 或 A,正值向上移动,负值向下移动) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":CHANnel{0}:OFFSet {1:F3}{2}", channel, offset, ScpiDelimiter); await SendAsync(cmd, ct); } #endregion #region 4. Timebase 水平时基子系统 /// /// 设置示波器的水平时基档位 (Time/Div)。 /// /// 水平时基数值 (单位:秒/格,如 1.00E-03 表示 1ms/div) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置水平时基(double scale, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":TIMebase:SCALe {0:E6}{1}", scale, ScpiDelimiter); await SendAsync(cmd, ct); } #endregion #region 5. Trigger 触发设置子系统 /// /// 设置边沿触发的触发源。 /// /// 触发源标识符的字符串 (例如: "C1", "C2", "EX", "LINE") /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置边沿触发源(string source, CancellationToken ct = default) { await SendAsync($":TRIGger:EDGE:SOURce {source.ToUpper()}{ScpiDelimiter}", ct); } /// /// 设置边沿触发的电平值。 /// /// 触发电平数值 (根据通道单位,通常为 V 或 A) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置边沿触发电平(double level, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":TRIGger:EDGE:LEVel {0:F3}{1}", level, ScpiDelimiter); await SendAsync(cmd, ct); } public enum TriggerSlope { RISing, FALLing, ALTernate } /// /// 设置边沿触发的斜率类型 (测试 AC 泄放时间需使用 FALLing 下降沿)。 /// /// 斜率枚举类型 (RISing 上升沿, FALLing 下降沿, ALTernate 任意沿) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置边沿触发斜率(TriggerSlope slope, CancellationToken ct = default) { await SendAsync($":TRIGger:EDGE:SLOPe {slope}{ScpiDelimiter}", ct); } #endregion #region 6. Cursor 光标子系统 (用于计算启动时间、泄放时间的 ΔT) /// /// 开启手动光标并设置为 X 轴(时间轴)测量模式。 /// /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 开启手动X光标(CancellationToken ct = default) { await SendAsync($":CURSor:MODE MANual,X{ScpiDelimiter}", ct); } /// /// 设置 X1 光标的绝对位置。 /// /// 光标在时间轴上的位置 (单位:秒) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置光标X1位置(double position, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":CURSor:X1 {0:E6}{1}", position, ScpiDelimiter); await SendAsync(cmd, ct); } /// /// 设置 X2 光标的绝对位置。 /// /// 光标在时间轴上的位置 (单位:秒) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置光标X2位置(double position, CancellationToken ct = default) { string cmd = string.Format(CultureInfo.InvariantCulture, ":CURSor:X2 {0:E6}{1}", position, ScpiDelimiter); await SendAsync(cmd, ct); } /// /// 查询光标 X2 和 X1 当前位置的时间差 (ΔT = X2 - X1)。 /// /// 异步取消令牌 /// 返回 X 轴两光标之间的时间差数值 (double 类型,单位:秒) public virtual async Task 查询光标X时间差(CancellationToken ct = default) { string res = await WriteReadAsync($":CURSor:XDELta?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(res); } #endregion #region 7. Measure 基本测量子系统 (测试纹波PKPK、冲击电流MAX) /// /// 设置基本测量系统的全局参考信源。 /// /// 作为测量对象的通道号 (1 - 4) /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置基本测量源(int channel, CancellationToken ct = default) { await SendAsync($":MEASure:SIMPle:SOURce C{channel}{ScpiDelimiter}", ct); } /// /// 开启或关闭某项基本测量参数 (在屏幕下方显示或隐藏该测量项)。 /// /// 要设置的测量参数名称 (如: "PKPK", "MAX", "MIN") /// 布尔值:true 表示开启显示, false 表示关闭 /// 异步取消令牌 /// 表示异步操作的 Task public virtual async Task 设置基本测量项(string parameter, bool enable, CancellationToken ct = default) { string state = enable ? "ON" : "OFF"; await SendAsync($":MEASure:SIMPle:ITEM {parameter.ToUpper()},{state}{ScpiDelimiter}", ct); } /// /// 查询基本测量中指定测量项的当前实时数值。 /// /// 要查询的测量参数名称 (如: "PKPK" 获取峰峰值, "MAX" 获取最大值) /// 异步取消令牌 /// 返回该测量参数提取到的实时数值 (double 类型) public virtual async Task 查询基本测量值(string type, CancellationToken ct = default) { string res = await WriteReadAsync($":MEASure:SIMPle:VALue? {type.ToUpper()}{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(res); } #endregion #region 8. 屏幕截图导出子系统 /// /// 【一键获取并保存截图】 /// 自动下发 :PRINt? PNG 指令,接收示波器返回的纯 PNG 二进制流并直接保存到上位机本地作测试记录。 /// /// 目标保存文件的绝对路径 (例如: @"D:\Oscilloscope\RippleTest.png") /// 异步取消令牌 /// 布尔值:true 表示成功获取并保存截图,false 表示获取或保存失败 public virtual async Task 获取屏幕图像并保存(string saveFilePath, CancellationToken ct = default) { try { string FileDirectory = Path.GetDirectoryName(saveFilePath) ?? ""; string fileNameWithoutExt = Path.GetFileNameWithoutExtension(saveFilePath); string extension = Path.GetExtension(saveFilePath); // 自动追加当前时间戳避免同名文件覆盖 string timeStamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); saveFilePath = Path.Combine(FileDirectory, $"{fileNameWithoutExt}_{timeStamp}{extension}"); // 下发截图指令,接收纯字节流 byte[] pureImageBytes = await ReadAllBytesAsync($":PRINt? PNG{ScpiDelimiter}", ct); if (pureImageBytes == null || pureImageBytes.Length < 8) return false; // 验证 PNG 格式头,以确认确实是一张合法图像 if (pureImageBytes[0] != 0x89 || pureImageBytes[1] != 'P' || pureImageBytes[2] != 'N' || pureImageBytes[3] != 'G') return false; string directory = Path.GetDirectoryName(saveFilePath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 将接收到的二进制数组写入文件 using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write)) { await fs.WriteAsync(pureImageBytes, 0, pureImageBytes.Length, ct); await fs.FlushAsync(ct); } return true; } catch (Exception) { return false; } } #endregion } }