This commit is contained in:
178
Services/LanScpiSocket.cs
Normal file
178
Services/LanScpiSocket.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 接收缓冲区大小(字节),建议至少 1MB 以支持大量数据
|
||||
/// </summary>
|
||||
public int ReceiveBufferSize { get; set; } = 2 * 1024 * 1024; // 2 MB
|
||||
|
||||
/// <summary>
|
||||
/// 连接超时(毫秒)
|
||||
/// </summary>
|
||||
public int ConnectTimeoutMs { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 读写超时(毫秒)
|
||||
/// </summary>
|
||||
public int ReadWriteTimeoutMs { get; set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 连接到 TH1963
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">仪器 IP 地址,如 "192.168.1.241"</param>
|
||||
/// <param name="port">端口号,默认 45454</param>
|
||||
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>
|
||||
/// 发送 SCPI 命令并等待响应(适用于查询类命令)
|
||||
/// </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[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送命令但不等待响应(适用于设置类命令)
|
||||
/// </summary>
|
||||
public async Task SendCommandAsync(string command)
|
||||
{
|
||||
EnsureConnected();
|
||||
byte[] cmdBytes = Encoding.ASCII.GetBytes(command + "\n");
|
||||
await _stream.WriteAsync(cmdBytes, 0, cmdBytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置为高速直流电压测量(用于批量采集)
|
||||
/// 设置:0.02 PLC(≈400μs积分时间),自动量程,关闭自动归零
|
||||
/// </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"); // 外部触发(与PLC同步)
|
||||
await SendCommandAsync("TRIG:DEL:AUTO OFF");
|
||||
await SendCommandAsync("TRIG:DEL 0");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量采集指定数量的电压值(使用外部触发,需等待硬件触发信号)
|
||||
/// 调用前必须已执行 ConfigureForHighSpeedDcvAsync 和 SAMP:COUN
|
||||
/// </summary>
|
||||
/// <param name="sampleCount">采样点数(最大 5000)</param>
|
||||
/// <returns>电压数组(单位:伏特)</returns>
|
||||
public async Task<double[]> AcquireBatchAsync(int sampleCount)
|
||||
{
|
||||
// 预置采样点数
|
||||
await SendCommandAsync($"SAMP:COUN {sampleCount}");
|
||||
// 进入等待触发状态
|
||||
await SendCommandAsync("INIT");
|
||||
|
||||
// 等待采集完成(估算:sampleCount / 1000 秒 + 稳定时间)
|
||||
int waitMs = (int)(sampleCount / 1000.0 * 1000) + 200;
|
||||
await Task.Delay(waitMs);
|
||||
|
||||
// 取回数据
|
||||
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]}");
|
||||
}
|
||||
if (values.Length != sampleCount)
|
||||
throw new Exception($"期望 {sampleCount} 个点,实际收到 {values.Length}");
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位仪器
|
||||
/// </summary>
|
||||
public async Task ResetAsync() => await SendCommandAsync("*RST");
|
||||
|
||||
/// <summary>
|
||||
/// 查询仪器标识
|
||||
/// </summary>
|
||||
public async Task<string> IdentifyAsync() => await QueryAsync("*IDN?");
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (_tcpClient == null || !_tcpClient.Connected)
|
||||
throw new InvalidOperationException("未连接到 TH1963,请先调用 ConnectAsync");
|
||||
}
|
||||
|
||||
// 实现 IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_stream?.Close();
|
||||
_tcpClient?.Close();
|
||||
_stream?.Dispose();
|
||||
_tcpClient?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,10 +48,12 @@ public class PlcService : IPlcService
|
||||
// 现在可以直接使用扩展方法
|
||||
await _tcpClient.ConnectAsync(_config.PlcConnection.IpAddress, _config.PlcConnection.Port).WithCancellation(cts.Token);
|
||||
_master = ModbusIpMaster.CreateIp(_tcpClient);
|
||||
await ConnectAsync();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) when (i < retryCount - 1)
|
||||
{
|
||||
await DisconnectAsync();
|
||||
System.Diagnostics.Debug.WriteLine($"连接失败,{500}ms 后重试... {ex.Message}");
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user