视觉修改
This commit is contained in:
		
							
								
								
									
										236
									
								
								Check.Main/Common/EasyE5Options.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								Check.Main/Common/EasyE5Options.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | ||||
| using NPOI.SS.Formula.Functions; | ||||
| using Sunny.UI; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Net.Sockets; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Check.Main.Common | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 汇川 Easy 系列 PLC 客户端 (Modbus TCP) | ||||
|     /// 支持 D/M/X/Y 地址读写,整数、浮点数、字符串 | ||||
|     /// </summary> | ||||
|     public class EasyPlcClient : IDisposable | ||||
|     { | ||||
|         private readonly TcpClient _tcpClient = new(); | ||||
|         private NetworkStream _stream; | ||||
|         private ushort _transactionId = 0; | ||||
|         private readonly SemaphoreSlim _lock = new(1, 1); | ||||
|  | ||||
|         private readonly string _ip; | ||||
|         private readonly int _port; | ||||
|         private readonly byte _slaveId; | ||||
|  | ||||
|         public EasyPlcClient(string ip, int port = 502, byte slaveId = 1) | ||||
|         { | ||||
|             _ip = ip; | ||||
|             _port = port; | ||||
|             _slaveId = slaveId; | ||||
|         } | ||||
|  | ||||
|         public async Task ConnectAsync() | ||||
|         { | ||||
|             await _tcpClient.ConnectAsync(_ip, _port); | ||||
|             _stream = _tcpClient.GetStream(); | ||||
|         } | ||||
|  | ||||
|         #region ---- 基础 Modbus ---- | ||||
|         private ushort GetTransactionId() => | ||||
|             unchecked((ushort)Interlocked.Increment(ref Unsafe.As<ushort, int>(ref _transactionId))); | ||||
|  | ||||
|         private async Task<byte[]> SendAndReceiveAsync(byte[] pdu, ushort startAddr, ushort length = 1) | ||||
|         { | ||||
|             ushort tid = GetTransactionId(); | ||||
|             ushort lengthField = (ushort)(pdu.Length + 1); | ||||
|             byte[] mbap = { | ||||
|                 (byte)(tid >> 8), (byte)tid, | ||||
|                 0x00, 0x00, // Protocol = 0 | ||||
|                 (byte)(lengthField >> 8), (byte)lengthField, | ||||
|                 _slaveId | ||||
|             }; | ||||
|  | ||||
|             byte[] adu = new byte[mbap.Length + pdu.Length]; | ||||
|             Buffer.BlockCopy(mbap, 0, adu, 0, mbap.Length); | ||||
|             Buffer.BlockCopy(pdu, 0, adu, mbap.Length, pdu.Length); | ||||
|  | ||||
|             await _lock.WaitAsync(); | ||||
|             try | ||||
|             { | ||||
|                 await _stream.WriteAsync(adu, 0, adu.Length); | ||||
|  | ||||
|                 byte[] header = new byte[7]; | ||||
|                 await _stream.ReadAsync(header, 0, 7); | ||||
|                 int respLength = (header[4] << 8) + header[5]; | ||||
|                 byte[] resp = new byte[respLength - 1]; | ||||
|                 await _stream.ReadAsync(resp, 0, resp.Length); | ||||
|  | ||||
|                 return resp; | ||||
|             } | ||||
|             finally { _lock.Release(); } | ||||
|         } | ||||
|         #endregion | ||||
|  | ||||
|         #region ---- 地址解析 ---- | ||||
|         private void ParseAddress(string address, out string area, out ushort offset) | ||||
|         { | ||||
|             var letters = Regex.Match(address, @"^[A-Za-z]+").Value.ToUpper(); | ||||
|             var digits = Regex.Match(address, @"\d+").Value; | ||||
|  | ||||
|             if (string.IsNullOrEmpty(letters) || string.IsNullOrEmpty(digits)) | ||||
|                 throw new ArgumentException($"地址 {address} 无效"); | ||||
|  | ||||
|             area = letters; | ||||
|             offset = (ushort)(ushort.Parse(digits) - 1); | ||||
|  | ||||
|             // 👉 注意:这里的偏移需根据你的 PLC Modbus 地址表调整 | ||||
|             // 例如 D 区可能是 40001 起,M 区可能是 00001 起 | ||||
|             // 目前假设 D -> Holding Register, M/Y -> Coil, X -> Discrete Input | ||||
|         } | ||||
|         #endregion | ||||
|  | ||||
|         #region ---- 整数读写 ---- | ||||
|         public async Task WriteAsync(string address, int value) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|  | ||||
|             switch (area) | ||||
|             { | ||||
|                 case "D": // 写寄存器 | ||||
|                     byte[] pdu = { | ||||
|                         0x06, | ||||
|                         (byte)(offset >> 8), (byte)offset, | ||||
|                         (byte)(value >> 8), (byte)value | ||||
|                     }; | ||||
|                     await SendAndReceiveAsync(pdu, offset); | ||||
|                     break; | ||||
|  | ||||
|                 case "M": | ||||
|                 case "Y": // 写单线圈 | ||||
|                     byte[] coil = { | ||||
|                         0x05, | ||||
|                         (byte)(offset >> 8), (byte)offset, | ||||
|                         (byte)(value != 0 ? 0xFF : 0x00), 0x00 | ||||
|                     }; | ||||
|                     await SendAndReceiveAsync(coil, offset); | ||||
|                     break; | ||||
|  | ||||
|                 default: | ||||
|                     throw new NotSupportedException($"写 {area} 区未支持"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public async Task<int> ReadAsync(string address) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|  | ||||
|             switch (area) | ||||
|             { | ||||
|                 case "D": // Holding Register | ||||
|                     byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 }; | ||||
|                     var resp = await SendAndReceiveAsync(pdu, offset); | ||||
|                     return (resp[1] << 8) + resp[2]; | ||||
|  | ||||
|                 case "M": | ||||
|                 case "Y": // Coils | ||||
|                     byte[] pdu1 = { 0x01, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 }; | ||||
|                     var resp1 = await SendAndReceiveAsync(pdu1, offset); | ||||
|                     return (resp1[2] & 0x01) != 0 ? 1 : 0; | ||||
|  | ||||
|                 case "X": // Inputs | ||||
|                     byte[] pdu2 = { 0x02, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 }; | ||||
|                     var resp2 = await SendAndReceiveAsync(pdu2, offset); | ||||
|                     return (resp2[2] & 0x01) != 0 ? 1 : 0; | ||||
|  | ||||
|                 default: | ||||
|                     throw new NotSupportedException($"读 {area} 区未支持"); | ||||
|             } | ||||
|         } | ||||
|         #endregion | ||||
|  | ||||
|         #region ---- 浮点读写 (2寄存器) ---- | ||||
|         public async Task WriteFloatAsync(string address, float value) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|             if (area != "D") throw new NotSupportedException("浮点仅支持 D 区"); | ||||
|  | ||||
|             byte[] bytes = BitConverter.GetBytes(value); | ||||
|             if (BitConverter.IsLittleEndian) Array.Reverse(bytes); | ||||
|  | ||||
|             byte[] pdu = { | ||||
|                 0x10, | ||||
|                 (byte)(offset >> 8), (byte)offset, | ||||
|                 0x00, 0x02, | ||||
|                 0x04, | ||||
|                 bytes[0], bytes[1], bytes[2], bytes[3] | ||||
|             }; | ||||
|             await SendAndReceiveAsync(pdu, offset, 2); | ||||
|         } | ||||
|  | ||||
|         public async Task<float> ReadFloatAsync(string address) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|             if (area != "D") throw new NotSupportedException("浮点仅支持 D 区"); | ||||
|  | ||||
|             byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, 0x00, 0x02 }; | ||||
|             var resp = await SendAndReceiveAsync(pdu, offset, 2); | ||||
|  | ||||
|             byte[] data = { resp[1], resp[2], resp[3], resp[4] }; | ||||
|             if (BitConverter.IsLittleEndian) Array.Reverse(data); | ||||
|  | ||||
|             return BitConverter.ToSingle(data, 0); | ||||
|         } | ||||
|         #endregion | ||||
|  | ||||
|         #region ---- 字符串读写 ---- | ||||
|         public async Task WriteStringAsync(string address, string value, int maxLength) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|             if (area != "D") throw new NotSupportedException("字符串仅支持 D 区"); | ||||
|  | ||||
|             byte[] strBytes = Encoding.ASCII.GetBytes(value); | ||||
|             if (strBytes.Length > maxLength) Array.Resize(ref strBytes, maxLength); | ||||
|  | ||||
|             // 每寄存器2字节 | ||||
|             int regCount = (strBytes.Length + 1) / 2; | ||||
|             byte[] data = new byte[regCount * 2]; | ||||
|             Array.Copy(strBytes, data, strBytes.Length); | ||||
|  | ||||
|             byte[] pdu = new byte[7 + data.Length]; | ||||
|             pdu[0] = 0x10; | ||||
|             pdu[1] = (byte)(offset >> 8); pdu[2] = (byte)offset; | ||||
|             pdu[3] = (byte)(regCount >> 8); pdu[4] = (byte)regCount; | ||||
|             pdu[5] = (byte)(data.Length); | ||||
|             Buffer.BlockCopy(data, 0, pdu, 6, data.Length); | ||||
|  | ||||
|             await SendAndReceiveAsync(pdu, offset, (ushort)regCount); | ||||
|         } | ||||
|  | ||||
|         public async Task<string> ReadStringAsync(string address, int length) | ||||
|         { | ||||
|             ParseAddress(address, out string area, out ushort offset); | ||||
|             if (area != "D") throw new NotSupportedException("字符串仅支持 D 区"); | ||||
|  | ||||
|             int regCount = (length + 1) / 2; | ||||
|             byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, (byte)(regCount >> 8), (byte)regCount }; | ||||
|             var resp = await SendAndReceiveAsync(pdu, offset, (ushort)regCount); | ||||
|  | ||||
|             byte[] data = new byte[resp[0]]; | ||||
|             Array.Copy(resp, 1, data, 0, data.Length); | ||||
|  | ||||
|             return Encoding.ASCII.GetString(data).TrimEnd('\0'); | ||||
|         } | ||||
|         #endregion | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _stream?.Dispose(); | ||||
|             _tcpClient?.Close(); | ||||
|             _lock?.Dispose(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user