178 lines
7.0 KiB
C#
178 lines
7.0 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
} |