设备命令修改

This commit is contained in:
“hsc”
2026-08-04 10:38:13 +08:00
parent ef52530a14
commit 857bd3fb56
2 changed files with 264 additions and 72 deletions

View File

@@ -49,64 +49,102 @@ namespace DeviceCommand.Devices
#endregion
#region 3. LIST
#region 3. LIST ( - )
/// <summary>
/// 配置单相的 LIST 序列参数
/// 传入的数组长度必须保持一致,代表 LIST 的每个 StepSEQ
/// 切换目标相位并设置波形为正弦波
/// 建议在发送具体 LIST 参数前调用一次即可
/// </summary>
/// <param name="phase">相位 (1: L1, 2: L2, 3: L3)</param>
/// <param name="degrees">各步起始角度 (如 [0, 0, 0])</param>
/// <param name="vacStart">各步交流起始电压 (如 [230, 11.5, 230])</param>
/// <param name="vacEnd">各步交流结束电压 (如 [230, 11.5, 230])</param>
/// <param name="vdcStart">各步直流起始电压</param>
/// <param name="vdcEnd">各步直流结束电压</param>
/// <param name="freqStart">各步起始频率</param>
/// <param name="freqEnd">各步结束频率</param>
/// <param name="dwellTimeMs">各步执行时间(单位: 毫秒)</param>
public virtual async Task List序列(
int phase,
double[] degrees,
double[] vacStart, double[] vacEnd,
double[] vdcStart, double[] vdcEnd,
double[] freqStart, double[] freqEnd,
double[] dwellTimeMs,
CancellationToken ct = default)
public virtual async Task Async(int phase, CancellationToken ct = default)
{
if (phase < 1 || phase > 3) throw new ArgumentOutOfRangeException(nameof(phase), "相位必须为 1, 2, 或 3");
if (phase < 1 || phase > 3)
throw new ArgumentOutOfRangeException(nameof(phase), "相位必须为 1, 2, 或 3");
// 选择相位并设置为正弦波
await SendAsync($"INST:NSEL {phase}{ScpiDelimiter}", ct);
await SendAsync($"FUNC:SHAP:A SINE{ScpiDelimiter}", ct);
// 转换数组为 SCPI 需要的逗号分隔字符串
string strDegrees = JoinParameters(degrees);
string strVacStart = JoinParameters(vacStart);
string strVacEnd = JoinParameters(vacEnd);
string strVdcStart = JoinParameters(vdcStart);
string strVdcEnd = JoinParameters(vdcEnd);
string strFreqStart = JoinParameters(freqStart);
string strFreqEnd = JoinParameters(freqEnd);
string strDwell = JoinParameters(dwellTimeMs);
// 下发序列参数
// 注意 Chroma 部分指令是 DEGR部分固件版本是 DEGRee这里统一使用简写 DEGR兼容性最好
await SendAsync($"LIST:DEGR {strDegrees}{ScpiDelimiter}", ct);
await SendAsync($"LIST:VOLT:AC:STAR {strVacStart}{ScpiDelimiter}", ct);
await SendAsync($"LIST:VOLT:AC:END {strVacEnd}{ScpiDelimiter}", ct);
await SendAsync($"LIST:VOLT:DC:STAR {strVdcStart}{ScpiDelimiter}", ct);
await SendAsync($"LIST:VOLT:DC:END {strVdcEnd}{ScpiDelimiter}", ct);
await SendAsync($"LIST:FREQ:STAR {strFreqStart}{ScpiDelimiter}", ct);
await SendAsync($"LIST:FREQ:END {strFreqEnd}{ScpiDelimiter}", ct);
await SendAsync($"LIST:DWEL {strDwell}{ScpiDelimiter}", ct);
}
/// <summary>
/// 辅助方法:将 double 数组转换为逗号分隔的字符串,确保小数点格式正确
/// 配置 LIST 各步起始角度
/// </summary>
private string JoinParameters(double[] values)
public virtual async Task List起始角度Async(double step1, double step2, double step3, CancellationToken ct = default)
{
return string.Join(",", values.Select(v => v.ToString("0.###", CultureInfo.InvariantCulture)));
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:DEGR {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步交流起始电压
/// </summary>
public virtual async Task List交流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:VOLT:AC:STAR {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步交流结束电压
/// </summary>
public virtual async Task List交流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:VOLT:AC:END {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步直流起始电压
/// </summary>
public virtual async Task List直流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:VOLT:DC:STAR {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步直流结束电压
/// </summary>
public virtual async Task List直流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:VOLT:DC:END {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步起始频率
/// </summary>
public virtual async Task List起始频率Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:FREQ:STAR {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步结束频率
/// </summary>
public virtual async Task List结束频率Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:FREQ:END {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 配置 LIST 各步执行时间 (单位: 毫秒)
/// </summary>
public virtual async Task List执行时间Async(double step1, double step2, double step3, CancellationToken ct = default)
{
string strValues = JoinParameters(step1, step2, step3);
await SendAsync($"LIST:DWEL {strValues}{ScpiDelimiter}", ct);
}
/// <summary>
/// 辅助方法:将三个 double 值转换为逗号分隔的字符串,确保小数点格式正确
/// </summary>
private string JoinParameters(double val1, double val2, double val3)
{
return $"{val1.ToString("0.###", CultureInfo.InvariantCulture)}," +
$"{val2.ToString("0.###", CultureInfo.InvariantCulture)}," +
$"{val3.ToString("0.###", CultureInfo.InvariantCulture)}";
}
#endregion

View File

@@ -3,18 +3,20 @@ 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 采集模式
/// Tektronix MSO/MDO 采集模式
/// </summary>
public enum TekAcquisitionMode
{
@@ -31,7 +33,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// Tektronix MSO 测量类型 (部分常用)
/// Tektronix MSO/MDO 测量类型 (部分常用)
/// </summary>
public enum TekMeasurementType
{
@@ -71,7 +73,7 @@ namespace DeviceCommand.Devices
public virtual async Task (CancellationToken ct = default)
{
// 指示仪器执行信号路径校准 (SPC),需要几分钟时间[cite: 1]
// 指示仪器执行信号路径校准 (SPC)[cite: 1]
await SendAsync($"*CAL?{ScpiDelimiter}", ct);
}
@@ -117,25 +119,24 @@ namespace DeviceCommand.Devices
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
}
public virtual async Task (double , CancellationToken ct = default)
public virtual async Task<string> (CancellationToken ct = default)
{
// 设置或查询水平采样率[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SAMPLERate {0:E}{1}", , ScpiDelimiter);
await SendAsync(cmd, ct);
// 查询水平采样率。注:此命令在当前系列设备中为只读[cite: 1]
return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct);
}
#endregion
#region 4. (Display & Vertical)
#region 4. (Vertical)
/// <summary>
/// 全局启用或关闭某通道的显示
/// </summary>
public virtual async Task (int , bool , CancellationToken ct = default)
{
// 设置或查询指定通道的显示模式(开或关)[cite: 1]
// 开启或关闭指定通道的波形显示[cite: 1]
string = ? "ON" : "OFF";
await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct);
await SendAsync($"SELect:CH{通道号} {参数}{ScpiDelimiter}", ct);
}
/// <summary>
@@ -143,8 +144,8 @@ namespace DeviceCommand.Devices
/// </summary>
public virtual async Task (int , double , CancellationToken ct = default)
{
// 设置或查询指定Waveform View内指定通道的垂直缩放比例WaveView<x> 通常为1[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:SCAle {1:E}{2}", , , ScpiDelimiter);
// 设置指定通道的垂直刻度[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", , , ScpiDelimiter);
await SendAsync(cmd, ct);
}
@@ -153,8 +154,8 @@ namespace DeviceCommand.Devices
/// </summary>
public virtual async Task (int , double , CancellationToken ct = default)
{
// 设置或查询指定Waveform View内指定通道的垂直位置(以格为单位)[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:POSition {1:F2}{2}", , , ScpiDelimiter);
// 设置指定通道的通道垂直位置[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", , , ScpiDelimiter);
await SendAsync(cmd, ct);
}
@@ -163,37 +164,190 @@ namespace DeviceCommand.Devices
#region 5. (Measurement)
/// <summary>
/// 新增一个测量项槽位
/// 开启指定的测量项槽位 (1-8)
/// </summary>
public virtual async Task (CancellationToken ct = default)
public virtual async Task (int , CancellationToken ct = default)
{
// 添加一个测量项[cite: 1]
await SendAsync($"MEASUrement:ADDMEAS{ScpiDelimiter}", ct);
// 指定该测量槽位是否计算并在屏幕显示[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:STATE ON{ScpiDelimiter}", ct);
}
public virtual async Task (int , TekMeasurementType , CancellationToken ct = default)
{
// 设置或查询测量类型[cite: 1]
// 设置或查询指定测量槽位的测量类型[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
}
public virtual async Task (int , string , CancellationToken ct = default)
{
// 设置或查询局部输入源 (例如 CH1, CH2)[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOURCE {源名称}{ScpiDelimiter}", ct);
// 设置单通道测量的 "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);
}
[Monitorable("测量项当前采集的平均值")]
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回当前采集的指定测量的平均值[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
// 返回自上次统计重置以来累积的平均值[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{测量项编号}:RESUlts:CURRentacq:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
// 返回自上次统计重置以来找到的最大值[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