using System.Net.Sockets; using System.Text.RegularExpressions; namespace FinsTcp; //This is a copy of a Open library POmronFinsTCP.Net //I have modified tcpclient sendtimeout and receivetimeout time to make sure it can know connect status when device connect into a switch //Please ignore all the warnings and Messages,I know the code is in a mess, but it is working. //I will modified and optimize the code later - Zixuan //Fix all warnings and Messages and make code more readable - 2025/06/10 Zixuan public class FinsTcpBase : Tcp { public byte PlcNode { get; private set; } public byte PcNode { get; private set; } // // Summary: // Fins读写指令生成 // // Parameters: // rw: // 读写类型 // // mr: // 寄存器类型 // // mt: // 地址类型 // // ch: // 起始地址 // // offset: // 位地址:00-15,字地址则为00 // // cnt: // 地址个数,按位读写只能是1 public bool Link(string rIP, int rPort = 9600, int sendTimeout = 1000, int receiveTimeout = 1000) { if (this._client is null) return false; _client.SendTimeout = sendTimeout; _client.ReceiveTimeout = receiveTimeout; _client.NoDelay = true; if (!_client.ConnectAsync(rIP, rPort).Wait(sendTimeout)) return false; _stream = _client.GetStream(); Thread.Sleep(10); if (!SendData(FinsClass.HandShakePack)) return false; byte[] array = new byte[24]; if (!ReceiveData(array)) return false; if (array[15] != 0) return false; PcNode = array[19]; PlcNode = array[23]; return true; } public bool Close() { try { _stream?.Close(); _client?.Close(); return true; } catch { return false; } } public bool ReadWords(string mrch, short cnt, out short[]? reData) { reData = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return ReadWords(plcMemory, short.Parse(txtq), cnt, out reData); } public bool ReadWords(PlcMemory mr, short ch, short cnt, out short[] reData) { reData = new short[cnt]; int num = 30 + cnt * 2; byte[] array = new byte[num]; byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, cnt, PlcNode, PcNode); if (!SendData(sd)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; for (int i = 0; i < cnt; i++) { byte[] value = [array[30 + i * 2 + 1], array[30 + i * 2]]; reData[i] = BitConverter.ToInt16(value, 0); } return true; } public bool ReadWord(string mrch, out short reData) { reData = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return ReadWord(plcMemory, short.Parse(txtq), out reData); } public bool ReadWord(PlcMemory mr, short ch, out short reData) { reData = 0; if (!ReadWords(mr, ch, 1, out var reData2)) return false; reData = reData2[0]; return true; } public bool WriteWords(string mrch, short cnt, short[] inData) { if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return WriteWords(plcMemory, short.Parse(txtq), cnt, inData); } public bool WriteWords(PlcMemory mr, short ch, short cnt, short[] inData) { byte[] array = new byte[30]; byte[] array2 = FinsClass.FinsCmd(Operation.Write, mr, MemoryType.Word, ch, 0, cnt, PlcNode, PcNode); byte[] array3 = new byte[cnt * 2]; for (int i = 0; i < cnt; i++) { byte[] bytes = BitConverter.GetBytes(inData[i]); array3[i * 2] = bytes[1]; array3[i * 2 + 1] = bytes[0]; } byte[] array4 = new byte[cnt * 2 + 34]; array2.CopyTo(array4, 0); array3.CopyTo(array4, 34); if (!SendData(array4)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; return ErrorCode.CheckEndCode(array[28], array[29]); } public bool WriteWord(string mrch, short inData) { if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return WriteWord(plcMemory, short.Parse(txtq), inData); } public bool WriteWord(PlcMemory mr, short ch, short inData) { short[] inData2 = [inData]; if (!WriteWords(mr, ch, 1, inData2)) return false; return true; } public bool GetBitState(string mrch, out short bs) { bs = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return GetBitState(plcMemory, txtq, out bs); } public bool GetBitStates(string mrch, out bool[]? bs, short cnt = 1) { bs = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return GetBitStates(plcMemory, txtq, out bs, cnt); } public bool GetBitStates(PlcMemory mr, string ch, out bool[] bs, short cnt = 1) { bs = new bool[cnt]; byte[] array = new byte[30 + cnt]; short ch2 = short.Parse(ch.Split(['.'])[0]); short offset = short.Parse(ch.Split(['.'])[1]); byte[] command = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Bit, ch2, offset, cnt, PlcNode, PcNode); if (!SendData(command)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; for (int i = 0; i < cnt; i++) bs[i] = array[30 + i] == 1; return true; } public bool GetBitState(PlcMemory mr, string ch, out short bs) { bs = 0; byte[] array = new byte[31]; string[] spilts = ch.Split('.'); short ch2 = short.Parse(spilts[0]); short offset = short.Parse(spilts[1]); byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Bit, ch2, offset, 1, PlcNode, PcNode); if (!SendData(sd)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; bs = array[30]; return true; } public bool SetBitState(string mrch, BitState bs) { if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return SetBitState(plcMemory, txtq, bs); } public bool SetBitState(PlcMemory mr, string ch, BitState bs) { byte[] array = new byte[30]; string[] spilts = ch.Split('.'); short ch2 = short.Parse(spilts[0]); short offset = short.Parse(spilts[1]); byte[] array2 = FinsClass.FinsCmd(Operation.Write, mr, MemoryType.Bit, ch2, offset, 1, PlcNode, PcNode); byte[] array3 = new byte[35]; array2.CopyTo(array3, 0); array3[34] = (byte)bs; if (!SendData(array3)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; return true; } public bool ReadReal(string mrch, out float reData) { reData = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return ReadReal(plcMemory, short.Parse(txtq), out reData); } public bool ReadReal(PlcMemory mr, short ch, out float reData) { reData = 0f; int num = 34; byte[] array = new byte[num]; byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, 2, PlcNode, PcNode); if (!SendData(sd)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; byte[] value = [array[31], array[30], array[33], array[32]]; reData = BitConverter.ToSingle(value, 0); return true; } public bool WriteReal(string mrch, float reData) { if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return WriteReal(plcMemory, short.Parse(txtq), reData); } public bool WriteReal(PlcMemory mr, short ch, float reData) { if (BitConverter.GetBytes(reData) is not byte[] bytes) return false; short[] array = new short[2]; array[0] = BitConverter.ToInt16(bytes, 0); if (bytes.Length > 2) array[1] = BitConverter.ToInt16(bytes, 2); return WriteWords(mr, ch, 2, array); } public bool ReadInt32(string mrch, out int reData) { reData = default; if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return ReadInt32(plcMemory, short.Parse(txtq), out reData); } public bool ReadInt32(PlcMemory mr, short ch, out int reData) { reData = 0; int num = 34; byte[] array = new byte[num]; byte[] sd = FinsClass.FinsCmd(Operation.Read, mr, MemoryType.Word, ch, 0, 2, PlcNode, PcNode); if (!SendData(sd)) return false; if (!ReceiveData(array)) return false; bool flag = array[11] switch { 3 => ErrorCode.CheckHeadError(array[15]), _ => true }; if (!flag) return false; if (!ErrorCode.CheckEndCode(array[28], array[29])) return false; byte[] value = [array[31], array[30], array[33], array[32]]; reData = BitConverter.ToInt32(value, 0); return true; } public bool WriteInt32(string mrch, int reData) { if (!ConvertClass.GetPlcMemory(mrch, out string txtq, out PlcMemory plcMemory)) return false; return WriteInt32(plcMemory, short.Parse(txtq), reData); } public bool WriteInt32(PlcMemory mr, short ch, int reData) { if (BitConverter.GetBytes(reData) is not byte[] bytes) return false; short[] array = new short[2]; array[0] = BitConverter.ToInt16(bytes, 0); if (bytes.Length > 2) array[1] = BitConverter.ToInt16(bytes, 2); return WriteWords(mr, ch, 2, array); } } public class Tcp { public Tcp() { this._client = new(); } protected readonly TcpClient _client; protected NetworkStream? _stream; protected bool SendData(byte[] sd) { if (_stream == null) return false; try { _stream.Write(sd, 0, sd.Length); return true; } catch { return false; } } protected bool ReceiveData(byte[] rd) { if (_stream == null) return false; try { int num = 0; do { int num2 = _stream.Read(rd, num, rd.Length - num); if (num2 == 0) return false; num += num2; } while (num < rd.Length); return true; } catch { return false; } } } internal class ConvertClass { // // Summary: // 得到枚举值 // // Parameters: // txt: // 如:D100,W100.1 // // txtq:100.1 internal static bool GetPlcMemory(string txt, out string txtq, out PlcMemory plcMemory) { txtq = string.Empty; char c = txt.Trim().ToUpper().FirstOrDefault(); plcMemory = c switch { 'D' => PlcMemory.DM, 'W' => PlcMemory.WR, 'H' => PlcMemory.HR, 'A' => PlcMemory.AR, 'C' => PlcMemory.CNT, 'I' => PlcMemory.CIO, 'T' => PlcMemory.TIM, _ => PlcMemory.Undefined, }; if (plcMemory == PlcMemory.Undefined) return false; txtq = Regex.Replace(txt, "[^0-9.]", ""); return true; } } internal class ErrorCode { // // Summary: // (若返回的头指令为3)检查命令头中的错误代码 // // Parameters: // Code: // 错误代码 // // Returns: // 指示程序是否可以继续进行 internal static bool CheckHeadError(byte Code) { Console.WriteLine($"Error Code {Code}"); return Code == 0; } // // Summary: // 检查命令帧中的EndCode // // Parameters: // Main: // 主码 // // Sub: // 副码 // // Returns: // 指示程序是否可以继续进行 internal static bool CheckEndCode(byte Main, byte Sub) { //Totally dont understand what's doing here, Replace with the code below return Main == 0 && (Sub == 64 || Sub == 0); //switch (Main) //{ // case 0: // switch (Sub) // { // case 0: // case 64: // return true; // case 1: // return false; // } // break; // case 1: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // case 5: // case 6: // return false; // } // break; // case 2: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // case 5: // return false; // } // break; // case 3: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // return false; // } // break; // case 4: // switch (Sub) // { // case 1: // case 2: // return false; // } // break; // case 5: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // return false; // } // break; // case 16: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // case 5: // return false; // } // break; // case 17: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // case 6: // case 9: // case 10: // case 11: // case 12: // return false; // } // break; // case 32: // switch (Sub) // { // case 2: // case 3: // case 4: // case 5: // case 6: // case 7: // return false; // } // break; // case 33: // switch (Sub) // { // case 1: // case 2: // case 3: // case 5: // case 6: // case 7: // case 8: // return false; // } // break; // case 34: // switch (Sub) // { // case 1: // case 2: // case 3: // case 4: // case 5: // case 6: // case 7: // case 8: // return false; // } // break; // case 35: // switch (Sub) // { // case 1: // case 2: // case 3: // return false; // } // break; // case 36: // { // byte b5 = Sub; // byte b6 = b5; // if (b6 != 1) // { // break; // } // return false; // } // case 37: // switch (Sub) // { // case 2: // case 3: // case 4: // case 5: // case 6: // case 7: // case 9: // case 10: // case 13: // case 15: // case 16: // return false; // } // break; // case 38: // switch (Sub) // { // case 1: // case 2: // case 4: // case 5: // case 6: // case 7: // case 8: // case 9: // case 10: // case 11: // return false; // } // break; // case 48: // { // byte b3 = Sub; // byte b4 = b3; // if (b4 != 1) // { // break; // } // return false; // } // case 64: // { // byte b = Sub; // byte b2 = b; // if (b2 != 1) // { // break; // } // return false; // } //} //return false; } } internal class FinsClass { // // Summary: // 获取内存区码 // // Parameters: // mr: // 寄存器类型 // // mt: // 地址类型 internal static byte GetMemoryCode(PlcMemory mr, MemoryType mt) { if (mt == MemoryType.Bit) { return mr switch { PlcMemory.CIO => 48, PlcMemory.WR => 49, PlcMemory.HR => 50, PlcMemory.AR => 51, PlcMemory.DM => 2, PlcMemory.CNT or PlcMemory.TIM => 9, _ => 0, }; } return mr switch { PlcMemory.CIO => 176, PlcMemory.WR => 177, PlcMemory.HR => 178, PlcMemory.AR => 179, PlcMemory.DM => 130, PlcMemory.CNT or PlcMemory.TIM => 137, _ => 0, }; } internal static byte[] FinsCmd(Operation rw, PlcMemory mr, MemoryType mt, short ch, short offset, short cnt, byte PlcNode, byte PcNode) { byte[] array = [ 70, 73, 78, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; if (rw == Operation.Read) { array[6] = 0; array[7] = 26; } else if (mt == MemoryType.Word) { array[6] = (byte)((cnt * 2 + 26) / 256); array[7] = (byte)((cnt * 2 + 26) % 256); } else { array[6] = 0; array[7] = 27; } array[11] = 2; array[16] = 128; array[18] = 2; array[20] = PlcNode; array[23] = PcNode; array[25] = byte.MaxValue; if (rw == Operation.Read) { array[26] = 1; array[27] = 1; } else { array[26] = 1; array[27] = 2; } array[28] = FinsClass.GetMemoryCode(mr, mt); if (mr == PlcMemory.CNT) { array[29] = (byte)(ch / 256 + 128); array[30] = (byte)(ch % 256); } else { array[29] = (byte)(ch / 256); array[30] = (byte)(ch % 256); array[31] = (byte)offset; } array[32] = (byte)(cnt / 256); array[33] = (byte)(cnt % 256); return array; } public static byte[] Header = [0x46, 0x49, 0x4E, 0x53]; public static byte[] HandShakePack = [ 0x46, 0x49, 0x4E, 0x53, //Header 0x00, 0x00, 0x00, 0x0c, //Length 0x00, 0x00, 0x00, 0x00, //Command 0x00, 0x00, 0x00, 0x00, //ErrorCode 0x00, 0x00, 0x00, 0x00 //Client Node ]; } public enum PlcMemory { CIO, WR, HR, AR, DM, CNT, TIM, Undefined, } public enum Operation { Read, Write } public enum MemoryType { Bit, Word } public enum BitState { ON = 1, OFF = 0 }