设备指令移植
This commit is contained in:
parent
7adb7db2b8
commit
b16533be6a
183
DeviceCommand/Base/ModbusRtu.cs
Normal file
183
DeviceCommand/Base/ModbusRtu.cs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
using NModbus;
|
||||||
|
using NModbus.Serial;
|
||||||
|
using System;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Base
|
||||||
|
{
|
||||||
|
public class ModbusRtu
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
public SerialPort SerialPort { get; private set; } = new SerialPort();
|
||||||
|
public IModbusMaster Modbus { get; private set; }
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
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 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 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
|
||||||
|
{
|
||||||
|
var writeTask = Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(WriteTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(writeTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU写单寄存器超时");
|
||||||
|
await writeTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var writeTask = Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(WriteTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(writeTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU写多寄存器超时");
|
||||||
|
await writeTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var readTask = Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReadTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU读取保持寄存器超时");
|
||||||
|
return await readTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var writeTask = Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(WriteTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(writeTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU写单线圈超时");
|
||||||
|
await writeTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var readTask = Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReadTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU读取线圈超时");
|
||||||
|
return await readTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var readTask = Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReadTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask)
|
||||||
|
throw new TimeoutException("ModbusRTU读取输入寄存器超时");
|
||||||
|
return await readTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,138 +9,153 @@ namespace DeviceCommand.Base
|
|||||||
{
|
{
|
||||||
public class ModbusTcp
|
public class ModbusTcp
|
||||||
{
|
{
|
||||||
public string IPAddress { get; set; } = "127.0.0.1";
|
public string IPAddress { get; private set; } = "127.0.0.1";
|
||||||
public int Port { get; set; } = 502;
|
public int Port { get; private set; } = 502;
|
||||||
public int SendTimeout { get; set; } = 3000;
|
public int SendTimeout { get; private set; } = 3000;
|
||||||
public int ReceiveTimeout { get; set; } = 3000;
|
public int ReceiveTimeout { get; private set; } = 3000;
|
||||||
public TcpClient TcpClient { get; set; } = new();
|
public TcpClient TcpClient { get; private set; } = new TcpClient();
|
||||||
public IModbusMaster Modbus { get; set; }
|
public IModbusMaster Modbus { get; private set; }
|
||||||
|
|
||||||
public ModbusTcp CreateDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
private readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||||
{
|
{
|
||||||
IPAddress = ipAddress;
|
IPAddress = ipAddress;
|
||||||
Port = port;
|
Port = port;
|
||||||
SendTimeout = sendTimeout;
|
SendTimeout = sendTimeout;
|
||||||
ReceiveTimeout = receiveTimeout;
|
ReceiveTimeout = receiveTimeout;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ChangeDeviceConfig(ModbusTcp modbusTcp, string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
modbusTcp.IPAddress = ipAddress;
|
await _commLock.WaitAsync(ct);
|
||||||
modbusTcp.Port = port;
|
try
|
||||||
if (sendTimeout > 0) modbusTcp.SendTimeout = sendTimeout;
|
{
|
||||||
if (receiveTimeout > 0) modbusTcp.ReceiveTimeout = receiveTimeout;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<bool> ConnectAsync(ModbusTcp modbusTcp, CancellationToken ct = default)
|
await TcpClient.ConnectAsync(IPAddress, Port, ct);
|
||||||
{
|
Modbus = new ModbusFactory().CreateMaster(TcpClient);
|
||||||
if (!modbusTcp.TcpClient.Connected)
|
|
||||||
{
|
|
||||||
modbusTcp.TcpClient.Close();
|
|
||||||
modbusTcp.TcpClient.Dispose();
|
|
||||||
modbusTcp.TcpClient = new TcpClient();
|
|
||||||
await modbusTcp.TcpClient.ConnectAsync(modbusTcp.IPAddress, modbusTcp.Port, ct);
|
|
||||||
modbusTcp.Modbus = new ModbusFactory().CreateMaster(modbusTcp.TcpClient);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var remoteEndPoint = (IPEndPoint)modbusTcp.TcpClient.Client.RemoteEndPoint!;
|
|
||||||
var ip = remoteEndPoint.Address.MapToIPv4().ToString();
|
|
||||||
bool isSameAddress = ip.Equals(modbusTcp.IPAddress);
|
|
||||||
bool isSamePort = remoteEndPoint.Port == modbusTcp.Port;
|
|
||||||
if (!isSameAddress || !isSamePort)
|
|
||||||
{
|
|
||||||
modbusTcp.TcpClient.Close();
|
|
||||||
modbusTcp.TcpClient.Dispose();
|
|
||||||
modbusTcp.TcpClient = new TcpClient();
|
|
||||||
await modbusTcp.TcpClient.ConnectAsync(modbusTcp.IPAddress, modbusTcp.Port, ct);
|
|
||||||
modbusTcp.Modbus = new ModbusFactory().CreateMaster(modbusTcp.TcpClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (TcpClient.Connected) TcpClient.Close();
|
||||||
|
}
|
||||||
|
|
||||||
public static void ModbusTcpInitialize(ModbusTcp modbusTcp)
|
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (!modbusTcp.TcpClient.Connected)
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
modbusTcp.TcpClient = new TcpClient();
|
var sendTask = Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).WaitAsync(ct);
|
||||||
modbusTcp.TcpClient.SendTimeout = modbusTcp.SendTimeout;
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
modbusTcp.TcpClient.ReceiveTimeout = modbusTcp.ReceiveTimeout;
|
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写单寄存器超时");
|
||||||
|
await sendTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Close(ModbusTcp modbusTcp)
|
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (modbusTcp.TcpClient.Connected)
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
modbusTcp.TcpClient.Close();
|
var sendTask = Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
|
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写多寄存器超时");
|
||||||
|
await sendTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ushort[]> ReadHoldingRegistersAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var readTask = modbusTcp.Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
try
|
||||||
|
{
|
||||||
|
var readTask = Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取保持寄存器超时");
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取保持寄存器超时");
|
||||||
return await readTask;
|
return await readTask;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public static async Task WriteSingleRegisterAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
var sendTask = modbusTcp.Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).WaitAsync(ct);
|
_commLock.Release();
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
}
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写单寄存器超时");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task WriteMultipleRegistersAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
|
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var sendTask = modbusTcp.Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values).WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
try
|
||||||
|
{
|
||||||
|
var sendTask = Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写多寄存器超时");
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写单线圈超时");
|
||||||
|
await sendTask;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<bool[]> ReadCoilAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var readTask = modbusTcp.Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
try
|
||||||
|
{
|
||||||
|
var readTask = Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取线圈超时");
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取线圈超时");
|
||||||
return await readTask;
|
return await readTask;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public static async Task WriteSingleCoilAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
var sendTask = modbusTcp.Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).WaitAsync(ct);
|
_commLock.Release();
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
}
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写单线圈超时");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task WriteMultipleCoilsAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, bool[] values, CancellationToken ct = default)
|
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var sendTask = modbusTcp.Modbus.WriteMultipleCoilsAsync(slaveAddress, startAddress, values).WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
try
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写多线圈超时");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ModbusTcpStopNow(ModbusTcp modbusTcp)
|
|
||||||
{
|
{
|
||||||
if (modbusTcp.TcpClient.Connected) modbusTcp.TcpClient.Close();
|
var readTask = Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
||||||
}
|
var timeoutTask = Task.Delay(ReceiveTimeout, ct);
|
||||||
|
|
||||||
public static async Task<ushort[]> ReadInputRegisterAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
var readTask = modbusTcp.Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct);
|
|
||||||
var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct);
|
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取输入寄存器超时");
|
if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取输入寄存器超时");
|
||||||
return await readTask;
|
return await readTask;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
using System.Text;
|
using System;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Base
|
namespace DeviceCommand.Base
|
||||||
{
|
{
|
||||||
@ -12,9 +15,12 @@ namespace DeviceCommand.Base
|
|||||||
public Parity Parity { get; set; } = Parity.None;
|
public Parity Parity { get; set; } = Parity.None;
|
||||||
public int ReadTimeout { get; set; } = 3000;
|
public int ReadTimeout { get; set; } = 3000;
|
||||||
public int WriteTimeout { get; set; } = 3000;
|
public int WriteTimeout { get; set; } = 3000;
|
||||||
public SerialPort _SerialPort { get; set; } = new SerialPort();
|
public SerialPort _SerialPort { get; private set; } = new SerialPort();
|
||||||
|
private readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
public Serial_Port CreateDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
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;
|
PortName = portName;
|
||||||
BaudRate = baudRate;
|
BaudRate = baudRate;
|
||||||
@ -23,138 +29,93 @@ namespace DeviceCommand.Base
|
|||||||
Parity = parity;
|
Parity = parity;
|
||||||
ReadTimeout = readTimeout;
|
ReadTimeout = readTimeout;
|
||||||
WriteTimeout = writeTimeout;
|
WriteTimeout = writeTimeout;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_SerialPort.IsOpen) _SerialPort.Close();
|
||||||
|
|
||||||
public static void ChangeDeviceConfig(Serial_Port serialPort, string PortName, int BaudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int ReadTimeout = 3000, int WriteTimeout = 3000)
|
_SerialPort.PortName = PortName;
|
||||||
{
|
_SerialPort.BaudRate = BaudRate;
|
||||||
serialPort.PortName = PortName;
|
_SerialPort.DataBits = DataBits;
|
||||||
serialPort.BaudRate = BaudRate;
|
_SerialPort.StopBits = StopBits;
|
||||||
serialPort.DataBits = dataBits;
|
_SerialPort.Parity = Parity;
|
||||||
serialPort.StopBits = stopBits;
|
_SerialPort.ReadTimeout = ReadTimeout;
|
||||||
serialPort.Parity = parity;
|
_SerialPort.WriteTimeout = WriteTimeout;
|
||||||
if (ReadTimeout > 0) serialPort.ReadTimeout = ReadTimeout;
|
|
||||||
if (WriteTimeout > 0) serialPort.WriteTimeout = WriteTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> ConnectAsync(Serial_Port serialPort, CancellationToken ct = default)
|
_SerialPort.Open();
|
||||||
{
|
|
||||||
if (serialPort._SerialPort.PortName != serialPort.PortName
|
|
||||||
|| serialPort._SerialPort.BaudRate != serialPort.BaudRate
|
|
||||||
|| serialPort._SerialPort.Parity != serialPort.Parity
|
|
||||||
|| serialPort._SerialPort.DataBits != serialPort.DataBits
|
|
||||||
|| serialPort._SerialPort.StopBits != serialPort.StopBits
|
|
||||||
|| serialPort._SerialPort.ReadTimeout != serialPort.ReadTimeout
|
|
||||||
|| serialPort._SerialPort.WriteTimeout != serialPort.WriteTimeout)
|
|
||||||
{
|
|
||||||
serialPort._SerialPort.Close();
|
|
||||||
serialPort._SerialPort.PortName = serialPort.PortName;
|
|
||||||
serialPort._SerialPort.BaudRate = serialPort.BaudRate;
|
|
||||||
serialPort._SerialPort.Parity = serialPort.Parity;
|
|
||||||
serialPort._SerialPort.DataBits = serialPort.DataBits;
|
|
||||||
serialPort._SerialPort.StopBits = serialPort.StopBits;
|
|
||||||
serialPort._SerialPort.ReadTimeout = serialPort.ReadTimeout;
|
|
||||||
serialPort._SerialPort.WriteTimeout = serialPort.WriteTimeout;
|
|
||||||
serialPort._SerialPort.Open();
|
|
||||||
}
|
|
||||||
else if (!serialPort._SerialPort.IsOpen)
|
|
||||||
{
|
|
||||||
serialPort._SerialPort.Open();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public static void Close(Serial_Port serialPort)
|
|
||||||
{
|
{
|
||||||
if (serialPort._SerialPort.IsOpen) serialPort._SerialPort.Close();
|
_commLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task SendAsync(Serial_Port serialPort, byte[] bytes, CancellationToken ct = default)
|
public void Close()
|
||||||
{
|
{
|
||||||
if (!serialPort._SerialPort.IsOpen) return;
|
if (_SerialPort.IsOpen) _SerialPort.Close();
|
||||||
var timeoutMs = serialPort.WriteTimeout;
|
|
||||||
if (timeoutMs <= 0)
|
|
||||||
{
|
|
||||||
serialPort._SerialPort.Write(bytes, 0, bytes.Length);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
var sendTask = Task.Run(() => serialPort._SerialPort.Write(bytes, 0, bytes.Length), ct);
|
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_SerialPort.IsOpen) return;
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
|
||||||
|
var timeoutTask = Task.Delay(WriteTimeout > 0 ? WriteTimeout : Timeout.Infinite, ct);
|
||||||
|
var sendTask = Task.Run(() => _SerialPort.Write(bytes, 0, bytes.Length), ct);
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"串口通讯异常:写入操作在 {timeoutMs} ms内未完成");
|
if (completedTask == timeoutTask) throw new TimeoutException($"写入操作在 {WriteTimeout} ms内未完成");
|
||||||
await sendTask;
|
await sendTask;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
public static async Task SendAsync(Serial_Port serialPort, string str, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
if (!serialPort._SerialPort.IsOpen) return;
|
_commLock.Release();
|
||||||
byte[] bytes = Encoding.UTF8.GetBytes(str);
|
}
|
||||||
await SendAsync(serialPort, bytes, ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<byte[]> ReadAsync(Serial_Port serialPort, byte[] buffer, CancellationToken ct = default)
|
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (!serialPort._SerialPort.IsOpen) return null;
|
await _commLock.WaitAsync(ct);
|
||||||
var timeoutMs = serialPort.ReadTimeout;
|
try
|
||||||
var readTask = ReadByte(serialPort, buffer, ct);
|
{
|
||||||
if (timeoutMs <= 0) return await readTask;
|
if (!_SerialPort.IsOpen) return null;
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"串口通讯异常:读取操作在 {timeoutMs} ms内未完成");
|
|
||||||
return await readTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]> ReadByte(Serial_Port serialPort, byte[] buffer, CancellationToken ct)
|
|
||||||
{
|
|
||||||
int bytesRead = 0;
|
|
||||||
while (bytesRead < buffer.Length)
|
|
||||||
{
|
|
||||||
if (serialPort._SerialPort.BytesToRead > 0)
|
|
||||||
{
|
|
||||||
bytesRead += serialPort._SerialPort.Read(buffer, bytesRead, buffer.Length - bytesRead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> ReadAsync(Serial_Port serialPort, string delimiter = "\n", CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
if (!serialPort._SerialPort.IsOpen) return null;
|
|
||||||
var timeoutMs = serialPort.ReadTimeout;
|
|
||||||
var readTask = ReadDefaultString(serialPort, delimiter, ct);
|
|
||||||
if (timeoutMs <= 0) return await readTask;
|
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask) throw new TimeoutException($"串口通讯异常:读取操作在 {timeoutMs} ms内未完成");
|
|
||||||
return await readTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<string> ReadDefaultString(Serial_Port serialPort, string delimiter, CancellationToken ct)
|
|
||||||
{
|
|
||||||
delimiter ??= "\n";
|
delimiter ??= "\n";
|
||||||
MemoryStream memoryStream = new();
|
var sb = new StringBuilder();
|
||||||
byte[] buffer = new byte[2048];
|
byte[] buffer = new byte[1024];
|
||||||
string data = string.Empty;
|
|
||||||
await Task.Run(() =>
|
while (!ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
while (true)
|
if (_SerialPort.BytesToRead > 0)
|
||||||
{
|
{
|
||||||
if (ct.IsCancellationRequested) return;
|
int bytesRead = _SerialPort.Read(buffer, 0, buffer.Length);
|
||||||
if (serialPort._SerialPort.BytesToRead > 0)
|
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||||
|
|
||||||
|
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
int bytesRead = serialPort._SerialPort.Read(buffer, 0, buffer.Length);
|
return sb.ToString(0, index).Trim();
|
||||||
memoryStream.Write(buffer, 0, bytesRead);
|
|
||||||
data = Encoding.UTF8.GetString(memoryStream.ToArray());
|
|
||||||
int lineEndIndex = data.IndexOf(delimiter);
|
|
||||||
if (lineEndIndex >= 0)
|
|
||||||
{
|
|
||||||
data = data.Substring(0, lineEndIndex).Trim();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(10, ct); // 避免 CPU 空转
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OperationCanceledException("读取被取消");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
}
|
}
|
||||||
}, ct);
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,169 +1,160 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Base
|
namespace DeviceCommand.Base
|
||||||
{
|
{
|
||||||
public class Tcp
|
public class Tcp
|
||||||
{
|
{
|
||||||
public string IPAddress { get; set; } = "127.0.0.1";
|
public string IPAddress { get; private set; } = "127.0.0.1";
|
||||||
public int Port { get; set; } = 502;
|
public int Port { get; private set; } = 502;
|
||||||
public int SendTimeout { get; set; } = 3000;
|
public int SendTimeout { get; private set; } = 3000;
|
||||||
public int ReceiveTimeout { get; set; } = 3000;
|
public int ReceiveTimeout { get; private set; } = 3000;
|
||||||
public TcpClient TcpClient { get; set; } = new();
|
public TcpClient TcpClient { get; private set; } = new TcpClient();
|
||||||
|
private readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
public Tcp CreateDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||||
{
|
{
|
||||||
IPAddress = ipAddress;
|
IPAddress = ipAddress;
|
||||||
Port = port;
|
Port = port;
|
||||||
SendTimeout = sendTimeout;
|
SendTimeout = sendTimeout;
|
||||||
ReceiveTimeout = receiveTimeout;
|
ReceiveTimeout = receiveTimeout;
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ChangeDeviceConfig(Tcp tcp, string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
|
||||||
{
|
|
||||||
tcp.IPAddress = ipAddress;
|
|
||||||
tcp.Port = port;
|
|
||||||
if (sendTimeout > 0) tcp.SendTimeout = sendTimeout;
|
|
||||||
if (receiveTimeout > 0) tcp.ReceiveTimeout = receiveTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> ConnectAsync(Tcp tcp, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
if (!tcp.TcpClient.Connected)
|
|
||||||
{
|
|
||||||
tcp.TcpClient.Close();
|
|
||||||
tcp.TcpClient.Dispose();
|
|
||||||
tcp.TcpClient = new TcpClient();
|
|
||||||
await tcp.TcpClient.ConnectAsync(tcp.IPAddress, tcp.Port, ct);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var remoteEndPoint = (IPEndPoint)tcp.TcpClient.Client.RemoteEndPoint!;
|
|
||||||
var ip = remoteEndPoint.Address.MapToIPv4().ToString();
|
|
||||||
bool isSameAddress = ip.Equals(tcp.IPAddress);
|
|
||||||
bool isSamePort = remoteEndPoint.Port == tcp.Port;
|
|
||||||
|
|
||||||
if (!isSameAddress || !isSamePort)
|
|
||||||
{
|
|
||||||
tcp.TcpClient.Close();
|
|
||||||
tcp.TcpClient.Dispose();
|
|
||||||
tcp.TcpClient = new TcpClient();
|
|
||||||
await tcp.TcpClient.ConnectAsync(tcp.IPAddress, tcp.Port, ct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Close(Tcp tcp)
|
|
||||||
{
|
|
||||||
tcp.TcpClient.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SendAsync(Tcp tcp, byte[] bytes, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
var timeoutMs = tcp.SendTimeout;
|
|
||||||
if (timeoutMs <= 0)
|
|
||||||
{
|
|
||||||
await tcp.TcpClient.Client.SendAsync(bytes, ct);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sendTask = tcp.TcpClient.Client.SendAsync(bytes, ct).AsTask();
|
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask)
|
|
||||||
throw new TimeoutException($"TCP通讯异常:写入操作在 {timeoutMs} ms内未完成");
|
|
||||||
|
|
||||||
await sendTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SendAsync(Tcp tcp, string str, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync(tcp, Encoding.UTF8.GetBytes(str), ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]> ReadAsync(Tcp tcp, byte[] buffer, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
if (!tcp.TcpClient.Connected) return null;
|
|
||||||
|
|
||||||
var timeoutMs = tcp.ReceiveTimeout;
|
|
||||||
if (timeoutMs <= 0)
|
|
||||||
return await ReadBytes(tcp, buffer, ct);
|
|
||||||
|
|
||||||
var readTask = ReadBytes(tcp, buffer, ct);
|
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask)
|
|
||||||
throw new TimeoutException($"TCP通讯异常:读取操作在 {timeoutMs} ms内未完成");
|
|
||||||
|
|
||||||
return await readTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<byte[]> ReadBytes(Tcp tcp, byte[] buffer, CancellationToken ct)
|
|
||||||
{
|
|
||||||
NetworkStream stream = tcp.TcpClient.GetStream();
|
|
||||||
int bytesRead = 0;
|
|
||||||
while (bytesRead < buffer.Length)
|
|
||||||
{
|
|
||||||
int read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead, ct);
|
|
||||||
if (read == 0) return null;
|
|
||||||
bytesRead += read;
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> ReadAsync(Tcp tcp, string delimiter = "\n", CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
delimiter ??= "\n";
|
|
||||||
var timeoutMs = tcp.ReceiveTimeout;
|
|
||||||
if (timeoutMs <= 0)
|
|
||||||
return await ReadString(tcp, delimiter, ct);
|
|
||||||
|
|
||||||
var readTask = ReadString(tcp, delimiter, ct);
|
|
||||||
var timeoutTask = Task.Delay(timeoutMs, ct);
|
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(readTask, timeoutTask);
|
|
||||||
if (completedTask == timeoutTask)
|
|
||||||
throw new TimeoutException($"TCP通讯异常:读取操作在 {timeoutMs} ms内未完成");
|
|
||||||
|
|
||||||
return await readTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<string> ReadString(Tcp tcp, string delimiter, CancellationToken ct)
|
|
||||||
{
|
|
||||||
NetworkStream stream = tcp.TcpClient.GetStream();
|
|
||||||
MemoryStream memoryStream = new();
|
|
||||||
byte[] buffer = new byte[2048];
|
|
||||||
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = await stream.ReadAsync(buffer, ct)) > 0)
|
|
||||||
{
|
|
||||||
memoryStream.Write(buffer, 0, bytesRead);
|
|
||||||
string data = Encoding.UTF8.GetString(memoryStream.ToArray());
|
|
||||||
int lineEndIndex = data.IndexOf(delimiter);
|
|
||||||
if (lineEndIndex >= 0)
|
|
||||||
return data[..lineEndIndex].Trim();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> WriteRead(Tcp tcp, string str, string endstr, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync(tcp, str, ct);
|
|
||||||
return await ReadAsync(tcp, endstr, ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await ConnectAsync(this, ct);
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if(TcpClient.Connected) TcpClient.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!TcpClient.Connected) return;
|
||||||
|
NetworkStream stream = TcpClient.GetStream();
|
||||||
|
|
||||||
|
var timeoutTask = Task.Delay(SendTimeout > 0 ? SendTimeout : Timeout.Infinite, ct);
|
||||||
|
var sendTask = stream.WriteAsync(buffer, 0, buffer.Length, ct);
|
||||||
|
|
||||||
|
var completedTask = await Task.WhenAny(sendTask, timeoutTask);
|
||||||
|
if (completedTask == timeoutTask) throw new TimeoutException($"写入操作在 {SendTimeout} ms 内未完成");
|
||||||
|
|
||||||
|
await sendTask;
|
||||||
|
}
|
||||||
|
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 (!TcpClient.Connected) return null!;
|
||||||
|
NetworkStream stream = TcpClient.GetStream();
|
||||||
|
byte[] buffer = new byte[length];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||||
|
|
||||||
|
while (offset < length)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
||||||
|
if (read == 0) break;
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset == 0) return null!;
|
||||||
|
return buffer[..offset];
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!TcpClient.Connected) return null!;
|
||||||
|
delimiter ??= "\n";
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
NetworkStream stream = TcpClient.GetStream();
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
||||||
|
|
||||||
|
while (!cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (stream.DataAvailable)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(10, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException("读取超时或对端关闭");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync(command, ct);
|
||||||
|
return await ReadAsync(delimiter, ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,427 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Device
|
||||||
{
|
{
|
||||||
|
//是德
|
||||||
public class E36233A:Tcp
|
public class E36233A:Tcp
|
||||||
{
|
{
|
||||||
|
public E36233A(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
|
||||||
|
{
|
||||||
|
ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
|
||||||
|
}
|
||||||
|
#region SCPI 常用命令方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询仪器标识,返回制造商、型号、序列号、固件版本
|
||||||
|
/// </summary>
|
||||||
|
public Task<string> QueryIDAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return SendCommandAndReadAsync("*IDN?", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 恢复出厂设置,不清除错误队列
|
||||||
|
/// </summary>
|
||||||
|
public Task SendResetAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return SendAsync("*RST", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 存储当前仪器状态到指定位置(0~9)
|
||||||
|
/// </summary>
|
||||||
|
public Task SaveStateAsync(int slot, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (slot < 0 || slot > 9) throw new ArgumentOutOfRangeException(nameof(slot));
|
||||||
|
return SendAsync($"*SAV {slot}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 调用指定位置的仪器状态(0~9)
|
||||||
|
/// </summary>
|
||||||
|
public Task RecallStateAsync(int slot, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (slot < 0 || slot > 9) throw new ArgumentOutOfRangeException(nameof(slot));
|
||||||
|
return SendAsync($"*RCL {slot}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作完成标记,发送命令等待操作完成
|
||||||
|
/// </summary>
|
||||||
|
public Task SendOPCAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return SendAsync("*OPC", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询操作完成标记,返回 "1" 表示完成
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> QueryOPCAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await SendCommandAndReadAsync("*OPC?", ct);
|
||||||
|
return result == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除状态寄存器与错误队列
|
||||||
|
/// </summary>
|
||||||
|
public Task ClearStatusAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return SendAsync("*CLS", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询状态字节(STB),返回状态值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> QueryStatusByteAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await SendCommandAndReadAsync("*STB?", ct);
|
||||||
|
return int.TryParse(result, out var value) ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通用方法:发送命令并读取响应
|
||||||
|
/// </summary>
|
||||||
|
private async Task<string> SendCommandAndReadAsync(string command, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"{command}\r\n", ct);
|
||||||
|
return await ReadAsync("\n", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 通道与输出控制命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选择操作通道
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">通道号 1 或 2</param>
|
||||||
|
public Task SelectChannelAsync(int channel, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (channel < 1 || channel > 2) throw new ArgumentOutOfRangeException(nameof(channel));
|
||||||
|
return SendAsync($"INST:SEL CH{channel}\r\n", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开启/关闭输出
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">true 表示开,false 表示关</param>
|
||||||
|
/// <param name="channels">通道列表,如 "1", "1:2"</param>
|
||||||
|
public Task SetOutputStateAsync(bool state, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(channels)) throw new ArgumentNullException(nameof(channels));
|
||||||
|
string cmd = $"OUTP {(state ? "ON" : "OFF")}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出上升延迟设置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delay">延迟时间 0~3600 秒</param>
|
||||||
|
/// <param name="channels">通道列表,如 "1", "1:2"</param>
|
||||||
|
public Task SetOutputRiseDelayAsync(double delay, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (delay < 0 || delay > 3600) throw new ArgumentOutOfRangeException(nameof(delay));
|
||||||
|
string cmd = $"OUTP:DEL:RISE {delay}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出下降延迟设置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delay">延迟时间 0~3600 秒</param>
|
||||||
|
/// <param name="channels">通道列表,如 "1", "1:2"</param>
|
||||||
|
public Task SetOutputFallDelayAsync(double delay, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (delay < 0 || delay > 3600) throw new ArgumentOutOfRangeException(nameof(delay));
|
||||||
|
string cmd = $"OUTP:DEL:FALL {delay}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置串/并联模式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">OFF(独立)、SER(串联)、PAR(并联)</param>
|
||||||
|
public Task SetOutputPairModeAsync(string mode, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (mode != "OFF" && mode != "SER" && mode != "PAR")
|
||||||
|
throw new ArgumentException("模式必须为 OFF, SER, PAR", nameof(mode));
|
||||||
|
return SendAsync($"OUTP:PAIR {mode}\r\n", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通道耦合控制
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelList">ALL(全耦合)、NONE(无耦合)、或指定通道,如 @1:2</param>
|
||||||
|
public Task SetChannelCoupleAsync(string channelList, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(channelList)) throw new ArgumentNullException(nameof(channelList));
|
||||||
|
return SendAsync($"OUTP:COUP:CHAN {channelList}\r\n", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 电压 / 电流参数设置命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置立即电压(单通道或多通道)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetVoltageAsync(double voltage, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (voltage < 0 || voltage > 30.9) throw new ArgumentOutOfRangeException(nameof(voltage));
|
||||||
|
string cmd = $"SOUR:VOLT {voltage}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置立即电流(单通道或多通道)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetCurrentAsync(double current, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (current < 0 || current > 20.6) throw new ArgumentOutOfRangeException(nameof(current));
|
||||||
|
string cmd = $"SOUR:CURR {current}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置触发电压(步进/序列模式使用)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetTriggeredVoltageAsync(double voltage, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:TRIG {voltage}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置触发电流(步进/序列模式使用)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetTriggeredCurrentAsync(double current, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:CURR:TRIG {current}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置电压模式(FIX/STEP/LIST)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetVoltageModeAsync(string mode, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (mode != "FIX" && mode != "STEP" && mode != "LIST") throw new ArgumentException("模式必须为 FIX, STEP, LIST", nameof(mode));
|
||||||
|
string cmd = $"SOUR:VOLT:MODE {mode}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置电流模式(FIX/STEP/LIST)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetCurrentModeAsync(string mode, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (mode != "FIX" && mode != "STEP" && mode != "LIST") throw new ArgumentException("模式必须为 FIX, STEP, LIST", nameof(mode));
|
||||||
|
string cmd = $"SOUR:CURR:MODE {mode}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过压保护 OVP(110% 最大电压内)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOVPAsync(double voltage, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:PROT {voltage}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过流保护 OCP(110% 最大电流内)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOCPAsync(double current, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:CURR:PROT {current}, (@{channels})\r\n";
|
||||||
|
return SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 测量与状态查询命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测量指定通道的实际电压
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> MeasureVoltageAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"MEAS:VOLT? (@{channels})\r\n";
|
||||||
|
string result = await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
return double.TryParse(result, out var voltage) ? voltage : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测量指定通道的实际电流
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> MeasureCurrentAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"MEAS:CURR? (@{channels})\r\n";
|
||||||
|
string result = await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
return double.TryParse(result, out var current) ? current : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询错误队列(FIFO 返回下一个错误)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> QueryNextErrorAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = "SYST:ERR? \r\n";
|
||||||
|
return await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询 OVP(过压保护)状态,返回 true 表示触发
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> QueryOVPTrippedAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:PROT:TRIP? (@{channels})\r\n";
|
||||||
|
string result = await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
return result == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询 OCP(过流保护)状态,返回 true 表示触发
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> QueryOCPTrippedAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:CURR:PROT:TRIP? (@{channels})\r\n";
|
||||||
|
string result = await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
return result == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询远程 / 本地模式,返回 LOC / REM / RWL
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> QueryRemoteLocalStateAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = "SYST:COMM:RLST?\r\n";
|
||||||
|
return await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 电压/电流序列设置与触发
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置通道的电压序列
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="voltages">电压序列,逗号分隔</param>
|
||||||
|
/// <param name="channels">通道列表,如 "1" 或 "1:2"</param>
|
||||||
|
public async Task SetVoltageListAsync(double[] voltages, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string values = string.Join(",", voltages);
|
||||||
|
string cmd = $"SOUR:LIST:VOLT {values}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置通道的电流序列
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetCurrentListAsync(double[] currents, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string values = string.Join(",", currents);
|
||||||
|
string cmd = $"SOUR:LIST:CURR {values}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置每步的 dwell 时间序列
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetDwellListAsync(double[] dwellTimes, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string values = string.Join(",", dwellTimes);
|
||||||
|
string cmd = $"SOUR:LIST:DWEL {values}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置序列循环次数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">循环次数,1~9999 或 "INF"</param>
|
||||||
|
public async Task SetSequenceCountAsync(string count, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:LIST:COUN {count}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动序列触发
|
||||||
|
/// </summary>
|
||||||
|
public async Task StartSequenceAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"INIT (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置序列触发源
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">IMMediate、BUS、PIN1、PIN2、PIN3</param>
|
||||||
|
public async Task SetTriggerSourceAsync(string source, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"TRIG:SOUR {source}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 特殊功能命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置远端 sensing 模式
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">INTernal(本地2线)、EXTernal(远端4线)</param>
|
||||||
|
/// <param name="channels">通道列表,如 "1" 或 "1:2"</param>
|
||||||
|
public async Task SetRemoteSensingAsync(string mode, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:SENS {mode}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置电压上升斜率
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slewRate">单位 V/s</param>
|
||||||
|
/// <param name="channels">通道列表</param>
|
||||||
|
public async Task SetVoltageRiseSlewAsync(double slewRate, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:SLEW:RIS {slewRate}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置电压下降斜率
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetVoltageFallSlewAsync(double slewRate, string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:SLEW:FALL {slewRate}, (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动数据日志
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">日志文件路径,如 Internal:/log1.dlog</param>
|
||||||
|
public async Task StartDataLogAsync(string filename, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"INIT:DLOG \"{filename}\"\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除保护状态(OVP/OCP)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channels">通道列表</param>
|
||||||
|
public async Task ClearProtectionAsync(string channels = "1", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = $"SOUR:VOLT:PROT:CLE (@{channels})\r\n";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,5 +9,475 @@ namespace DeviceCommand.Device
|
|||||||
{
|
{
|
||||||
public class EAEL9080:ModbusTcp
|
public class EAEL9080:ModbusTcp
|
||||||
{
|
{
|
||||||
|
#region 一、基础控制与远程模式寄存器
|
||||||
|
public async Task SetRemoteControlAsync(bool activate, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(activate ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 402, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetOutputAsync(bool on, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(on ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 405, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetWorkModeAsync(bool useResistanceMode, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(useResistanceMode ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 409, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetAlarmAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 411, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteUserTextAsync(string text, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return;
|
||||||
|
|
||||||
|
text = text.Length > 40 ? text[..40] : text;
|
||||||
|
int frameCount = (int)Math.Ceiling(text.Length / 4.0);
|
||||||
|
|
||||||
|
for (int i = 0; i < frameCount; i++)
|
||||||
|
{
|
||||||
|
string frame = text.Substring(i * 4, Math.Min(4, text.Length - i * 4));
|
||||||
|
ushort[] values = new ushort[frame.Length];
|
||||||
|
for (int j = 0; j < frame.Length; j++)
|
||||||
|
{
|
||||||
|
values[j] = frame[j];
|
||||||
|
}
|
||||||
|
await WriteMultipleRegistersAsync(slaveAddress, (ushort)(171 + i), values, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<bool> QueryRemoteControlAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 402, 1, ct);
|
||||||
|
return result[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> QueryOutputAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 405, 1, ct);
|
||||||
|
return result[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> QueryWorkModeAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 409, 1, ct);
|
||||||
|
return result[0] == 0xFF00; // true 表示 UIR(电阻模式),false 表示 UIP
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort[]> ReadUserTextAsync(byte slaveAddress = 1, int length = 40, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
int frameCount = (int)Math.Ceiling(length / 4.0);
|
||||||
|
ushort[] textValues = new ushort[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < frameCount; i++)
|
||||||
|
{
|
||||||
|
ushort[] frame = await ReadHoldingRegistersAsync(slaveAddress, (ushort)(171 + i), 4, ct);
|
||||||
|
for (int j = 0; j < frame.Length && (i * 4 + j) < length; j++)
|
||||||
|
{
|
||||||
|
textValues[i * 4 + j] = frame[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textValues;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 二、电压 / 电流 / 功率 / 电阻设定值寄存器
|
||||||
|
|
||||||
|
public async Task SetVoltageAsync(double voltage, double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(voltage / ratedVoltage * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 500, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 500, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetCurrentAsync(double current, double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / ratedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 501, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetCurrentAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 501, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetPowerAsync(double power, double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / ratedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 502, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetPowerAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 502, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResistanceAsync(double resistance, double maxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(resistance / maxResistance * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 503, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetResistanceAsync(double maxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 503, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * maxResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sink 模式示例写方法
|
||||||
|
public async Task SetSinkPowerAsync(double power, double sinkRatedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / sinkRatedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 498, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSinkCurrentAsync(double current, double sinkRatedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / sinkRatedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 499, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSinkResistanceAsync(double resistance, double sinkMaxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(resistance / sinkMaxResistance * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 504, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读方法
|
||||||
|
public async Task<double> GetSinkPowerAsync(double sinkRatedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 498, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkRatedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetSinkCurrentAsync(double sinkRatedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 499, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkRatedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetSinkResistanceAsync(double sinkMaxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 504, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkMaxResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 三、设备状态与实际值查询寄存器
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取设备状态寄存器(32 位)
|
||||||
|
/// Bit 7:DC 输出/输入开启
|
||||||
|
/// Bit 10:远程控制激活
|
||||||
|
/// Bit 12:sink 模式
|
||||||
|
/// </summary>
|
||||||
|
public async Task<uint> ReadDeviceStatusAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 505, 2, ct); // 32位寄存器 = 2 x 16位
|
||||||
|
return (uint)(reg[0] << 16 | reg[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDcOnAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 7)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsRemoteActiveAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 10)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsSinkModeActiveAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 12)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际电压值(单位 V)
|
||||||
|
/// 公式: 实际值 = 寄存器值 / 0xCCCC * 额定电压
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 507, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际电流值(单位 A)
|
||||||
|
/// sink 模式下可能为负值,需要根据 Bit 12 判断
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualCurrentAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 508, 1, ct);
|
||||||
|
double current = reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
|
||||||
|
if (await IsSinkModeActiveAsync(slaveAddress, ct))
|
||||||
|
current = -current; // sink 模式电流为负
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际功率值(单位 W)
|
||||||
|
/// sink 模式下可能为负值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualPowerAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 509, 1, ct);
|
||||||
|
double power = reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
|
||||||
|
if (await IsSinkModeActiveAsync(slaveAddress, ct))
|
||||||
|
power = -power;
|
||||||
|
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定电压(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedVoltageAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 121, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定电流(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedCurrentAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 122, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定功率(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedPowerAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 123, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 四、保护与报警相关寄存器
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过压保护(OVP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverVoltageLimitAsync(double voltage, double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(voltage / ratedVoltage * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 510, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过压保护(OVP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverVoltageLimitAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 510, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过流保护(OCP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverCurrentLimitAsync(double current, double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / ratedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 511, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过流保护(OCP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverCurrentLimitAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 511, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过功率保护(OPP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverPowerLimitAsync(double power, double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / ratedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 512, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过功率保护(OPP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverPowerLimitAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 512, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OVP 报警计数器,读取后复位
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOVPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 520, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OCP 报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOCPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 521, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OPP 报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOPPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 522, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过温(OT)报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOTCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 523, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 五、函数发生器与特殊功能寄存器
|
||||||
|
|
||||||
|
// 寄存器 850:函数发生器启动/停止
|
||||||
|
public async Task SetFunctionGeneratorAsync(bool start, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = start ? (ushort)0xFF00 : (ushort)0x0000;
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 850, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionGeneratorAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 850, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 851:函数发生器作用于电压
|
||||||
|
public async Task SetFunctionVoltageParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 851, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionVoltageParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 851, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 852:函数发生器作用于电流
|
||||||
|
public async Task SetFunctionCurrentParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 852, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionCurrentParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 852, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 859~861:序列控制
|
||||||
|
public async Task SetSequenceStartPointAsync(ushort startPoint, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (startPoint < 1 || startPoint > 99) throw new ArgumentOutOfRangeException(nameof(startPoint));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 859, startPoint, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceStartPointAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 859, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSequenceEndPointAsync(ushort endPoint, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (endPoint < 1 || endPoint > 99) throw new ArgumentOutOfRangeException(nameof(endPoint));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 860, endPoint, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceEndPointAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 860, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSequenceLoopCountAsync(ushort loopCount, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (loopCount > 999) throw new ArgumentOutOfRangeException(nameof(loopCount));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 861, loopCount, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceLoopCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 861, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 11000:MPP 跟踪模式
|
||||||
|
public async Task SetMPPModeAsync(ushort mode, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (mode != 0x0001 && mode != 0x0002 && mode != 0x0004) throw new ArgumentOutOfRangeException(nameof(mode));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 11000, mode, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadMPPModeAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 11000, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,157 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Linq;
|
using System.Threading;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Device
|
||||||
{
|
{
|
||||||
public class IT6724C: Serial_Port
|
public class IT6724C : Serial_Port
|
||||||
{
|
{
|
||||||
|
public IT6724C(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One,
|
||||||
|
Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
|
||||||
|
{
|
||||||
|
ConfigureDevice(portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 设置命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置输出状态,true 为开,false 为关
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOutputAsync(bool state, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"OUTPut {(state ? 1 : 0)}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换到远程控制模式
|
||||||
|
/// </summary>
|
||||||
|
public Task SetRemoteModeAsync(CancellationToken ct = default)
|
||||||
|
=> SendAsync("SYSTem:REMote\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 蜂鸣器测试
|
||||||
|
/// </summary>
|
||||||
|
public Task BeeperTestAsync(CancellationToken ct = default)
|
||||||
|
=> SendAsync("SYSTem:BEEPer\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置输出电流(单位:A)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetCurrentAsync(double current, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"CURRent {current}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过流保护电流(OCP,单位:A)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOCPCurrentAsync(double current, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"CURRent:PROTection {current}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过流保护状态,true 为启用,false 为禁用
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOCPStateAsync(bool state, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"CURRent:PROTection:STATe {(state ? 1 : 0)}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除过流保护触发状态
|
||||||
|
/// </summary>
|
||||||
|
public Task ClearOCPAsync(CancellationToken ct = default)
|
||||||
|
=> SendAsync("CURRent:PROTection:CLEar\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置输出电压(单位:V)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetVoltageAsync(double voltage, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"VOLTage {voltage}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过压保护电压(OVP,单位:V)
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOVPVoltageAsync(double voltage, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"VOLT:PROTection {voltage}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过压保护状态,true 为启用,false 为禁用
|
||||||
|
/// </summary>
|
||||||
|
public Task SetOVPStateAsync(bool state, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"VOLT:PROTection:STATe {(state ? 1 : 0)}\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除过压保护触发状态
|
||||||
|
/// </summary>
|
||||||
|
public Task ClearOVPAsync(CancellationToken ct = default)
|
||||||
|
=> SendAsync("VOLT:PROTection:CLEar\r\n", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送自定义命令
|
||||||
|
/// </summary>
|
||||||
|
public Task SendCustomCommandAsync(string command, CancellationToken ct = default)
|
||||||
|
=> SendAsync($"{command}\r\n", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询命令
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询仪器标识,返回制造商、型号、序列号、固件版本
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> QueryIDAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("*IDN?\r\n", ct);
|
||||||
|
return await ReadAsync(ct: ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询过流保护是否触发,返回 true 表示触发
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> QueryOCPTrippedAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("CURRent:PROTection:TRIPed?\r\n", ct);
|
||||||
|
var result = await ReadAsync(ct: ct);
|
||||||
|
return result == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询过压保护是否触发,返回 true 表示触发
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> QueryOVPTrippedAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("VOLT:PROTection:TRIPed?\r\n", ct);
|
||||||
|
var result = await ReadAsync(ct: ct);
|
||||||
|
return result == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询实际输出电流(单位:A)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> QueryCurrentAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("MEASure:CURRent?\r\n", ct);
|
||||||
|
var result = await ReadAsync(ct: ct);
|
||||||
|
return Convert.ToDouble(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询实际输出电压(单位:V)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> QueryVoltageAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("MEASure:VOLTage?\r\n", ct);
|
||||||
|
var result = await ReadAsync(ct: ct);
|
||||||
|
return Convert.ToDouble(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询实际输出功率(单位:W)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> QueryPowerAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync("MEASure:POWer?\r\n", ct);
|
||||||
|
var result = await ReadAsync(ct: ct);
|
||||||
|
return Convert.ToDouble(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,140 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Linq;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Device
|
||||||
{
|
{
|
||||||
public class LQ7500_D: Tcp
|
public class LQ7500_D : ModbusRtu
|
||||||
{
|
{
|
||||||
|
public byte SlaveAddress { get; set; } = 1; // default slave address
|
||||||
|
|
||||||
|
private static readonly int[] Pow10Table =
|
||||||
|
{ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||||
|
|
||||||
|
private async Task EnsureConnectionAsync()
|
||||||
|
{
|
||||||
|
if (SerialPort == null || !SerialPort.IsOpen)
|
||||||
|
await ConnectAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Read Methods
|
||||||
|
|
||||||
|
public async Task<ushort> ReadTemperatureResolutionAsync()
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
return (await ReadHoldingRegistersAsync(SlaveAddress, 35, 1))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> ReadInternalSensorTemperatureAsync(ushort resolution = 0)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
if (resolution == 0) resolution = await ReadTemperatureResolutionAsync();
|
||||||
|
var data = await ReadHoldingRegistersAsync(SlaveAddress, 0, 1);
|
||||||
|
return Math.Round(data[0] / (double)Pow10Table[resolution], resolution + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> ReadExternalSensorTemperatureAsync(ushort resolution = 0)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
if (resolution == 0) resolution = await ReadTemperatureResolutionAsync();
|
||||||
|
var data = await ReadHoldingRegistersAsync(SlaveAddress, 1, 1);
|
||||||
|
return data[0] / (double)Pow10Table[resolution];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadFaultCodeAsync()
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
return (await ReadHoldingRegistersAsync(SlaveAddress, 1, 1))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadDeviceStatusAsync()
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
return (await ReadHoldingRegistersAsync(SlaveAddress, 3, 1))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadPumpStatusAsync()
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
return (await ReadHoldingRegistersAsync(SlaveAddress, 4, 1))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> ReadFlowAsync()
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
var data = await ReadHoldingRegistersAsync(SlaveAddress, 17, 1);
|
||||||
|
return Math.Round(data[0] / 10.0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Write Methods
|
||||||
|
|
||||||
|
public async Task WriteFlowSettingAsync(double value)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short temp = (short)(value * 10);
|
||||||
|
ushort result = Unsafe.As<short, ushort>(ref temp);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 24, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteDeviceStatusAsync(bool on)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 3, (ushort)(on ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WritePumpStatusAsync(bool on)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 4, (ushort)(on ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetTemperatureAsync(double temperature)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short temp = (short)(temperature * 100);
|
||||||
|
ushort result = Unsafe.As<short, ushort>(ref temp);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 2, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetTemperatureUpperLimitAsync(double temperature)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short temp = (short)(temperature * 100);
|
||||||
|
ushort result = Unsafe.As<short, ushort>(ref temp);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 5, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetTemperatureLowerLimitAsync(double temperature)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short temp = (short)(temperature * 100);
|
||||||
|
ushort result = Unsafe.As<short, ushort>(ref temp);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 6, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSoftwareOverTemperatureAsync(double upper, double lower)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short v1 = (short)(upper * 100);
|
||||||
|
short v2 = (short)(lower * 100);
|
||||||
|
ushort[] data = MemoryMarshal.Cast<short, ushort>(new short[] { v1, v2 }).ToArray();
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 30, data[0]);
|
||||||
|
await Task.Delay(5);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 31, data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetHardwareOverTemperatureAsync(double upper)
|
||||||
|
{
|
||||||
|
await EnsureConnectionAsync();
|
||||||
|
short v1 = (short)(upper * 100);
|
||||||
|
ushort result = Unsafe.As<short, ushort>(ref v1);
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 32, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,5 +9,474 @@ namespace DeviceCommand.Device
|
|||||||
{
|
{
|
||||||
public class PSB11000: ModbusTcp
|
public class PSB11000: ModbusTcp
|
||||||
{
|
{
|
||||||
|
#region 一、基础控制与远程模式寄存器
|
||||||
|
public async Task SetRemoteControlAsync(bool activate, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(activate ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 402, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetOutputAsync(bool on, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(on ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 405, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetWorkModeAsync(bool useResistanceMode, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(useResistanceMode ? 0xFF00 : 0x0000);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 409, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetAlarmAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 411, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteUserTextAsync(string text, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return;
|
||||||
|
|
||||||
|
text = text.Length > 40 ? text[..40] : text;
|
||||||
|
int frameCount = (int)Math.Ceiling(text.Length / 4.0);
|
||||||
|
|
||||||
|
for (int i = 0; i < frameCount; i++)
|
||||||
|
{
|
||||||
|
string frame = text.Substring(i * 4, Math.Min(4, text.Length - i * 4));
|
||||||
|
ushort[] values = new ushort[frame.Length];
|
||||||
|
for (int j = 0; j < frame.Length; j++)
|
||||||
|
{
|
||||||
|
values[j] = frame[j];
|
||||||
|
}
|
||||||
|
await WriteMultipleRegistersAsync(slaveAddress, (ushort)(171 + i), values, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<bool> QueryRemoteControlAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 402, 1, ct);
|
||||||
|
return result[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> QueryOutputAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 405, 1, ct);
|
||||||
|
return result[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> QueryWorkModeAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 409, 1, ct);
|
||||||
|
return result[0] == 0xFF00; // true 表示 UIR(电阻模式),false 表示 UIP
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort[]> ReadUserTextAsync(byte slaveAddress = 1, int length = 40, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
int frameCount = (int)Math.Ceiling(length / 4.0);
|
||||||
|
ushort[] textValues = new ushort[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < frameCount; i++)
|
||||||
|
{
|
||||||
|
ushort[] frame = await ReadHoldingRegistersAsync(slaveAddress, (ushort)(171 + i), 4, ct);
|
||||||
|
for (int j = 0; j < frame.Length && (i * 4 + j) < length; j++)
|
||||||
|
{
|
||||||
|
textValues[i * 4 + j] = frame[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textValues;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 二、电压 / 电流 / 功率 / 电阻设定值寄存器
|
||||||
|
|
||||||
|
public async Task SetVoltageAsync(double voltage, double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(voltage / ratedVoltage * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 500, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 500, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetCurrentAsync(double current, double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / ratedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 501, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetCurrentAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 501, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetPowerAsync(double power, double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / ratedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 502, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetPowerAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 502, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResistanceAsync(double resistance, double maxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(resistance / maxResistance * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 503, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetResistanceAsync(double maxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 503, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * maxResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sink 模式示例写方法
|
||||||
|
public async Task SetSinkPowerAsync(double power, double sinkRatedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / sinkRatedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 498, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSinkCurrentAsync(double current, double sinkRatedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / sinkRatedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 499, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSinkResistanceAsync(double resistance, double sinkMaxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(resistance / sinkMaxResistance * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 504, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读方法
|
||||||
|
public async Task<double> GetSinkPowerAsync(double sinkRatedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 498, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkRatedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetSinkCurrentAsync(double sinkRatedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 499, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkRatedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> GetSinkResistanceAsync(double sinkMaxResistance, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 504, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * sinkMaxResistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 三、设备状态与实际值查询寄存器
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取设备状态寄存器(32 位)
|
||||||
|
/// Bit 7:DC 输出/输入开启
|
||||||
|
/// Bit 10:远程控制激活
|
||||||
|
/// Bit 12:sink 模式
|
||||||
|
/// </summary>
|
||||||
|
public async Task<uint> ReadDeviceStatusAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 505, 2, ct); // 32位寄存器 = 2 x 16位
|
||||||
|
return (uint)(reg[0] << 16 | reg[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsDcOnAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 7)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsRemoteActiveAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 10)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsSinkModeActiveAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
uint status = await ReadDeviceStatusAsync(slaveAddress, ct);
|
||||||
|
return (status & (1 << 12)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际电压值(单位 V)
|
||||||
|
/// 公式: 实际值 = 寄存器值 / 0xCCCC * 额定电压
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 507, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际电流值(单位 A)
|
||||||
|
/// sink 模式下可能为负值,需要根据 Bit 12 判断
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualCurrentAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 508, 1, ct);
|
||||||
|
double current = reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
|
||||||
|
if (await IsSinkModeActiveAsync(slaveAddress, ct))
|
||||||
|
current = -current; // sink 模式电流为负
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取实际功率值(单位 W)
|
||||||
|
/// sink 模式下可能为负值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadActualPowerAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 509, 1, ct);
|
||||||
|
double power = reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
|
||||||
|
if (await IsSinkModeActiveAsync(slaveAddress, ct))
|
||||||
|
power = -power;
|
||||||
|
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定电压(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedVoltageAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 121, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定电流(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedCurrentAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 122, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取额定功率(IEEE 754 float)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<float> ReadRatedPowerAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 123, 2, ct);
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[0] = (byte)(reg[1] & 0xFF);
|
||||||
|
bytes[1] = (byte)(reg[1] >> 8);
|
||||||
|
bytes[2] = (byte)(reg[0] & 0xFF);
|
||||||
|
bytes[3] = (byte)(reg[0] >> 8);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 四、保护与报警相关寄存器
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过压保护(OVP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverVoltageLimitAsync(double voltage, double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(voltage / ratedVoltage * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 510, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过压保护(OVP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverVoltageLimitAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 510, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过流保护(OCP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverCurrentLimitAsync(double current, double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(current / ratedCurrent * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 511, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过流保护(OCP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverCurrentLimitAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 511, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置过功率保护(OPP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOverPowerLimitAsync(double power, double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = (ushort)(power / ratedPower * 0xCCCC);
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 512, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过功率保护(OPP)阈值
|
||||||
|
/// </summary>
|
||||||
|
public async Task<double> ReadOverPowerLimitAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 512, 1, ct);
|
||||||
|
return reg[0] / (double)0xCCCC * ratedPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OVP 报警计数器,读取后复位
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOVPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 520, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OCP 报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOCPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 521, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 OPP 报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOPPCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 522, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取过温(OT)报警计数器
|
||||||
|
/// </summary>
|
||||||
|
public async Task<ushort> ReadOTCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 523, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 五、函数发生器与特殊功能寄存器
|
||||||
|
|
||||||
|
// 寄存器 850:函数发生器启动/停止
|
||||||
|
public async Task SetFunctionGeneratorAsync(bool start, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort value = start ? (ushort)0xFF00 : (ushort)0x0000;
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 850, value, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionGeneratorAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 850, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 851:函数发生器作用于电压
|
||||||
|
public async Task SetFunctionVoltageParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 851, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionVoltageParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 851, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 852:函数发生器作用于电流
|
||||||
|
public async Task SetFunctionCurrentParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 852, 0xFF00, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadFunctionCurrentParamAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 852, 1, ct);
|
||||||
|
return reg[0] == 0xFF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 859~861:序列控制
|
||||||
|
public async Task SetSequenceStartPointAsync(ushort startPoint, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (startPoint < 1 || startPoint > 99) throw new ArgumentOutOfRangeException(nameof(startPoint));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 859, startPoint, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceStartPointAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 859, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSequenceEndPointAsync(ushort endPoint, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (endPoint < 1 || endPoint > 99) throw new ArgumentOutOfRangeException(nameof(endPoint));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 860, endPoint, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceEndPointAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 860, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetSequenceLoopCountAsync(ushort loopCount, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (loopCount > 999) throw new ArgumentOutOfRangeException(nameof(loopCount));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 861, loopCount, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadSequenceLoopCountAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 861, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寄存器 11000:MPP 跟踪模式
|
||||||
|
public async Task SetMPPModeAsync(ushort mode, byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (mode != 0x0001 && mode != 0x0002 && mode != 0x0004) throw new ArgumentOutOfRangeException(nameof(mode));
|
||||||
|
await WriteSingleRegisterAsync(slaveAddress, 11000, mode, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort> ReadMPPModeAsync(byte slaveAddress = 1, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 11000, 1, ct);
|
||||||
|
return reg[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,237 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Threading;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Device
|
||||||
{
|
{
|
||||||
public class SQ0030G1D: Tcp
|
/// <summary>
|
||||||
|
/// Sequoia SQ0030G1D 控制类(SCPI 指令封装)
|
||||||
|
/// </summary>
|
||||||
|
public class SQ0030G1D : Tcp
|
||||||
{
|
{
|
||||||
|
#region ========== 通用 SCPI 封装 ==========
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送 SCPI 指令(无返回)
|
||||||
|
/// </summary>
|
||||||
|
protected async Task WriteAsync(string cmd, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync(cmd + "\n", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送 SCPI 指令并读取返回
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<string> QueryAsync(string cmd, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync(cmd + "\n", ct);
|
||||||
|
return await ReadAsync(ct: ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 一、设备基础控制 ==========
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询设备标识 (*IDN?)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> GetIdAsync(CancellationToken ct = default)
|
||||||
|
=> await QueryAsync("*IDN?", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 恢复出厂设置(*RST)
|
||||||
|
/// </summary>
|
||||||
|
public async Task ResetAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("*RST", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存参数到内存 (*SAV n)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SavePresetAsync(int index, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"*SAV {index}", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从内存调用参数 (*RCL n)
|
||||||
|
/// </summary>
|
||||||
|
public async Task RecallPresetAsync(int index, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"*RCL {index}", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换到本地模式(SYST:LOC)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetLocalAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("SYST:LOC", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换到远程模式(SYST:REM)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetRemoteAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("SYST:REM", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 控制输出开关(ON/OFF)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetOutputAsync(bool on, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"OUTP:STAT {(on ? "ON" : "OFF")}", ct);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询输出状态(返回 true 表示 ON)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> GetOutputAsync(CancellationToken ct = default)
|
||||||
|
=> (await QueryAsync("OUTP:STAT?", ct)).Trim().ToUpper() == "ON";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 二、电压控制 ==========
|
||||||
|
|
||||||
|
public async Task SetVoltageAsync(double voltage, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"VOLT {voltage}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetVoltageSettingAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("VOLT?", ct)).Replace("V", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task SetVoltageOffsetAsync(double value, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"VOLT:OFFS {value}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetVoltageOffsetAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("VOLT:OFFS?", ct)).Replace("V", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task SetVoltageMaxAsync(double value, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"VOLT:MAX {value}", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 三、电流控制 ==========
|
||||||
|
|
||||||
|
public async Task SetCurrentAsync(double current, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"CURR {current}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetCurrentSettingAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("CURR?", ct)).Replace("A", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task SetRegenerativeLimitAsync(double value, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"CURR:REG {value}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetRegenerativeLimitAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("CURR:REG?", ct)).Replace("A", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 四、频率控制 ==========
|
||||||
|
|
||||||
|
public async Task SetFrequencyAsync(double freq, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"FREQ {freq}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetFrequencyAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("FREQ?", ct)).Replace("Hz", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 五、功率控制 ==========
|
||||||
|
|
||||||
|
public async Task SetPowerAsync(double power, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"POW {power}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetPowerSettingAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("POW?", ct)).Replace("W", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task SetReactivePowerAsync(double var, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"POW:REAC {var}", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 六、测量查询 ==========
|
||||||
|
|
||||||
|
public async Task<double> MeasureVoltageAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("MEAS:VOLT?", ct)).Replace("V", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task<double> MeasureCurrentAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("MEAS:CURR?", ct)).Replace("A", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task<double> MeasurePowerAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("MEAS:POW?", ct)).Replace("W", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 同时测量电压、电流、功率
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(double volt, double curr, double pow)> MeasureArrayAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string result = await QueryAsync("MEAS:ARR?", ct);
|
||||||
|
var parts = result.Split(',');
|
||||||
|
|
||||||
|
return (
|
||||||
|
double.Parse(parts[0].Replace("V", ""), CultureInfo.InvariantCulture),
|
||||||
|
double.Parse(parts[1].Replace("A", ""), CultureInfo.InvariantCulture),
|
||||||
|
double.Parse(parts[2].Replace("W", ""), CultureInfo.InvariantCulture)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> MeasureHarmonicAsync(int n, CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync($"MEAS:HARM? {n}", ct)).Replace("%", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 七、保护设定 ==========
|
||||||
|
|
||||||
|
public async Task SetOVPAsync(double v, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"VOLT:PROT {v}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetOVPAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("VOLT:PROT?", ct)).Replace("V", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task SetOCPAsync(double a, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"CURR:PROT {a}", ct);
|
||||||
|
|
||||||
|
public async Task<double> GetOCPAsync(CancellationToken ct = default)
|
||||||
|
=> double.Parse((await QueryAsync("CURR:PROT?", ct)).Replace("A", ""), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
public async Task<bool> GetOTPStatusAsync(CancellationToken ct = default)
|
||||||
|
=> (await QueryAsync("SYST:PROT:OTP?", ct)).Trim().ToUpper() == "ON";
|
||||||
|
|
||||||
|
public async Task ClearProtectionAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("SYST:PROT:CLE", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 八、电子负载 ==========
|
||||||
|
|
||||||
|
public async Task SetLoadModeCurrentAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("LOAD:MODE CURR", ct);
|
||||||
|
|
||||||
|
public async Task SetLoadModePowerAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("LOAD:MODE POW", ct);
|
||||||
|
|
||||||
|
public async Task SetLoadModeRLCAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("LOAD:MODE RLC", ct);
|
||||||
|
|
||||||
|
public async Task SetLoadRLCResistanceAsync(double res, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"LOAD:RLC:RES {res}", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 九、瞬态编程 ==========
|
||||||
|
|
||||||
|
public async Task SetTransientStateAsync(bool on, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"TRAN:STAT {(on ? "ON" : "OFF")}", ct);
|
||||||
|
|
||||||
|
public async Task SetTransientStepVoltageAsync(int step, double volt, CancellationToken ct = default)
|
||||||
|
=> await WriteAsync($"TRAN:STEP {step}:VOLT {volt}", ct);
|
||||||
|
|
||||||
|
public async Task RunTransientAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("TRAN:RUN", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== 十、多设备同步 ==========
|
||||||
|
|
||||||
|
public async Task SetSyncSourceExternalAsync(CancellationToken ct = default)
|
||||||
|
=> await WriteAsync("SYNC:SOUR EXT", ct);
|
||||||
|
|
||||||
|
public async Task<string> GetSyncStatusAsync(CancellationToken ct = default)
|
||||||
|
=> await QueryAsync("SYNC:STAT?", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,148 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Threading;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Device
|
||||||
{
|
{
|
||||||
public class WS_68030_380T:ModbusTcp
|
public class WS_68030_380T : ModbusTcp
|
||||||
{
|
{
|
||||||
|
private const byte SlaveAddress = 1;
|
||||||
|
|
||||||
|
#region 设置模式
|
||||||
|
public async Task Set_Mode_LocalAuto(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 51, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_Mode_LocalManual(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 51, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_Mode_ATEControl(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 51, 2, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_AlarmMute(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 52, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_FaultReset(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 53, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_SystemStop(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 54, 1, ct);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 负载模式
|
||||||
|
public async Task Set_ATE_Load_CurrentMode(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 100, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_PowerMode(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 100, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_Uninstall(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 103, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_Load(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveAddress, 103, 1, ct);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 设置电流/功率
|
||||||
|
public async Task Set_ATE_Load_A_Current(float current, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(current);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 105, regs, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_A_Power(float power, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(power);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 109, regs, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_B_Current(float current, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(current);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 115, regs, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_B_Power(float power, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(power);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 119, regs, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_C_Current(float current, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(current);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 125, regs, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set_ATE_Load_C_Power(float power, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = ConvertFromFloat(power);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveAddress, 129, regs, ct);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询
|
||||||
|
public async Task<float> Get_A_Voltage(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = await ReadHoldingRegistersAsync(SlaveAddress, 150, 2, ct);
|
||||||
|
return ConvertToFloat(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> Get_A_Current(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = await ReadHoldingRegistersAsync(SlaveAddress, 152, 2, ct);
|
||||||
|
return ConvertToFloat(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> Get_A_ActivePower(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var regs = await ReadHoldingRegistersAsync(SlaveAddress, 154, 2, ct);
|
||||||
|
return ConvertToFloat(regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// …同理其他相和三相测量,直接调用基类 ReadHoldingRegistersAsync
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助方法
|
||||||
|
private float ConvertToFloat(ushort[] values)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[3] = (byte)(values[0] >> 8);
|
||||||
|
bytes[2] = (byte)(values[0] & 0xFF);
|
||||||
|
bytes[1] = (byte)(values[1] >> 8);
|
||||||
|
bytes[0] = (byte)(values[1] & 0xFF);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort[] ConvertFromFloat(float value)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
return new ushort[]
|
||||||
|
{
|
||||||
|
(ushort)((bytes[3] << 8) | bytes[2]),
|
||||||
|
(ushort)((bytes[1] << 8) | bytes[0])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||||
|
<PackageReference Include="NModbus.Serial" Version="3.0.81" />
|
||||||
<PackageReference Include="System.IO.Ports" Version="10.0.0" />
|
<PackageReference Include="System.IO.Ports" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user