Files
CheckDevice/Check.Main/Common/EasyE5Options.cs
17860779768 2e46747ba9 视觉修改
2025-08-25 16:33:58 +08:00

237 lines
8.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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