349 lines
14 KiB
C#
349 lines
14 KiB
C#
using Common.Attributes;
|
||
using DeviceCommand.Base;
|
||
using Model.Models;
|
||
using System;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace DeviceCommand.Devices
|
||
{
|
||
/// <summary>
|
||
/// 示波器 MSO 4/5/6 系列
|
||
/// </summary>
|
||
#region 枚举定义
|
||
|
||
/// <summary>
|
||
/// Tektronix MSO 采集模式
|
||
/// </summary>
|
||
public enum TekAcquisitionMode
|
||
{
|
||
/// <summary> 采样模式 </summary>
|
||
SAMple,
|
||
/// <summary> 峰值检测模式 </summary>
|
||
PEAKdetect,
|
||
/// <summary> 高分辨率模式 </summary>
|
||
HIRes,
|
||
/// <summary> 平均模式 </summary>
|
||
AVErage,
|
||
/// <summary> 包络模式 </summary>
|
||
ENVelope
|
||
}
|
||
|
||
/// <summary>
|
||
/// Tektronix MSO 测量类型 (部分常用)
|
||
/// </summary>
|
||
public enum TekMeasurementType
|
||
{
|
||
AMPLitude,
|
||
FREQuency,
|
||
MEAN,
|
||
PK2PK,
|
||
MAXimum,
|
||
MINimum
|
||
}
|
||
|
||
#endregion
|
||
|
||
[ACPCommand]
|
||
public class TektronixMSO : Tcp
|
||
{
|
||
// SCPI 指令结束符 (消息终止符需使用 LF)
|
||
private const string ScpiDelimiter = "\n";
|
||
|
||
public TektronixMSO(TcpConfig config) : base(config)
|
||
{
|
||
}
|
||
|
||
#region 1. 公共命令与状态查询
|
||
|
||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||
{
|
||
// 返回仪器的标识代码[cite: 2]
|
||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
public virtual async Task<string> 查询忙碌状态(CancellationToken ct = default)
|
||
{
|
||
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 2]
|
||
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
public virtual async Task 执行自校准(CancellationToken ct = default)
|
||
{
|
||
// 指示仪器执行信号路径校准 (SPC)[cite: 2]
|
||
await SendAsync($"*CAL?{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 2. 采集系统控制 (Acquisition)
|
||
|
||
/// <summary>
|
||
/// 启动或停止采集
|
||
/// </summary>
|
||
public virtual async Task 设置采集状态(bool 运行, CancellationToken ct = default)
|
||
{
|
||
// 启动、停止或返回采集状态[cite: 2]
|
||
string 参数 = 运行 ? "RUN" : "STOP";
|
||
await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
public virtual async Task<string> 查询采集状态(CancellationToken ct = default)
|
||
{
|
||
// 启动、停止或返回采集状态[cite: 2]
|
||
return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
public virtual async Task 设置采集模式(TekAcquisitionMode 模式, CancellationToken ct = default)
|
||
{
|
||
// 设置或查询采集模式[cite: 2]
|
||
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 3. 水平系统控制 (Horizontal)
|
||
|
||
public virtual async Task 设置水平刻度(double 秒每格, CancellationToken ct = default)
|
||
{
|
||
// 设置或查询水平刻度 (Scale)[cite: 2]
|
||
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SCAle {0:E}{1}", 秒每格, ScpiDelimiter);
|
||
await SendAsync(cmd, ct);
|
||
}
|
||
|
||
public virtual async Task 设置记录长度(long 长度, CancellationToken ct = default)
|
||
{
|
||
// 设置或查询水平记录长度[cite: 2]
|
||
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
public virtual async Task<string> 查询采样率(CancellationToken ct = default)
|
||
{
|
||
// 查询水平采样率[cite: 2]
|
||
return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 4. 通道与显示控制 (Vertical)
|
||
|
||
/// <summary>
|
||
/// 全局启用或关闭某通道的显示
|
||
/// </summary>
|
||
public virtual async Task 设置通道显示开关(int 通道号, bool 开启, CancellationToken ct = default)
|
||
{
|
||
// 4/5/6 Series MSO 标准通道显示指令[cite: 2]
|
||
string 参数 = 开启 ? "ON" : "OFF";
|
||
await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置指定通道的垂直缩放比例 (Volts/Div)
|
||
/// </summary>
|
||
public virtual async Task 设置通道垂直刻度(int 通道号, double 伏特每格, CancellationToken ct = default)
|
||
{
|
||
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", 通道号, 伏特每格, ScpiDelimiter);
|
||
await SendAsync(cmd, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置指定通道的垂直位置 (Divisions)
|
||
/// </summary>
|
||
public virtual async Task 设置通道垂直位置(int 通道号, double 格数, CancellationToken ct = default)
|
||
{
|
||
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", 通道号, 格数, ScpiDelimiter);
|
||
await SendAsync(cmd, ct);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 5. 自动测量 (Measurement)
|
||
|
||
/// <summary>
|
||
/// 动态添加并开启一个测量项
|
||
/// </summary>
|
||
public virtual async Task 开启测量项(int 测量项编号, CancellationToken ct = default)
|
||
{
|
||
// 4/5/6 Series MSO 必须先动态创建测量项[cite: 2]
|
||
await SendAsync($"MEASUrement:ADDNew \"MEAS{测量项编号}\"{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
public virtual async Task 设置测量类型(int 测量项编号, TekMeasurementType 类型, CancellationToken ct = default)
|
||
{
|
||
// 设置或查询测量类型[cite: 2]
|
||
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
public virtual async Task 设置测量源(int 测量项编号, string 源名称, CancellationToken ct = default)
|
||
{
|
||
// 4/5/6 Series 中设置测量源[cite: 2]
|
||
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
[Monitorable("测量项的当前采集平均值")]
|
||
public virtual async Task<string> 查询测量项当前值(int 测量项编号, CancellationToken ct = default)
|
||
{
|
||
// 4/5/6 Series 获取当前采集值的标准方式,查询平均值[cite: 2]
|
||
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
public virtual async Task<string> 查询测量项全局平均值(int 测量项编号, CancellationToken ct = default)
|
||
{
|
||
// 获取所有采集(AllAcqs)历史累积的平均值[cite: 2]
|
||
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
public virtual async Task<string> 查询测量项全局最大值(int 测量项编号, CancellationToken ct = default)
|
||
{
|
||
// 获取所有采集(AllAcqs)历史累积的最大值[cite: 2]
|
||
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 6. 屏幕截图与文件系统 (Hard Copy & File System)
|
||
|
||
/// <summary>
|
||
/// 捕获示波器屏幕并保存到上位机本地路径
|
||
/// </summary>
|
||
/// <param name="上位机文件路径">保存到电脑本地的文件路径 (例如: @"C:\temp\screen.png")</param>
|
||
/// <param name="ct">异步取消令牌</param>
|
||
public virtual async Task 保存屏幕截图(string 上位机文件路径, CancellationToken ct = default)
|
||
{
|
||
string 临时文件 = "E:/temp1.png";
|
||
|
||
// 1. 直接保存截图到示波器U盘 (自动识别 .png 扩展名)[cite: 2]
|
||
await SendAsync($"SAVe:IMAGe \"{临时文件}\"{ScpiDelimiter}", ct);
|
||
|
||
// 2. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘[cite: 2]
|
||
await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
|
||
// 3. 将文件内容读取到当前通信接口[cite: 2]
|
||
await SendAsync($"FILESystem:READFile \"{临时文件}\"{ScpiDelimiter}", ct);
|
||
|
||
// 4. 提取底层的二进制块 (Block Data)
|
||
byte[] imageBytes = await ReadBinaryBlockAsync(ct);
|
||
|
||
// 5. 将提取的二进制字节流写入上位机本地磁盘
|
||
await File.WriteAllBytesAsync(上位机文件路径, imageBytes, ct);
|
||
|
||
// 6. 删除示波器上的临时文件,保持设备存储干净[cite: 2]
|
||
await SendAsync($"FILESystem:DELEte \"{临时文件}\"{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⚠️ 请在这里将您的网络流对接起来!
|
||
/// 依据您 Tcp 基类的设计,返回用于读取底层字节流的 Stream。
|
||
/// </summary>
|
||
protected virtual Stream GetNetworkStream()
|
||
{
|
||
// 【修改提示】:
|
||
// 如果您的基类暴露了 TcpClient,请取消注释并使用这一行:
|
||
// return this.TcpClient.GetStream();
|
||
|
||
// 如果您的基类直接叫 _stream 或 Stream,请返回它:
|
||
// return this.Stream;
|
||
|
||
// 假如不知道怎么填,请查看 Tcp 类的源码。
|
||
throw new NotImplementedException("请在此方法中返回基类的 NetworkStream 或 Socket 流对象。");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 辅助方法:读取 IEEE 488.2 标准的二进制块数据 (Block Data)
|
||
/// </summary>
|
||
protected virtual async Task<byte[]> ReadBinaryBlockAsync(CancellationToken ct = default)
|
||
{
|
||
var stream = GetNetworkStream();
|
||
byte[] singleByte = new byte[1];
|
||
|
||
// 1. 逐字节读取,直到读到起始符 '#'
|
||
while (true)
|
||
{
|
||
int read = await stream.ReadAsync(singleByte, 0, 1, ct);
|
||
if (read == 0) throw new EndOfStreamException("通信中断:未找到二进制块起始符 '#'");
|
||
if (singleByte[0] == (byte)'#') break;
|
||
}
|
||
|
||
// 2. 读取 1 个字符 <NZDig>,它代表接下来有多少个字符表示数据长度
|
||
await stream.ReadAsync(singleByte, 0, 1, ct);
|
||
char nzDigChar = (char)singleByte[0];
|
||
if (!char.IsDigit(nzDigChar)) throw new InvalidDataException($"无效的块头标识: {nzDigChar}");
|
||
int lengthBytesCount = int.Parse(nzDigChar.ToString());
|
||
|
||
// 3. 读取 <NZDig> 所指示的长度字符,将其解析为整数 N
|
||
byte[] lengthBytes = new byte[lengthBytesCount];
|
||
int currentRead = 0;
|
||
while (currentRead < lengthBytesCount)
|
||
{
|
||
int read = await stream.ReadAsync(lengthBytes, currentRead, lengthBytesCount - currentRead, ct);
|
||
if (read == 0) throw new EndOfStreamException("通信中断:读取数据长度失败");
|
||
currentRead += read;
|
||
}
|
||
int dataLength = int.Parse(Encoding.ASCII.GetString(lengthBytes));
|
||
|
||
// 4. 从流中精确读取 N 个字节 <DChar>...,这就是完整的图像数据
|
||
byte[] imageData = new byte[dataLength];
|
||
int totalBytesRead = 0;
|
||
while (totalBytesRead < dataLength)
|
||
{
|
||
int read = await stream.ReadAsync(imageData, totalBytesRead, dataLength - totalBytesRead, ct);
|
||
if (read == 0) throw new EndOfStreamException("通信中断:读取图像数据不完整");
|
||
totalBytesRead += read;
|
||
}
|
||
|
||
// 5. 结尾可能包含 <EOM> (例如 LF \n),将其读取并丢弃以清空缓冲区
|
||
try
|
||
{
|
||
await stream.ReadAsync(singleByte, 0, 1, ct);
|
||
}
|
||
catch { /* 忽略末尾符读取的可能超时 */ }
|
||
|
||
return imageData;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 7. 状态与错误队列 (Status and Error)
|
||
|
||
/// <summary>
|
||
/// 清除所有的状态寄存器和错误/事件队列
|
||
/// </summary>
|
||
public virtual async Task 清除错误队列Async(CancellationToken ct = default)
|
||
{
|
||
// 发送 *CLS 指令清空状态[cite: 2]
|
||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询错误队列中当前的事件/错误数量
|
||
/// </summary>
|
||
public virtual async Task<string> 查询错误数量Async(CancellationToken ct = default)
|
||
{
|
||
// 返回事件队列中的事件数量
|
||
return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出
|
||
/// </summary>
|
||
public virtual async Task<string> 读取单条错误消息Async(CancellationToken ct = default)
|
||
{
|
||
// 返回事件队列中的事件代码和消息
|
||
return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取并清空队列中的所有错误和事件消息
|
||
/// </summary>
|
||
public virtual async Task<string> 读取所有错误消息Async(CancellationToken ct = default)
|
||
{
|
||
// 返回所有事件及其消息
|
||
return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |