Files
ASTM-D7896-19TransientHot-W…/Services/LanScpiSocket.cs
2026-05-28 16:38:40 +08:00

199 lines
7.3 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;
// 默认电压量程(伏特),可根据实际信号修改,例如 0.1, 1, 10, 100, 1000
public double DefaultVoltageRange { get; set; } = 10.0;
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;
}
/// <summary>
/// 发送命令并等待完整响应(支持多行/大数据量)
/// </summary>
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[65536];
int bytesRead;
bool endReached = false;
while (!endReached)
{
bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0) break;
string chunk = Encoding.ASCII.GetString(buffer, 0, bytesRead);
responseBuilder.Append(chunk);
// 如果收到换行符再等50ms确认没有更多数据TCP分包情况
if (chunk.Contains("\n"))
{
await Task.Delay(50);
if (!_stream.DataAvailable)
endReached = true;
}
}
return responseBuilder.ToString().Trim();
}
/// <summary>
/// 发送命令,不等待响应
/// </summary>
public async Task SendCommandAsync(string command)
{
EnsureConnected();
byte[] cmdBytes = Encoding.ASCII.GetBytes(command + "\n");
await _stream.WriteAsync(cmdBytes, 0, cmdBytes.Length);
}
/// <summary>
/// 配置为高速直流电压测量BUS触发模式固定量程0.02PLC,关闭自归零)
/// </summary>
public async Task ConfigureForHighSpeedDcvAsync()
{
// 1. 重置到默认状态(可选)
await SendCommandAsync("*RST");
await Task.Delay(100);
// 2. 固定量程(避免自动量程降低速度)
await SendCommandAsync($"VOLT:DC:RANG {DefaultVoltageRange}");
// 3. 设置积分时间 0.02PLC(最快速度)
await SendCommandAsync("VOLT:DC:NPLC 0.02");
// 4. 关闭自动归零(提高速度)
await SendCommandAsync("VOLT:DC:ZERO:AUTO OFF");
// 5. 触发源设为 BUS软件触发
await SendCommandAsync("TRIG:SOUR BUS");
// 6. 关闭自动延迟延迟设为0
await SendCommandAsync("TRIG:DEL:AUTO OFF");
await SendCommandAsync("TRIG:DEL 0");
}
/// <summary>
/// 预置采样点数并进入等待触发状态(发送 INIT
/// </summary>
public async Task PrepareBatchAsync(int sampleCount)
{
await SendCommandAsync($"SAMP:COUN {sampleCount}");
await SendCommandAsync("INIT"); // 进入等待触发状态
}
/// <summary>
/// 发送软件触发信号(*TRG开始采集
/// </summary>
public async Task TriggerAsync()
{
await SendCommandAsync("*TRG");
}
/// <summary>
/// 获取批量采集结果FETCh?
/// </summary>
public async Task<double[]> FetchBatchAsync()
{
string response = await QueryAsync("FETCh?");
return ParseResponse(response);
}
/// <summary>
/// 一次性批量采集(简化接口,内部自动完成 SAMP:COUN + READ?
/// 注意:此方法会阻塞直到采集完成,适合不需要分离触发时序的场景
/// </summary>
public async Task<double[]> ReadBatchAsync(int sampleCount)
{
await SendCommandAsync($"SAMP:COUN {sampleCount}");
string response = await QueryAsync("READ?");
return ParseResponse(response);
}
/// <summary>
/// 解析逗号分隔的电压值数组
/// </summary>
private double[] ParseResponse(string response)
{
if (string.IsNullOrWhiteSpace(response))
throw new Exception("返回数据为空");
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;
}
/// <summary>
/// 单次读取电压(保留,但不推荐用于高速采集)
/// </summary>
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;
}
}
}
}