Files
ACP/DeviceCommand/Devices/TektronixMSO.cs
2026-08-04 15:10:37 +08:00

349 lines
14 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 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
}
}