Files
ASTM-D7896-19TransientHot-W…/Services/LanScpiSocket.cs
2026-05-20 19:46:52 +08:00

178 lines
7.0 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 System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ASTM_D7896_Tester.Services
{
/// <summary>
/// 基于 LAN (TCP/IP) 的 TH1963 数字多用表通信服务
/// 支持 SCPI 命令,实现高速批量采集
/// </summary>
public class Th1963LanService : IDisposable
{
private TcpClient _tcpClient;
private NetworkStream _stream;
private bool _disposed = false;
public int ReceiveBufferSize { get; set; } = 2 * 1024 * 1024;
public int ConnectTimeoutMs { get; set; } = 3000;
public int ReadWriteTimeoutMs { get; set; } = 5000;
public async Task ConnectAsync(string ipAddress, int port = 45454)
{
if (_tcpClient != null && _tcpClient.Connected)
return;
_tcpClient = new TcpClient();
_tcpClient.ReceiveBufferSize = ReceiveBufferSize;
_tcpClient.SendBufferSize = ReceiveBufferSize;
var connectTask = _tcpClient.ConnectAsync(ipAddress, port);
if (await Task.WhenAny(connectTask, Task.Delay(ConnectTimeoutMs)) != connectTask)
throw new TimeoutException($"连接 TH1963 ({ipAddress}:{port}) 超时");
_stream = _tcpClient.GetStream();
_stream.ReadTimeout = ReadWriteTimeoutMs;
_stream.WriteTimeout = ReadWriteTimeoutMs;
}
public async Task<string> QueryAsync(string command)
{
EnsureConnected();
byte[] cmdBytes = Encoding.ASCII.GetBytes(command + "\n");
await _stream.WriteAsync(cmdBytes, 0, cmdBytes.Length);
var responseBuilder = new StringBuilder();
byte[] buffer = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
string chunk = Encoding.ASCII.GetString(buffer, 0, bytesRead);
responseBuilder.Append(chunk);
if (chunk.Contains("\n")) break;
}
return responseBuilder.ToString().Trim();
}
public async Task SendCommandAsync(string command)
{
EnsureConnected();
byte[] cmdBytes = Encoding.ASCII.GetBytes(command + "\n");
await _stream.WriteAsync(cmdBytes, 0, cmdBytes.Length);
}
/// <summary>
/// 配置为高速直流电压测量(外部触发模式)
/// </summary>
public async Task ConfigureForHighSpeedDcvAsync()
{
await SendCommandAsync("CONF:VOLT:DC AUTO");
await SendCommandAsync("VOLT:DC:NPLC 0.02");
await SendCommandAsync("VOLT:DC:ZERO:AUTO OFF");
await SendCommandAsync("TRIG:SOUR EXT");
await SendCommandAsync("TRIG:DEL:AUTO OFF");
await SendCommandAsync("TRIG:DEL 0");
}
/// <summary>
/// 预置采样点数并进入等待触发状态
/// </summary>
public async Task PrepareBatchAsync(int sampleCount)
{
await SendCommandAsync($"SAMP:COUN {sampleCount}");
await SendCommandAsync("INIT"); // 等待外部触发
}
/// <summary>
/// 发送触发信号(软件触发,用于测试,但外部触发模式不使用)
/// 此方法仅用于 BUS 触发模式,当前配置为 EXT 触发,实际触发由硬件信号完成。
/// 保留此方法以备将来切换触发模式。
/// </summary>
public async Task TriggerAsync()
{
// 当前为外部触发模式,不需要软件触发;若需软件触发,请先执行 TRIG:SOUR BUS
// 保留空实现或抛出 NotSupportedException根据需求选择
// 为避免编译错误,提供一个空实现
await Task.CompletedTask;
}
/// <summary>
/// 批量采集(假设已经配置并等待外部触发完成)
/// </summary>
public async Task<double[]> AcquireBatchAsync(int sampleCount)
{
await SendCommandAsync($"SAMP:COUN {sampleCount}");
await SendCommandAsync("INIT");
int waitMs = (int)(sampleCount / 1000.0 * 1000) + 200;
await Task.Delay(waitMs);
string response = await QueryAsync("FETCh?");
return ParseResponse(response, sampleCount);
}
/// <summary>
/// 获取批量采集结果(在 PrepareBatchAsync 和外部触发之后调用)
/// </summary>
public async Task<double[]> FetchBatchAsync()
{
string response = await QueryAsync("FETCh?");
// 注意:此时不知道预期点数,但可以从返回数组长度得知
string[] parts = response.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
double[] values = new double[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
if (!double.TryParse(parts[i], System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out values[i]))
throw new Exception($"解析电压值失败: {parts[i]}");
}
return values;
}
private double[] ParseResponse(string response, int expectedCount)
{
string[] parts = response.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
double[] values = new double[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
if (!double.TryParse(parts[i], System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out values[i]))
throw new Exception($"解析失败: {parts[i]}");
}
if (values.Length != expectedCount)
throw new Exception($"期望 {expectedCount} 个点,实际收到 {values.Length}");
return values;
}
public async Task<double> ReadVoltageAsync()
{
string resp = await QueryAsync("MEAS:VOLT:DC?");
if (double.TryParse(resp, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out double value))
return value;
throw new Exception($"电压读数无效: {resp}");
}
public async Task ResetAsync() => await SendCommandAsync("*RST");
public async Task<string> IdentifyAsync() => await QueryAsync("*IDN?");
private void EnsureConnected()
{
if (_tcpClient == null || !_tcpClient.Connected)
throw new InvalidOperationException("未连接到 TH1963请先调用 ConnectAsync");
}
public void Dispose()
{
if (!_disposed)
{
_stream?.Close();
_tcpClient?.Close();
_stream?.Dispose();
_tcpClient?.Dispose();
_disposed = true;
}
}
}
}