添加项目文件。
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
163
DeviceCommand/Base/ModbusTcp.cs
Normal file
163
DeviceCommand/Base/ModbusTcp.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Model.Models;
|
||||
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(TcpConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.IPAddress, config.Port, config.SendTimeout, config.ReceiveTimeout);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
227
DeviceCommand/Base/Serial_Port.cs
Normal file
227
DeviceCommand/Base/Serial_Port.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Serial_Port : ISerialPort, IDisposable
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 <see cref="SerialPortConfig"/> 一次性配置串口通信参数。
|
||||
/// </summary>
|
||||
public Serial_Port(SerialPortConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.PortName, config.BaudRate, config.DataBits, config.StopBits, config.Parity, config.ReadTimeout, config.WriteTimeout);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 允许底层保留默认设置,控制权交给外层 WaitAsync
|
||||
_serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
|
||||
_serialPort.WriteTimeout = SerialPort.InfiniteTimeout;
|
||||
|
||||
_serialPort.Open();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort?.IsOpen == true) _serialPort.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
try
|
||||
{
|
||||
if (WriteTimeout > 0)
|
||||
{
|
||||
// 核心修改:使用 WaitAsync 精准控制写入超时
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
throw new TimeoutException($"串口写入数据超时({WriteTimeout} ms)", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(data, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int bytesRead;
|
||||
if (ReadTimeout > 0)
|
||||
{
|
||||
// 核心修改:使用 WaitAsync 精准控制单次异步读取超时
|
||||
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (bytesRead == 0) continue;
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
string currentText = sb.ToString();
|
||||
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return currentText.Substring(0, index).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 关键防污染策略:串口发生命令错、超时不响应时,WaitAsync 会立功捕获异常
|
||||
// 我们直接清空串口驱动内部的输入缓冲区,把残余垃圾数据物理抹除,确保下一条指令安全、不掉线
|
||||
ClearHardwareBuffer();
|
||||
throw new TimeoutException($"串口读取超时(未收到结束符 '{delimiter}'),等待时间:{ReadTimeout} ms", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// 发送新命令前先冲洗一下缓冲区,双重保险
|
||||
ClearHardwareBuffer();
|
||||
|
||||
await LoglessSendAsync(command, ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物理强制清空串口驱动层及软件层的缓冲区
|
||||
/// </summary>
|
||||
private void ClearHardwareBuffer()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serialPort?.IsOpen == true)
|
||||
{
|
||||
_serialPort.DiscardInBuffer(); // 清空硬件接收缓冲区
|
||||
_serialPort.DiscardOutBuffer(); // 清空硬件发送缓冲区
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略清理异常
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_serialPort != null)
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
}
|
||||
commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
322
DeviceCommand/Base/TCP.cs
Normal file
322
DeviceCommand/Base/TCP.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using Model.Models;
|
||||
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, IDisposable
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 <see cref="TcpConfig"/> 一次性配置 TCP 通信参数。
|
||||
/// </summary>
|
||||
public Tcp(TcpConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.IPAddress, config.Port, config.SendTimeout, config.ReceiveTimeout);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return await ResetConnectionAsync(ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心内部方法:无锁状态下安全重置并重建连接
|
||||
/// </summary>
|
||||
private async Task<bool> ResetConnectionAsync(CancellationToken ct)
|
||||
{
|
||||
if (_tcpClient != null)
|
||||
{
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
}
|
||||
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient?.Connected == true) _tcpClient.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
try
|
||||
{
|
||||
if (SendTimeout > 0)
|
||||
{
|
||||
// 使用 WaitAsync 控制发送超时
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 发送超时,安全起见直接断开重建
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"TCP 发送数据超时({SendTimeout} ms),连接已重置以确保安全。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
while (offset < length)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// 计算剩余超时时间(这里简单使用配置值,若要求极精准可加入不长计算)
|
||||
int read = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, offset, length - offset, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, offset, length - offset, ct).ConfigureAwait(false);
|
||||
|
||||
if (read == 0) throw new IOException("远程主机已关闭连接");
|
||||
offset += read;
|
||||
}
|
||||
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 关键安全重置:WaitAsync 超时后抛弃老连接,防止未完成的 Read 污染后续 Buffer
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"TCP 读取定长数据超时({ReceiveTimeout} ms),连接已重置避免数据错乱。", ex);
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int bytesRead = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
string currentText = sb.ToString();
|
||||
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return currentText.Substring(0, index).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 当 SCPI 仪表由于命令发错不响应时,WaitAsync 会立功并抛出 TimeoutException
|
||||
// 此时我们在内部秒速“重置连接”,上层业务只会收到一个干净的超时报错,但由于锁和自动重连,下一次通信直接恢复!
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"SCPI 读取超时(未收到结束符 '{delimiter}',等待:{ReceiveTimeout} ms),通信链路已自动刷新。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
#region 扩展:读取所有可用的二进制网络字节 (无 SCPI 块头解析)
|
||||
|
||||
/// <summary>
|
||||
/// 无锁核心方法:发送命令并一次性读取所有回传的二进制原始数据包(不进行 # 协议头解析,专用于读取纯文件流如 PNG)
|
||||
/// </summary>
|
||||
private async Task<byte[]> LoglessReadAllBytesAsync(string queryCommand, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
// 1. 发送查询命令(例如 :PRINt? PNG\n)
|
||||
await LoglessSendAsync(Encoding.UTF8.GetBytes(queryCommand), ct);
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
|
||||
// 2. 鼎阳示波器的截图数据大概在 100KB - 800KB 左右,使用动态内存流接收整个包
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
byte[] buffer = new byte[8192]; // 8KB 缓冲区
|
||||
|
||||
try
|
||||
{
|
||||
// 先给设备短暂的反应时间,等待数据到达网络缓冲区
|
||||
int delayCount = 0;
|
||||
while (!_tcpClient.GetStream().DataAvailable && delayCount < 50)
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
delayCount++;
|
||||
}
|
||||
|
||||
// 循环读取,直到网络流中没有更多数据
|
||||
do
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int read = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
|
||||
if (read == 0) break; // 远程流关闭
|
||||
|
||||
ms.Write(buffer, 0, read);
|
||||
|
||||
// 如果流里没有剩余数据了,退出读取(防止 ReadAsync 在没有数据时无限阻塞等待)
|
||||
if (!stream.DataAvailable)
|
||||
{
|
||||
// 极短延时再确认一次,防止分包网络延迟引起的“假结束”
|
||||
await Task.Delay(30, ct);
|
||||
if (!stream.DataAvailable)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"读取二进制大包超时(等待:{ReceiveTimeout} ms),链路已重置。", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【公开方法】发送命令并读取设备回传的全部原始二进制字节数组(不带协议头解析,直接返回整个字节缓冲区)
|
||||
/// </summary>
|
||||
public async Task<byte[]> ReadAllBytesAsync(string queryCommand, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAllBytesAsync(queryCommand, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
DeviceCommand/DeviceCommand.csproj
Normal file
19
DeviceCommand/DeviceCommand.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<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" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
@@ -0,0 +1,540 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class GantryControlTcp : ModbusTcp
|
||||
{
|
||||
private readonly byte _slaveAddress=1;
|
||||
|
||||
// 向后兼容:TcpDevice 即自身(继承自 ModbusTcp)
|
||||
public ModbusTcp TcpDevice => this;
|
||||
|
||||
public GantryControlTcp(TcpConfig config) : base(config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// ------------------ Coil / Bit 状态与控制 (位元件) ------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_M_ESTOP = 20496; // 0x5010: 紧停
|
||||
private const ushort ADDR_M_X_STOP = 41; // 0x29: X轴停止
|
||||
private const ushort ADDR_M_Y_STOP = 42; // 0x2A: Y轴停止
|
||||
private const ushort ADDR_M_Z_STOP = 43; // 0x2B: Z轴停止
|
||||
|
||||
private const ushort ADDR_M_X_START = 30; // 0x1E: X轴指定位置启动
|
||||
private const ushort ADDR_M_Y_START = 31; // 0x1F: Y轴指定位置启动
|
||||
private const ushort ADDR_M_Z_START = 35; // 0x23: Z轴指定位置启动
|
||||
|
||||
private const ushort ADDR_M_HOME_TRIGGER = 100; // 0x64: 回原点触发
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 轴使能控制 (HM 寄存器) ------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_HM_AXIS1_ENABLE = 49408; // 0xC100: 轴1使能 (X轴)
|
||||
private const ushort ADDR_HM_AXIS2_ENABLE = 49409; // 0xC101: 轴2使能 (Y轴)
|
||||
private const ushort ADDR_HM_AXIS3_ENABLE = 49410; // 0xC102: 轴3使能 (Z轴)
|
||||
private const ushort ADDR_HM_AXIS4_ENABLE = 49411; // 0xC103: 轴4使能
|
||||
private const ushort ADDR_HM_AXIS5_ENABLE = 49412; // 0xC104: 轴5使能
|
||||
private const ushort ADDR_HM_AXIS6_ENABLE = 49413; // 0xC105: 轴6使能
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 速度与位置参数 (HD 32位数据寄存器) --------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_HD_HOME_POS_SPEED = 41288; // 0xA148: 原点定位速度 (32位)
|
||||
|
||||
// 轴原点位置
|
||||
private const ushort ADDR_HD_X_HOME_POS = 41198; // 0xA0EE: X轴原点位 (32位)
|
||||
private const ushort ADDR_HD_Z_HOME_POS = 41208; // 0xA0F8: Z轴原点位 (32位)
|
||||
private const ushort ADDR_HD_Y_HOME_POS = 41218; // 0xA102: Y轴原点位 (32位)
|
||||
|
||||
// 轴运行目标位置
|
||||
private const ushort ADDR_HD_Y_TARGET = 41228; // 0xA10C: Y轴指定位置 (32位)
|
||||
private const ushort ADDR_HD_Z_TARGET = 41238; // 0xA116: Z轴指定位置 (32位)
|
||||
private const ushort ADDR_HD_X_TARGET = 41248; // 0xA120: X轴指定位置 (32位)
|
||||
|
||||
// 轴运行速度
|
||||
private const ushort ADDR_HD_X_SPEED = 41318; // 0xA166: X轴速度 (32位)
|
||||
private const ushort ADDR_HD_Z_SPEED = 41328; // 0xA170: Z轴速度 (32位)
|
||||
private const ushort ADDR_HD_Y_SPEED = 41338; // 0xA17A: Y轴速度 (32位)
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 轴绝对位置(只读,相对原点)------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_ABS_AXIS1_POS = 47232; // 轴1绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS2_POS = 47236; // 轴2绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS3_POS = 47240; // 轴3绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS4_POS = 47244; // 轴4绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS5_POS = 47248; // 轴5绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS6_POS = 47252; // 轴6绝对位置 (32位)
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 核心控制逻辑与参数读写 ------------------------
|
||||
// =================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 轴使能控制
|
||||
/// </summary>
|
||||
/// <param name="axis">轴编号 (1 - 6)</param>
|
||||
public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HM_AXIS1_ENABLE,
|
||||
2 => ADDR_HM_AXIS2_ENABLE,
|
||||
3 => ADDR_HM_AXIS3_ENABLE,
|
||||
4 => ADDR_HM_AXIS4_ENABLE,
|
||||
5 => ADDR_HM_AXIS5_ENABLE,
|
||||
6 => ADDR_HM_AXIS6_ENABLE,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1 - 6", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, enable, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴的使能状态
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task<bool> GetAxisEnableAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HM_AXIS1_ENABLE,
|
||||
2 => ADDR_HM_AXIS2_ENABLE,
|
||||
3 => ADDR_HM_AXIS3_ENABLE,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
bool[] coils = await ReadCoilsAsync(_slaveAddress, address, 1, ct);
|
||||
return coils != null && coils.Length > 0 && coils[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同时设置 X/Y/Z 轴的原点位置(带各自独立的范围检查)
|
||||
/// </summary>
|
||||
/// <param name="xPos">X轴原点位置 (范围: 0 ~ 50000)</param>
|
||||
/// <param name="yPos">Y轴原点位置 (范围: 0 ~ 50000)</param>
|
||||
/// <param name="zPos">Z轴原点位置 (范围: 0 ~ 20000)</param>
|
||||
public async Task SetHomePositionAsync(int xPos, int yPos, int zPos, CancellationToken ct = default)
|
||||
{
|
||||
if (xPos < 0 || xPos > 50000)
|
||||
throw new ArgumentOutOfRangeException(nameof(xPos), $"X轴原点设置值 [{xPos}] 超出合法范围 (0 - 50000)");
|
||||
|
||||
if (yPos < 0 || yPos > 50000)
|
||||
throw new ArgumentOutOfRangeException(nameof(yPos), $"Y轴原点设置值 [{yPos}] 超出合法范围 (0 - 50000)");
|
||||
|
||||
if (zPos < 0 || zPos > 20000)
|
||||
throw new ArgumentOutOfRangeException(nameof(zPos), $"Z轴原点设置值 [{zPos}] 超出合法范围 (0 - 20000)");
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, Int32ToUshorts(xPos), ct);
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_HOME_POS, Int32ToUshorts(yPos), ct);
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_HOME_POS, Int32ToUshorts(zPos), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量查询当前 X、Y、Z 三轴设定的原点位置值
|
||||
/// </summary>
|
||||
/// <returns>返回包含三轴原点位置的元组 (X, Y, Z)</returns>
|
||||
public async Task<(int X, int Y, int Z)> GetHomePositionAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 41218(Y) - 41198(X) = 20,加Y轴自身的 2 个寄存器,共批量读取 22 个连续寄存器
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, 22, ct);
|
||||
|
||||
if (registers == null || registers.Length < 22)
|
||||
throw new InvalidOperationException("从 PLC 读取三轴原点位置失败,返回数据长度不足。");
|
||||
|
||||
int xHomePos = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||
int zHomePos = UshortsToInt32(new ushort[] { registers[10], registers[11] });
|
||||
int yHomePos = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||
|
||||
return (xHomePos, yHomePos, zHomePos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置回原点定位速度(带 0 - 100000 范围检查)
|
||||
/// </summary>
|
||||
public async Task SetHomePositionSpeedAsync(int speed, CancellationToken ct = default)
|
||||
{
|
||||
if (speed < 0 || speed > 100000)
|
||||
throw new ArgumentOutOfRangeException(nameof(speed), $"回原点定位速度值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, Int32ToUshorts(speed), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询当前设定的回原点定位速度
|
||||
/// </summary>
|
||||
public async Task<int> GetHomePositionSpeedAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, 2, ct);
|
||||
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException("从 PLC 读取回原点定位速度失败,返回数据长度不足。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定轴的运行速度(带 0 - 100000 范围检查)
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task SetAxisSpeedAsync(int axis, int speed, CancellationToken ct = default)
|
||||
{
|
||||
if (speed < 0 || speed > 100000)
|
||||
throw new ArgumentOutOfRangeException(nameof(speed), $"轴速度设置值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_SPEED,
|
||||
2 => ADDR_HD_Y_SPEED,
|
||||
3 => ADDR_HD_Z_SPEED,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, address, Int32ToUshorts(speed), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴当前设定的运行速度
|
||||
/// </summary>
|
||||
public async Task<int> GetAxisSpeedAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_SPEED,
|
||||
2 => ADDR_HD_Y_SPEED,
|
||||
3 => ADDR_HD_Z_SPEED,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 速度失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入指定轴的目标位置(动态安全校验:目标位置不能超过 [轴最大物理极限 - 当前轴原点设置值])
|
||||
/// </summary>
|
||||
public async Task SetAxisTargetPositionAsync(int axis, int targetPos, CancellationToken ct = default)
|
||||
{
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
|
||||
ushort targetAddress;
|
||||
int maxLimit;
|
||||
int currentHomePos;
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case 1:
|
||||
targetAddress = ADDR_HD_X_TARGET;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = xHome;
|
||||
break;
|
||||
case 2:
|
||||
targetAddress = ADDR_HD_Y_TARGET;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = yHome;
|
||||
break;
|
||||
case 3:
|
||||
targetAddress = ADDR_HD_Z_TARGET;
|
||||
maxLimit = 20000;
|
||||
currentHomePos = zHome;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||
}
|
||||
|
||||
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||
|
||||
if (targetPos < 0 || targetPos > allowedMaxLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(targetPos),
|
||||
$"轴 {axis} 目标位置 [{targetPos}] 不合法!当前原点为 [{currentHomePos}],允许的有效写入范围为: 0 - {allowedMaxLimit}"
|
||||
);
|
||||
}
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, targetAddress, Int32ToUshorts(targetPos), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴当前设定的目标指定位置
|
||||
/// </summary>
|
||||
public async Task<int> GetAxisTargetPositionAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_TARGET,
|
||||
2 => ADDR_HD_Y_TARGET,
|
||||
3 => ADDR_HD_Z_TARGET,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 目标位置失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统紧停锁定状态 (true: 锁定激活, false: 解除锁定)
|
||||
/// </summary>
|
||||
public async Task SetEStopStateAsync(bool state, CancellationToken ct = default)
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_ESTOP, state, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定轴的停止控制状态 (true: 持续输出停止信号, false: 释放停止信号)
|
||||
/// </summary>
|
||||
public async Task SetStopStateAsync(int axis, bool state, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_M_X_STOP,
|
||||
2 => ADDR_M_Y_STOP,
|
||||
3 => ADDR_M_Z_STOP,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, state, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发)
|
||||
/// </summary>
|
||||
public async Task HomeAsync(CancellationToken ct = default)
|
||||
{
|
||||
int currentSpeed = await GetHomePositionSpeedAsync(ct);
|
||||
|
||||
if (currentSpeed == 0)
|
||||
throw new InvalidOperationException("无法触发回原点!当前回原点定位速度为 0,请先设置合法的回零速度。");
|
||||
|
||||
await TriggerCoilAsync(ADDR_M_HOME_TRIGGER, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单轴指定位置启动(含速度判定、使能自动补齐、动作目标位置独立范围校验)
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task StartSingleAxisAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort startAddress;
|
||||
int maxLimit;
|
||||
int currentHomePos;
|
||||
|
||||
// 1. 获取基础参数与校验边界
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
switch (axis)
|
||||
{
|
||||
case 1:
|
||||
startAddress = ADDR_M_X_START;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = xHome;
|
||||
break;
|
||||
case 2:
|
||||
startAddress = ADDR_M_Y_START;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = yHome;
|
||||
break;
|
||||
case 3:
|
||||
startAddress = ADDR_M_Z_START;
|
||||
maxLimit = 20000;
|
||||
currentHomePos = zHome;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||
}
|
||||
|
||||
// 2. 检查动作轴的速度
|
||||
int currentSpeed = await GetAxisSpeedAsync(axis, ct);
|
||||
if (currentSpeed == 0)
|
||||
throw new InvalidOperationException($"轴 {axis} 无法启动!当前设定速度为 0。");
|
||||
|
||||
// 3. 检查动作轴使能,若为 false 则自动补写 true 激活驱动器
|
||||
bool isEnabled = await GetAxisEnableAsync(axis, ct);
|
||||
if (!isEnabled)
|
||||
{
|
||||
await SetAxisEnableAsync(axis, true, ct);
|
||||
await Task.Delay(100, ct); // 等待硬件继电器响应与伺服驱动就绪
|
||||
}
|
||||
|
||||
// 4. 精准独立校验当前需要动作轴的目标定位是否越界
|
||||
int currentTarget = await GetAxisTargetPositionAsync(axis, ct);
|
||||
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||
if (currentTarget < 0 || currentTarget > allowedMaxLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"轴 {axis} 无法启动!当前指定目标位置 [{currentTarget}] 超过了允许的最大安全极限值 [{allowedMaxLimit}]。");
|
||||
}
|
||||
|
||||
// 5. 校验完成,边缘触发点动启动
|
||||
await TriggerCoilAsync(startAddress, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三轴同时启动(全开:严格校验全部运行轴速度、批量检测与补正1~6号轴使能、联动校验三轴目标范围极限)
|
||||
/// </summary>
|
||||
public async Task StartAllAxesAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 1. 一次性读取三轴速度,严禁任何一轴速度为 0
|
||||
int xSpeed = await GetAxisSpeedAsync(1, ct);
|
||||
int ySpeed = await GetAxisSpeedAsync(2, ct);
|
||||
int zSpeed = await GetAxisSpeedAsync(3, ct);
|
||||
|
||||
if (xSpeed == 0 || ySpeed == 0 || zSpeed == 0)
|
||||
throw new InvalidOperationException($"无法全开启动!存在运行速度为 0 的轴。当前速度 -> X:{xSpeed}, Y:{ySpeed}, Z:{zSpeed}");
|
||||
|
||||
// 2. 批量读取 1 ~ 6 号轴连续的使能状态线圈,极大地优化网络性能
|
||||
bool[] enableStates = await ReadCoilsAsync(_slaveAddress, ADDR_HM_AXIS1_ENABLE, 6, ct);
|
||||
if (enableStates == null || enableStates.Length < 6)
|
||||
throw new InvalidOperationException("从 PLC 读取 1~6 号轴使能状态失败。");
|
||||
|
||||
bool hasModifiedEnable = false;
|
||||
ushort[] enableAddresses = { ADDR_HM_AXIS1_ENABLE, ADDR_HM_AXIS2_ENABLE, ADDR_HM_AXIS3_ENABLE, ADDR_HM_AXIS4_ENABLE, ADDR_HM_AXIS5_ENABLE, ADDR_HM_AXIS6_ENABLE };
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!enableStates[i])
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, enableAddresses[i], true, ct);
|
||||
hasModifiedEnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果执行过使能补齐补齐写入,在循环外进行统一延时,保证PLC时序安全
|
||||
if (hasModifiedEnable)
|
||||
await Task.Delay(100, ct);
|
||||
|
||||
// 3. 获取三轴原点及当前设定的目标指定位置
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
|
||||
int xTarget = await GetAxisTargetPositionAsync(1, ct);
|
||||
int yTarget = await GetAxisTargetPositionAsync(2, ct);
|
||||
int zTarget = await GetAxisTargetPositionAsync(3, ct);
|
||||
|
||||
// X轴安全范围联动校验
|
||||
int xMaxAllowed = 50000 - xHome;
|
||||
if (xTarget < 0 || xTarget > xMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(xTarget), $"X轴目标位置 [{xTarget}] 越界,当前原点下最大范围 0-{xMaxAllowed}");
|
||||
|
||||
// Y轴安全范围联动校验
|
||||
int yMaxAllowed = 50000 - yHome;
|
||||
if (yTarget < 0 || yTarget > yMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(yTarget), $"Y轴目标位置 [{yTarget}] 越界,当前原点下最大范围 0-{yMaxAllowed}");
|
||||
|
||||
// Z轴安全范围联动校验
|
||||
int zMaxAllowed = 20000 - zHome;
|
||||
if (zTarget < 0 || zTarget > zMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(zTarget), $"Z轴目标位置 [{zTarget}] 越界,当前原点下最大范围 0-{zMaxAllowed}");
|
||||
|
||||
// 4. 全部联动校验通过,三轴点动同步启动
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, true, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, true, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, true, ct);
|
||||
|
||||
await Task.Delay(100, ct); // 保持高电平状态以适配 PLC 扫描周期
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, false, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, false, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, false, ct);
|
||||
}
|
||||
|
||||
#region 内部高低字工具转化方法
|
||||
|
||||
/// <summary>
|
||||
/// 触发点动脉冲信号(置 1 后,等待 100ms 自动置 0 释放)
|
||||
/// </summary>
|
||||
private async Task TriggerCoilAsync(ushort address, CancellationToken ct = default)
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, true, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, false, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(低字在前 CDAB 格式)
|
||||
/// </summary>
|
||||
private ushort[] Int32ToUshorts(int value)
|
||||
{
|
||||
ushort lowWord = (ushort)(value & 0xFFFF);
|
||||
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
|
||||
return new ushort[] { lowWord, highWord };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Modbus 的 2 个 16 位无符号整数转换为 32 位整型(低字在前 CDAB 格式)
|
||||
/// </summary>
|
||||
private int UshortsToInt32(ushort[] registers)
|
||||
{
|
||||
uint lowWord = registers[0];
|
||||
uint highWord = registers[1];
|
||||
return (int)((highWord << 16) | lowWord);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 轴绝对位置读取(只读)
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴的绝对位置(相对原点的实时位置,只读)
|
||||
/// </summary>
|
||||
/// <param name="axis">轴编号 (1-6)</param>
|
||||
public async Task<int> GetAbsolutePositionAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_ABS_AXIS1_POS,
|
||||
2 => ADDR_ABS_AXIS2_POS,
|
||||
3 => ADDR_ABS_AXIS3_POS,
|
||||
4 => ADDR_ABS_AXIS4_POS,
|
||||
5 => ADDR_ABS_AXIS5_POS,
|
||||
6 => ADDR_ABS_AXIS6_POS,
|
||||
_ => throw new ArgumentException("轴编号错误,仅支持 1-6", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"读取轴 {axis} 绝对位置失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量读取 1-6 轴的绝对位置(一次性读取 22 个寄存器,高效)
|
||||
/// </summary>
|
||||
/// <returns>返回 6 轴绝对位置元组</returns>
|
||||
public async Task<(int Axis1, int Axis2, int Axis3, int Axis4, int Axis5, int Axis6)> GetAllAbsolutePositionsAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 从 47232 开始连续读取 22 个寄存器(覆盖 47232-47253)
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, 22, ct);
|
||||
if (registers == null || registers.Length < 22)
|
||||
throw new InvalidOperationException("批量读取 6 轴绝对位置失败,返回数据长度不足。");
|
||||
|
||||
// 每轴间隔 4 个寄存器(2个数据 + 2个间隙),提取各轴数据
|
||||
int axis1 = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||
int axis2 = UshortsToInt32(new ushort[] { registers[4], registers[5] });
|
||||
int axis3 = UshortsToInt32(new ushort[] { registers[8], registers[9] });
|
||||
int axis4 = UshortsToInt32(new ushort[] { registers[12], registers[13] });
|
||||
int axis5 = UshortsToInt32(new ushort[] { registers[16], registers[17] });
|
||||
int axis6 = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||
|
||||
return (axis1, axis2, axis3, axis4, axis5, axis6);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
49
DeviceCommand/Devices/IOBoard.cs
Normal file
49
DeviceCommand/Devices/IOBoard.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
//[ACPCommand]
|
||||
public class IOBoard : ModbusTcp
|
||||
{
|
||||
|
||||
public IOBoard(TcpConfig config) : base(config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task 写输出开关(byte 站号, ushort 开始地址, bool data)
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(站号, 开始地址, data);
|
||||
}
|
||||
public async Task 批量写输出开关(byte 站号, ushort 开始地址, bool[] datas)
|
||||
{
|
||||
var 批量写入 = await Modbus.ReadCoilsAsync(1, 0, 16);
|
||||
for (int i = 0; i < datas.Length; i++)
|
||||
{
|
||||
if (开始地址 + i < 批量写入.Length)
|
||||
{
|
||||
批量写入[开始地址 + i] = datas[i];
|
||||
}
|
||||
}
|
||||
await Modbus.WriteMultipleCoilsAsync(站号, 0, 批量写入);
|
||||
}
|
||||
public async Task 读输出开关(byte 站号, ushort 开始地址)
|
||||
{
|
||||
await Modbus.ReadCoilsAsync(站号, 开始地址, 1);
|
||||
}
|
||||
public async Task<bool[]> 批量读输出开关(byte 站号, ushort 开始地址,ushort 数量)
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(站号, 开始地址, 数量);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Common.Attributes;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using DeviceCommand.Base;
|
||||
using DeviceCommand.Devices;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统控制功能类型枚举 (中文)
|
||||
/// </summary>
|
||||
public enum 功能动作 : byte
|
||||
{
|
||||
单相充电,
|
||||
抛负载,
|
||||
短路测试,
|
||||
蜂鸣器报警
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 精简后的 IO 端口信息结构体
|
||||
/// </summary>
|
||||
public struct IoPortInfo
|
||||
{
|
||||
public int ModuleIndex { get; set; } // IO板卡/模块序号 (1 或 2)
|
||||
public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1)
|
||||
|
||||
public IoPortInfo(int moduleIndex, ushort address)
|
||||
{
|
||||
ModuleIndex = moduleIndex;
|
||||
Address = address;
|
||||
}
|
||||
}
|
||||
|
||||
[ACPCommand]
|
||||
public class IOBoardGroup
|
||||
{
|
||||
// 声明底层的 2 个 IO 板卡实例
|
||||
public IOBoard Board1 { get; private set; }
|
||||
public IOBoard Board2 { get; private set; }
|
||||
|
||||
private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1
|
||||
|
||||
// 核心:严格对齐图片数据的双层嵌套配置字典 (2个模块,1-8台架)
|
||||
private static readonly Dictionary<int, Dictionary<功能动作, IoPortInfo>> IoMapping
|
||||
= new Dictionary<int, Dictionary<功能动作, IoPortInfo>>()
|
||||
{
|
||||
{ 1, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 5) }, // Y6 -> 5
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 0) }, // Y1 -> 0
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 15) } // Y9 -> 8
|
||||
}},
|
||||
{ 2, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 6) }, // Y7 -> 6
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 2) }, // Y3 -> 2
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 14) } // Y10 -> 9
|
||||
}},
|
||||
{ 3, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 7) }, // Y8 -> 7
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 3) }, // Y4 -> 3
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 13) } // Y11 -> 10
|
||||
}},
|
||||
{ 4, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 8) }, // Y9 -> 8
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 4) }, // Y5 -> 4
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) } // Y12 -> 11
|
||||
}},
|
||||
{ 5, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 4) }, // Y5 -> 4
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 0) }, // Y1 -> 0
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 11) } // Y13 -> 12
|
||||
}},
|
||||
{ 6, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 5) }, // Y6 -> 5
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 1) }, // Y2 -> 1
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 10) } // Y14 -> 13
|
||||
}},
|
||||
{ 7, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 6) }, // Y7 -> 6
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 2) }, // Y3 -> 2
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 9) } // Y15 -> 14
|
||||
}},
|
||||
{ 8, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 7) }, // Y8 -> 7
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 3) }, // Y4 -> 3
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 8) } // Y16 -> 15
|
||||
}}
|
||||
};
|
||||
|
||||
public IOBoardGroup(IOBoard board1, IOBoard board2)
|
||||
{
|
||||
Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。");
|
||||
Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁)
|
||||
/// </summary>
|
||||
/// <param name="台架序号">台架/工位序号 (1 - 8)</param>
|
||||
/// <param name="动作">控制功能类型 (单相充电, 抛负载, 短路测试, 蜂鸣器报警)</param>
|
||||
/// <param name="开关状态">开关状态 (true: 开启吸合, false: 关闭断开)</param>
|
||||
/// <param name="取消令牌">异步取消令牌</param>
|
||||
public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default)
|
||||
{
|
||||
// 1. 根据传入的台架序号和动作检索字典映射关系
|
||||
if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息))
|
||||
{
|
||||
throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。");
|
||||
}
|
||||
|
||||
// 2. 软件防错高危互锁逻辑
|
||||
if (开关状态)
|
||||
{
|
||||
if (动作 == 功能动作.单相充电)
|
||||
{
|
||||
var 短路点位 = IoMapping[台架序号][功能动作.短路测试];
|
||||
await GetBoardInstance(短路点位.ModuleIndex).写输出开关(_slaveAddress, 短路点位.Address, false);
|
||||
}
|
||||
else if (动作 == 功能动作.短路测试)
|
||||
{
|
||||
var 充电点位 = IoMapping[台架序号][功能动作.单相充电];
|
||||
await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 动态获取目标板卡实例并直接驱动开关端口
|
||||
IOBoard 目标板卡 = GetBoardInstance(io信息.ModuleIndex);
|
||||
await 目标板卡.写输出开关(_slaveAddress, io信息.Address, 开关状态);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部路由辅助方法:根据板卡索引安全获取当前连接的实例对象
|
||||
/// </summary>
|
||||
private IOBoard GetBoardInstance(int moduleIndex)
|
||||
{
|
||||
IOBoard board = moduleIndex switch
|
||||
{
|
||||
1 => Board1,
|
||||
2 => Board2,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1# 和 2# 模块。")
|
||||
};
|
||||
|
||||
return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。");
|
||||
}
|
||||
}
|
||||
}
|
||||
263
DeviceCommand/Devices/IT7800E.cs
Normal file
263
DeviceCommand/Devices/IT7800E.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
#region 枚举定义
|
||||
|
||||
/// <summary>
|
||||
/// 电源电气/耦合模式
|
||||
/// </summary>
|
||||
public enum PowerCouplingMode
|
||||
{
|
||||
/// <summary> 纯交流模式 </summary>
|
||||
AC,
|
||||
/// <summary> 纯直流模式 </summary>
|
||||
DC,
|
||||
/// <summary> 交直流混合模式 </summary>
|
||||
ACDC,
|
||||
/// <summary> 直交流混合模式 </summary>
|
||||
DCAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备操作运行角色/模式
|
||||
/// </summary>
|
||||
public enum DeviceOperationMode
|
||||
{
|
||||
/// <summary> 电压源模式(常规源) </summary>
|
||||
VOLTage,
|
||||
/// <summary> 电子负载模式(放电拉载) </summary>
|
||||
LOAD,
|
||||
/// <summary> 电流源模式 </summary>
|
||||
CURRent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[ACPCommand]
|
||||
public class IT7800E : Tcp
|
||||
{
|
||||
// 八台产品共用一个,使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符[cite: 1]
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
public IT7800E(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 耦合模式与拉载开关控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源输出源的电气模式/工作耦合模式 (AC, DC, ACDC, DCAC)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置电源模式(PowerCouplingMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SOURce:FUNCtion {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前的工作电气模式[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SOURce:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF"; //[cite: 1]
|
||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
||||
|
||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
[Monitorable("交流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
[Monitorable("交流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
[Monitorable("交流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:PFACtor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. 保护参数设置与系统监控
|
||||
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection:RMS {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection:PEAK {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. 充放电与能量统计扩展命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除最近的安时(Ah)与瓦时(Wh)统计数据[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 清除充放电电量统计(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SENSe:AHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
||||
await SendAsync($"SENSe:WHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前充放电累计的安时值 (单位: Ah)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询累计安时_Ah(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:AHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前充放电累计的瓦时值 (单位: Wh)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询累计瓦时_Wh(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:WHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取充放电积分持续时间 (单位: 秒)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询积分时间(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:ENERgy:TIME?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换设备的操作模式 (VOLTage: 电源模式, LOAD: 负载模式/放电, CURRent: 电流源)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置操作模式(DeviceOperationMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SYSTem:OPERation:MODE {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置放电截止电压/电压下限 (单位: V,用于防止电池过放)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:LIMit:LOW {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
386
DeviceCommand/Devices/N36200.cs
Normal file
386
DeviceCommand/Devices/N36200.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
//[ACPCommand]
|
||||
public class N36200 : Tcp
|
||||
{
|
||||
//只有一个,八台产品一起用一个
|
||||
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 N36200/N36300 设备通信参数。
|
||||
/// </summary>
|
||||
public N36200(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#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>
|
||||
[Monitorable("高压直流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流电源功率")]
|
||||
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
|
||||
}
|
||||
}
|
||||
484
DeviceCommand/Devices/N36600.cs
Normal file
484
DeviceCommand/Devices/N36600.cs
Normal file
@@ -0,0 +1,484 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
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(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 修正:去除前缀冒号,规范应用 APPL 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "APPL {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($"APPL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5 MEASure 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:CURR?
|
||||
return await WriteReadAsync($"MEAS:CURR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:POW?
|
||||
return await WriteReadAsync($"MEAS:POW?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:VOLT?
|
||||
return await WriteReadAsync($"MEAS:VOLT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.5 查询通道输出端子上测得的输出时间长度
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:TIME:OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6 OUTPut 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 设置电源输出开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用 OUTP 短格式
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTP {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2 控制电源输出定时器的开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTP:TIME {状态}{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, "OUTP:TIME: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($"OUTP:MODE {模式}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7 SOURce 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.1 清除输出电流保护(OCP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"CURR:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,将 STATE 改为更常用的 STAT 简写
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($"CURR:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号。按 NGI 通用指令树,加上层级限制或者直接作用于 CURR:PROT
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR:PROT {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.7 设置电源的输出电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 CURR 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.1 清除输出电压保护(OVP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"VOLT:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($"VOLT:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:PROT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.7 设置电源的输出电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 VOLT 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT {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, "VOLT:LIM {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 3.7.2.9 清除电压上限限制(恢复为设备物理允许的最大电压值)
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压上限限制(CancellationToken ct = default)
|
||||
{
|
||||
// 发送 MAXimum 设为最大值以达到“清除限制”的效果
|
||||
await SendAsync($"VOLT:LIMIT MAX{SCPIDelimiter}", ct); // 对应手册 3.7.2.8
|
||||
}
|
||||
/// <summary>
|
||||
/// 3.7.3.1 设置输出功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 POW 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "POW {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($"POW:STAT {状态}{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($"SYST:BEEP:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.2 使蜂鸣器强制鸣叫一声
|
||||
/// </summary>
|
||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:BEEP{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR:COUN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:LOC{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.8 设置电源为远程控制模式 (Remote 状态)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:REM{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.9 通过通信接口设置电源为远程控制锁定模式
|
||||
/// </summary>
|
||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:RWL{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
575
DeviceCommand/Devices/N69200.cs
Normal file
575
DeviceCommand/Devices/N69200.cs
Normal file
@@ -0,0 +1,575 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class N69200 : Tcp
|
||||
{
|
||||
// 手册第 15 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 N69200 电子负载通信参数。
|
||||
/// </summary>
|
||||
public N69200(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列 (*CLS)
|
||||
/// </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($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电子负载设备识别字符串 (*IDN?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电子负载恢复到出厂默认配置状态 (*RST)
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器 (*STB?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前系统参数到指定的存储单元 (1~100)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
if (单元 < 1 || 单元 > 100)
|
||||
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||
|
||||
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用指定存储单元中保存的系统参数 (1~100)
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
if (单元 < 1 || 单元 > 100)
|
||||
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||
|
||||
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 电子负载模式切换及拉载开关
|
||||
|
||||
/// <summary>
|
||||
/// 控制电子负载的输入控制开关(INPut:STATe <bool>)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "1" : "0";
|
||||
await SendAsync($"INPut:STATe {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前的拉载开关状态 (返回 0 或 1)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"INPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的基本工作模式 (INPut:FUNCtion <mode>)
|
||||
/// (支持 CC, CV, CR, CP, CCD, CVD, CRD, CPD, OCP, OPP, ESR, MPP, SWEEP 等)
|
||||
/// </summary>
|
||||
public enum DeviceWorkMode
|
||||
{
|
||||
CC,
|
||||
CV,
|
||||
CR,
|
||||
CP,
|
||||
CCD,
|
||||
ESR,
|
||||
AUTO,
|
||||
DISCHARGE,
|
||||
CHARGE,
|
||||
OCP,
|
||||
CVD,
|
||||
CRD,
|
||||
MPP,
|
||||
CVCC,
|
||||
CRCC,
|
||||
CPCC,
|
||||
CVCR,
|
||||
WAVE,
|
||||
SWEEP,
|
||||
OPP,
|
||||
CPD,
|
||||
SZ
|
||||
}
|
||||
public virtual async Task 设置负载模式(DeviceWorkMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"INPut:FUNCtion {Enum.GetName(模式)}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前处于何种工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"INPut:FUNCtion?{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, "STATic:CC:HIGH:LEVel {0:F4}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电流模式大量程下设定的拉载电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CC:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电压模式(CV)大量程下的拉载电压目标值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CV:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电压模式大量程下设定的拉载电压值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CV:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒功率模式(CP)大量程下的拉载功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CP:HIGH:LEVel {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒功率模式大量程下设定的拉载功率值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CP:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电阻模式(CR)大量程下的等效拉载电阻阻值 (单位: Ω)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CR:HIGH:LEVel {0:F4}{1}", 电阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电阻模式大量程下设定的拉载电阻值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CR:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护限制参数设置
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的电流软保护阈值 (单位: A, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的功率软保护阈值 (单位: W, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:POWer:PROTection {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的电压软保护阈值 (单位: V, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:VOLTage:PROTection {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 系统控制及报警清除
|
||||
|
||||
/// <summary>
|
||||
/// 读取负载报警/事件寄存器信息 (MEASure:EVENT?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询报警事件信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除负载当前的保护触发锁定状态 (INPut:CLearPROTect)
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"INPut:CLearPROTect{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7. 手册第 112~124 页中的原子控制命令
|
||||
|
||||
#region 组合测试 (COMBination:*)
|
||||
|
||||
public virtual async Task 设置组合量程_CVCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压速率_CVCC(int 速率, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCC:HIGH:RATE {速率}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压大量程电压_CVCC(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CVCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CRCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CRCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒阻小量程电阻_CRCC(double 电阻, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:LEVel {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CRCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置上升斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置下降斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CPCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CPCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒功率小量程功率_CPCC(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:LOW:LEVel {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CPCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CVCR(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCR:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压大量程电压_CVCR(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电阻_CVCR(double 电阻, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:CLIMit {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 瞬态测试 (DYNAmic:*)
|
||||
|
||||
public virtual async Task 设置瞬态运行方式_CC(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CC:MODe {模式}{ScpiDelimiter}", ct); // 0-连续, 1-单次, 2-A/B
|
||||
|
||||
public virtual async Task 设置瞬态量程_CC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值电流_CC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:MAIN {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态值电流_CC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:TRANSient {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态上升斜率_CC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态下降斜率_CC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态运行方式_CV(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CV:MODe {模式}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态量程_CV(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CV:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值电压_CV(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:MAIN {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态值电压_CV(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:TRANSient {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 充放电测试 (DISCHarge:* / CHARge:*)
|
||||
|
||||
public virtual async Task 设置放电电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:TIMe {0:F0}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止容量(double 容量_Ah, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CAPacity {0:F3}{1}", 容量_Ah, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询放电实时时间(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"DISCHarge:ECHO:TIMe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 查询放电实时容量(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"DISCHarge:ECHo:CAPacity?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置充电电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置充电电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置充电恒压时间(int 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync($"CHARge:TIMe {时间_s}{ScpiDelimiter}", ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region OCP / OPP 保护扫描测试 (OCP:* / OPP:*)
|
||||
|
||||
public virtual async Task 设置OCP初始电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:BCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP步进电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:SCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP单步时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:Time {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询OCP状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OCP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取OCP结果电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OCP:RCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置OPP初始功率(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:BPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP步进功率(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:SPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP单步时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询OPP状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OPP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取OPP结果功率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OPP:RPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 扫频 SWEEP 与 波形拉载 WAVE (SWEEP:* / WAVE:*)
|
||||
|
||||
public virtual async Task 设置扫频最小电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:TCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频最大电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:MCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频起始频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:SFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频结束频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:EFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频单步时间(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:STPTime {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频占空比(double 占空比, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:DUTYcycle {0:F1}{1}", 占空比, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 读取扫频当前运行频率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"SWEEP:CFRequency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取扫频测试结果(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"SWEEP:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置波形量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"WAVE:RANGe {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置波形类型(int 类型, CancellationToken ct = default)
|
||||
=> await SendAsync($"WAVE:TYPe {类型}{ScpiDelimiter}", ct); // 0-正弦波, 1-方波, 2-三角波, 3-锯齿波, 4-自定义
|
||||
|
||||
public virtual async Task 设置波形频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:FREQuency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形幅值(double 幅值, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:SETValue {0:F3}{1}", 幅值, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形时间(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:TIMe {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形叠加值(double 叠加值, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:BVALue {0:F3}{1}", 叠加值, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内阻测试 ESR (ESR:*)
|
||||
|
||||
public virtual async Task 设置ESR带载量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:LOADRange {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置ESR大量程带载电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "ESR:CURRenthigh {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置ESR测量方法(int 方法, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:MODE {方法}{ScpiDelimiter}", ct); // 0-方波平均法, 1-单脉冲法
|
||||
|
||||
public virtual async Task 设置ESR采样量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:MEASurerange {量程}{ScpiDelimiter}", ct); // 0-1000mV, 1-100mV, 2-10mV
|
||||
|
||||
public virtual async Task<string> 查询ESR状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"ESR:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取ESR结果(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"ESR:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region MPPT 测试 (MPP:*)
|
||||
|
||||
public virtual async Task 设置MPPT步进电压(double 电压_V, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:BVOLtage {0:F3}{1}", 电压_V, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置MPPT步进时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置MPPT模式(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"MPP:MODE {模式}{ScpiDelimiter}", ct); // 0-扫描模式, 1-跟踪模式
|
||||
|
||||
public virtual async Task<string> 查询MPPT状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:STATus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率电压(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT开路电压(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:OVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT短路电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:SCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
326
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
326
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class SDS2000X_HD : Tcp
|
||||
{
|
||||
// 示波器底层 Socket 字符串命令以换行符 \n 结束
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
||||
/// </summary>
|
||||
public SDS2000X_HD(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#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($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询先前操作是否完成。
|
||||
/// </summary>
|
||||
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
||||
{
|
||||
string res = await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
return res.Trim() == "1";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
||||
|
||||
/// <summary>
|
||||
/// 控制示波器开始捕获波形
|
||||
/// </summary>
|
||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止捕获波形
|
||||
/// </summary>
|
||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"TRIGger:STOP{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制示波器进入单次触发捕获模式
|
||||
/// </summary>
|
||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":TRIGger:MODE SINGle{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发一次波形采样
|
||||
/// </summary>
|
||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"::TRIGger:MODE FTRIG{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 示波器触发控制模式(符合 SDS2000X-HD 规范)。
|
||||
/// </summary>
|
||||
public enum TriggerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动触发模式 (即使无触发信号也周期性刷屏)
|
||||
/// </summary>
|
||||
AUTO,
|
||||
|
||||
/// <summary>
|
||||
/// 普通触发模式 (仅当满足触发条件时才刷新)
|
||||
/// </summary>
|
||||
NORM,
|
||||
|
||||
/// <summary>
|
||||
/// 单次触发模式 (捕捉到一次满足条件的信号后立刻 STOP)
|
||||
/// </summary>
|
||||
SINGLE
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置触发模式 (AUTO 自动, NORM 普通, SINGLE 单次)
|
||||
/// </summary>
|
||||
/// <param name="mode">触发模式枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置触发模式(TriggerMode mode, CancellationToken ct = default)
|
||||
{
|
||||
// 例如发送:TRMD AUTO\n、TRMD NORM\n 或 TRMD SINGLE\n
|
||||
string cmd = $"TRMD {mode}{ScpiDelimiter}";
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||
|
||||
/// <summary>
|
||||
/// 开启或关闭指定的模拟通道 (符合手册 57 页规范)
|
||||
/// </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)
|
||||
/// </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>
|
||||
/// 查询指定通道当前的电压档位
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"C{channel}:VDIV?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直偏移量 (Offset)
|
||||
/// </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>
|
||||
/// 示波器通道输入阻抗类型。
|
||||
/// </summary>
|
||||
public enum ImpedanceType
|
||||
{
|
||||
/// <summary>50Ω 阻抗</summary>
|
||||
FIFty,
|
||||
|
||||
/// <summary>1MΩ 阻抗</summary>
|
||||
ONEM
|
||||
}
|
||||
public virtual async Task 设置通道阻抗(int channel, ImpedanceType impedance, CancellationToken ct = default)
|
||||
{
|
||||
// 例如发送:C1:IMPedance FIFty\n 或 C1:IMPedance ONEM\n
|
||||
string cmd = $"CHANnel{channel}:IMPedance {impedance}{ScpiDelimiter}";
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. Timebase 水平时基子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的水平时基档位 (Time/Div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. Trigger 触发子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的电平值 (Trigger Level)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:EDGE: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($"TRSE EDGE,SR,{source.ToUpper()}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. Measure 自动测量参数回读
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道自动测量项的当前实时测量数值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||
{
|
||||
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
|
||||
|
||||
#region 7. 屏幕截图导出子系统
|
||||
/// 【一键获取并保存截图】
|
||||
/// 自动下发 :PRINt? PNG 指令,接收示波器返回的纯 PNG 二进制流并直接保存到指定路径。
|
||||
/// </summary>
|
||||
/// <param name="saveFilePath">本地绝对保存路径 (例如: @"D:\Oscilloscope\Screen_01.png")</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
/// <returns>返回是否获取并保存成功</returns>
|
||||
public virtual async Task<bool> 获取屏幕图像并保存(string saveFilePath, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 调用我们在基类 Tcp 中全新实现的 ReadRawAsync
|
||||
// 它会在内部下发 ":PRINt? PNG\n",自动接收完整的网络字节流
|
||||
byte[] pureImageBytes = await ReadAllBytesAsync($":PRINt? PNG{ScpiDelimiter}", ct);
|
||||
|
||||
// 2. 校验返回数据
|
||||
if (pureImageBytes == null || pureImageBytes.Length < 8)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("错误:接收到的图片数据为空或长度不足。");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 核心校验:验证是否为标准的 PNG 文件格式 (PNG 头通常为 89 50 4E 47)
|
||||
if (pureImageBytes[0] != 0x89 || pureImageBytes[1] != 'P' || pureImageBytes[2] != 'N' || pureImageBytes[3] != 'G')
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("错误:接收到的二进制数据不符合标准 PNG 格式头!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 如果传入的目标文件夹路径不存在,自动帮其创建
|
||||
string directory = Path.GetDirectoryName(saveFilePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// 5. 使用文件流直接将 PNG 字节数据落地写入本地硬盘
|
||||
using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
await fs.WriteAsync(pureImageBytes, 0, pureImageBytes.Length, ct);
|
||||
await fs.FlushAsync(ct); // 强制刷新,确保数据完整落地
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"成功:截图已保存至路径: {saveFilePath}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"崩溃:保存截图时发生未知异常: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
259
DeviceCommand/Devices/SPAW7000.cs
Normal file
259
DeviceCommand/Devices/SPAW7000.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class SPAW7000 : Tcp
|
||||
{
|
||||
//只有两个,八台产品公用两两分组各用一个
|
||||
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 SPAW7000 功率分析记录仪通信参数。
|
||||
/// </summary>
|
||||
public SPAW7000(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 查询错误信息 发hsl居然可以但是手册上没有而且软件上也用不了这个命令
|
||||
/// </summary>
|
||||
//public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
//{
|
||||
// string query = string.Format(":SYSTem:ERRor?", ScpiDelimiter);
|
||||
// return await WriteReadAsync(query, 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. NUMeric 核心数据测量与轮询 (动态单项读取)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 先配置 ITEM1 为指定通道的电压有效值 (URMS)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 URMS,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 再读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的电流有效值 (IRMS)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 IRMS,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的有功功率 (P)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 P,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的电压频率 (FU)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 FU,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的功率因数 (Power Factor)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的功率因数 (LAMBda)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 LAMBda,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. INPut 通道电气参数配置
|
||||
/// <summary>
|
||||
/// SPAW7000 电压量程枚举 (单位: V)
|
||||
/// </summary>
|
||||
public enum VoltageRange
|
||||
{
|
||||
V_15 = 15,
|
||||
V_30 = 30,
|
||||
V_60 = 60,
|
||||
V_100 = 100,
|
||||
V_150 = 150,
|
||||
V_300 = 300,
|
||||
V_600 = 600,
|
||||
V_1000 = 1000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SPAW7000 电流量程枚举 (单位: A)
|
||||
/// </summary>
|
||||
public enum CurrentRange
|
||||
{
|
||||
A_1 = 1,
|
||||
A_2 = 2,
|
||||
A_5 = 5,
|
||||
A_10 = 10,
|
||||
A_20 = 20,
|
||||
A_50 = 50
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置指定通道的电压量程
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||
/// <param name="range">电压量程枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置电压量程(int channel, VoltageRange range, CancellationToken ct = default)
|
||||
{
|
||||
// 正确格式例如: :INPut:VOLTage:RANGe:ELEMent1 300\n
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||
":INPut:VOLTage:RANGe:ELEMent{0} {1}{2}",
|
||||
channel, (int)range, ScpiDelimiter);
|
||||
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电流量程
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||
/// <param name="range">电流量程枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置电流量程(int channel, CurrentRange range, CancellationToken ct = default)
|
||||
{
|
||||
// 正确格式例如: :INPut:CURRent:RANGe:ELEMent1 5\n
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||
":INPut:CURRent:RANGe:ELEMent{0} {1}{2}",
|
||||
channel, (int)range, 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
|
||||
}
|
||||
}
|
||||
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using Common.Attributes;
|
||||
using NModbus;
|
||||
using NModbus.Serial;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FModbusRTU
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static SerialPort CreatePort(
|
||||
string portName,
|
||||
int baudRate,
|
||||
int dataBits,
|
||||
StopBits stopBits,
|
||||
Parity parity,
|
||||
int readTimeout,
|
||||
int writeTimeout)
|
||||
{
|
||||
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||
{
|
||||
ReadTimeout = readTimeout > 0 ? readTimeout : SerialPort.InfiniteTimeout,
|
||||
WriteTimeout = writeTimeout > 0 ? writeTimeout : SerialPort.InfiniteTimeout
|
||||
};
|
||||
}
|
||||
|
||||
private static IModbusMaster CreateMaster(SerialPort port)
|
||||
{
|
||||
return new ModbusFactory().CreateRtuMaster(port);
|
||||
}
|
||||
|
||||
#region Holding Register
|
||||
|
||||
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (readTimeout > 0)
|
||||
cts.CancelAfter(readTimeout);
|
||||
|
||||
return await master
|
||||
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleRegisterAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort registerAddress,
|
||||
ushort value,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (writeTimeout > 0)
|
||||
cts.CancelAfter(writeTimeout);
|
||||
|
||||
await master
|
||||
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coil
|
||||
|
||||
public static async Task<bool[]> ReadCoilsAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (readTimeout > 0)
|
||||
cts.CancelAfter(readTimeout);
|
||||
|
||||
return await master
|
||||
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleCoilAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort coilAddress,
|
||||
bool value,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (writeTimeout > 0)
|
||||
cts.CancelAfter(writeTimeout);
|
||||
|
||||
await master
|
||||
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout), cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Common.Attributes;
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FModbusTCP
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static async Task<IModbusMaster> ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct)
|
||||
{
|
||||
var tcpClient = new TcpClient();
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (sendTimeout > 0)
|
||||
cts.CancelAfter(sendTimeout);
|
||||
|
||||
await tcpClient.ConnectAsync(ipAddress, port, cts.Token);
|
||||
return new ModbusFactory().CreateMaster(tcpClient);
|
||||
}
|
||||
|
||||
#region Holding Registers
|
||||
|
||||
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
var result = await ((IModbusMaster)master)
|
||||
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleRegisterAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort registerAddress,
|
||||
ushort value,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
await ((IModbusMaster)master)
|
||||
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coils
|
||||
|
||||
public static async Task<bool[]> ReadCoilsAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
var result = await ((IModbusMaster)master)
|
||||
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleCoilAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort coilAddress,
|
||||
bool value,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
await ((IModbusMaster)master)
|
||||
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FSerialPort
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static SerialPort CreatePort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int sendTimeout,int receiveTimeout)
|
||||
{
|
||||
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
WriteTimeout = sendTimeout > 0 ? sendTimeout : SerialPort.InfiniteTimeout,
|
||||
ReadTimeout = receiveTimeout > 0 ? receiveTimeout : SerialPort.InfiniteTimeout
|
||||
};
|
||||
}
|
||||
|
||||
#region 最常用:只发送字符串
|
||||
|
||||
public static async Task SendAsync(string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (sendTimeout > 0)
|
||||
cts.CancelAfter(sendTimeout);
|
||||
|
||||
var bytes = port.Encoding.GetBytes(command);
|
||||
await port.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
||||
await port.BaseStream.FlushAsync(cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 最常用:发送字符串并读取字符串
|
||||
|
||||
public static async Task<string> SendReadAsync(string portName,int baudRate, Parity parity, int dataBits, StopBits stopBits,int sendTimeout, int receiveTimeout,string command,string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
// Send
|
||||
var sendBytes = port.Encoding.GetBytes(command);
|
||||
await port.BaseStream.WriteAsync(sendBytes, 0, sendBytes.Length, ct);
|
||||
await port.BaseStream.FlushAsync(ct);
|
||||
|
||||
// Read
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[256];
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (receiveTimeout > 0)
|
||||
cts.CancelAfter(receiveTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
int read = await port.BaseStream.ReadAsync(
|
||||
buffer, 0, buffer.Length, cts.Token);
|
||||
|
||||
if (read == 0)
|
||||
break;
|
||||
|
||||
sb.Append(port.Encoding.GetString(buffer, 0, read));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
|
||||
throw new TimeoutException("串口读取超时");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
115
DeviceCommand/Flexible/FTCP.cs
Normal file
115
DeviceCommand/Flexible/FTCP.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Common.Attributes;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FTCP
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
#region Send
|
||||
|
||||
public static async Task SendAsync(string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.SendTimeout = sendTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.GetStream();
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct).WaitAsync(TimeSpan.FromMilliseconds(sendTimeout));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static Task SendAsync(string ipAddress, int port,int sendTimeout, string text,CancellationToken ct = default)
|
||||
{
|
||||
return SendAsync( ipAddress, port, sendTimeout, Encoding.UTF8.GetBytes(text),ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
public static async Task<byte[]> ReadAsync(string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.ReceiveTimeout = receiveTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.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 buffer[..offset];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> ReadLineAsync( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.ReceiveTimeout = receiveTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.GetStream();
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
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) break;
|
||||
|
||||
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("读取超时或对端关闭");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user