示波器修改:visa连接方式+截图保存
This commit is contained in:
@@ -4,7 +4,6 @@ using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -55,10 +54,11 @@ namespace DeviceCommand.Devices
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Tektronix 示波器 (MSO 4/5/6 系列),支持采集控制、通道设置、自动测量与截图导出
|
||||
/// Tektronix 示波器 (MSO 4/5/6 系列),支持采集控制、通道设置、自动测量与截图导出。
|
||||
/// 传输层为 VISA(资源字符串寻址,如 TCPIP0::IP::inst0::INSTR),SCPI 指令集不变。
|
||||
/// </summary>
|
||||
[ACPCommand]
|
||||
public class TektronixMSO : Tcp
|
||||
public class TektronixMSO : Visa
|
||||
{
|
||||
// SCPI 指令结束符 (消息终止符需使用 LF)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
@@ -102,7 +102,7 @@ namespace DeviceCommand.Devices
|
||||
|
||||
#endregion
|
||||
|
||||
public TektronixMSO(TcpConfig config) : base(config)
|
||||
public TektronixMSO(VisaConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -327,103 +327,36 @@ namespace DeviceCommand.Devices
|
||||
#region 6. 屏幕截图与文件系统 (Hard Copy & File System)
|
||||
|
||||
/// <summary>
|
||||
/// 捕获示波器屏幕并保存到上位机本地路径
|
||||
/// 捕获示波器当前屏幕并保存为 PNG 到上位机本地路径 (SAVe:IMAGe / FILESystem:READFile 指令)。
|
||||
/// <para>执行流程:① SAVe:IMAGe 将屏幕截图写入示波器本地临时文件;② *OPC? 阻塞等待写盘完成;
|
||||
/// ③ FILESystem:READFile 以 IEEE 488.2 定长块(#9<长度><数据>)回传文件,基类自动剥离块头后返回纯 PNG 字节;
|
||||
/// ④ 写入上位机本地磁盘;⑤ 删除示波器临时文件,保持设备存储干净。</para>
|
||||
/// </summary>
|
||||
/// <param name="上位机文件路径">保存到电脑本地的文件路径 (例如: @"C:\temp\screen.png")</param>
|
||||
/// <param name="ct">异步取消令牌</param>
|
||||
public virtual async Task 保存屏幕截图(string 上位机文件路径, CancellationToken ct = default)
|
||||
{
|
||||
string 临时文件 = "E:/temp1.png";
|
||||
// 示波器端临时文件:截图先落到设备本地,再回传上位机
|
||||
const string 临时文件 = "E:/Temp.png";
|
||||
|
||||
// 1. 直接保存截图到示波器U盘 (自动识别 .png 扩展名)
|
||||
// 1. 命令示波器把当前屏幕保存为 PNG 到设备本地临时文件
|
||||
await SendAsync($"SAVe:IMAGe \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||
|
||||
// 2. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘
|
||||
// 2. 发送 *OPC? 并等待返回,确保图像文件已完全写入磁盘
|
||||
await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
// 3. 将文件内容读取到当前通信接口
|
||||
await SendAsync($"FILESystem:READFile \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||
// 3. 读取示波器临时文件回传的 IEEE 488.2 定长块(基类自动剥离 #9 块头,返回纯 PNG 字节)
|
||||
byte[] imageBytes = await ReadAllBytesAsync($"FILESystem:READFile \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||
|
||||
// 4. 提取底层的二进制块 (Block Data)
|
||||
byte[] imageBytes = await ReadBinaryBlockAsync(ct);
|
||||
|
||||
// 5. 将提取的二进制字节流写入上位机本地磁盘
|
||||
// 4. 确保本地目录存在后,将二进制字节流写入上位机磁盘
|
||||
string? 目录 = Path.GetDirectoryName(上位机文件路径);
|
||||
if (!string.IsNullOrEmpty(目录)) Directory.CreateDirectory(目录);
|
||||
await File.WriteAllBytesAsync(上位机文件路径, imageBytes, ct);
|
||||
|
||||
// 6. 删除示波器上的临时文件,保持设备存储干净
|
||||
// 5. 删除示波器上的临时文件,保持设备存储干净
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user