视觉修改

This commit is contained in:
17860779768
2025-08-25 16:33:58 +08:00
commit 2e46747ba9
49 changed files with 11062 additions and 0 deletions

View 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();
}
}
}