添加项目文件。
This commit is contained in:
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface IBaseInterface
|
||||
{
|
||||
public bool IsConnected { get; }
|
||||
public Task<bool> ConnectAsync(CancellationToken ct = default);
|
||||
public void Close();
|
||||
}
|
||||
}
|
||||
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using NModbus;
|
||||
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface IModbusDevice : IBaseInterface,IDisposable
|
||||
{
|
||||
IModbusMaster Modbus { get; }
|
||||
|
||||
Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default);
|
||||
Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default);
|
||||
Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default);
|
||||
Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
13
DeviceCommand/Base/ISerialPort.cs
Normal file
13
DeviceCommand/Base/ISerialPort.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface ISerialPort : IBaseInterface,IDisposable
|
||||
{
|
||||
Task SendAsync(string data, CancellationToken ct = default);
|
||||
Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
15
DeviceCommand/Base/ITcp.cs
Normal file
15
DeviceCommand/Base/ITcp.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public interface ITcp : IBaseInterface,IDisposable
|
||||
{
|
||||
Task SendAsync(byte[] buffer, CancellationToken ct = default);
|
||||
Task SendAsync(string str, CancellationToken ct = default);
|
||||
Task<byte[]> ReadAsync(int length, CancellationToken ct = default);
|
||||
Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using NModbus;
|
||||
using NModbus.Serial;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class ModbusRtu : IModbusDevice
|
||||
{
|
||||
public string PortName { get; private set; } = "COM1";
|
||||
public int BaudRate { get; private set; } = 9600;
|
||||
public int DataBits { get; private set; } = 8;
|
||||
public StopBits StopBits { get; private set; } = StopBits.One;
|
||||
public Parity Parity { get; private set; } = Parity.None;
|
||||
public int ReadTimeout { get; private set; } = 3000;
|
||||
public int WriteTimeout { get; private set; } = 3000;
|
||||
|
||||
private SerialPort _serialPort;
|
||||
public IModbusMaster Modbus { get; private set; }
|
||||
public bool IsConnected => _serialPort?.IsOpen ?? false;
|
||||
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public ModbusRtu()
|
||||
{
|
||||
_serialPort = new SerialPort();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
||||
{
|
||||
PortName = portName;
|
||||
BaudRate = baudRate;
|
||||
DataBits = dataBits;
|
||||
StopBits = stopBits;
|
||||
Parity = parity;
|
||||
ReadTimeout = readTimeout;
|
||||
WriteTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
|
||||
_serialPort.PortName = PortName;
|
||||
_serialPort.BaudRate = BaudRate;
|
||||
_serialPort.DataBits = DataBits;
|
||||
_serialPort.StopBits = StopBits;
|
||||
_serialPort.Parity = Parity;
|
||||
_serialPort.ReadTimeout = ReadTimeout;
|
||||
_serialPort.WriteTimeout = WriteTimeout;
|
||||
_serialPort.Open();
|
||||
|
||||
Modbus = new ModbusFactory().CreateRtuMaster(_serialPort);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort.IsOpen)
|
||||
_serialPort.Close();
|
||||
}
|
||||
|
||||
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_serialPort?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
158
DeviceCommand/Base/ModbusTcp.cs
Normal file
158
DeviceCommand/Base/ModbusTcp.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class ModbusTcp : IModbusDevice
|
||||
{
|
||||
public string IPAddress { get; private set; } = "127.0.0.1";
|
||||
public int Port { get; private set; } = 502;
|
||||
public int SendTimeout { get; private set; } = 3000;
|
||||
public int ReceiveTimeout { get; private set; } = 3000;
|
||||
|
||||
private TcpClient _tcpClient;
|
||||
public IModbusMaster Modbus { get; private set; }
|
||||
public bool IsConnected => _tcpClient?.Connected ?? false;
|
||||
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public ModbusTcp()
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
Port = port;
|
||||
SendTimeout = sendTimeout;
|
||||
ReceiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_tcpClient.Connected)
|
||||
{
|
||||
var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!;
|
||||
if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port)
|
||||
return true;
|
||||
}
|
||||
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||
Modbus = new ModbusFactory().CreateMaster(_tcpClient);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient.Connected) _tcpClient.Close();
|
||||
}
|
||||
|
||||
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
// 修复:FromMinutes 改为 FromMilliseconds
|
||||
await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
156
DeviceCommand/Base/Serial_Port.cs
Normal file
156
DeviceCommand/Base/Serial_Port.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Serial_Port : ISerialPort
|
||||
{
|
||||
public string PortName { get; set; } = "COM1";
|
||||
public int BaudRate { get; set; } = 9600;
|
||||
public int DataBits { get; set; } = 8;
|
||||
public StopBits StopBits { get; set; } = StopBits.One;
|
||||
public Parity Parity { get; set; } = Parity.None;
|
||||
public int ReadTimeout { get; set; } = 3000;
|
||||
public int WriteTimeout { get; set; } = 3000;
|
||||
|
||||
private SerialPort _serialPort;
|
||||
public bool IsConnected => _serialPort?.IsOpen ?? false;
|
||||
protected readonly SemaphoreSlim commLock = new(1, 1);
|
||||
|
||||
public Serial_Port()
|
||||
{
|
||||
_serialPort = new SerialPort();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
||||
{
|
||||
PortName = portName;
|
||||
BaudRate = baudRate;
|
||||
DataBits = dataBits;
|
||||
StopBits = stopBits;
|
||||
Parity = parity;
|
||||
ReadTimeout = readTimeout;
|
||||
WriteTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
|
||||
_serialPort.PortName = PortName;
|
||||
_serialPort.BaudRate = BaudRate;
|
||||
_serialPort.DataBits = DataBits;
|
||||
_serialPort.StopBits = StopBits;
|
||||
_serialPort.Parity = Parity;
|
||||
_serialPort.ReadTimeout = ReadTimeout;
|
||||
_serialPort.WriteTimeout = WriteTimeout;
|
||||
|
||||
_serialPort.Open();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
}
|
||||
|
||||
// 内部无锁发送方法,供原子组合操作调用
|
||||
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (WriteTimeout > 0) cts.CancelAfter(WriteTimeout);
|
||||
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
||||
}
|
||||
|
||||
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(data, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 内部无锁读取方法,利用 BaseStream 挂起线程,高性能不吃 CPU
|
||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReadTimeout > 0) cts.CancelAfter(ReadTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
// 核心优化:利用流异步挂起,替代原先的 BytesToRead 循环延时
|
||||
int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
if (bytesRead == 0) continue;
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
}
|
||||
throw new TimeoutException("读取数据超时");
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 核心优化:保证多线程环境下发送和等待回包是一个原子过程
|
||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(command, ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_serialPort?.Dispose();
|
||||
commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
179
DeviceCommand/Base/TCP.cs
Normal file
179
DeviceCommand/Base/TCP.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Tcp : ITcp
|
||||
{
|
||||
public string IPAddress { get; set; } = "127.0.0.1";
|
||||
public int Port { get; set; } = 502;
|
||||
public int SendTimeout { get; set; } = 3000;
|
||||
public int ReceiveTimeout { get; set; } = 3000;
|
||||
|
||||
private TcpClient _tcpClient;
|
||||
public bool IsConnected => _tcpClient?.Connected ?? false;
|
||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
public Tcp()
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
}
|
||||
|
||||
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
Port = port;
|
||||
SendTimeout = sendTimeout;
|
||||
ReceiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_tcpClient.Connected) return true;
|
||||
|
||||
// 修复:释放并彻底清空旧的连接实例,否则复用引发异常
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient.Connected) _tcpClient.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (SendTimeout > 0) cts.CancelAfter(SendTimeout);
|
||||
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
}
|
||||
|
||||
public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(buffer, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(string str, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync(Encoding.UTF8.GetBytes(str), ct);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadAsync(int length, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
byte[] buffer = new byte[length];
|
||||
int offset = 0;
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||
|
||||
while (offset < length)
|
||||
{
|
||||
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
||||
if (read == 0) break;
|
||||
offset += read;
|
||||
}
|
||||
|
||||
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
|
||||
throw new TimeoutException("读取数据超时");
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// 核心优化:确保发送与读取在同一组锁生命周期内
|
||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(Encoding.UTF8.GetBytes(command), ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DeviceCommand/DeviceCommand.csproj
Normal file
18
DeviceCommand/DeviceCommand.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NModbus" Version="3.0.83" />
|
||||
<PackageReference Include="NModbus.Serial" Version="3.0.83" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
249
DeviceCommand/Devices/IT7800E.cs
Normal file
249
DeviceCommand/Devices/IT7800E.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class IT7800E : Tcp
|
||||
{
|
||||
// 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 IT7800E 交直流电源通信参数
|
||||
/// </summary>
|
||||
public IT7800E(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电源识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电源恢复到出厂默认预定义安全值
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 耦合模式与拉载开关控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源输出源的电气模式/工作耦合模式
|
||||
/// (AC: 纯交流, DC: 纯直流, ACDC: 交直流混合模式)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电源模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
string modeUpper = 模式.ToUpper();
|
||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
||||
throw new ArgumentException("工作模式只能为 AC, DC, 或 ACDC");
|
||||
|
||||
await SendAsync($":SOURce:MODE {modeUpper}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前的工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SOURce:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制电源的主输出开关 (True: 开启输出, False: 关闭输出)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 注意:虽然方法名叫设置DC输出,但对交直流电源它代表通用的主输出控制(Main Output)
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前主输出开关状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
||||
|
||||
/// <summary>
|
||||
/// 设定交流(AC)模式下的电压有效值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:AC {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定直流(DC)模式或交直流(AC+DC)模式下的直流偏置电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定电源交流输出的频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置硬件交流输出的有效值限流门限 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的电压有效值(RMS,支持 AC 或 DC 模式下的回测)(单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的电流有效值(RMS)(单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的视在功率 (单位: VA)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询通道实时测得的输出频率 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询当前的功率因数 (PF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:PF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. 保护参数设置与系统监控
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源的过流保护(OCP)值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源的过压保护(OVP)值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection {0:F2}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取系统的出错记录信息(获取上一条未处理的错误码)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换仪器至计算机控制的远程控制模式 (锁定前面板按键)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换仪器至前面板操作的本地模式 (前面板解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除仪器当前的保护状态(例如触发 OCP/OVP 被锁死后进行软件解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
382
DeviceCommand/Devices/N36200.cs
Normal file
382
DeviceCommand/Devices/N36200.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N36200 : Tcp
|
||||
{
|
||||
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 N36200/N36300 设备通信参数
|
||||
/// </summary>
|
||||
public N36200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1. 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4. 读取直流电源相关信息(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.7. 恢复出厂设置 (注意:设备重置保存数据大约需要 10 秒)
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.8. 读取状态字节寄存器(只读寄存器,读取时不会清除位)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 设定输出电压与限流值
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 设定输出电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 查询输出电压设定值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 设置输出限流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 查询输出限流值设定值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.3. 设置输出模拟内阻值 (单位: mΩ)
|
||||
/// </summary>
|
||||
public virtual async Task 设置内阻(double 内阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:INTErnalres {0:F1}{1}", 内阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.4. 保存当前测试参数到指定存储组 (范围: 1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 保存测试参数(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
|
||||
await SendAsync($"SOURce:FUNCtion:SAVe {组别}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.5. 调用指定存储组的测试参数 (范围: 1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 调用测试参数(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
|
||||
await SendAsync($"SOURce:FUNCtion:RECAll {组别}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.3. 输出控制及状态
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 控制电源输出开关 (True: 开启, False: 关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:ONOFF {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 查询电源输出开关状态 (返回 "ON" 或 "OFF")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出开关状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:ONOFF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 设定电源工作模式
|
||||
/// (NORMal: 普通模式, CHARge: 电池充电, SEQuence: 序列模式, CPOWer: 恒功率模式, CARWave: 汽车测试, APG: 外部编程)
|
||||
/// </summary>
|
||||
public virtual async Task 设置运行模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:MODE {模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 查询电源当前运行工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询运行模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.11. 设置电源输出 CV/CC 优先权 (CV 或 CC)
|
||||
/// </summary>
|
||||
public virtual async Task 设置CVCC优先权(string 优先模式, CancellationToken ct = default)
|
||||
{
|
||||
if (优先模式 != "CV" && 优先模式 != "CC") throw new ArgumentException("优先模式只能为 'CV' 或 'CC'");
|
||||
await SendAsync($"OUTPut:PRIority {优先模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.12. 获取电源状态字(通过解析返回整数的二进制 Bit 位获取全状态环路及告警)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备状态字(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 获取电源事件告警状态值 (返回整数通过位定义标识 UVP/OVP/OCP/OPP/OTP)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询事件告警状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 清除当前的告警状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:EVENT 0{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.16. 打开/关闭设备定时关机功能
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时关机开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:TIMing:SWITch {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.17. 设定设备定时关机倒计时时间 (单位: s)
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时关机时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTPut:TIMing:DWELI {0:F1}{1}", 秒数, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.18. 控制泄放电路(Bleeder)开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置泄放电路开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:DISRes {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 读取输出电压电流及功率值 (实时轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.4. 回读电池充电模式下当前累计已充入的容量值 (单位: mAh)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询已充电容量MAH(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:MAH?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.5. 获取当前电源的硬件额定电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.6. 获取当前电源的硬件额定电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护功能
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1. 设置欠压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过欠压保护_UVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:LESS:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4. 设置过压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:OVER:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.6. 设置硬件硬件过流保护硬指标参数 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.8. 设置硬件过功率保护门限值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.12. 设置可调节的用户软输出电压下限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit:LOW {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.13. 设置可调节的用户软输出电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.14. 设置可调节的用户软输出电流下限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流下限(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit:LOW {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.15. 设置可调节的用户软输出电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流上限(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 恒功率模式配置 (CPOWer)
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1. 设定恒功率工作模式下的限定电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2. 设定恒功率工作模式下的限定电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3. 设定恒功率工作模式下的运行功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
459
DeviceCommand/Devices/N36600.cs
Normal file
459
DeviceCommand/Devices/N36600.cs
Normal file
@@ -0,0 +1,459 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N36600 : Tcp
|
||||
{
|
||||
private CancellationTokenSource? _heartbeatCts;
|
||||
private Task? _heartbeatTask;
|
||||
private const int HeartbeatInterval = 3000; // 心跳间隔 3 秒
|
||||
|
||||
public bool IsActive { get; private set; } = false;
|
||||
public int ReConnectionAttempts { get; private set; } = 0;
|
||||
public const int MaxReconnectAttempts = 10;
|
||||
|
||||
// 手册第 4 页明确规定:每条命令后面都要加结束符 0x0A (\n)
|
||||
private const string SCPIDelimiter = "\n";
|
||||
|
||||
public N36600(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立 TCP 连接,成功后自动激活心跳
|
||||
/// </summary>
|
||||
public new async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
bool isConnected = await base.ConnectAsync(ct);
|
||||
if (isConnected)
|
||||
{
|
||||
IsActive = true;
|
||||
ReConnectionAttempts = 0;
|
||||
StartHeartbeat();
|
||||
}
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
StopHeartbeat();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
#region 心跳与断线重连逻辑
|
||||
|
||||
[Browsable(false)]
|
||||
public void StartHeartbeat()
|
||||
{
|
||||
if (_heartbeatTask != null && !_heartbeatTask.IsCompleted)
|
||||
return;
|
||||
|
||||
_heartbeatCts?.Cancel();
|
||||
_heartbeatCts?.Dispose();
|
||||
_heartbeatCts = new CancellationTokenSource();
|
||||
|
||||
_heartbeatTask = Task.Run(() => HeartbeatLoop(_heartbeatCts.Token));
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public void StopHeartbeat()
|
||||
{
|
||||
IsActive = false;
|
||||
if (_heartbeatCts != null && !_heartbeatCts.IsCancellationRequested)
|
||||
{
|
||||
_heartbeatCts.Cancel();
|
||||
}
|
||||
_heartbeatTask = null;
|
||||
}
|
||||
|
||||
private async Task HeartbeatLoop(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(HeartbeatInterval, ct);
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
// 使用公共查询命令发送心跳,确保通道连接正常,且不破坏远程或本地锁定状态
|
||||
await SendAsync($"*IDN?{SCPIDelimiter}", ct);
|
||||
IsActive = true;
|
||||
ReConnectionAttempts = 0;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
IsActive = false;
|
||||
ReConnectionAttempts++;
|
||||
|
||||
if (ReConnectionAttempts > MaxReconnectAttempts)
|
||||
{
|
||||
StopHeartbeat();
|
||||
base.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
await ReconnectDeviceAsync(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReconnectDeviceAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync(ct);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 静默处理,等待下一轮心跳重试
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.1 IEEE 488.2 公用命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1 读取电源的相关信息(制造商, 产品标号, 产品序列号, 软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.2 从指定的单位中恢复 *SAV 命令保存的设定值 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*RCL {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.3 将仪器当前系统状态保存到非易失性内存中 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*SAV {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4 远程控制触发一次
|
||||
/// </summary>
|
||||
public virtual async Task 远程触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*TRG{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.5 返回状态、采集电压、采集电流 (格式: 状态码,电压值,电流值)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询全部状态及采样(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*ALL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2 APPLy 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 设置输出电压和输出电流值
|
||||
/// </summary>
|
||||
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":APPLY {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 查询当前设定的输出电压和电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":APPLY?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5 MEASure 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.5 查询通道输出端子上测得的电池容量
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询测得电池容量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CAPAcity?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.6 查询通道输出端子上测得的恒流输出时间长度 (单位: ms)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒流输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIMEr:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.7 查询通道输出端子上测得的输出时间长度
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:TIME:OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6 OUTPut 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 设置电源输出开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2 控制电源输出定时器的开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":OUTPut:TIMEr {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3 设定电源输出定时器的时间 (单位: s, 范围: 0.1 ~ 999999.9)
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":OUTPut:TIMEr:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.4 设定电源输出的模式优先权 (CV优先 或 CC优先)
|
||||
/// </summary>
|
||||
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
||||
{
|
||||
string 模式 = 是CV优先 ? "CV" : "CC";
|
||||
await SendAsync($":OUTPut:MODE {模式}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7 SOURce 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.1 清除输出电流保护(OCP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":CURRent:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":CURRent:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent:PROTection {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.7 设置电源的输出电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.1 清除输出电压保护(OVP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":VOLTage:PROTection:CLEar{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":VOLTage:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:PROTection {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.7 设置电源的输出电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.8 设定电压输出范围的上限电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:LIMIT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.3.1 设置输出功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.3.2 禁止或使能当前恒功率(CP)输出
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($":POWer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.9 SYSTem 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.1 控制蜂鸣器开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:BEEPer:STATE {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.2 使蜂鸣器强制鸣叫一声
|
||||
/// </summary>
|
||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:BEEPer{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor:COUNT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.8 设置电源为远程控制模式 (Remote 状态)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.9 通过通信接口设置电源为远程控制锁定模式
|
||||
/// </summary>
|
||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:RWLock{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
294
DeviceCommand/Devices/N69200.cs
Normal file
294
DeviceCommand/Devices/N69200.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class N69200 : Tcp
|
||||
{
|
||||
// 手册第 15 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 N69200 电子负载通信参数
|
||||
/// </summary>
|
||||
public N69200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电子负载设备识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电子负载恢复到出厂默认配置状态
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前系统参数到指定的存储单元 (单元范围依型号而定)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用指定存储单元中保存的系统参数
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 电子负载模式切换及拉载开关
|
||||
|
||||
/// <summary>
|
||||
/// 控制电子负载的输入控制开关(True: 开启拉载输入, False: 关闭拉载输入)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($":INPut {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前的拉载开关状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":INPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的基本工作模式
|
||||
/// (CC: 恒电流, CV: 恒电压, CP: 恒功率, CR: 恒电阻)
|
||||
/// </summary>
|
||||
public virtual async Task 设置负载模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
// 转换为大写以确保设备命令解析兼容
|
||||
string modeUpper = 模式.ToUpper();
|
||||
if (modeUpper != "CC" && modeUpper != "CV" && modeUpper != "CP" && modeUpper != "CR")
|
||||
throw new ArgumentException("工作模式只能为 CC, CV, CP, 或 CR");
|
||||
|
||||
await SendAsync($":MODE {modeUpper}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前处于何种工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.3. 负载参数设置 (CC / CV / CP / CR)
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电流模式(CC)下的拉载电流目标值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F4}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电流模式下设定的拉载电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电压模式(CV)下的拉载电压目标值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电压模式下设定的拉载电压值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒功率模式(CP)下的拉载功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒功率模式下设定的拉载功率值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电阻模式(CR)下的等效拉载电阻阻值 (单位: Ω)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":RESistance {0:F4}{1}", 电阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电阻模式下设定的拉载电阻值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":RESistance?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一键回读当前负载测得的电压、电流和功率的组合数组数据
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VAP?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护限制参数设置
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过流保护(OCP)报警值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过功率保护(OPP)报警值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的过压保护(OVP)报警值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 系统控制及报警清除
|
||||
|
||||
/// <summary>
|
||||
/// 读取仪器的出错记录记录 (当系统产生告警或指令解析异常时读取)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换电子负载到本地面板操作状态 (前面板按键解锁)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换电子负载到远程计算机操作状态 (前面板按键锁定)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除负载当前的软件过流/过功率等保护(Protection)锁定触发状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
213
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
213
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class SDS2000X_HD : Tcp
|
||||
{
|
||||
// 示波器底层 Socket 字符串命令通常以换行符 \n 结束
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化示波器通信参数 (鼎阳示波器网口 Socket 默认端口通常为 5025)
|
||||
/// </summary>
|
||||
public SDS2000X_HD(string ipAddress, int port = 5025, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使示波器恢复到默认的出厂设置
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
||||
}
|
||||
#endregion
|
||||
#region 2. 运行与捕获控制控制 (Run / Stop / Single)
|
||||
|
||||
/// <summary>
|
||||
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
||||
/// </summary>
|
||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"RUN{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止捕获波形 (等同于按下前端面板的 Stop 键)
|
||||
/// </summary>
|
||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"STOP{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制示波器进入单次触发捕获模式
|
||||
/// </summary>
|
||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SINGle{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发一次波形采样 (当触发源设为 Manual 时使用)
|
||||
/// </summary>
|
||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*TRG{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||
|
||||
/// <summary>
|
||||
/// 开启或关闭指定的模拟通道 (例如: channel=1 代表 C1)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
string state = enable ? "ON" : "OFF";
|
||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直电压档位 (Volts/Div,单位: V,例如 0.05 代表 50mV/div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:VDIV {1:F4}{2}", channel, volts, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置通道的输入阻抗 (1MΩ 或 50Ω)
|
||||
/// </summary>
|
||||
/// <param name="is50Ohm">True: 50欧姆, False: 1M欧姆</param>
|
||||
public virtual async Task 设置通道输入阻抗(int channel, bool is50Ohm, CancellationToken ct = default)
|
||||
{
|
||||
string value = is50Ohm ? "50" : "1M";
|
||||
await SendAsync($"C{channel}:COUPling {value}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. Timebase 水平时基子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的水平时基档位 (Time/Div,单位: s,例如 0.001 代表 1ms/div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TIME_DIV {0:E6}{1}", scale, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:DELay {0:E6}{1}", delay, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. Trigger 触发子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的电平值 (Trigger Level,单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:LEVel {0:F3}{1}", level, ScpiDelimiter); //
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的触发源 (例如: C1, C2, C3, C4, EX, LINE)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. Measure 自动测量参数回读 (高频轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道自动测量项的当前实时测量数值
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (1-4)</param>
|
||||
/// <param name="paramName">参数名称助记符:
|
||||
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
|
||||
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
|
||||
/// <returns>设备返回的科学计数法或自定义字符串数值</returns>
|
||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||
{
|
||||
// 语法格式示例:C1:PAVA? FREQ
|
||||
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter); //
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "PKPK", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的频率值 (Frequency)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "FREQ", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "RMS", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
217
DeviceCommand/Devices/SPAW7000.cs
Normal file
217
DeviceCommand/Devices/SPAW7000.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Device
|
||||
{
|
||||
[ADPCommand]
|
||||
public class SPAW7000 : Tcp
|
||||
{
|
||||
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:初始化 SPAW7000 功率分析记录仪通信参数
|
||||
/// </summary>
|
||||
public SPAW7000(string ipAddress, int port, int sendTimeout, int receiveTimeout)
|
||||
{
|
||||
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使功率分析仪恢复到出厂默认配置状态
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. MEASure / NUMeric 核心数据测量与轮询 (高频轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? U,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? I,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? P,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? FREQuency,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的功率因数 (Power Factor)
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
||||
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? PF,{0}{1}", channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义组合参数批量读取接口
|
||||
/// </summary>
|
||||
/// <param name="parameter">参数助记符 (如 "U,I,P" 或 "S,Q,LAMBda")</param>
|
||||
/// <param name="channel">通道号</param>
|
||||
public virtual async Task<string> 查询自定义测量组合(string parameter, int channel, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? {0},{1}{2}", parameter, channel, ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. INPut 通道电气参数配置
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电压量程 (例如: 15, 30, 60, 150, 300, 600, 1000)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压量程(int channel, double range, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:VOLTage:RANGe {0},{1:F1}{2}", channel, range, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电流量程 (取决于接线单元或传感器输入类型)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流量程(int channel, double range, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:CURRent:RANGe {0},{1:F3}{2}", channel, range, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置通道的耦合模式 (AC, DC, ACDC)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道耦合模式(int channel, string mode, CancellationToken ct = default)
|
||||
{
|
||||
string modeUpper = mode.ToUpper();
|
||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
||||
throw new ArgumentException("耦合模式只能为 AC, DC, 或 ACDC");
|
||||
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:COUPling {0},{1}{2}", channel, modeUpper, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. SYSTem 系统设置与状态查询
|
||||
|
||||
/// <summary>
|
||||
/// 14. 查询仪器型号名称
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备型号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:MODel?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 16. 查询仪器唯一序列号
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备序列号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:SERial?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 15. 设置数值数据显示的分辨率 (5位或6位)
|
||||
/// </summary>
|
||||
/// <param name="resolution">有效值只能为 5 或 6</param>
|
||||
public virtual async Task 设置显示分辨率(int resolution, CancellationToken ct = default)
|
||||
{
|
||||
if (resolution != 5 && resolution != 6) throw new ArgumentException("分辨率只能设置为 5 或 6 位");
|
||||
await SendAsync($":SYSTem:RESolution {resolution}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 13. 设置或查询屏幕 LCD 的亮度级别 (1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 设置显示亮度(int brightness, CancellationToken ct = default)
|
||||
{
|
||||
if (brightness < 1 || brightness > 10) throw new ArgumentOutOfRangeException(nameof(brightness), "亮度范围必须在 1~10 之间");
|
||||
await SendAsync($":SYSTem:LCD:BRIGhtness {brightness}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 18. 设置或查询触摸锁的开/关状态 (锁定时防止人工误触触控屏)
|
||||
/// </summary>
|
||||
public virtual async Task 设置屏幕触摸锁定(bool isLocked, CancellationToken ct = default)
|
||||
{
|
||||
string state = isLocked ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:TLOCK {state}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 17. 设置或读取分析仪当前的系统内部时间
|
||||
/// </summary>
|
||||
/// <param name="timeStr">格式必须为 "HH:MM:SS"</param>
|
||||
public virtual async Task 设置系统时间(string timeStr, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:TIME \"{timeStr}\"{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user