From 73b6b15ed63f33124e15a9257c9678f94f3b40a3 Mon Sep 17 00:00:00 2001 From: Gavin Date: Thu, 7 Dec 2017 15:47:48 +0800 Subject: [PATCH] add OMRON Udp drvier,fix bugs --- SCADA/Program/DataExchange.sln | 22 + SCADA/Program/ModbusDriver/ModbusTCPDriver.cs | 66 +- .../OmronPlcDriver/OmronPlcDriver.csproj | 55 ++ .../Program/OmronPlcDriver/OmronUdpReader.cs | 875 ++++++++++++++++++ .../OmronPlcDriver/Properties/AssemblyInfo.cs | 36 + SCADA/dll/ModbusDriver.dll | Bin 28672 -> 29184 bytes SCADA/dll/OmronPlcDriver.dll | Bin 0 -> 17408 bytes 7 files changed, 1023 insertions(+), 31 deletions(-) create mode 100644 SCADA/Program/OmronPlcDriver/OmronPlcDriver.csproj create mode 100644 SCADA/Program/OmronPlcDriver/OmronUdpReader.cs create mode 100644 SCADA/Program/OmronPlcDriver/Properties/AssemblyInfo.cs create mode 100644 SCADA/dll/OmronPlcDriver.dll diff --git a/SCADA/Program/DataExchange.sln b/SCADA/Program/DataExchange.sln index 492bf4f..3a0ccad 100644 --- a/SCADA/Program/DataExchange.sln +++ b/SCADA/Program/DataExchange.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagConfig", "TagConfig\TagC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PanasonicDriver", "PanasonicDriver\PanasonicDriver.csproj", "{0D1C943D-594F-4E07-AA39-90CEC4E37C8E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmronPlcDriver", "OmronPlcDriver\OmronPlcDriver.csproj", "{BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -299,6 +301,26 @@ Global {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x64.Build.0 = Release|Any CPU {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x86.ActiveCfg = Release|Any CPU {0D1C943D-594F-4E07-AA39-90CEC4E37C8E}.Release|x86.Build.0 = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Itanium.ActiveCfg = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Itanium.Build.0 = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|x64.ActiveCfg = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|x64.Build.0 = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Debug|x86.Build.0 = Debug|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Any CPU.Build.0 = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Itanium.ActiveCfg = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Itanium.Build.0 = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|x64.ActiveCfg = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|x64.Build.0 = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|x86.ActiveCfg = Release|Any CPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs index a4f4d6f..febbb3e 100644 --- a/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs +++ b/SCADA/Program/ModbusDriver/ModbusTCPDriver.cs @@ -236,49 +236,53 @@ namespace ModbusDriver return data; } + object _async = new object(); private byte[] WriteSyncData(byte[] write_data) { short id = BitConverter.ToInt16(write_data, 0); if (IsClosed) CallException(id, write_data[7], Modbus.excExceptionConnectionLost); else { - try + lock (_async) { - tcpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None);//是否存在lock的问题? - int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + try + { + tcpSynCl.Send(write_data, 0, write_data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); - byte function = tcpSynClBuffer[7]; - byte[] data; + byte function = tcpSynClBuffer[7]; + byte[] data; - if (result == 0) CallException(id, write_data[7], Modbus.excExceptionConnectionLost); + if (result == 0) CallException(id, write_data[7], Modbus.excExceptionConnectionLost); - // ------------------------------------------------------------ - // Response data is slave ModbusModbus.exception - if (function > Modbus.excExceptionOffset) - { - function -= Modbus.excExceptionOffset; - CallException(id, function, tcpSynClBuffer[8]); - return null; - } - // ------------------------------------------------------------ - // Write response data - else if ((function >= Modbus.fctWriteSingleCoil) && (function != Modbus.fctReadWriteMultipleRegister)) - { - data = new byte[2]; - Array.Copy(tcpSynClBuffer, 10, data, 0, 2); + // ------------------------------------------------------------ + // Response data is slave ModbusModbus.exception + if (function > Modbus.excExceptionOffset) + { + function -= Modbus.excExceptionOffset; + CallException(id, function, tcpSynClBuffer[8]); + return null; + } + // ------------------------------------------------------------ + // Write response data + else if ((function >= Modbus.fctWriteSingleCoil) && (function != Modbus.fctReadWriteMultipleRegister)) + { + data = new byte[2]; + Array.Copy(tcpSynClBuffer, 10, data, 0, 2); + } + // ------------------------------------------------------------ + // Read response data + else + { + data = new byte[tcpSynClBuffer[8]]; + Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]); + } + return data; } - // ------------------------------------------------------------ - // Read response data - else + catch (SocketException) { - data = new byte[tcpSynClBuffer[8]]; - Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]); + CallException(id, write_data[7], Modbus.excExceptionConnectionLost); } - return data; - } - catch (SocketException) - { - CallException(id, write_data[7], Modbus.excExceptionConnectionLost); } } return null; @@ -568,7 +572,7 @@ namespace ModbusDriver byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 if (rcvBytes == null || rcvBytes.Length == 0) { - offset += area.Len / 2; + //offset += area.Len / 2; //_plcReader.Connect(); continue; } diff --git a/SCADA/Program/OmronPlcDriver/OmronPlcDriver.csproj b/SCADA/Program/OmronPlcDriver/OmronPlcDriver.csproj new file mode 100644 index 0000000..ae3c4d3 --- /dev/null +++ b/SCADA/Program/OmronPlcDriver/OmronPlcDriver.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {BAF8082B-8DD8-4E7C-9C5B-D9213E08C3B1} + Library + Properties + OmronPlcDriver + OmronPlcDriver + v4.0 + 512 + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + {8965e389-6466-4b30-bd43-83c909044637} + DataService + + + + + \ No newline at end of file diff --git a/SCADA/Program/OmronPlcDriver/OmronUdpReader.cs b/SCADA/Program/OmronPlcDriver/OmronUdpReader.cs new file mode 100644 index 0000000..7778e61 --- /dev/null +++ b/SCADA/Program/OmronPlcDriver/OmronUdpReader.cs @@ -0,0 +1,875 @@ +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; + } +} diff --git a/SCADA/Program/OmronPlcDriver/Properties/AssemblyInfo.cs b/SCADA/Program/OmronPlcDriver/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5483ec6 --- /dev/null +++ b/SCADA/Program/OmronPlcDriver/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OmronPlcDriver")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("OmronPlcDriver")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("baf8082b-8dd8-4e7c-9c5b-d9213e08c3b1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SCADA/dll/ModbusDriver.dll b/SCADA/dll/ModbusDriver.dll index 5acf58451062e6724c1ac732467af7b0b0d6177d..18131be84f918c10eb4d15afc4b517fc73d47fe6 100644 GIT binary patch delta 9005 zcmb_i3wTw?PU`LZ-aK*65u(y~ zYCBP$Q>>+&8%1Sk>QdAw!n4e|QzXR-=f|Q_taeU`lI*fxxA9bbX=KpUIYg^R6A9<| z(sBLQX+>$`tg|M)T6@UnG&fX6G6sZYNF!xZ;~M8wdYNrXv+1{NL+a&LAZ5zH-JLaN z{7uWB49P(Jo-wdx`APa;wt~TK3?yWQWGL9(iBh{2mipbDRwQnvHEv-eaVwha1g6WN zOl!X;OGXO|7k_qfe86W(DN_bzS?MIxO&Pj(i4~KvxDCw+IU*G&t$Ptn%1{?v5+c-) z>GzWiWwmpEGCZKiF?35>DIXhRmC=F$-O7*|HFuFrgCtF+$p}Q5GV_Yn=g*&~ zQwC4vBsJcwso^D(Wtgc!fih+WOW88J=AXEnSYTyc z?4z6^KFT4P^Rkue`6y534f-g*@j-#UweICSzr3y$`082-unJ^>-MxxSg|g6pqg5me zy1PKLFwDsyGg%Zd5zQ%zY2r3LhT z%4K;f>bW$}b*aqDYA?6)WIkq9AuCdG9+7>~kV=zUGK(;d23Uhp{=#S~Wo6B7N=~38 zFVrOyDZQx0=(Hi7B}Lq-l2tX&6E)taW>nC<8fFs%T71A3R<*27jT}DB0l|wsQjWZM z4wRhBU8&{^tYq9uCC90zGSxolBoyy;4b;e*lpG8!&kIS5WD1dzk|~)S&~2+$*4F%# zc)V5~yzTmDlpJ-%tbz0A<5nHqVahsxJJz92b%Cg?L*+VIC$r$ndRbrdh8t%&C;c+3 zJ&ZYy(5lv8idW43`26`(=_9m(c!VgoYc^zbawICW%;5b>RJ;xFDxL+15d+`+Va3DX z2^4F*KJBa zRPx0OlB}-v7l_tfjs|AehW>A6{zk976`HU=RmM@v_7YvUb6w1`0^kX|);W7~?!kcS zb7zWf$Ii_Y8~gX<)@cyEm|qRixx$3l=*YrCXH-EZq*Ds&hDm==5Qp@+f`%c|(U2T2 z%yJeMW?dq!y+o4bv=t4lSL!@c6oZ9BMWct*EpW<;e^|Q-Q}9)728NPkViBx5gR$M;^6K!A<#FyQ&z$Wo2YSXbRRg=cs-i+{ab{O!13N3G0Do9f0(_~W68KR?gJ^SVDwCCE?h%CT ztViN$ff2r{FR^W2VD8Wt_ubtY@9K6|R3_|=&{!B^5{mpjqziTQ`lUY=(@bA}U(o>C zRbh4}Nu#I-WI(e5GLWhm+ z6;R@9R068tu1tKRN5Q)hexoR4&3Jf!^T%0HHA`%Beo}R- z__9-5ofG^rjGBq|sxLdO)%RON_Yk`nd&m(ZEwQdYbL8D3>aAuZkjmz@+@BnY*gBb9 z23%YdcQ)6Wv8!M&lFT*ZRTx*OYP+*GHF}78;wS0}tlr|hJXGzJ){Iw`8^j&Xy)_)g zi)*r+Q#ILQyYpd961=Ro82D+0ZlCi-uXCdITGox4BYOJRk6I!2bQ54?F81H!PG&>3=yWDFG}^e6)UdO?Y-?VeZ7=Ue zGA!GMhGg4_jJU}bmjoo+DCl6aO_%9S$8BCz+*_hvB1uzMln5>^G0wJpOL4PpEKADa z*_N**gW1-*!0^qb9D$9C3tv#&Y^&}!7xKS!lWoLJwzeG1wz$1qoNb3rL=uLMhzwyE z{Do|Lp)=l2aTy;$*9Cb#S)po_v|l$<{`;El}CE0NFMu3kI`o z0Sw@(BuyB=MQJeG`jKr-&bEWecE?}-R4UYv-!ZWnSEg0v7#(+Sq-1r56Kz zB1*3TO*$vqAQ`PBAtlM7GZ?PV&vpKwBncl6^j)n1*oA?H=y3Q?FdacnlfG;2Lj6r4 zw)P8?tqJNA%Sp#%qV$o;b`C%irGT$b*ddx8ek=qN=3}8K-7nNog53Bmtqo(GC^|8N ziPUBy`KA2{HBX1Qv*!)gzOOT06X0&bY250!5iWlR9hh{xu_qFxDXO5&TcY z=pp}`f!0r0E!h1?ElH)z46`moHHvLfH4DkERrKqMR@04&6{?0~=uVij#THG_Ol`gp z^jes)I?@OnA9~dg^yk1%Xdl;pgYwbvZ-F)91~IQEZZe(^G42g8KBcfk$=g&}W3AoO ztH^SNJCyorKR5WJ!MIIhJf+GXN4R`Kb?`ZbAB0(Qo04Cz%3lErx>F7D-;B2~p!fZM z01ll-hQSkf&*aJ2m(JtwH+GxxEQk9*h22Kh-GGOH!;T2dXI(txPZ_;k6HeR8K9$J=>JUcK5c{P(RR8aaHW|;2Rya|ERSZCv+hjr`!qo-pblm> zZ4YFM$z}m}UJx=u0sjK~Y$WmeHnPkBptmsw4(dF;>nK3vY< z_t+!BC(Io2h?YYSBmG$@zg=h9^-hrg?c^4@$@;`=P|^SZGN8e z_@?g#R44867zfmH%EP^zbsSJDX_v=1pt>jnS8djDK&_#j9^-ggOR(O3*Mp|=}3#q zc#CeLlP*(d;AVQ?V;qQ^DKbgb400fDwyDgc9Eh8#&SM;iHZAlR2ck{uJjQ`&Q;%Zq z>Cr>GJjSO-5AE}6a*%GJr#;3&x`keGYpR273)yW~yW{34?WF`?ozyv(>F=c;k9GMA z{C82&6j%2&Rr&kqz^9q<892&+FP-z45gOTU?M_;*u>$9912p=T63BRDL- zMeo=i<+$8O8GKj6it@gHh>j_?l@1u!`X8bL(^$8a`pwz?hbiM)wIZLdW_G&etN=V zd&`ytW$UaB=ymlP2&(RT&amYST?djuhIzM;ipI&BrPm_b*lIv?P|HCWTF|54yV{aLF1i7?k68HvSAWaU`QPcOr! zpl^h@=Bu#dqX(27SF*@frd{$csN|#Pf)^TGT6Q~r5H$r|K#f|JoePr7)x!T8-4-4+ zH@y7zC*5eChq!~kU9MF_A6kcfsS*2?J&GS5lrIhXv7dXyWNh~fINX_B^*_vuojFCV z+23^L|In8_PK8o{;Qt0)hcTAe|NPT87`Kqg~$hLOM)eAt6t*awrm$7vjS80lGpSRR*C^be@GHO^>I0gzTS?BU@}Gf4 zcwt;@HUMwIeY99SWQ$hn6x`(t^pcp1m-6v=`949VMkdNF#u{29=4d^%LF^ZIq12yRX$tYP5Tlj$An=XQ_h`4MP1{d<#ZR>#)1%_) zp#3Y_F9PBi9TNW;d5^Nir_yT0ZZRV?T4akSsZne})$u6zhZ`Y((U=6BXI!JoGsIqW zc0IJW;yrRV+TtBijPR`meoAy>$lXW@kBY14Ya#=~{SL~@=t+^M%4LkUctPAv{|bE8 zcuSgXp0D4sAd2&8JE5%lZkg zK`QVyZ3c$IjZcNYrJeTVulOI<&Wc}(4^aQ#!{@byY7iZ25H0Xot!NPo%tmpT_~y|f zj^a(bMLZ5)&QJ^8WQ#)I4%tBN511CQ~L$% z+jIRBef8RD|Es^i|C=DsgYz5~#&r3%2@e$)dY}x%3#Y zlnwxs^gM91!tn~PR`Tg|6!JMrvOwX@^gBqFQxw+LQf}aC{G@nK;6dO+foFj~pA|Sw z4mvwc-^8o^w*{A+*p(+wP~CXRc;Z!)(G zLsyHx>cbKIi|9?bi%iuzfRhx?7E4hsj;sJq39a_yj&fi4c918CMdiWMx$D3uEz;wHsFTm(CjR*QTs;=FfdepV_slv%hNIa!s4EzJF)?lftQAG|d^9 zAM5|l{D0JA|2*pBvg+@?-F)Ak*MIZk>&uGz*DZcb%edCxkZ%SuWzTLQd-meLd_(IW z^Vy`xOZSIOEl&#=kZ5>J(nMZN({ZNdIV*2|EMu&{=+dS&9Vy|{%)I^^Z~47eLxcYV z3oezEhbg?W9nVst>*q|KGplFb$k(sBW9qiW^#}iWBwXFUVrf8(Nc3LtPXm{W`ikRP fS+Daz=j#5VRog^=blFEfZE|D3T=gf>SLgq4^DEz4+HpYz(NSrnk#^{~O{?8Aw*6t~bMAXF#iAeG-{hnII{!K6 zZs(r+&VBXK{t0V8%+{~zJ3l@5VsQQrL*J;ERRA=hLbsK#tlseWg=~q%Scx`l6+n)a z&lA=(R^r)n1$spB(`2n>aW>C-hLy0ZtV67C=A*0a#$)k|g9fh200RdAFsq??aOWf* zO<~8ag=wYiY3rV}ia-YNwnhL?3y$k4&IsrqS=QTWg{C2lnmGRvTp(Qg10Huf5P?Jxr*{cnu!q=?^yICMzq$d=J_W_ZHhBT3u*R2LJL`K=oAW~72DpExXs)8aor1kjm z<8Vl$r5uFn%ekChX1owrDl$Wj8v-E|Oq7Uk*hDBon?PiS8v~grokd=s%M381=o;0PxIFE+_=TU{dVW;$kvx$jV*+z(u zfH-Z`DGR}vp3q`IO449)77Ub%(y~_os_&L7inJ}nZo}O?*ln^vKhZDI@5E(xGurI9 zh@ZR$h)<=VWb^5mHG#7DVA)iZHTDDv#arzT%0+oX^dy!Og?Io&8D>hEC=-cp)eQ6( z{mY&Qnr|Q%qix5hLR1Wy*nRwXFi?r%&_$)ui2G0}hrq0?MCVFTDZ&`aDp6JTk)3DR z2MrN!^x_)(@Yo_;;*i>7$B!RM>%+T)K7j0&^HB!}LjgSwLhlE4^)Ld@E`Wuf_wh3i z6Gg*s`Xl|Xk%v)uQ#nzekGB6rU6=JxU^QE8m8Gx6$H%kj%e`0A2s|9hS^P8W?2L>7 z*GS$L1yFfg1hHjluO`&8te5b%A~fqQ*LJSX_=8%q7%g5SQAmkPt-~#C~Rox8`T(uoazavMV^McIB3$>e5J$0os$vEJUo7n^k6nn5=vce;W&ZW7!&=yPAmXm@D8%k?D`HD#leN2G4GuD)@FsSJ z^A|` zrC`MIEUM{v?p2{C!bq*pi>7B^haRywf>+Nof*UsJ5w{WAut{r*U1!ZM9?h<^9x3jN z_SjLqJoAc!U}Z$I5U90P%h}VbbPpIMKl0^yWUbt7Zu)sLwfY^%wRY7xe}TlYTW1H zimHdF$H=6I8)G+E8%ys!;iOPBPdO>1Y`MB~X1{eT)!9SrF_U)iyLdm$qr${I#O>ud z*49KC`?>Yo!~lnT%hGVyvden-{Ia3a611K#%VDdm17(zx+slJiQF#`&tSyftUr?Tp zxK85s@>aIm%I`mp^!+EZj?NeRU&UCJ)xR;UgY!oUvjR@75kU07NTDy==0b>(lXh}68MEZ20nT*t>199Ia3C~AraMylVTKF2%3 z)KlR_*IJeeZy@UMDVui2Y#d9;Yo!%7#Uu6Q{#{M z1YB`7y9{S~HcZex@V)N)|8oZQ{~I~VObuldrf@R9?W0L7;)F9LzKav`z)m+!YZo^2!139@^!EX5@QFWEd$CfT*pGXmw3nd>Aig=vz8Wy1lm7CTzX zIyi%K`NfREzk3POeAS4>o;NfGU%1wz^o z{TL~K=_Y*}$!7Z{Qgwqg4>QQ{Cyj6gCwyPZM|>o|BL|oz@t~J9Go*gFlv@xPw8|+S z(hlN;zBE2YOkPHcMho~{r^VRIFq7Bu(~X^GIDAjz-nNsXv>Qj=tKq+K$R>wHIBiM0 z!(x)%<*>orkG3tQLr1YHd@AgA*tgyhoWntfDL#g_E{7#NpK5r&rl$lBxZHaaS0HRN zR^Y7y1(rHYG5&&{TO8&yzSmr^$7yGAMR$YR7cZLB&HYF{Fsv`JRblRS>t1jb5bJ=8 zj10XGv`V%L4!U#o4A|?iuiS-t2pS7zI~eCG)3e}dhpj}G1NHcfq7k;i2%ZOXBwOW5 zWrOrQSYCwn1_yLyu^OanY*#kdGeVC*v_v*!Kl6;%qfqUz8{Jt-KFo;Owiwjw1#p+c zrb44$2=6-Vh&o3v0w3OGad>uz)~Xl7B!?N^6?zQTBg65JvPs?>^-}o2F@B@oj`!}r zI?Qr!##f?xe43JHqxW&W47NG!8P8LCfAHb$jBK;;8Bz%|999MYp$~v(9VWc5>w_RY zVS8@Y-q8oc5@IGcJf(kKnbiyRyfoLoMu; z%uc;p=yDjPUakGi$KX@y)k2sm05VFwS}2svJnE*@tAk07ky5V?njA){R|iWRMyWRv zZgv=@-bmOYnVmBuA&S59P%J1B&x21Svoq*CsK>&GOOhE>58G^J0;SMI7&yo_QVKP| zdWTU8O@_o-WV2IfDvYw3%#x|_#b9ZZS#lw$)i$HdzX&QEMwveY8XZRQoeAkRwx>x6 zH4~ykY)T0=3uZWs66#X8%VCsIvth5pD4{NcblfRgFD29*nBp+Xr@64nVU$n*2G2T- z^63hw7-qL?G%=x?VY@>qq2@!yaN9@;btPtSn4o3=u2U{!{&O;5quf!b{aDNg}w|vahTx8^lQOaD?63vcR3W=Oa^~B zd{Ac(NJn%9d}TBF3|tAWk#;*u#FY?r7$xFLsCF18qKR~rLn#qWXmuDRq6uppMu}*` zZIaoKk9OGRFnW5l!?TVjC20rjbr>aS2Xxt<^1;>tGtRN6O-X95fhb*<lnJE6;Al(M@ZXQJJXQuenn$zhbT&%+H4qm+FCo--XvIr|cPU^AJsFM}U{ zlcIA^IlCLiI&3cH#qVH-%^0QZ?_jyZC}m%PEt1hAX^inI_$N^>_OtU%lo%LVFPdXG zUB{O(z{2@o-0xRmIrIB97$BYgtoORw&AxQ?`j3(RFL=q{@ALb0tjd1B0ei4d1|1&q z+>8AvaHs6Y)?6few&fS`{)y)8?%oEc%J#tP(33$gau_E&>ec)+*4bXWHr3$_ zJcC#!M~gNq)oY_KOWxRz2|UhsFQ=~mmA?iZI{BQee;v0;h5K-L&9S~CH!j)7&irJJ%zxF%|73%G3QpCW$#H#M z9yYrz9XthW&FQuh^Z#EtHmBXoe`PCce>hsnJ9O$^+N(Lup|ai{J=ElG2KeJJz7J<2 zdhl06?)F2a#2Sfp63>&^AaR<+*@zj?Eaim~mr1-4F$*>z>abC2K9%x!h*5l1Y}Bg| zFTt1Je0HD78laickgvfTY%0Ep55yPl5AnB&43vjx3t=Iv=k0jX9%nbBJVa|pJ$1cV zB0j~jEn{IU@{Cm@cCcOiK9pbJq{bhC~9gjT7>jsTf^x6+^k{m@oHAXO0^qM^@OpS?*O{_)G&HQ{m^Mp=b^zU*>ey5G zrd`J@jA*?n_q&EEo&se7jM4`xU$beRAxb8D#8-O zH8tQ=E>Id+4EMf)ohR{mUk1tr-dU(WpkIslb9NhIx^|mV%4r$bI&sIwcy1Tr5T!#NZQEg9IE!tbi`W zf$$k(HGH9_LJhdlXBhmC>Vo0W2jyD$3iWjmLU|N?gYsw-vXG30zhbj`+1(`B-4xkf zqwH=5cBjFm*pUWv;QtVt;BSZv;O~g7a13z?bR#asN2m*vxIjyVTo%*%;1T1Rk4_j) z`buV?HG`8ilZVin%S|%h$jN*QH?$JGKQ3~WgBO+<c-IjKfaE0TPEw zJV)xs!}F-0EH%?5UJmb}Wr|5zzqjfp1R}r3Umr<%qVHNshr-6rt0$#<4tMmTL;w&}AaO>Qqp@QSbI(qrmjI)gV(|A8LC&4=3o%<)A==onJ zc;SK^oL=K9{4@bvFnQGEtaEc-zN}&3=+2uiIk#i~J%8xzYI3pEO{;rX`^;*qyE%(L ay4uQ`*V>u7;O`25b#SL>{WIH8Vf+I@t}8+S diff --git a/SCADA/dll/OmronPlcDriver.dll b/SCADA/dll/OmronPlcDriver.dll new file mode 100644 index 0000000000000000000000000000000000000000..c3ccb6e7b1913b1d79280401c3f6826cbff92e25 GIT binary patch literal 17408 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