using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; namespace OmronLib { /* *Copyright: Copyright (c) 2019 *Created on 2019-11-7 *Author: coder_li@outlook.com */ /// /// 欧姆龙PLC使用FINS通讯,在与PLC建立tcp连接后,必须先进行读写准备,获取到DA1和SA1 /// 这里默认读取200个D区地址,从D5000开始到D5199,读取的返回值都是高位在前低位在后 /// 写入时都是单个寄存器写入,写入的内容也是高位在前低位在后 /// public class Omron { /// /// PLCIP /// public IPAddress IPAddr { get; set; } /// /// PLC端口号 /// public int Port { get; set; } public int Timeout = 1000; /// /// 获取是否已经连接到PLC /// /// public bool IsConnected { get { if (PlcClient == null) return false; else return PlcClient.Connected; } } /// /// PLC内存区域类型 /// public enum AreaType { CIO_Bit = 0x30, WR_Bit = 0x31, HR_Bit = 0x32, AR_Bit = 0x33, DM_Bit = 0x02, CIO_Word = 0xB0, WR_Word = 0xB1, HR_Word = 0xB2, AR_Word = 0xB3, DM_Word = 0x82 } private readonly object locker = new object(); private TcpClient PlcClient = null; private NetworkStream Stream = null; private IPEndPoint PlcIP = null; private byte DA1 = 0x00, SA1 = 0x00; public Omron() { } #region 命令模板 /// /// 写命令 /// private readonly byte[] WriteCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S 0x00, 0x00, 0x00, 0x1c, // 命令长度 0x00, 0x00, 0x00, 0x02, // 命令码 0x00, 0x00, 0x00, 0x00, // 错误码 0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA 0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 0x00, 0xDA, // SA2 SID 0x01, 0x02, // FINS主命令 FINS从命令 (01 02 写) 0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(从高到低) 开始位地址 0x00, 0x01 }; // 写入数量(从高到低) 后面都是写入的内容(可以加长) /// /// 读命令 /// private readonly byte[] ReadCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S 0x00, 0x00, 0x00, 0x1a, // 命令长度 0x00, 0x00, 0x00, 0x02, // 命令码 0x00, 0x00, 0x00, 0x00, // 错误码 0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA *************** 0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 FINS Header 0x00, 0xDA, // SA2 SID *************** 0x01, 0x01, // FINS主命令 FINS从命令 (01 01 读) *************** 0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(高,低) 开始位地址 FINS Command 0x00, 0x01 }; // 读取数量(高,低) *************** /// /// 获取节点地址的命令 /// private readonly byte[] PrepareCmd = new byte[] { 0x46, 0x49, 0x4e, 0x53, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endregion /// /// 向PLC发送指令 /// /// 指令 /// 报错信息 /// 是否发送成功 private bool DataSend(byte[] commands) { string info; try { if (this.PlcClient != null && PlcClient.Connected) { //lock (locker) { if (Stream == null) Stream = PlcClient.GetStream(); Stream.Write(commands, 0, commands.Length); Stream.Flush(); } return true; } else { info = "连接已断开"; return false; } } catch (Exception exp) { info = "发送指令到PLC失败 " + exp.Message; return false; } } /// /// 从PLC读取数据 /// /// /// 报错信息 /// private string DataRead() { byte[] buffer = new byte[1024]; int bytestoread = 0; string info; try { //if (Stream.DataAvailable) { int start = Environment.TickCount; do { bytestoread = Stream.Read(buffer, 0, buffer.Length); if (Math.Abs(Environment.TickCount - start) >= 20000) { info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!"; break; } } while (bytestoread == 0); if (buffer[11] == 3) { if (buffer[15] == 1) { info = "发送的命令不是有效的FINS命令!"; return ""; } else if (buffer[15] == 2) { info = "发送的命令长度超出范围!"; return ""; } else if (buffer[15] == 3) { info = "PLC不支持该‘FINS’命令!"; return ""; } } if (buffer[25] == WriteCommand[25]) return DataTools.ByteToHexString(buffer, bytestoread); else { info = "PLC返回错误,返回值 " + buffer[0].ToString(); return ""; } } } catch { return ""; } } /// /// 判断是否成功写入命令到PLC /// /// private bool Write2PlcSuccess() { byte[] buffer = new byte[1024]; int bytestoread = 0; string info; int start = Environment.TickCount; try { //lock (locker) { do { bytestoread = Stream.Read(buffer, 0, buffer.Length); if (Math.Abs(Environment.TickCount - start) >= 1000) { info = "PLC应答超时,请确认PLC连接线路及cpu是否有报错!"; break; } } while (bytestoread == 0); } if (buffer[25] == WriteCommand[25]) return true; else { info = "PLC返回错误,返回值 " + buffer[1].ToString(); return false; } } catch { return false; } } /// /// 发送连接字符串,获取PLC返回的DA1和SA1值 /// /// private bool ConnectPrepare() { try { Stream.Write(PrepareCmd, 0, PrepareCmd.Length); byte[] res = new byte[24]; Stream.Read(res, 0, res.Length); DA1 = res[23]; SA1 = res[19]; } catch { return false; } return true; } /// /// 检查命令帧中的EndCode /// /// 主码 /// 副码 /// 错误信息 /// 指示程序是否可以继续进行 private bool CheckEndCode(byte Main, byte Sub, out string info) { info = ""; switch (Main) { case 0x00: switch (Sub) { case 0x00: return true;//the only situation of success case 0x01: info = "service canceled"; return false; } break; case 0x01: switch (Sub) { case 0x01: info = "local node not in network"; return false; case 0x02: info = "token timeout"; return false; case 0x03: info = "retries failed"; return false; case 0x04: info = "too many send frames"; return false; case 0x05: info = "node address range error"; return false; case 0x06: info = "node address duplication"; return false; } break; case 0x02: switch (Sub) { case 0x01: info = "destination node not in network"; return false; case 0x02: info = "unit missing"; return false; case 0x03: info = "third node missing"; return false; case 0x04: info = "destination node busy"; return false; case 0x05: info = "response timeout"; return false; } break; case 0x03: switch (Sub) { case 0x01: info = "communications controller error"; return false; case 0x02: info = "CPU unit error"; return false; case 0x03: info = "controller error"; return false; case 0x04: info = "unit number error"; return false; } break; case 0x04: switch (Sub) { case 0x01: info = "undefined command"; return false; case 0x02: info = "not supported by model/version"; return false; } break; case 0x05: switch (Sub) { case 0x01: info = "destination address setting error"; return false; case 0x02: info = "no routing tables"; return false; case 0x03: info = "routing table error"; return false; case 0x04: info = "too many relays"; return false; } break; case 0x10: switch (Sub) { case 0x01: info = "command too long"; return false; case 0x02: info = "command too short"; return false; case 0x03: info = "elements/data don't match"; return false; case 0x04: info = "command format error"; return false; case 0x05: info = "header error"; return false; } break; case 0x11: switch (Sub) { case 0x01: info = "area classification missing"; return false; case 0x02: info = "access size error"; return false; case 0x03: info = "address range error"; return false; case 0x04: info = "address range exceeded"; return false; case 0x06: info = "program missing"; return false; case 0x09: info = "relational error"; return false; case 0x0a: info = "duplicate data access"; return false; case 0x0b: info = "response too long"; return false; case 0x0c: info = "parameter error"; return false; } break; case 0x20: switch (Sub) { case 0x02: info = "protected"; return false; case 0x03: info = "table missing"; return false; case 0x04: info = "data missing"; return false; case 0x05: info = "program missing"; return false; case 0x06: info = "file missing"; return false; case 0x07: info = "data mismatch"; return false; } break; case 0x21: switch (Sub) { case 0x01: info = "read-only"; return false; case 0x02: info = "protected , cannot write data link table"; return false; case 0x03: info = "cannot register"; return false; case 0x05: info = "program missing"; return false; case 0x06: info = "file missing"; return false; case 0x07: info = "file name already exists"; return false; case 0x08: info = "cannot change"; return false; } break; case 0x22: switch (Sub) { case 0x01: info = "not possible during execution"; return false; case 0x02: info = "not possible while running"; return false; case 0x03: info = "wrong PLC mode"; return false; case 0x04: info = "wrong PLC mode"; return false; case 0x05: info = "wrong PLC mode"; return false; case 0x06: info = "wrong PLC mode"; return false; case 0x07: info = "specified node not polling node"; return false; case 0x08: info = "step cannot be executed"; return false; } break; case 0x23: switch (Sub) { case 0x01: info = "file device missing"; return false; case 0x02: info = "memory missing"; return false; case 0x03: info = "clock missing"; return false; } break; case 0x24: switch (Sub) { case 0x01: info = "table missing"; return false; } break; case 0x25: switch (Sub) { case 0x02: info = "memory error"; return false; case 0x03: info = "I/O setting error"; return false; case 0x04: info = "too many I/O points"; return false; case 0x05: info = "CPU bus error"; return false; case 0x06: info = "I/O duplication"; return false; case 0x07: info = "CPU bus error"; return false; case 0x09: info = "SYSMAC BUS/2 error"; return false; case 0x0a: info = "CPU bus unit error"; return false; case 0x0d: info = "SYSMAC BUS No. duplication"; return false; case 0x0f: info = "memory error"; return false; case 0x10: info = "SYSMAC BUS terminator missing"; return false; } break; case 0x26: switch (Sub) { case 0x01: info = "no protection"; return false; case 0x02: info = "incorrect password"; return false; case 0x04: info = "protected"; return false; case 0x05: info = "service already executing"; return false; case 0x06: info = "service stopped"; return false; case 0x07: info = "no execution right"; return false; case 0x08: info = "settings required before execution"; return false; case 0x09: info = "necessary items not set"; return false; case 0x0a: info = "number already defined"; return false; case 0x0b: info = "error will not clear"; return false; } break; case 0x30: switch (Sub) { case 0x01: info = "no access right"; return false; } break; case 0x40: switch (Sub) { case 0x01: info = "service aborted"; return false; } break; } info = "unknown exception"; return false; } /// /// PLC初始化 /// /// /// private bool PlcInit() { bool flag = false; PlcClient = new TcpClient(); IAsyncResult asyncResult = PlcClient.BeginConnect(IPAddr, Port, null, null); if (!asyncResult.AsyncWaitHandle.WaitOne(Timeout)) { return flag; } PlcClient.EndConnect(asyncResult); if (PlcClient != null) { Stream = PlcClient.GetStream(); Stream.ReadTimeout = Timeout; Stream.WriteTimeout = Timeout; if (ConnectPrepare()) { flag = true; } } return flag; } /// /// 连接PLC /// /// /// public bool PlcConnect(out string info) { info = ""; try { if (PlcClient == null) { PlcInit(); } if (!PlcClient.Connected) // 没连上的话重试一遍 { PlcClient.Close(); PlcClient = null; PlcInit(); } return PlcClient.Connected; } catch (Exception exp) { info = "连接PLC失败\n" + exp.Message; return false; } } /// /// 断开PLC连接 /// /// public bool DisConnect() { if (this.PlcClient == null) return true; if (!this.PlcClient.Connected) return true; this.PlcClient.Close(); return true; } #region 读 /// /// 读PLC字或位 /// 读位时从左到右是低位到高位 /// /// PLC内存区类型 /// 开始地址(字) /// 开始地址(位) /// 长度 /// 返回的字符数组,每个元素表示一个字 /// public bool Read(AreaType type, int startword, int startbit, int count, out string[] result) { result = new string[0]; byte[] cmd = (byte[])ReadCommand.Clone(); cmd[20] = DA1; //连接时获取到的DA1 cmd[23] = SA1; //连接时获取到的SA1 // 内存类型 cmd[28] = (byte)type; // 读取起始地址 byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 cmd[29] = bytesAddr[1]; cmd[30] = bytesAddr[0]; cmd[31] = (byte)startbit; // 读取长度 byte[] bytesLength = BitConverter.GetBytes((short)count); cmd[32] = bytesLength[1]; cmd[33] = bytesLength[0]; //开始读取 lock (locker) { if (DataSend(cmd)) { string res = DataRead(); if (res.Length == 0) return false; result = DataTools.StrToStrArray(res.Substring(60)); return true; } } return false; } /// /// 读取DM区的字 /// /// /// /// /// public bool ReadDWords(int startword, int count, out string[] result) { string[] res; bool isSucess = Read(AreaType.DM_Word, startword, 0, count, out res); result = res; return isSucess; } /// /// 读取DM区的位 /// /// /// /// /// /// public bool ReadDBits(int startword, int startbit, int count, out string[] result) { string[] res; bool isSucess = Read(AreaType.DM_Bit, startword, startbit, count, out res); result = res; return isSucess; } #endregion #region 写 /// /// 写PLC字 /// /// 地址类型 /// 开始字地址 /// 字数 /// 值 /// 写入成功与否 public bool WriteWords(AreaType type, int startword, int count, int[] paras) { byte[] hexadd = BitConverter.GetBytes(startword); byte[] sendCmd = WriteCommand.Concat(new byte[count * 2]).ToArray(); // 这里扩容sendCmd数组,不然会溢出 sendCmd[7] = (byte)(26 + count * 2); sendCmd[20] = DA1; sendCmd[23] = SA1; // 内存类型 sendCmd[28] = (byte)type; // 写入起始地址 byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 sendCmd[29] = bytesAddr[1]; sendCmd[30] = bytesAddr[0]; sendCmd[31] = 0; // 写入长度 byte[] bytesLength = BitConverter.GetBytes((short)count); sendCmd[32] = bytesLength[1]; sendCmd[33] = bytesLength[0]; byte[] hexPara; for (int i = 0; i < count; i++) { hexPara = BitConverter.GetBytes(paras[i]); sendCmd[34 + i * 2] = hexPara[1]; sendCmd[34 + i * 2 + 1] = hexPara[0]; } lock (locker) { if (DataSend(sendCmd)) { if (Write2PlcSuccess()) return true; } } return false; } /// /// 写PLC位 /// /// /// 地址类型 /// 开始字地址 /// 开始位地址 /// 写入位数 /// 值 /// 写入成功与否 public bool WriteBits(AreaType type, int startword, int startbit, int count, bool[] paras) { byte[] hexadd = BitConverter.GetBytes(startword); byte[] sendCmd = WriteCommand.Concat(new byte[count]).ToArray(); // 这里扩容sendCmd数组,不然会溢出 // 命令长度 sendCmd[7] = (byte)(26 + count); sendCmd[20] = DA1; sendCmd[23] = SA1; // 内存类型 sendCmd[28] = (byte)type; // 写入起始地址 byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列 sendCmd[29] = bytesAddr[1]; sendCmd[30] = bytesAddr[0]; sendCmd[31] = (byte)startbit; // 写入长度 byte[] bytesLength = BitConverter.GetBytes((short)count); sendCmd[32] = bytesLength[1]; sendCmd[33] = bytesLength[0]; for (int i = 0; i < count; i++) { sendCmd[34 + i] = Convert.ToByte(paras[i]); } lock (locker) { if (DataSend(sendCmd)) { if (Write2PlcSuccess()) return true; } } return false; } /// /// 写D字 /// /// /// /// /// public bool WriteDWords(int startword, int count, int[] paras) { return WriteWords(AreaType.DM_Word, startword, count, paras); } /// /// 写D位 /// /// /// /// /// /// public bool WriteDBits(int startword, int startbit, int count, bool[] paras) { return WriteBits(AreaType.DM_Bit, startword, startbit, count, paras); } public bool ReadString(int address, int count, out string str) { try { string[] res; bool isSucess = ReadDWords(address, count, out res); if (!isSucess) { str = ""; return false; } byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); List list = new List(); for (int i = 0; i < bRes.Length; i += 2) { list.Add((int)(bRes[i] << 8 | bRes[i + 1])); } str = GetQRCodeByData(list.ToArray()); return true; } catch (Exception ex) { str = ex.Message; return false; } } public bool ReadFloat(int address, out float value) { try { string[] res; bool isSucess = ReadDWords(address, 2, out res); if (!isSucess) { value = 0; return false; } byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); if (bRes.Length != 4) { value = 0; return false; } int i = 0; value = BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0); return true; } catch (Exception ex) { value = 0; return false; } } public bool ReadFloats(int address, int count, out float[] values) { try { string[] res; bool isSucess = ReadDWords(address, count * 2, out res); if (!isSucess) { values = null; return false; } byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); if (bRes.Length != (count * 4)) { values = null; return false; } List list = new List(); for (int i = 0; i < bRes.Length; i += 4) { list.Add(BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0)); } values = list.ToArray(); return true; } catch (Exception ex) { values = null; return false; } } public bool WriteFloat(int address, float[] values) { List writeValues = new List(); foreach (float f in values) { byte[] bytes = BitConverter.GetBytes(f); writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[0], bytes[1], 0, 0 }, 0)); writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[2], bytes[3], 0, 0 }, 0)); } return WriteWords(AreaType.DM_Word, address, values.Length * 2, writeValues.ToArray()); } public bool ReadShort(int address, out short value) { try { string[] res; bool isSucess = ReadDWords(address, 1, out res); if (!isSucess) { value = 0; return false; } byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); if (bRes.Length != 2) { value = 0; return false; } value = BitConverter.ToInt16(new byte[] { bRes[1], bRes[0] }, 0); return true; } catch (Exception ex) { value = 0; return false; } } public bool ReadShorts(int address, int count, out short[] values) { try { string[] res; bool isSucess = ReadDWords(address, count, out res); if (!isSucess) { values = null; return false; } byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray(); if (bRes.Length != (count * 2)) { values = null; return false; } List list = new List(); for (int i = 0; i < bRes.Length; i += 2) { list.Add(BitConverter.ToInt16(new byte[] { bRes[i + 1], bRes[i] }, 0)); } values = list.ToArray(); return true; } catch (Exception ex) { values = null; return false; } } public bool WriteShort(int address, short[] values) { List writeValues = new List(); foreach (short f in values) { writeValues.Add(f); } return WriteWords(AreaType.DM_Word, address, values.Length, writeValues.ToArray()); } public bool WriteString(int address, string str, int count) { try { int[] aaa = GetDataByQRCode(str, count); return WriteDWords(address, count, aaa); } catch (Exception ex) { return false; } } private int[] GetDataByQRCode(string qrcode, int totalCount) { byte[] byteList = new byte[2 * totalCount]; byte[] bytes = Encoding.ASCII.GetBytes(qrcode); for (int i = 0; i < byteList.Length; i++) { if (i >= bytes.Length) { break; } byteList[i] = bytes[i]; } List shortList = new List(); for (int i = 0; i < (2 * totalCount); i += 2) { //将字节数组转为int存入list shortList.Add(BitConverter.ToInt32(new byte[] { byteList[i], byteList[i + 1], 0x00, 0x00 }, 0)); } return shortList.ToArray(); } /// /// 将PLC中获取到的数据转成string /// /// /// private string GetQRCodeByData(int[] qrcode) { List list = new List(); for (int i = 0; i < qrcode.Length; i++) { //int中取两个字节存入list list.AddRange(BitConverter.GetBytes(qrcode[i]).Take(2)); } //去掉多余的/0和空格 转为string string qrcodes = Encoding.ASCII.GetString(list.ToArray()).Trim('\0').Trim(); return qrcodes; } #endregion } }