using DataService; using System; using System.Collections.Generic; using System.ComponentModel; using System.Net; using System.Net.Sockets; using System.Text; using System.Timers; namespace OmronPlcDriver { [Description("Omron(CS/CJ) UDP协议")] public sealed class OmronUdpReader : IPLCDriver, IMultiReadWrite { #region /****************************************/ //更新人:新增_pdu,可以根据实际情况进行采集 //更新日期:20171205 //更新原因:根据现场进行参数调整以提供采集响应速度 /***************************************/ /// /// PDU的值 /// int _pdu; /// /// 获取PDU的值 /// public int PDU { get { return _pdu; } } /// /// 获取设备地址 /// /// /// public DeviceAddress GetDeviceAddress(string address) { DeviceAddress dv = DeviceAddress.Empty; if (string.IsNullOrEmpty(address)) return dv; dv.Area = _plcNodeId; switch (address[0]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int index = address.IndexOf('.'); dv.DBNumber = OmronCSCJ.fctCIO; if (index > 0) { dv.Start = int.Parse(address.Substring(0, index - 1)); dv.Bit = byte.Parse(address.Substring(index + 1)); } else dv.Start = int.Parse(address); } break; case 'D'://DM区 { int index = address.IndexOf('.'); dv.DBNumber = OmronCSCJ.fctDM; if (index > 0) { dv.Start = int.Parse(address.Substring(1, index - 1)); dv.Bit = byte.Parse(address.Substring(index + 1)); } else dv.Start = int.Parse(address.Substring(index + 1)); } break; case 'H'://HR区 { int index = address.IndexOf('.'); dv.DBNumber = OmronCSCJ.fctHR; if (index > 0) { dv.Start = int.Parse(address.Substring(1, index - 1)); dv.Bit = byte.Parse(address.Substring(index + 1)); } else dv.Start = int.Parse(address.Substring(1)); } break; case 'A'://AR区 { int index = address.IndexOf('.'); dv.DBNumber = OmronCSCJ.fctA; if (index > 0) { dv.Start = int.Parse(address.Substring(1, index - 1)); dv.Bit = byte.Parse(address.Substring(index + 1)); } else dv.Start = int.Parse(address.Substring(1)); } break; } return dv; } public string GetAddress(DeviceAddress address) { return string.Empty; } #endregion private int _timeout;//超时数据 private Socket udpSynCl; private byte[] udpSynClBuffer = new byte[1024]; short _id;//驱动id //驱动id public short ID { get { return _id; } } string _name;//驱动名称 /// /// 驱动名称 /// public string Name { get { return _name; } } string _ip;//服务ip int _port = 9600; //服务端口 public string ServerName { get { return _ip + ":" + _port; } set { if (!string.IsNullOrEmpty(value)) { int index = value.IndexOf(':'); _ip = value.Substring(0, index - 1);//ip地址 _port = int.Parse(value.Substring(index + 1));//端口号 } } } /// /// 是否关闭 /// public bool IsClosed { get { return udpSynCl == null || udpSynCl.Connected == false; } } /// /// 超时时间 /// public int TimeOut { get { return _timeout; } set { _timeout = value; } } byte _plcNodeId;//plc节点号 /// /// plc节点号,从para1参数读取 /// public byte PlcNodeId { get { return _pcNodeId; } set { _pcNodeId = value; } } byte _pcNodeId;//电脑节点号 /// /// 电脑节点号,从para2参数读取 /// public byte PcNodeId { get { return _pcNodeId; } set { _pcNodeId = value; } } List _grps = new List(20); public IEnumerable Groups { get { return _grps; } } IDataServer _server; public IDataServer Parent { get { return _server; } } public OmronUdpReader(IDataServer server, short id, string name, string servername, int timeOut = 500, string spare1 = null, string spare2 = null) { _id = id; _name = name; _server = server; if (!string.IsNullOrEmpty(servername)) { int index = servername.IndexOf(':'); _ip = servername.Substring(0, index - 1);//ip地址 _port = int.Parse(servername.Substring(index + 1));//端口号 } _timeout = timeOut; byte.TryParse(spare1, out _plcNodeId); byte.TryParse(spare2, out _pcNodeId); } /// /// 连接 /// /// public bool Connect() { try { if (udpSynCl != null) udpSynCl.Close(); //IPAddress ip = IPAddress.Parse(_ip); // ---------------------------------------------------------------- // Connect synchronous client udpSynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Udp); udpSynCl.SendTimeout = _timeout; udpSynCl.ReceiveTimeout = _timeout; udpSynCl.NoDelay = true; udpSynCl.Connect(_ip, _port); return true; } catch (SocketException error) { if (OnClose != null) OnClose(this, new ShutdownRequestEventArgs(error.Message)); return false; } } /// /// 生成读命令头 /// /// 电脑节点号,设置和PLC节点不一致即可 /// 读取的起始地址 /// 读取长度 /// /// PLC节点号,可为0 /// private byte[] CreateReadHeader(byte pcnode, int startAddress, ushort length, byte function, byte plcnode = 0) { byte[] data = new byte[18]; data[0] = 0x80; data[1] = 0; data[2] = 0; //80 00 02 固定帧头 data[2] = 0; data[3] = plcnode; data[5] = 0; //设备的网络号,节点号,单元号 data[6] = 0; data[7] = pcnode; data[8] = 0; //PC的网络号,节点号,单元号 data[9] = 0; data[10] = 1; data[11] = 1; //SID+MRC+SRC data[12] = function; //数据区代码 byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress)); data[13] = _adr[0]; // 首地址高字节 data[14] = _adr[1]; // 首地址低字节 data[15] = 0; // 固定0 byte[] _length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)length)); data[16] = _length[0]; // 读取数量高字节 data[17] = _length[1]; // 读取数量低字节 return data; } /// /// 生成写命令头 /// /// /// /// /// /// /// private byte[] CreateWriteHeader(byte pcnode, int startAddress, ushort numData, byte function, byte plcnode = 0) { byte[] data = new byte[numData + 18]; data[0] = 0x80; data[1] = 0; data[2] = 0; //80 00 02 固定帧头 data[3] = 0; data[4] = plcnode; data[5] = 0; //设备的网络号,节点号,单元号 data[6] = 0; data[7] = pcnode; data[8] = 0; //PC的网络号,节点号,单元号 data[9] = 0; data[10] = 1; data[11] = 2; //SID+MRC+SRC data[12] = function; //数据区代码 byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress)); data[13] = _adr[0]; // 首地址高字节 data[14] = _adr[1]; // 首地址低字节 data[15] = 0; // 固定0 byte[] length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)numData / 2)); data[16] = length[0]; // 写取数量高字节 data[17] = length[1]; // 写取数量低字节 return data; } /// /// 同步写数据到udp /// /// 写的字节数组 /// private byte[] WriteSyncData(byte[] write_data) { short id = BitConverter.ToInt16(write_data, 4); if (IsClosed) CallException(id, write_data[12], OmronCSCJ.excExceptionConnectionLost); else { try { udpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None);//是否存在lock的问题? int result = udpSynCl.Receive(udpSynClBuffer, 0, 1024, SocketFlags.None); byte function = udpSynClBuffer[11];//读写功能 byte[] data; int err = udpSynClBuffer[12] * 256 + udpSynClBuffer[13]; if (result == 0) CallException(id, write_data[12], OmronCSCJ.excExceptionConnectionLost); // ------------------------------------------------------------ // Response data is slave ModbusModbus.exception if (err != 0) { CallException(id, function, 4); return null; } // ------------------------------------------------------------ // Write response data else if (function == 0x2) { data = new byte[2]; Array.Copy(udpSynClBuffer, 10, data, 0, 2); } // ------------------------------------------------------------ // Read response data else { data = new byte[(write_data[16] * 256 + write_data[17]) * 2]; Array.Copy(udpSynClBuffer, 14, data, 0, data.Length); } return data; } catch (SocketException) { CallException(id, write_data[12], OmronCSCJ.excExceptionConnectionLost); } } return null; } /// /// 写单个寄存器 /// /// 电脑节点号 /// 起始地址 /// 内存区功能吗 /// 写值数组 /// plc节点号 /// public byte[] WriteSingleRegister(byte pcnode, int startAddress, byte function, byte[] values, byte plcnode = 0) { byte[] data; data = CreateWriteHeader(pcnode, startAddress, 2, function, plcnode); data[19] = values[0]; data[20] = values[1]; return WriteSyncData(data); } /// /// 写多个寄存器 /// /// 电脑节点号 /// 起始地址 /// 内存区功能号 /// 写值数组 /// plc节点号 /// public byte[] WriteMultipleRegister(byte pcnode, int startAddress, byte function, byte[] values, byte plcnode = 0) { ushort numBytes = Convert.ToUInt16(values.Length); if (numBytes % 2 > 0) numBytes++; byte[] data; data = CreateWriteHeader(pcnode, startAddress, numBytes, function, plcnode); Array.Copy(values, 0, data, 19, values.Length); return WriteSyncData(data); } public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) { OmronUdpGroup grp = new OmronUdpGroup(id, name, updateRate, active, this); _grps.Add(grp); return grp; } public bool RemoveGroup(IGroup grp) { grp.IsActive = false; return _grps.Remove(grp); } public void Dispose() { if (udpSynCl != null) { try { udpSynCl.Shutdown(SocketShutdown.Both); } catch { } udpSynCl.Close(); udpSynCl = null; } foreach (IGroup grp in _grps) { grp.Dispose(); } _grps.Clear(); } internal string GetErrorString(byte exception) { switch (exception) { case OmronCSCJ.excIllegalFunction: return "Constant for OmronCSCJ.exception illegal function."; case OmronCSCJ.excIllegalDataAdr: return "Constant for OmronCSCJ.exception illegal data address."; case OmronCSCJ.excIllegalDataVal: return "Constant for OmronCSCJ.exception illegal data value."; case OmronCSCJ.excSlaveDeviceFailure: return "Constant for OmronCSCJ.exception slave device failure."; case OmronCSCJ.excAck: return "Constant for OmronCSCJ.exception acknowledge."; case OmronCSCJ.excSlaveIsBusy: return "Constant for OmronCSCJ.exception slave is busy/booting up."; case OmronCSCJ.excGatePathUnavailable: return "Constant for OmronCSCJ.exception gate path unavailable."; case OmronCSCJ.excExceptionNotConnected: return "Constant for OmronCSCJ.exception not connected."; case OmronCSCJ.excExceptionConnectionLost: return "Constant for OmronCSCJ.exception connection lost."; case OmronCSCJ.excExceptionTimeout: return "Constant for OmronCSCJ.exception response timeout."; case OmronCSCJ.excExceptionOffset: return "Constant for OmronCSCJ.exception wrong offset."; case OmronCSCJ.excSendFailt: return "Constant for OmronCSCJ.exception send failt."; } return string.Empty; } internal void CallException(int id, byte function, byte exception) { if (udpSynCl == null) return; if (exception == OmronCSCJ.excExceptionConnectionLost && IsClosed == false) { if (OnClose != null) OnClose(this, new ShutdownRequestEventArgs(GetErrorString(exception))); } } /// /// 读取字节数组 /// /// 标签变量地址结构 /// 长度 /// public byte[] ReadBytes(DeviceAddress address, ushort size) { ushort len = size; if (len % 2 != 0) { len++; } return WriteSyncData(CreateReadHeader(PcNodeId, address.Start, (ushort)(len / 2), (byte)address.DBNumber, (byte)address.Area)); } /// /// 读取32位整数 /// /// 标签变量地址结构 /// public ItemData ReadInt32(DeviceAddress address) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, 2, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(0, 0, QUALITIES.QUALITY_BAD); else return new ItemData(IPAddress.HostToNetworkOrder(BitConverter.ToInt32(data, 0)), 0, QUALITIES.QUALITY_GOOD); } /// /// 读取16位整数 /// /// 标签变量地址结构 /// public ItemData ReadInt16(DeviceAddress address) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, 1, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(0, 0, QUALITIES.QUALITY_BAD); else return new ItemData(IPAddress.HostToNetworkOrder(BitConverter.ToInt16(data, 0)), 0, QUALITIES.QUALITY_GOOD); } /// /// 读取1字节 /// /// 标签变量地址结构 /// public ItemData ReadByte(DeviceAddress address) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, 1, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(0, 0, QUALITIES.QUALITY_BAD); else return new ItemData(data[0], 0, QUALITIES.QUALITY_GOOD); } /// /// 读取字符串 /// /// 标签变量地址结构 /// 长度 /// public ItemData ReadString(DeviceAddress address, ushort size) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, size, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD); else return new ItemData(Encoding.ASCII.GetString(data, 0, data.Length), 0, QUALITIES.QUALITY_GOOD);//是否考虑字节序问题? } /// /// 读取32位浮点数 /// /// 标签变量地址结构 /// public unsafe ItemData ReadFloat(DeviceAddress address) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, 2, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(0.0f, 0, QUALITIES.QUALITY_BAD); else { int value = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(data, 0)); return new ItemData(*(((float*)&value)), 0, QUALITIES.QUALITY_GOOD); } } /// /// 读取1位 /// /// 标签变量地址结构体 /// public ItemData ReadBit(DeviceAddress address) { byte[] data = WriteSyncData(CreateReadHeader(PcNodeId, address.Start, 1, (byte)address.DBNumber, (byte)address.Area)); if (data == null) return new ItemData(false, 0, QUALITIES.QUALITY_BAD); if (data.Length == 1) return new ItemData(data[0] > 0, 0, QUALITIES.QUALITY_GOOD); unsafe { fixed (byte* p = data) { short* p1 = (short*)p; return new ItemData((*p1 & (1 << address.Bit.BitSwap())) != 0, 0, QUALITIES.QUALITY_GOOD); } } } /// /// 读object类型 /// /// /// public ItemData ReadValue(DeviceAddress address) { return this.ReadValueEx(address); } /// /// 写字节数组到设备 /// /// 标签变量地址结构 /// 需写的字节数组 /// public int WriteBytes(DeviceAddress address, byte[] bits) { var data = WriteMultipleRegister(PcNodeId, address.Start, (byte)address.DBNumber, bits, (byte)address.Area); return data == null ? -1 : 0; } public int WriteBit(DeviceAddress address, bool bit) { ItemData item = ReadInt16(address); short value = (short)((bit ? 1 : 0) << (int)address.Bit); short result = (short)(item.Value | value); var data = WriteSingleRegister(PcNodeId, address.Start, (byte)address.DBNumber, BitConverter.GetBytes(result), (byte)address.Area); return data == null ? -1 : 0; } public int WriteBits(DeviceAddress address, byte bits) { var data = WriteSingleRegister(PcNodeId, address.Start, (byte)address.DBNumber, new byte[] { bits }, (byte)address.Area); return data == null ? -1 : 0; } public int WriteInt16(DeviceAddress address, short value) { var data = WriteSingleRegister(PcNodeId, address.Start, (byte)address.DBNumber, BitConverter.GetBytes(value), (byte)address.Area); return data == null ? -1 : 0; } public int WriteInt32(DeviceAddress address, int value) { var data = WriteMultipleRegister(PcNodeId, address.Start, (byte)address.DBNumber, BitConverter.GetBytes(value), (byte)address.Area); return data == null ? -1 : 0; } public int WriteFloat(DeviceAddress address, float value) { var data = WriteMultipleRegister(PcNodeId, address.Start, (byte)address.DBNumber, BitConverter.GetBytes(value), (byte)address.Area); return data == null ? -1 : 0; } public int WriteString(DeviceAddress address, string str) { var data = WriteMultipleRegister(PcNodeId, address.Start, (byte)address.DBNumber, Encoding.ASCII.GetBytes(str), (byte)address.Area); return data == null ? -1 : 0; } public int WriteValue(DeviceAddress address, object value) { return this.WriteValueEx(address, value); } public event ShutdownRequestEventHandler OnClose; public int Limit { get { return 60; } } public ItemData[] ReadMultiple(DeviceAddress[] addrsArr) { return this.PLCReadMultiple(new NetShortCacheReader(), addrsArr); } public int WriteMultiple(DeviceAddress[] addrArr, object[] buffer) { return this.PLCWriteMultiple(new NetShortCacheReader(), addrArr, buffer, Limit); } } public sealed class OmronUdpGroup : PLCGroup { public OmronUdpGroup(short id, string name, int updateRate, bool active, IPLCDriver plcReader) { this._id = id; this._name = name; this._updateRate = updateRate; this._isActive = active; this._plcReader = plcReader; this._server = _plcReader.Parent; this._timer = new Timer(); this._changedList = new List(); this._cacheReader = new NetShortCacheReader(); } protected override unsafe void Poll() { short[] cache = (short[])_cacheReader.Cache; int offset = 0; foreach (PDUArea area in _rangeList) { byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 if (rcvBytes == null || rcvBytes.Length == 0) { //_plcReader.Connect(); continue; } else { int len = rcvBytes.Length / 2; fixed (byte* p1 = rcvBytes) { short* prcv = (short*)p1; int index = area.StartIndex;//index指向_items中的Tag元数据 int count = index + area.Count; while (index < count) { DeviceAddress addr = _items[index].Address; int iShort = addr.CacheIndex; int iShort1 = iShort - offset; if (addr.VarType == DataType.BOOL) { int tmp = prcv[iShort1] ^ cache[iShort]; DeviceAddress next = addr; if (tmp != 0) { while (addr.Start == next.Start) { if ((tmp & (1 << next.Bit)) > 0) _changedList.Add(index); if (++index < count) next = _items[index].Address; else break; } } else { while (addr.Start == next.Start && ++index < count) { next = _items[index].Address; } } } else { if (addr.DataSize <= 2) { if (prcv[iShort1] != cache[iShort]) _changedList.Add(index); } else { int size = addr.DataSize / 2; for (int i = 0; i < size; i++) { if (prcv[iShort1 + i] != cache[iShort + i]) { _changedList.Add(index); break; } } } index++; } } for (int j = 0; j < len; j++) { cache[j + offset] = prcv[j]; }//将PLC读取的数据写入到CacheReader中 } offset += len; } } } } /// /// 欧姆龙Omron CS/CJ系列PLC数据区功能号和错误代码常数定义类 /// public sealed class OmronCSCJ { /*****************************************************/ //OMRON PLC CS/CJ系列数据区功能号 /****************************************************/ /// /// CIO 0000 to CIO 6143 /// public const byte fctCIO = 0xB0;//CIO 0000 to CIO 6143 /// /// H000 to H511 /// public const byte fctHR = 0xB2;//H000 to H511 /// /// A448 to A959 /// public const byte fctA = 0xB3;//A448 to A959 /// /// //D00000 to D32767 /// public const byte fctDM = 0x82;//D00000 to D32767 /// /// E0_E00000 to E0_E32765 /// public const byte fctE0 = 0xA0;//E0_E00000 to E0_E32765 /// /// E1_E00000 to E1_E32765 /// public const byte fctE1 = 0xA1;//E1_E00000 to E1_E32765 /// /// E2_E00000 to E2_E32765 /// public const byte fctE2 = 0xA2;//E2_E00000 to E2_E32765 /// /// E3_E00000 to E3_E32765 /// public const byte fctE3 = 0xA3; /// /// E4_E00000 to E4_E32765 /// public const byte fctE4 = 0xA4;//E4_E00000 to E4_E32765 /// /// E5_E00000 to E5_E32765 /// public const byte fctE5 = 0xA5;//E5_E00000 to E5_E32765 /// /// E6_E00000 to E6_E32765 /// public const byte fctE6 = 0xA6;//E6_E00000 to E6_E32765 /// /// E7_E00000 to E7_E32765 /// public const byte fctE7 = 0xA7;//E7_E00000 to E7_E32765 /// /// E8_E00000 to E8_E32765 /// public const byte fctE8 = 0xA8;//E8_E00000 to E8_E32765 /// /// E9_E00000 to E9_E32765 /// public const byte fctE9 = 0xA9;//E9_E00000 to E9_E32765 /// /// EA_E00000 to EA_E32765 /// public const byte fctEA = 0xAA;//EA_E00000 to EA_E32765 /// /// EB_E00000 to EB_E32765 /// public const byte fctEB = 0xAB;//EB_E00000 to EB_E32765 /// /// EC_E00000 to EC_E32765 /// public const byte fctEC = 0xAC;//EC_E00000 to EC_E32765 /// /// E00000 to E32765 /// public const byte fctCurrentbank = 98;//E00000 to E32765 /// Constant for exception illegal function. public const byte excIllegalFunction = 1; /// Constant for exception illegal data address. public const byte excIllegalDataAdr = 2; /// Constant for exception illegal data value. public const byte excIllegalDataVal = 3; /// Constant for exception slave device failure. public const byte excSlaveDeviceFailure = 4; /// Constant for exception acknowledge. public const byte excAck = 5; /// Constant for exception slave is busy/booting up. public const byte excSlaveIsBusy = 6; /// Constant for exception gate path unavailable. public const byte excGatePathUnavailable = 10; /// Constant for exception not connected. public const byte excExceptionNotConnected = 253; /// Constant for exception connection lost. public const byte excExceptionConnectionLost = 254; /// Constant for exception response timeout. public const byte excExceptionTimeout = 255; /// Constant for exception wrong offset. public const byte excExceptionOffset = 128; /// Constant for exception send failt. public const byte excSendFailt = 100; } }