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

355 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>
/// 示波器 MDO/MSO/DPO 系列
/// </summary>
#region
/// <summary>
/// Tektronix MSO/MDO 采集模式
/// </summary>
public enum TekAcquisitionMode
{
/// <summary> 采样模式 </summary>
SAMple,
/// <summary> 峰值检测模式 </summary>
PEAKdetect,
/// <summary> 高分辨率模式 </summary>
HIRes,
/// <summary> 平均模式 </summary>
AVErage,
/// <summary> 包络模式 </summary>
ENVelope
}
/// <summary>
/// Tektronix MSO/MDO 测量类型 (部分常用)
/// </summary>
public enum TekMeasurementType
{
AMPLitude,
FREQuency,
MEAN,
PK2PK,
MAXimum,
MINimum
}
#endregion
[ACPCommand]
public class TektronixMSO : Tcp
{
// SCPI 指令结束符 (文档中提到消息终止符需使用 LF)[cite: 1]
private const string ScpiDelimiter = "\n";
public TektronixMSO(TcpConfig config) : base(config)
{
}
#region 1.
public virtual async Task<string> (CancellationToken ct = default)
{
// 返回仪器的标识代码[cite: 1]
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 1]
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task (CancellationToken ct = default)
{
// 指示仪器执行信号路径校准 (SPC)[cite: 1]
await SendAsync($"*CAL?{ScpiDelimiter}", ct);
}
#endregion
#region 2. (Acquisition)
/// <summary>
/// 启动或停止采集
/// </summary>
public virtual async Task (bool , CancellationToken ct = default)
{
// 启动、停止或返回采集状态[cite: 1]
string = ? "RUN" : "STOP";
await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task (TekAcquisitionMode , CancellationToken ct = default)
{
// 设置或查询采集模式[cite: 1]
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
}
#endregion
#region 3. (Horizontal)
public virtual async Task (double , CancellationToken ct = default)
{
// 设置或查询水平刻度 (Scale)[cite: 1]
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: 1]
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
// 查询水平采样率。注:此命令在当前系列设备中为只读[cite: 1]
return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct);
}
#endregion
#region 4. (Vertical)
/// <summary>
/// 全局启用或关闭某通道的显示
/// </summary>
public virtual async Task (int , bool , CancellationToken ct = default)
{
// 开启或关闭指定通道的波形显示[cite: 1]
string = ? "ON" : "OFF";
await SendAsync($"SELect:CH{通道号} {参数}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置指定通道的垂直缩放比例 (Volts/Div)
/// </summary>
public virtual async Task (int , double , CancellationToken ct = default)
{
// 设置指定通道的垂直刻度[cite: 1]
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)
{
// 设置指定通道的通道垂直位置[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", , , ScpiDelimiter);
await SendAsync(cmd, ct);
}
#endregion
#region 5. (Measurement)
/// <summary>
/// 开启指定的测量项槽位 (1-8)
/// </summary>
public virtual async Task (int , CancellationToken ct = default)
{
// 指定该测量槽位是否计算并在屏幕显示[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:STATE ON{ScpiDelimiter}", ct);
}
public virtual async Task (int , TekMeasurementType , CancellationToken ct = default)
{
// 设置或查询指定测量槽位的测量类型[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
}
public virtual async Task (int , string , CancellationToken ct = default)
{
// 设置单通道测量的 "from" 源信号[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct);
}
[Monitorable("测量项的当前值")]
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回指定的测量项当前值[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:VALue?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回自上次统计重置以来累积的平均值[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回自上次统计重置以来找到的最大值[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}: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)
{
// 根据之前的排查,您的设备使用 E: 作为 U 盘挂载点
string = "E:/temp1.png";
// 1. 设置保存图像的格式为 PNG[cite: 1]
await SendAsync($"SAVE:IMAGE:FILEFormat PNG{ScpiDelimiter}", ct);
// 2. 捕获屏幕并保存到示波器的 U 盘中[cite: 1]
await SendAsync($"SAVE:IMAGE \"{}\"{ScpiDelimiter}", ct);
// 3. 发送 *OPC? 并等待返回 1确保图像文件已经完全写入磁盘[cite: 1]
await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
// 4. 将文件内容读取到当前通信接口[cite: 1]
await SendAsync($"FILESystem:READFile \"{}\"{ScpiDelimiter}", ct);
// 5. 提取底层的二进制块 (Block Data)[cite: 1]
byte[] imageBytes = await ReadBinaryBlockAsync(ct);
// 6. 将提取的二进制字节流写入上位机本地磁盘
await File.WriteAllBytesAsync(, imageBytes, ct);
// 7. 删除示波器上的临时文件,保持设备存储干净[cite: 1]
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)
/// 手册规范:<Block> ::= {#<NZDig><Dig>[<Dig>...][<DChar>...]}[cite: 1]
/// </summary>
protected virtual async Task<byte[]> ReadBinaryBlockAsync(CancellationToken ct = default)
{
var stream = GetNetworkStream();
byte[] singleByte = new byte[1];
// 1. 逐字节读取,直到读到起始符 '#'[cite: 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>,它代表接下来有多少个字符表示数据长度[cite: 1]
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[cite: 1]
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>...,这就是完整的图像数据[cite: 1]
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),将其读取并丢弃以清空缓冲区[cite: 1]
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: 1]
await SendAsync($"*CLS{ScpiDelimiter}", ct);
}
/// <summary>
/// 查询错误队列中当前的事件/错误数量
/// </summary>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回事件队列中的事件数量[cite: 1]
return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出
/// </summary>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回事件队列中的事件代码和消息[cite: 1]
return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 读取并清空队列中的所有错误和事件消息
/// </summary>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回所有事件及其消息[cite: 1]
return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct);
}
#endregion
}
}