diff --git a/DeviceCommand/Base/ModbusRtu.cs b/DeviceCommand/Base/ModbusRtu.cs new file mode 100644 index 0000000..ee17f4e --- /dev/null +++ b/DeviceCommand/Base/ModbusRtu.cs @@ -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 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 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 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 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(); + } + } + } +} diff --git a/DeviceCommand/Base/ModbusTcp.cs b/DeviceCommand/Base/ModbusTcp.cs index 852c381..08d3329 100644 --- a/DeviceCommand/Base/ModbusTcp.cs +++ b/DeviceCommand/Base/ModbusTcp.cs @@ -9,138 +9,153 @@ namespace DeviceCommand.Base { public class ModbusTcp { - public string IPAddress { get; set; } = "127.0.0.1"; - public int Port { get; set; } = 502; - public int SendTimeout { get; set; } = 3000; - public int ReceiveTimeout { get; set; } = 3000; - public TcpClient TcpClient { get; set; } = new(); - public IModbusMaster Modbus { get; set; } + public string IPAddress { get; private set; } = "127.0.0.1"; + public int Port { get; private set; } = 502; + public int SendTimeout { get; private set; } = 3000; + public int ReceiveTimeout { get; private set; } = 3000; + public TcpClient TcpClient { get; private set; } = new TcpClient(); + 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; Port = port; SendTimeout = sendTimeout; ReceiveTimeout = receiveTimeout; - return this; } - public static void ChangeDeviceConfig(ModbusTcp modbusTcp, string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000) + public async Task ConnectAsync(CancellationToken ct = default) { - modbusTcp.IPAddress = ipAddress; - modbusTcp.Port = port; - if (sendTimeout > 0) modbusTcp.SendTimeout = sendTimeout; - if (receiveTimeout > 0) modbusTcp.ReceiveTimeout = receiveTimeout; - } - - public static async Task ConnectAsync(ModbusTcp modbusTcp, CancellationToken ct = default) - { - if (!modbusTcp.TcpClient.Connected) + await _commLock.WaitAsync(ct); + try { - 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) + if (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); + var remoteEndPoint = (IPEndPoint)TcpClient.Client.RemoteEndPoint!; + if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port) + return true; + + TcpClient.Close(); + TcpClient.Dispose(); + TcpClient = new TcpClient(); } - } - return true; - } - public static void ModbusTcpInitialize(ModbusTcp modbusTcp) - { - if (!modbusTcp.TcpClient.Connected) + await TcpClient.ConnectAsync(IPAddress, Port, ct); + Modbus = new ModbusFactory().CreateMaster(TcpClient); + return true; + } + finally { - modbusTcp.TcpClient = new TcpClient(); - modbusTcp.TcpClient.SendTimeout = modbusTcp.SendTimeout; - modbusTcp.TcpClient.ReceiveTimeout = modbusTcp.ReceiveTimeout; + _commLock.Release(); } } - - public static void Close(ModbusTcp modbusTcp) + public void Close() { - if (modbusTcp.TcpClient.Connected) + if (TcpClient.Connected) TcpClient.Close(); + } + + public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default) + { + await _commLock.WaitAsync(ct); + try { - modbusTcp.TcpClient.Close(); + var sendTask = Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).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 ReadHoldingRegistersAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) + public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default) { - var readTask = modbusTcp.Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct); - var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct); - var completedTask = await Task.WhenAny(readTask, timeoutTask); - if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取保持寄存器超时"); - return await readTask; + await _commLock.WaitAsync(ct); + try + { + 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 WriteSingleRegisterAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default) + public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { - var sendTask = modbusTcp.Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value).WaitAsync(ct); - var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct); - var completedTask = await Task.WhenAny(sendTask, timeoutTask); - if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写单寄存器超时"); + await _commLock.WaitAsync(ct); + try + { + var readTask = Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct); + var timeoutTask = Task.Delay(ReceiveTimeout, ct); + var completedTask = await Task.WhenAny(readTask, timeoutTask); + if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取保持寄存器超时"); + return await readTask; + } + finally + { + _commLock.Release(); + } } - 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); - var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct); - var completedTask = await Task.WhenAny(sendTask, timeoutTask); - if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP写多寄存器超时"); + await _commLock.WaitAsync(ct); + try + { + var sendTask = Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).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 ReadCoilAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) + public async Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { - var readTask = modbusTcp.Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct); - var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct); - var completedTask = await Task.WhenAny(readTask, timeoutTask); - if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取线圈超时"); - return await readTask; + await _commLock.WaitAsync(ct); + try + { + var readTask = Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct); + var timeoutTask = Task.Delay(ReceiveTimeout, ct); + var completedTask = await Task.WhenAny(readTask, timeoutTask); + if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取线圈超时"); + return await readTask; + } + finally + { + _commLock.Release(); + } } - public static async Task WriteSingleCoilAsync(ModbusTcp modbusTcp, byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default) + public async Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { - var sendTask = modbusTcp.Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value).WaitAsync(ct); - 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) - { - var sendTask = modbusTcp.Modbus.WriteMultipleCoilsAsync(slaveAddress, startAddress, values).WaitAsync(ct); - var timeoutTask = Task.Delay(modbusTcp.ReceiveTimeout, ct); - 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(); - } - - public static async Task 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); - if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取输入寄存器超时"); - return await readTask; + await _commLock.WaitAsync(ct); + try + { + var readTask = Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints).WaitAsync(ct); + var timeoutTask = Task.Delay(ReceiveTimeout, ct); + var completedTask = await Task.WhenAny(readTask, timeoutTask); + if (completedTask == timeoutTask) throw new TimeoutException($"ModbusTCP读取输入寄存器超时"); + return await readTask; + } + finally + { + _commLock.Release(); + } } } } diff --git a/DeviceCommand/Base/Serial_Port.cs b/DeviceCommand/Base/Serial_Port.cs index eb085bd..7dfcabb 100644 --- a/DeviceCommand/Base/Serial_Port.cs +++ b/DeviceCommand/Base/Serial_Port.cs @@ -1,5 +1,8 @@ -using System.Text; +using System; using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace DeviceCommand.Base { @@ -12,9 +15,12 @@ namespace DeviceCommand.Base public Parity Parity { get; set; } = Parity.None; public int ReadTimeout { 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; BaudRate = baudRate; @@ -23,138 +29,93 @@ namespace DeviceCommand.Base Parity = parity; ReadTimeout = readTimeout; WriteTimeout = writeTimeout; - return this; } - - 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) + public async Task ConnectAsync(CancellationToken ct = default) { - serialPort.PortName = PortName; - serialPort.BaudRate = BaudRate; - serialPort.DataBits = dataBits; - serialPort.StopBits = stopBits; - serialPort.Parity = parity; - if (ReadTimeout > 0) serialPort.ReadTimeout = ReadTimeout; - if (WriteTimeout > 0) serialPort.WriteTimeout = WriteTimeout; - } - - public static async Task ConnectAsync(Serial_Port serialPort, CancellationToken ct = default) - { - 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) + await _commLock.WaitAsync(ct); + try { - 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(); + if (_SerialPort.IsOpen) _SerialPort.Close(); + + _SerialPort.PortName = PortName; + _SerialPort.BaudRate = BaudRate; + _SerialPort.DataBits = DataBits; + _SerialPort.StopBits = StopBits; + _SerialPort.Parity = Parity; + _SerialPort.ReadTimeout = ReadTimeout; + _SerialPort.WriteTimeout = WriteTimeout; + + _SerialPort.Open(); + return true; } - else if (!serialPort._SerialPort.IsOpen) + finally { - serialPort._SerialPort.Open(); + _commLock.Release(); } - return true; } - public static void Close(Serial_Port serialPort) + public void Close() { - if (serialPort._SerialPort.IsOpen) serialPort._SerialPort.Close(); + if (_SerialPort.IsOpen) _SerialPort.Close(); } - public static async Task SendAsync(Serial_Port serialPort, byte[] bytes, CancellationToken ct = default) + public async Task SendAsync(string data, CancellationToken ct = default) { - if (!serialPort._SerialPort.IsOpen) return; - var timeoutMs = serialPort.WriteTimeout; - if (timeoutMs <= 0) + await _commLock.WaitAsync(ct); + try { - serialPort._SerialPort.Write(bytes, 0, bytes.Length); - return; + 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); + if (completedTask == timeoutTask) throw new TimeoutException($"写入操作在 {WriteTimeout} ms内未完成"); + await sendTask; } - var sendTask = Task.Run(() => serialPort._SerialPort.Write(bytes, 0, bytes.Length), ct); - var timeoutTask = Task.Delay(timeoutMs, ct); - var completedTask = await Task.WhenAny(sendTask, timeoutTask); - if (completedTask == timeoutTask) throw new TimeoutException($"串口通讯异常:写入操作在 {timeoutMs} ms内未完成"); - await sendTask; - } - - public static async Task SendAsync(Serial_Port serialPort, string str, CancellationToken ct = default) - { - if (!serialPort._SerialPort.IsOpen) return; - byte[] bytes = Encoding.UTF8.GetBytes(str); - await SendAsync(serialPort, bytes, ct); - } - - public static async Task ReadAsync(Serial_Port serialPort, byte[] buffer, CancellationToken ct = default) - { - if (!serialPort._SerialPort.IsOpen) return null; - var timeoutMs = serialPort.ReadTimeout; - var readTask = ReadByte(serialPort, buffer, 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; - } - - public static async Task ReadByte(Serial_Port serialPort, byte[] buffer, CancellationToken ct) - { - int bytesRead = 0; - while (bytesRead < buffer.Length) + finally { - if (serialPort._SerialPort.BytesToRead > 0) + _commLock.Release(); + } + } + + public async Task ReadAsync(string delimiter = "\n", CancellationToken ct = default) + { + await _commLock.WaitAsync(ct); + try + { + if (!_SerialPort.IsOpen) return null; + + delimiter ??= "\n"; + var sb = new StringBuilder(); + byte[] buffer = new byte[1024]; + + while (!ct.IsCancellationRequested) { - bytesRead += serialPort._SerialPort.Read(buffer, bytesRead, buffer.Length - bytesRead); - } - } - return buffer; - } - - public static async Task 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 ReadDefaultString(Serial_Port serialPort, string delimiter, CancellationToken ct) - { - delimiter ??= "\n"; - MemoryStream memoryStream = new(); - byte[] buffer = new byte[2048]; - string data = string.Empty; - await Task.Run(() => - { - while (true) - { - if (ct.IsCancellationRequested) return; - if (serialPort._SerialPort.BytesToRead > 0) + if (_SerialPort.BytesToRead > 0) { - int bytesRead = serialPort._SerialPort.Read(buffer, 0, buffer.Length); - memoryStream.Write(buffer, 0, bytesRead); - data = Encoding.UTF8.GetString(memoryStream.ToArray()); - int lineEndIndex = data.IndexOf(delimiter); - if (lineEndIndex >= 0) + int bytesRead = _SerialPort.Read(buffer, 0, buffer.Length); + sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + + int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal); + if (index >= 0) { - data = data.Substring(0, lineEndIndex).Trim(); - return; + return sb.ToString(0, index).Trim(); } } + else + { + await Task.Delay(10, ct); // 避免 CPU 空转 + } } - }, ct); - return data; + + throw new OperationCanceledException("读取被取消"); + } + finally + { + _commLock.Release(); + } } } } diff --git a/DeviceCommand/Base/TCP.cs b/DeviceCommand/Base/TCP.cs index 3abd9bb..9aa4fdf 100644 --- a/DeviceCommand/Base/TCP.cs +++ b/DeviceCommand/Base/TCP.cs @@ -1,169 +1,160 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Base { public class Tcp { - public string IPAddress { get; set; } = "127.0.0.1"; - public int Port { get; set; } = 502; - public int SendTimeout { get; set; } = 3000; - public int ReceiveTimeout { get; set; } = 3000; - public TcpClient TcpClient { get; set; } = new(); + public string IPAddress { get; private set; } = "127.0.0.1"; + public int Port { get; private set; } = 502; + public int SendTimeout { get; private set; } = 3000; + public int ReceiveTimeout { get; private set; } = 3000; + 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; Port = port; SendTimeout = sendTimeout; 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 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 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 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 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 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 WriteRead(Tcp tcp, string str, string endstr, CancellationToken ct = default) - { - await SendAsync(tcp, str, ct); - return await ReadAsync(tcp, endstr, ct); } public async Task 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 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 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 WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default) + { + await SendAsync(command, ct); + return await ReadAsync(delimiter, ct); } } } diff --git a/DeviceCommand/Device/E36233A.cs b/DeviceCommand/Device/E36233A.cs index eab3c2d..4347df7 100644 --- a/DeviceCommand/Device/E36233A.cs +++ b/DeviceCommand/Device/E36233A.cs @@ -7,7 +7,427 @@ using System.Threading.Tasks; namespace DeviceCommand.Device { + //是德 public class E36233A:Tcp { + public E36233A(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000) + { + ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout); + } + #region SCPI 常用命令方法 + + /// + /// 查询仪器标识,返回制造商、型号、序列号、固件版本 + /// + public Task QueryIDAsync(CancellationToken ct = default) + { + return SendCommandAndReadAsync("*IDN?", ct); + } + + /// + /// 恢复出厂设置,不清除错误队列 + /// + public Task SendResetAsync(CancellationToken ct = default) + { + return SendAsync("*RST", ct); + } + + /// + /// 存储当前仪器状态到指定位置(0~9) + /// + public Task SaveStateAsync(int slot, CancellationToken ct = default) + { + if (slot < 0 || slot > 9) throw new ArgumentOutOfRangeException(nameof(slot)); + return SendAsync($"*SAV {slot}", ct); + } + + /// + /// 调用指定位置的仪器状态(0~9) + /// + public Task RecallStateAsync(int slot, CancellationToken ct = default) + { + if (slot < 0 || slot > 9) throw new ArgumentOutOfRangeException(nameof(slot)); + return SendAsync($"*RCL {slot}", ct); + } + + /// + /// 操作完成标记,发送命令等待操作完成 + /// + public Task SendOPCAsync(CancellationToken ct = default) + { + return SendAsync("*OPC", ct); + } + + /// + /// 查询操作完成标记,返回 "1" 表示完成 + /// + public async Task QueryOPCAsync(CancellationToken ct = default) + { + var result = await SendCommandAndReadAsync("*OPC?", ct); + return result == "1"; + } + + /// + /// 清除状态寄存器与错误队列 + /// + public Task ClearStatusAsync(CancellationToken ct = default) + { + return SendAsync("*CLS", ct); + } + + /// + /// 查询状态字节(STB),返回状态值 + /// + public async Task QueryStatusByteAsync(CancellationToken ct = default) + { + var result = await SendCommandAndReadAsync("*STB?", ct); + return int.TryParse(result, out var value) ? value : 0; + } + + /// + /// 通用方法:发送命令并读取响应 + /// + private async Task SendCommandAndReadAsync(string command, CancellationToken ct = default) + { + await SendAsync($"{command}\r\n", ct); + return await ReadAsync("\n", ct); + } + + #endregion + + #region 通道与输出控制命令 + + /// + /// 选择操作通道 + /// + /// 通道号 1 或 2 + 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); + } + + /// + /// 开启/关闭输出 + /// + /// true 表示开,false 表示关 + /// 通道列表,如 "1", "1:2" + 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); + } + + /// + /// 输出上升延迟设置 + /// + /// 延迟时间 0~3600 秒 + /// 通道列表,如 "1", "1:2" + 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); + } + + /// + /// 输出下降延迟设置 + /// + /// 延迟时间 0~3600 秒 + /// 通道列表,如 "1", "1:2" + 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); + } + + /// + /// 设置串/并联模式 + /// + /// OFF(独立)、SER(串联)、PAR(并联) + 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); + } + + /// + /// 通道耦合控制 + /// + /// ALL(全耦合)、NONE(无耦合)、或指定通道,如 @1:2 + 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 电压 / 电流参数设置命令 + + /// + /// 设置立即电压(单通道或多通道) + /// + 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); + } + + /// + /// 设置立即电流(单通道或多通道) + /// + 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); + } + + /// + /// 设置触发电压(步进/序列模式使用) + /// + public Task SetTriggeredVoltageAsync(double voltage, string channels = "1", CancellationToken ct = default) + { + string cmd = $"SOUR:VOLT:TRIG {voltage}, (@{channels})\r\n"; + return SendAsync(cmd, ct); + } + + /// + /// 设置触发电流(步进/序列模式使用) + /// + public Task SetTriggeredCurrentAsync(double current, string channels = "1", CancellationToken ct = default) + { + string cmd = $"SOUR:CURR:TRIG {current}, (@{channels})\r\n"; + return SendAsync(cmd, ct); + } + + /// + /// 设置电压模式(FIX/STEP/LIST) + /// + 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); + } + + /// + /// 设置电流模式(FIX/STEP/LIST) + /// + 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); + } + + /// + /// 设置过压保护 OVP(110% 最大电压内) + /// + public Task SetOVPAsync(double voltage, string channels = "1", CancellationToken ct = default) + { + string cmd = $"SOUR:VOLT:PROT {voltage}, (@{channels})\r\n"; + return SendAsync(cmd, ct); + } + + /// + /// 设置过流保护 OCP(110% 最大电流内) + /// + 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 测量与状态查询命令 + + /// + /// 测量指定通道的实际电压 + /// + public async Task 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; + } + + /// + /// 测量指定通道的实际电流 + /// + public async Task 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; + } + + /// + /// 查询错误队列(FIFO 返回下一个错误) + /// + public async Task QueryNextErrorAsync(CancellationToken ct = default) + { + string cmd = "SYST:ERR? \r\n"; + return await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap(); + } + + /// + /// 查询 OVP(过压保护)状态,返回 true 表示触发 + /// + public async Task 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"; + } + + /// + /// 查询 OCP(过流保护)状态,返回 true 表示触发 + /// + public async Task 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"; + } + + /// + /// 查询远程 / 本地模式,返回 LOC / REM / RWL + /// + public async Task QueryRemoteLocalStateAsync(CancellationToken ct = default) + { + string cmd = "SYST:COMM:RLST?\r\n"; + return await SendAsync(cmd, ct).ContinueWith(_ => ReadAsync(ct: ct)).Unwrap(); + } + + #endregion + + #region 电压/电流序列设置与触发 + + /// + /// 设置通道的电压序列 + /// + /// 电压序列,逗号分隔 + /// 通道列表,如 "1" 或 "1:2" + 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); + } + + /// + /// 设置通道的电流序列 + /// + 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); + } + + /// + /// 设置每步的 dwell 时间序列 + /// + 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); + } + + /// + /// 设置序列循环次数 + /// + /// 循环次数,1~9999 或 "INF" + 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); + } + + /// + /// 启动序列触发 + /// + public async Task StartSequenceAsync(string channels = "1", CancellationToken ct = default) + { + string cmd = $"INIT (@{channels})\r\n"; + await SendAsync(cmd, ct); + } + + /// + /// 设置序列触发源 + /// + /// IMMediate、BUS、PIN1、PIN2、PIN3 + 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 特殊功能命令 + + /// + /// 设置远端 sensing 模式 + /// + /// INTernal(本地2线)、EXTernal(远端4线) + /// 通道列表,如 "1" 或 "1:2" + 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); + } + + /// + /// 设置电压上升斜率 + /// + /// 单位 V/s + /// 通道列表 + 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); + } + + /// + /// 设置电压下降斜率 + /// + 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); + } + + /// + /// 启动数据日志 + /// + /// 日志文件路径,如 Internal:/log1.dlog + public async Task StartDataLogAsync(string filename, CancellationToken ct = default) + { + string cmd = $"INIT:DLOG \"{filename}\"\r\n"; + await SendAsync(cmd, ct); + } + + /// + /// 清除保护状态(OVP/OCP) + /// + /// 通道列表 + public async Task ClearProtectionAsync(string channels = "1", CancellationToken ct = default) + { + string cmd = $"SOUR:VOLT:PROT:CLE (@{channels})\r\n"; + await SendAsync(cmd, ct); + } + + #endregion + + } } diff --git a/DeviceCommand/Device/EAEL9080.cs b/DeviceCommand/Device/EAEL9080.cs index 984a0e9..36d7a6c 100644 --- a/DeviceCommand/Device/EAEL9080.cs +++ b/DeviceCommand/Device/EAEL9080.cs @@ -9,5 +9,475 @@ namespace DeviceCommand.Device { 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 QueryRemoteControlAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 402, 1, ct); + return result[0] == 0xFF00; + } + + public async Task QueryOutputAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 405, 1, ct); + return result[0] == 0xFF00; + } + + public async Task 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 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 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 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 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 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 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 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 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 三、设备状态与实际值查询寄存器 + + /// + /// 读取设备状态寄存器(32 位) + /// Bit 7:DC 输出/输入开启 + /// Bit 10:远程控制激活 + /// Bit 12:sink 模式 + /// + public async Task 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 IsDcOnAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 7)) != 0; + } + + public async Task IsRemoteActiveAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 10)) != 0; + } + + public async Task IsSinkModeActiveAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 12)) != 0; + } + + /// + /// 读取实际电压值(单位 V) + /// 公式: 实际值 = 寄存器值 / 0xCCCC * 额定电压 + /// + public async Task ReadActualVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 507, 1, ct); + return reg[0] / (double)0xCCCC * ratedVoltage; + } + + /// + /// 读取实际电流值(单位 A) + /// sink 模式下可能为负值,需要根据 Bit 12 判断 + /// + public async Task 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; + } + + /// + /// 读取实际功率值(单位 W) + /// sink 模式下可能为负值 + /// + public async Task 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; + } + + /// + /// 读取额定电压(IEEE 754 float) + /// + public async Task 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); + } + + /// + /// 读取额定电流(IEEE 754 float) + /// + public async Task 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); + } + + /// + /// 读取额定功率(IEEE 754 float) + /// + public async Task 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 四、保护与报警相关寄存器 + + /// + /// 设置过压保护(OVP)阈值 + /// + 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); + } + + /// + /// 读取过压保护(OVP)阈值 + /// + public async Task ReadOverVoltageLimitAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 510, 1, ct); + return reg[0] / (double)0xCCCC * ratedVoltage; + } + + /// + /// 设置过流保护(OCP)阈值 + /// + 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); + } + + /// + /// 读取过流保护(OCP)阈值 + /// + public async Task ReadOverCurrentLimitAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 511, 1, ct); + return reg[0] / (double)0xCCCC * ratedCurrent; + } + + /// + /// 设置过功率保护(OPP)阈值 + /// + 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); + } + + /// + /// 读取过功率保护(OPP)阈值 + /// + public async Task ReadOverPowerLimitAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 512, 1, ct); + return reg[0] / (double)0xCCCC * ratedPower; + } + + /// + /// 读取 OVP 报警计数器,读取后复位 + /// + public async Task ReadOVPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 520, 1, ct); + return reg[0]; + } + + /// + /// 读取 OCP 报警计数器 + /// + public async Task ReadOCPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 521, 1, ct); + return reg[0]; + } + + /// + /// 读取 OPP 报警计数器 + /// + public async Task ReadOPPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 522, 1, ct); + return reg[0]; + } + + /// + /// 读取过温(OT)报警计数器 + /// + public async Task 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 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 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 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 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 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 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 ReadMPPModeAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 11000, 1, ct); + return reg[0]; + } + + #endregion + } } diff --git a/DeviceCommand/Device/IT6724C.cs b/DeviceCommand/Device/IT6724C.cs index 48e8632..3e248df 100644 --- a/DeviceCommand/Device/IT6724C.cs +++ b/DeviceCommand/Device/IT6724C.cs @@ -1,14 +1,157 @@ using DeviceCommand.Base; using System; -using System.Collections.Generic; using System.IO.Ports; -using System.Linq; -using System.Text; +using System.Threading; using System.Threading.Tasks; 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 设置命令 + + /// + /// 设置输出状态,true 为开,false 为关 + /// + public Task SetOutputAsync(bool state, CancellationToken ct = default) + => SendAsync($"OUTPut {(state ? 1 : 0)}\r\n", ct); + + /// + /// 切换到远程控制模式 + /// + public Task SetRemoteModeAsync(CancellationToken ct = default) + => SendAsync("SYSTem:REMote\r\n", ct); + + /// + /// 蜂鸣器测试 + /// + public Task BeeperTestAsync(CancellationToken ct = default) + => SendAsync("SYSTem:BEEPer\r\n", ct); + + /// + /// 设置输出电流(单位:A) + /// + public Task SetCurrentAsync(double current, CancellationToken ct = default) + => SendAsync($"CURRent {current}\r\n", ct); + + /// + /// 设置过流保护电流(OCP,单位:A) + /// + public Task SetOCPCurrentAsync(double current, CancellationToken ct = default) + => SendAsync($"CURRent:PROTection {current}\r\n", ct); + + /// + /// 设置过流保护状态,true 为启用,false 为禁用 + /// + public Task SetOCPStateAsync(bool state, CancellationToken ct = default) + => SendAsync($"CURRent:PROTection:STATe {(state ? 1 : 0)}\r\n", ct); + + /// + /// 清除过流保护触发状态 + /// + public Task ClearOCPAsync(CancellationToken ct = default) + => SendAsync("CURRent:PROTection:CLEar\r\n", ct); + + /// + /// 设置输出电压(单位:V) + /// + public Task SetVoltageAsync(double voltage, CancellationToken ct = default) + => SendAsync($"VOLTage {voltage}\r\n", ct); + + /// + /// 设置过压保护电压(OVP,单位:V) + /// + public Task SetOVPVoltageAsync(double voltage, CancellationToken ct = default) + => SendAsync($"VOLT:PROTection {voltage}\r\n", ct); + + /// + /// 设置过压保护状态,true 为启用,false 为禁用 + /// + public Task SetOVPStateAsync(bool state, CancellationToken ct = default) + => SendAsync($"VOLT:PROTection:STATe {(state ? 1 : 0)}\r\n", ct); + + /// + /// 清除过压保护触发状态 + /// + public Task ClearOVPAsync(CancellationToken ct = default) + => SendAsync("VOLT:PROTection:CLEar\r\n", ct); + + /// + /// 发送自定义命令 + /// + public Task SendCustomCommandAsync(string command, CancellationToken ct = default) + => SendAsync($"{command}\r\n", ct); + + #endregion + + #region 查询命令 + + /// + /// 查询仪器标识,返回制造商、型号、序列号、固件版本 + /// + public async Task QueryIDAsync(CancellationToken ct = default) + { + await SendAsync("*IDN?\r\n", ct); + return await ReadAsync(ct: ct); + } + + /// + /// 查询过流保护是否触发,返回 true 表示触发 + /// + public async Task QueryOCPTrippedAsync(CancellationToken ct = default) + { + await SendAsync("CURRent:PROTection:TRIPed?\r\n", ct); + var result = await ReadAsync(ct: ct); + return result == "1"; + } + + /// + /// 查询过压保护是否触发,返回 true 表示触发 + /// + public async Task QueryOVPTrippedAsync(CancellationToken ct = default) + { + await SendAsync("VOLT:PROTection:TRIPed?\r\n", ct); + var result = await ReadAsync(ct: ct); + return result == "1"; + } + + /// + /// 查询实际输出电流(单位:A) + /// + public async Task QueryCurrentAsync(CancellationToken ct = default) + { + await SendAsync("MEASure:CURRent?\r\n", ct); + var result = await ReadAsync(ct: ct); + return Convert.ToDouble(result); + } + + /// + /// 查询实际输出电压(单位:V) + /// + public async Task QueryVoltageAsync(CancellationToken ct = default) + { + await SendAsync("MEASure:VOLTage?\r\n", ct); + var result = await ReadAsync(ct: ct); + return Convert.ToDouble(result); + } + + /// + /// 查询实际输出功率(单位:W) + /// + public async Task QueryPowerAsync(CancellationToken ct = default) + { + await SendAsync("MEASure:POWer?\r\n", ct); + var result = await ReadAsync(ct: ct); + return Convert.ToDouble(result); + } + + #endregion + } } diff --git a/DeviceCommand/Device/LQ7500-D.cs b/DeviceCommand/Device/LQ7500-D.cs index 415fe31..2ddbf72 100644 --- a/DeviceCommand/Device/LQ7500-D.cs +++ b/DeviceCommand/Device/LQ7500-D.cs @@ -1,13 +1,140 @@ using DeviceCommand.Base; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading.Tasks; 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 ReadTemperatureResolutionAsync() + { + await EnsureConnectionAsync(); + return (await ReadHoldingRegistersAsync(SlaveAddress, 35, 1))[0]; + } + + public async Task 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 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 ReadFaultCodeAsync() + { + await EnsureConnectionAsync(); + return (await ReadHoldingRegistersAsync(SlaveAddress, 1, 1))[0]; + } + + public async Task ReadDeviceStatusAsync() + { + await EnsureConnectionAsync(); + return (await ReadHoldingRegistersAsync(SlaveAddress, 3, 1))[0]; + } + + public async Task ReadPumpStatusAsync() + { + await EnsureConnectionAsync(); + return (await ReadHoldingRegistersAsync(SlaveAddress, 4, 1))[0]; + } + + public async Task 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(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(ref temp); + await WriteSingleRegisterAsync(SlaveAddress, 2, result); + } + + public async Task SetTemperatureUpperLimitAsync(double temperature) + { + await EnsureConnectionAsync(); + short temp = (short)(temperature * 100); + ushort result = Unsafe.As(ref temp); + await WriteSingleRegisterAsync(SlaveAddress, 5, result); + } + + public async Task SetTemperatureLowerLimitAsync(double temperature) + { + await EnsureConnectionAsync(); + short temp = (short)(temperature * 100); + ushort result = Unsafe.As(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(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(ref v1); + await WriteSingleRegisterAsync(SlaveAddress, 32, result); + } + + #endregion } } diff --git a/DeviceCommand/Device/PSB11000.cs b/DeviceCommand/Device/PSB11000.cs index 7b000b6..b49691f 100644 --- a/DeviceCommand/Device/PSB11000.cs +++ b/DeviceCommand/Device/PSB11000.cs @@ -9,5 +9,474 @@ namespace DeviceCommand.Device { 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 QueryRemoteControlAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 402, 1, ct); + return result[0] == 0xFF00; + } + + public async Task QueryOutputAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] result = await ReadHoldingRegistersAsync(slaveAddress, 405, 1, ct); + return result[0] == 0xFF00; + } + + public async Task 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 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 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 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 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 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 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 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 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 三、设备状态与实际值查询寄存器 + + /// + /// 读取设备状态寄存器(32 位) + /// Bit 7:DC 输出/输入开启 + /// Bit 10:远程控制激活 + /// Bit 12:sink 模式 + /// + public async Task 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 IsDcOnAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 7)) != 0; + } + + public async Task IsRemoteActiveAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 10)) != 0; + } + + public async Task IsSinkModeActiveAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + uint status = await ReadDeviceStatusAsync(slaveAddress, ct); + return (status & (1 << 12)) != 0; + } + + /// + /// 读取实际电压值(单位 V) + /// 公式: 实际值 = 寄存器值 / 0xCCCC * 额定电压 + /// + public async Task ReadActualVoltageAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 507, 1, ct); + return reg[0] / (double)0xCCCC * ratedVoltage; + } + + /// + /// 读取实际电流值(单位 A) + /// sink 模式下可能为负值,需要根据 Bit 12 判断 + /// + public async Task 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; + } + + /// + /// 读取实际功率值(单位 W) + /// sink 模式下可能为负值 + /// + public async Task 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; + } + + /// + /// 读取额定电压(IEEE 754 float) + /// + public async Task 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); + } + + /// + /// 读取额定电流(IEEE 754 float) + /// + public async Task 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); + } + + /// + /// 读取额定功率(IEEE 754 float) + /// + public async Task 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 四、保护与报警相关寄存器 + + /// + /// 设置过压保护(OVP)阈值 + /// + 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); + } + + /// + /// 读取过压保护(OVP)阈值 + /// + public async Task ReadOverVoltageLimitAsync(double ratedVoltage, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 510, 1, ct); + return reg[0] / (double)0xCCCC * ratedVoltage; + } + + /// + /// 设置过流保护(OCP)阈值 + /// + 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); + } + + /// + /// 读取过流保护(OCP)阈值 + /// + public async Task ReadOverCurrentLimitAsync(double ratedCurrent, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 511, 1, ct); + return reg[0] / (double)0xCCCC * ratedCurrent; + } + + /// + /// 设置过功率保护(OPP)阈值 + /// + 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); + } + + /// + /// 读取过功率保护(OPP)阈值 + /// + public async Task ReadOverPowerLimitAsync(double ratedPower, byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 512, 1, ct); + return reg[0] / (double)0xCCCC * ratedPower; + } + + /// + /// 读取 OVP 报警计数器,读取后复位 + /// + public async Task ReadOVPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 520, 1, ct); + return reg[0]; + } + + /// + /// 读取 OCP 报警计数器 + /// + public async Task ReadOCPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 521, 1, ct); + return reg[0]; + } + + /// + /// 读取 OPP 报警计数器 + /// + public async Task ReadOPPCountAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 522, 1, ct); + return reg[0]; + } + + /// + /// 读取过温(OT)报警计数器 + /// + public async Task 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 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 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 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 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 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 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 ReadMPPModeAsync(byte slaveAddress = 1, CancellationToken ct = default) + { + ushort[] reg = await ReadHoldingRegistersAsync(slaveAddress, 11000, 1, ct); + return reg[0]; + } + + #endregion } } diff --git a/DeviceCommand/Device/SQ0030G1D.cs b/DeviceCommand/Device/SQ0030G1D.cs index ddbe3bc..e91ca60 100644 --- a/DeviceCommand/Device/SQ0030G1D.cs +++ b/DeviceCommand/Device/SQ0030G1D.cs @@ -1,13 +1,237 @@ using DeviceCommand.Base; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Globalization; +using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Device { - public class SQ0030G1D: Tcp + /// + /// Sequoia SQ0030G1D 控制类(SCPI 指令封装) + /// + public class SQ0030G1D : Tcp { + #region ========== 通用 SCPI 封装 ========== + + /// + /// 发送 SCPI 指令(无返回) + /// + protected async Task WriteAsync(string cmd, CancellationToken ct = default) + { + await SendAsync(cmd + "\n", ct); + } + + /// + /// 发送 SCPI 指令并读取返回 + /// + protected async Task QueryAsync(string cmd, CancellationToken ct = default) + { + await SendAsync(cmd + "\n", ct); + return await ReadAsync(ct: ct); + } + + #endregion + + #region ========== 一、设备基础控制 ========== + + /// + /// 查询设备标识 (*IDN?) + /// + public async Task GetIdAsync(CancellationToken ct = default) + => await QueryAsync("*IDN?", ct); + + /// + /// 恢复出厂设置(*RST) + /// + public async Task ResetAsync(CancellationToken ct = default) + => await WriteAsync("*RST", ct); + + /// + /// 保存参数到内存 (*SAV n) + /// + public async Task SavePresetAsync(int index, CancellationToken ct = default) + => await WriteAsync($"*SAV {index}", ct); + + /// + /// 从内存调用参数 (*RCL n) + /// + public async Task RecallPresetAsync(int index, CancellationToken ct = default) + => await WriteAsync($"*RCL {index}", ct); + + /// + /// 切换到本地模式(SYST:LOC) + /// + public async Task SetLocalAsync(CancellationToken ct = default) + => await WriteAsync("SYST:LOC", ct); + + /// + /// 切换到远程模式(SYST:REM) + /// + public async Task SetRemoteAsync(CancellationToken ct = default) + => await WriteAsync("SYST:REM", ct); + + /// + /// 控制输出开关(ON/OFF) + /// + public async Task SetOutputAsync(bool on, CancellationToken ct = default) + => await WriteAsync($"OUTP:STAT {(on ? "ON" : "OFF")}", ct); + + /// + /// 查询输出状态(返回 true 表示 ON) + /// + public async Task 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 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 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 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 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 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 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 MeasureVoltageAsync(CancellationToken ct = default) + => double.Parse((await QueryAsync("MEAS:VOLT?", ct)).Replace("V", ""), CultureInfo.InvariantCulture); + + public async Task MeasureCurrentAsync(CancellationToken ct = default) + => double.Parse((await QueryAsync("MEAS:CURR?", ct)).Replace("A", ""), CultureInfo.InvariantCulture); + + public async Task MeasurePowerAsync(CancellationToken ct = default) + => double.Parse((await QueryAsync("MEAS:POW?", ct)).Replace("W", ""), CultureInfo.InvariantCulture); + + /// + /// 同时测量电压、电流、功率 + /// + 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 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 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 GetOCPAsync(CancellationToken ct = default) + => double.Parse((await QueryAsync("CURR:PROT?", ct)).Replace("A", ""), CultureInfo.InvariantCulture); + + public async Task 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 GetSyncStatusAsync(CancellationToken ct = default) + => await QueryAsync("SYNC:STAT?", ct); + + #endregion } } diff --git a/DeviceCommand/Device/WS-68030-380T.cs b/DeviceCommand/Device/WS-68030-380T.cs index dc1aecd..739133d 100644 --- a/DeviceCommand/Device/WS-68030-380T.cs +++ b/DeviceCommand/Device/WS-68030-380T.cs @@ -1,13 +1,148 @@ using DeviceCommand.Base; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Threading; using System.Threading.Tasks; 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 Get_A_Voltage(CancellationToken ct = default) + { + var regs = await ReadHoldingRegistersAsync(SlaveAddress, 150, 2, ct); + return ConvertToFloat(regs); + } + + public async Task Get_A_Current(CancellationToken ct = default) + { + var regs = await ReadHoldingRegistersAsync(SlaveAddress, 152, 2, ct); + return ConvertToFloat(regs); + } + + public async Task 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 } } diff --git a/DeviceCommand/DeviceCommand.csproj b/DeviceCommand/DeviceCommand.csproj index b9d7cb2..748ffcd 100644 --- a/DeviceCommand/DeviceCommand.csproj +++ b/DeviceCommand/DeviceCommand.csproj @@ -8,6 +8,7 @@ +