diff --git a/SCADA/Program/CoreApp/DataService/.vs/DataService/v15/Server/sqlite3/db.lock b/SCADA/Program/CoreApp/DataService/.vs/DataService/v15/Server/sqlite3/db.lock new file mode 100644 index 0000000..e69de29 diff --git a/SCADA/Program/CoreApp/DataService/.vs/DataService/v15/Server/sqlite3/storage.ide b/SCADA/Program/CoreApp/DataService/.vs/DataService/v15/Server/sqlite3/storage.ide new file mode 100644 index 0000000..ddff1c1 Binary files /dev/null and b/SCADA/Program/CoreApp/DataService/.vs/DataService/v15/Server/sqlite3/storage.ide differ diff --git a/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.cs b/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.cs new file mode 100644 index 0000000..f80f146 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.cs @@ -0,0 +1,1129 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using DataService; + +namespace ClientDriver +{ + [Description("客户端驱动")] + public class ClientReader : IDriver//客户端存在对TLV数据的字节序转换问题,需测试 + { + short _id; + public short ID + { + get + { + return _id; + } + } + + private int _timeout = 0; + public int TimeOut + { + get { return _timeout; } + set { _timeout = value; } + } + + string _name; + public string Name + { + get + { + return _name; + } + } + + string _ip; + public string ServerName + { + get { return _ip; } + set { _ip = value; } + } + + internal Socket tcpSend; + internal Socket tcpRecive; + + public bool IsClosed + { + get + { + //return tcpASynCl.Poll(-1, SelectMode.SelectRead); + return tcpSend == null || tcpRecive == null || !tcpSend.Connected || !tcpRecive.Connected; + } + } + + List _grps = new List(1); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + + public ClientReader(IDataServer server, short id, string name, string ip, int timeout) + { + _id = id; + _server = server; + _ip = ip; + _name = name; + _timeout = timeout; + Connect(); + } + + public bool Connect() + { + int port = 6543; + lock (this) + { + try + { + if (tcpRecive != null) + { + tcpRecive.Dispose(); + } + if (tcpSend != null) + { + tcpSend.Dispose(); + } + tcpRecive = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpRecive.Connect(_ip, port); + tcpRecive.SendTimeout = _timeout; + tcpRecive.ReceiveTimeout = -1; + + tcpSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpSend.Connect(_ip, port); + tcpSend.SendTimeout = _timeout; + tcpSend.ReceiveTimeout = _timeout; + return true; + } + catch (SocketException error) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + return false; + } + } + } + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) + { + ClientGroup grp = new ClientGroup(id, name, updateRate, active, this); + _grps.Add(grp); + return grp; + } + + public bool RemoveGroup(IGroup grp) + { + grp.IsActive = false; + return _grps.Remove(grp); + } + + public event ShutdownRequestEventHandler OnClose; + + public void Dispose() + { + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + try + { + if (tcpRecive != null) + { + if (tcpRecive.Connected) + tcpRecive.Shutdown(SocketShutdown.Both); + tcpRecive.Dispose(); + } + if (tcpSend != null) + { + if (tcpSend.Connected) + tcpSend.Shutdown(SocketShutdown.Both); + tcpSend.Dispose(); + } + } + catch (SocketException err) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(err.Message)); + } + } + } + + public class ClientGroup : IGroup + { + bool _active = false; + public bool IsActive + { + get + { + return _active; + } + set + { + _active = value; + if (value) + { + if (_plcReader != null && _plcReader.tcpSend != null) + { + this._tcpRecive = _plcReader.tcpRecive; + this._tcpSend = _plcReader.tcpSend; + try + { + _addr = (_tcpSend.RemoteEndPoint as IPEndPoint).Address; + } + catch { } + Thread workItem = new Thread(new ThreadStart(ReciveData)); + workItem.Priority = ThreadPriority.Highest; + workItem.Start(); + if (_updateRate > 0) + ThreadPool.QueueUserWorkItem(new WaitCallback(this.OnUpdate)); + else + Init(); + } + } + } + } + + protected short _id; + public short ID + { + get + { + return _id; + } + } + + protected int _updateRate; + public int UpdateRate + { + get + { + return _updateRate; + } + set + { + _updateRate = value; + } + } + + protected DeviceAddress _start; + public DeviceAddress Start + { + get + { + return _start; + } + } + + protected string _name; + public string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } + + protected float _deadband; + public float DeadBand + { + get + { + return _deadband; + } + set + { + _deadband = value; + } + } + + protected ClientReader _plcReader; + public IDriver Parent + { + get + { + return _plcReader; + } + } + + protected Dictionary _items; + public IEnumerable Items + { + get { return _items.Values; } + } + + IDataServer _server; + public IDataServer Server + { + get + { + return _server; + } + } + + Socket _tcpSend, _tcpRecive; + byte[] tcpBuffer; + + IPAddress _addr; + public IPAddress RemoteAddress + { + get { return _addr; } + } + + public ClientGroup(short id, string name, int updateRate, bool active, ClientReader plcReader) + { + this._id = id; + this._name = name; + this._updateRate = updateRate; + this._active = active; + this._plcReader = plcReader; + this._server = plcReader.Parent; + tcpBuffer = new byte[8192]; + } + + object sendasync = new object(); + private byte[] ReadSingleData(DeviceAddress address, DataSource source = DataSource.Cache) + { + if (_tcpSend == null) return null; + short ID = (short)address.Start; + byte type = (byte)address.VarType; + byte len = (byte)(address.DataSize); + byte[] idbits = BitConverter.GetBytes(ID); + byte[] write_data = new byte[6] { FCTCOMMAND.fctHead, FCTCOMMAND.fctReadSingle, + source == DataSource.Cache?(byte)0:(byte)1, idbits[0], idbits[1] ,len}; + byte[] data = new byte[len]; + SocketError error; + lock (sendasync) + { + _tcpSend.Send(write_data, 0, 6, SocketFlags.None, out error); + int result = _tcpSend.Receive(tcpBuffer, 0, data.Length + 5, SocketFlags.None, out error); + } + Array.Copy(tcpBuffer, 5, data, 0, data.Length); + if (error == SocketError.Success) + return data; + else + { + return null; + } + } + + private int WriteSingleData(DeviceAddress address, byte[] value) + { + if (_tcpSend == null) return -1; + short ID = (short)address.Start; + byte type = (byte)address.VarType; + byte[] idbits = BitConverter.GetBytes(ID); + byte[] write_data = new byte[6] { FCTCOMMAND.fctHead, FCTCOMMAND.fctWriteSingle, 1, idbits[0], idbits[1], (byte)(value.Length) }; + byte[] data = new byte[6 + value.Length]; + write_data.CopyTo(data, 0); + value.CopyTo(data, 6); + SocketError error; + lock (sendasync) + { + _tcpSend.Send(data, 0, data.Length, SocketFlags.None, out error); + _tcpSend.Receive(tcpBuffer, 0, 2, SocketFlags.None, out error); + } + if (error == SocketError.Success) + return tcpBuffer[1];//可在此处返回一个错误号 + else + { + return (int)error; + } + } + + public void Init() + { + if (_items != null && _tcpSend != null) + { + int len = 0; + List tags = new List(); + foreach (var tag in _items.Values) + { + len += (3 + tag.Address.DataSize); + if (len >= tcpBuffer.Length - 10) + { + len = 0; + BatchRead(DataSource.Cache, true, tags.ToArray()); + tags.Clear(); + } + tags.Add(tag); + } + BatchRead(DataSource.Cache, true, tags.ToArray()); + } + } + + private void ReciveData() + { + if (!_active || _plcReader.tcpRecive == null) return; + List historys = null; + byte[] bytes = new byte[ushort.MaxValue]; + byte[] temp = new byte[_tcpRecive.ReceiveBufferSize]; + Storage value = Storage.Empty; + int result = 0; + int start = 0; + SocketError error; + do + { + if (!_tcpRecive.Connected) return; + result = _tcpRecive.Receive(bytes, 0, bytes.Length, SocketFlags.None, out error); + if (error == SocketError.Success) + { + if (DataChange != null) + historys = new List(); + //DateTime time = DateTime.Now;//当前时间戳 + if (start != 0 && temp[0] == FCTCOMMAND.fctHead) + { + int j = 3; + if (start < 0) + { + Array.Copy(bytes, 0, temp, -start, 5 + start); + } + short tc = BitConverter.ToInt16(temp, j);//总字节数 + if (start < 0) + start += tc; + Array.Copy(bytes, 0, temp, tc - start, start); + j += 2; + while (j < tc) + { + short id = BitConverter.ToInt16(temp, j);//标签ID、数据长度、数据值(T,L,V) + j += 2; + byte length = temp[j++]; + ITag tag; + if (_items.TryGetValue(id, out tag)) + { + //数据类型 + switch (tag.Address.VarType) + { + case DataType.BOOL: + value.Boolean = BitConverter.ToBoolean(temp, j); + break; + case DataType.BYTE: + value.Byte = temp[j]; + break; + case DataType.WORD: + case DataType.SHORT: + value.Int16 = BitConverter.ToInt16(temp, j);//需测试 + break; + case DataType.INT: + value.Int32 = BitConverter.ToInt32(temp, j);//需测试 + break; + case DataType.FLOAT: + value.Single = BitConverter.ToSingle(temp, j); + break; + case DataType.STR: + StringTag strTag = tag as StringTag; + if (strTag != null) + { + strTag.String = Encoding.ASCII.GetString(temp, j, length).Trim((char)0); + } + break; + default: + break; + } + j += length; + DateTime time = DateTime.FromFileTime(BitConverter.ToInt64(temp, j)); + j += 8; + tag.Update(value, time, QUALITIES.QUALITY_GOOD); + if (historys != null) + historys.Add(new HistoryData(id, QUALITIES.QUALITY_GOOD, value, time)); + } + else + j += length + 8; + } + } + byte head = bytes[start]; + int count = start; + while (head == FCTCOMMAND.fctHead && result > count) + { + if (count + 5 > bytes.Length) + { + start = count - bytes.Length; + Array.Copy(bytes, count, temp, 0, -start); + break; + } + int j = count + 3; + short tc = BitConverter.ToInt16(bytes, j);//总标签数 + count += tc; + if (count >= bytes.Length) + { + start = count - bytes.Length; + Array.Copy(bytes, count - tc, temp, 0, tc - start); + break; + } + else start = 0; + j += 2; + while (j < count) + { + short id = BitConverter.ToInt16(bytes, j);//标签ID、数据长度、数据值(T,L,V) + j += 2; + byte length = bytes[j++]; + ITag tag; + if (_items.TryGetValue(id, out tag)) + { + //数据类型 + switch (tag.Address.VarType) + { + case DataType.BOOL: + value.Boolean = BitConverter.ToBoolean(bytes, j); + break; + case DataType.BYTE: + value.Byte = bytes[j]; + break; + case DataType.WORD: + case DataType.SHORT: + value.Int16 = BitConverter.ToInt16(bytes, j);//需测试 + break; + case DataType.INT: + value.Int32 = BitConverter.ToInt32(bytes, j);//需测试 + break; + case DataType.FLOAT: + value.Single = BitConverter.ToSingle(bytes, j); + break; + case DataType.STR: + StringTag strTag = tag as StringTag; + if (strTag != null) + { + strTag.String = Encoding.ASCII.GetString(bytes, j, length).Trim((char)0); + } + break; + default: + break; + } + j += length; + DateTime time = DateTime.FromFileTime(BitConverter.ToInt64(bytes, j)); + j += 8; + tag.Update(value, time, QUALITIES.QUALITY_GOOD); + if (historys != null) + historys.Add(new HistoryData(id, QUALITIES.QUALITY_GOOD, value, time)); + } + else + j += length + 8; + } + head = bytes[count]; + } + if (DataChange != null && historys.Count > 0) + DataChange(this, new DataChangeEventArgs(1, historys)); + + } + else if (error == SocketError.ConnectionReset || error == SocketError.Interrupted + || error == SocketError.HostDown || error == SocketError.NetworkDown || error == SocketError.Shutdown) + { + _tcpRecive.Dispose(); + _active = false; + return; + } + } + while (result > 0); + } + + public void OnUpdate(object stateInfo) + { + while (true) + { + Thread.Sleep(_updateRate); + lock (this) + { + if (!_active) + { + return; + } + Init(); + } + } + } + + public bool AddItems(IList items) + { + int count = items.Count; + if (_items == null) _items = new Dictionary(count); + lock (_server.SyncRoot) + { + for (int i = 0; i < count; i++) + { + ITag dataItem = null; + TagMetaData meta = items[i]; + DeviceAddress addr = new DeviceAddress(0, 0, 0, meta.ID, meta.Size, 0, meta.DataType); + switch (meta.DataType) + { + case DataType.BOOL: + dataItem = new BoolTag(meta.ID, addr, this); + break; + case DataType.BYTE: + dataItem = new ByteTag(meta.ID, addr, this); + break; + case DataType.WORD: + case DataType.SHORT: + dataItem = new ShortTag(meta.ID, addr, this); + break; + case DataType.TIME: + case DataType.INT: + dataItem = new IntTag(meta.ID, addr, this); + break; + case DataType.FLOAT: + dataItem = new FloatTag(meta.ID, addr, this); + break; + case DataType.STR: + dataItem = new StringTag(meta.ID, addr, this); + break; + default: + dataItem = new BoolTag(meta.ID, addr, this); + break; + } + _items.Add(meta.ID, dataItem); + _server.AddItemIndex(meta.Name, dataItem); + } + } + //Init(); + return true; + } + + public bool AddTags(IEnumerable tags) + { + if (_items == null) + { + _items = new Dictionary(); + } + foreach (ITag tag in tags) + { + if (tag != null) + { + _items.Add(tag.ID, tag); + } + } + //Init(); + return true; + } + + public bool RemoveItems(params ITag[] items) + { + foreach (var item in items) + { + _server.RemoveItemIndex(item.GetTagName()); + _items.Remove(item.ID); + } + return true; + } + + public bool SetActiveState(bool active, params short[] items) + { + return true; + } + + public ITag FindItemByAddress(DeviceAddress addr) + { + ITag tag; + if (_items.TryGetValue((short)addr.Start, out tag)) + return tag; + return _server[(short)addr.Start]; + } + + public HistoryData[] BatchRead(DataSource source, bool isSync, params ITag[] itemArray) + { + if (itemArray.Length == 0 || _tcpSend == null || !_tcpSend.Connected) return null; + short len = (short)itemArray.Length; + byte[] bt = new byte[2]; + byte[] data = new byte[5 + len * 2]; + int j = 0; + data[j++] = FCTCOMMAND.fctHead; + data[j++] = FCTCOMMAND.fctReadMultiple; + data[j++] = source == DataSource.Cache ? (byte)0 : (byte)1; + bt = BitConverter.GetBytes(len); + data[j++] = bt[0]; + data[j++] = bt[1]; + for (int i = 0; i < len; i++) + { + bt = BitConverter.GetBytes(itemArray[i].ID); + data[j++] = bt[0]; + data[j++] = bt[1]; + } + SocketError error; + lock (sendasync) + { + _tcpSend.Send(data, 0, data.Length, SocketFlags.None, out error); + int result = _tcpSend.Receive(tcpBuffer, 0, tcpBuffer.Length, SocketFlags.None, out error); + } + //!!!!此处的协议应注意,如批量读入的数据量超过缓存,必须分批合并;在协议头应加入总字节数,以和result比较是否需要循环读入。 + j = 5; + if (error == SocketError.Success) + { + DateTime time = DateTime.Now; + HistoryData[] values = new HistoryData[len]; + for (int i = 0; i < len; i++) + { + short id = BitConverter.ToInt16(tcpBuffer, j); + j += 2; + ITag tag = _server[id]; + byte length = tcpBuffer[j++]; + if (tag != null && length > 0) + { + switch (tag.Address.VarType) + { + case DataType.BOOL: + values[i].Value.Boolean = BitConverter.ToBoolean(tcpBuffer, j); + break; + case DataType.BYTE: + values[i].Value.Byte = tcpBuffer[j]; + break; + case DataType.WORD: + case DataType.SHORT: + values[i].Value.Int16 = BitConverter.ToInt16(tcpBuffer, j); + break; + case DataType.INT: + values[i].Value.Int32 = BitConverter.ToInt32(tcpBuffer, j); + break; + case DataType.FLOAT: + values[i].Value.Single = BitConverter.ToSingle(tcpBuffer, j); + break; + case DataType.STR: + StringTag strTag = tag as StringTag; + if (strTag != null) + { + strTag.String = Encoding.ASCII.GetString(tcpBuffer, j, length).Trim(); + } + break; + } + if (values[i].Value != tag.Value) + tag.Update(values[i].Value, time, QUALITIES.QUALITY_GOOD); + } + j += length; + } + return values; + } + else + return null; + } + + public int BatchWrite(SortedDictionary items, bool isSync = true) + { + if (_tcpSend == null || !_tcpSend.Connected) return -1; + List list = new List(new byte[] { FCTCOMMAND.fctHead, FCTCOMMAND.fctWriteMultiple }); + list.AddRange(BitConverter.GetBytes((short)items.Count)); + foreach (var item in items) + { + ITag tag = item.Key; + list.AddRange(BitConverter.GetBytes(tag.ID)); + var addr = tag.Address; + if (addr.VarType != DataType.STR) + list.Add((byte)(addr.DataSize));//此处存疑 + switch (addr.VarType) + { + case DataType.BOOL: + list.Add(Convert.ToBoolean(item.Value) ? (byte)1 : (byte)0); + break; + case DataType.BYTE: + list.Add(Convert.ToByte(item.Value)); + break; + case DataType.WORD: + case DataType.SHORT: + list.AddRange(BitConverter.GetBytes(Convert.ToInt16(item.Value))); + break; + case DataType.INT: + list.AddRange(BitConverter.GetBytes(Convert.ToInt32(item.Value))); + break; + case DataType.FLOAT: + list.AddRange(BitConverter.GetBytes(Convert.ToSingle(item.Value))); + break; + case DataType.STR: + var bts = Encoding.ASCII.GetBytes(Convert.ToString(item.Value)); + list.Add((byte)bts.Length); + list.AddRange(bts); + break; + } + } + SocketError error; + lock (sendasync) + { + _tcpSend.Send(list.ToArray(), 0, list.Count, SocketFlags.None, out error); + _tcpSend.Receive(tcpBuffer, 0, 2, SocketFlags.None, out error); + } + if (error == SocketError.Success) + return tcpBuffer[1]; + else + { + return (int)error; + } + } + + public IEnumerable SendHdaRequest(DateTime start, DateTime end) + { + if (_tcpSend == null || !_tcpSend.Connected) yield break; + byte[] hdaReq = new byte[18]; + hdaReq[0] = FCTCOMMAND.fctHead; + hdaReq[1] = FCTCOMMAND.fctHdaRequest; + byte[] startbuffer = BitConverter.GetBytes(start.ToFileTime()); + startbuffer.CopyTo(hdaReq, 2); + byte[] endbuffer = BitConverter.GetBytes(end.ToFileTime()); + endbuffer.CopyTo(hdaReq, 10); + SocketError error; + HistoryData data = HistoryData.Empty; + short tempid = short.MinValue; + byte[] temp = new byte[14]; + ITag tag = null; + int index = 0; + int size = 0; + int result = 0; + short id = 0; + lock (sendasync) + { + _tcpSend.Send(hdaReq); + do + { + result = _tcpSend.Receive(tcpBuffer, 0, tcpBuffer.Length, SocketFlags.None, out error); + if (error == SocketError.ConnectionReset || error == SocketError.Interrupted || error == SocketError.HostDown || error == SocketError.NetworkDown || error == SocketError.Shutdown) + { + _tcpSend.Dispose(); + yield break; + } + if (index != 0) + { + Array.Copy(tcpBuffer, 0, temp, 14 - index, index); + id = BitConverter.ToInt16(temp, 0); + tempid = id; + tag = _server[id]; + if (tag == null) yield break; + data.ID = id; + size = tag.Address.DataSize; + index -= (4 - size); + switch (tag.Address.VarType) + { + case DataType.BOOL: + data.Value.Boolean = BitConverter.ToBoolean(temp, 2); + break; + case DataType.BYTE: + data.Value.Byte = tcpBuffer[index]; + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = BitConverter.ToInt16(temp, 2); + break; + case DataType.TIME: + case DataType.INT: + data.Value.Int32 = BitConverter.ToInt32(temp, 2); + break; + case DataType.FLOAT: + data.Value.Single = BitConverter.ToSingle(temp, 2); + break; + } + long fileTime = BitConverter.ToInt64(temp, 2 + size); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + yield return data; + } + while (result >= index + 2) + { + id = BitConverter.ToInt16(tcpBuffer, index); + if (tempid != id) + { + tempid = id; + tag = _server[id]; + if (tag == null) yield break; + size = tag.Address.DataSize; + } + if (index + 10 + size > result) + break; + data.ID = id; + index += 2; + switch (tag.Address.VarType) + { + case DataType.BOOL: + data.Value.Boolean = BitConverter.ToBoolean(tcpBuffer, index); + break; + case DataType.BYTE: + data.Value.Byte = tcpBuffer[index]; + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = BitConverter.ToInt16(tcpBuffer, index); + break; + case DataType.TIME: + case DataType.INT: + data.Value.Int32 = BitConverter.ToInt32(tcpBuffer, index); + break; + case DataType.FLOAT: + data.Value.Single = BitConverter.ToSingle(tcpBuffer, index); + break; + } + index += size; + long fileTime = BitConverter.ToInt64(tcpBuffer, index); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + index += 8; + yield return data; + } + if (index == result) + index = 0; + else + { + Array.Copy(tcpBuffer, index, temp, 0, result - index); + index += 14 - result; + } + } while (result > 0); + } + yield break; + } + + public IEnumerable SendHdaRequest(DateTime start, DateTime end, short id) + { + if (_tcpSend == null || !_tcpSend.Connected) yield break; + ITag tag = _server[id]; + if (tag == null) yield break; + byte[] hdaReq = new byte[20]; + hdaReq[0] = FCTCOMMAND.fctHead; + hdaReq[1] = FCTCOMMAND.fctHdaIdRequest; + byte[] startbuffer = BitConverter.GetBytes(start.ToFileTime()); + startbuffer.CopyTo(hdaReq, 2); + byte[] endbuffer = BitConverter.GetBytes(end.ToFileTime()); + endbuffer.CopyTo(hdaReq, 10); + byte[] idbuffer = BitConverter.GetBytes(id); + idbuffer.CopyTo(hdaReq, 18); + SocketError error; + int index = 0; + HistoryData data = HistoryData.Empty; + data.ID = id; + int result = 0; + lock (sendasync) + { + _tcpSend.Send(hdaReq); + switch (tag.Address.VarType) + { + case DataType.FLOAT: + do + { + result = _tcpSend.Receive(tcpBuffer, 0, tcpBuffer.Length, SocketFlags.None, out error); + if (error == SocketError.ConnectionReset || error == SocketError.Interrupted || error == SocketError.HostDown || error == SocketError.NetworkDown || error == SocketError.Shutdown) + { + _tcpSend.Dispose(); + yield break; + } + while (index + 12 <= result) + { + data.Value.Single = BitConverter.ToSingle(tcpBuffer, index);//未来可考虑量程转换和其他数据类型 + index += 4; + long fileTime = BitConverter.ToInt64(tcpBuffer, index); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + index += 8; + yield return data; + } + if (index == result) + index = 0; + else + index += 12 - result;//丢弃一个值 + } while (result > 0); + break; + case DataType.INT: + do + { + result = _tcpSend.Receive(tcpBuffer, 0, tcpBuffer.Length, SocketFlags.None, out error); + if (error == SocketError.ConnectionReset || error == SocketError.Interrupted || error == SocketError.HostDown || error == SocketError.NetworkDown || error == SocketError.Shutdown) + { + _tcpSend.Dispose(); + yield break; + } + while (index + 12 <= result) + { + data.Value.Int32 = BitConverter.ToInt32(tcpBuffer, index);//未来可考虑量程转换和其他数据类型 + index += 4; + long fileTime = BitConverter.ToInt64(tcpBuffer, index); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + index += 8; + yield return data; + } + if (index == result) + index = 0; + else + index += 12 - result;//丢弃一个值 + } while (result > 0); + break; + case DataType.BOOL://布尔量一个都不能少 + { + byte[] temp = new byte[9]; + do + { + result = _tcpSend.Receive(tcpBuffer, 0, tcpBuffer.Length, SocketFlags.None, out error); + if (error == SocketError.ConnectionReset || error == SocketError.Interrupted || error == SocketError.HostDown || error == SocketError.NetworkDown || error == SocketError.Shutdown) + { + _tcpSend.Dispose(); + yield break; + } + if (index != 0) + { + Array.Copy(tcpBuffer, 0, temp, 9 - index, index); + data.Value.Boolean = BitConverter.ToBoolean(temp, 0); + long fileTime = BitConverter.ToInt64(temp, 1); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + yield return data; + } + while (index + 9 <= result) + { + data.Value.Boolean = BitConverter.ToBoolean(tcpBuffer, index); + index += 1; + long fileTime = BitConverter.ToInt64(tcpBuffer, index); + if (fileTime == -1) yield break; + data.TimeStamp = DateTime.FromFileTime(fileTime); + index += 8; + yield return data; + } + if (index == result) + index = 0; + else + { + Array.Copy(tcpBuffer, index, temp, 0, result - index); + index += 9 - result; + } + } while (result > 0); + } + break; + } + } + } + + public int SendResetRequest() + { + if (_tcpSend != null && _tcpSend.Connected) + { + var ipaddr = (_tcpSend.LocalEndPoint as IPEndPoint).Address; + byte[] resetReq = new byte[6]; + resetReq[0] = FCTCOMMAND.fctHead; + resetReq[1] = FCTCOMMAND.fctReset; + ipaddr.GetAddressBytes().CopyTo(resetReq, 2); + lock (sendasync) + { + return _tcpSend.Send(resetReq); + } + } + return -1; + } + + public int SendAlarmRequest(DateTime? start, DateTime? end) + { + if (_tcpSend != null && _tcpSend.Connected) + { + byte[] alarmReq = new byte[18]; + alarmReq[0] = FCTCOMMAND.fctHead; + alarmReq[1] = FCTCOMMAND.fctAlarmRequest; + if (start.HasValue) + { + byte[] startbuffer = BitConverter.GetBytes(start.Value.ToFileTime()); + startbuffer.CopyTo(alarmReq, 2); + } + if (end.HasValue) + { + byte[] endbuffer = BitConverter.GetBytes(end.Value.ToFileTime()); + endbuffer.CopyTo(alarmReq, 10); + } + SocketError error; + lock (sendasync) + { + _tcpSend.Send(alarmReq); + _tcpSend.Receive(tcpBuffer, 0, 2, SocketFlags.None, out error); + } + return (int)error; + } + return -1; + } + + public ItemData ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt32(data, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt16(data, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(data[0], 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + if (data == null) + return new ItemData(0.0f, 0, QUALITIES.QUALITY_BAD); + else + { + int value = BitConverter.ToInt32(data, 0); + return new ItemData(*(((float*)&value)), 0, QUALITIES.QUALITY_GOOD); + } + } + + public ItemData ReadBool(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(false, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToBoolean(data, address.Bit), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD) : + new ItemData(Encoding.ASCII.GetString(data, 0, Math.Min((int)address.DataSize, 254)).Trim((char)0), 0, QUALITIES.QUALITY_GOOD); + } + + public int WriteInt32(DeviceAddress address, int value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteInt16(DeviceAddress address, short value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteFloat(DeviceAddress address, float value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteString(DeviceAddress address, string value) + { + return WriteSingleData(address, Encoding.ASCII.GetBytes(value)); + } + + public int WriteBit(DeviceAddress address, bool value) + { + return WriteSingleData(address, new byte[] { (byte)(value ? 1 : 0) }); + } + + public int WriteBits(DeviceAddress address, byte value) + { + return WriteSingleData(address, new byte[] { value }); + } + + public event DataChangeEventHandler DataChange; + + public void Dispose() + { + if (_items != null) + { + _items.Clear(); + } + _items = null; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.csproj b/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.csproj new file mode 100644 index 0000000..45f8072 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ClientDriver/ClientDriver.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.0 + + + + true + + + + + + + diff --git a/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.cs b/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.cs new file mode 100644 index 0000000..cd9ba9f --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.cs @@ -0,0 +1,198 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Debug; +using MySql.Data.MySqlClient; +using System; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Net; +using System.Text; + +namespace DatabaseLib +{ + public static class DataHelper + { + static string m_ConnStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=SharpSCADA;Integrated Security=True"; + static string m_Path = @"D:\HDA"; + static string m_host = Environment.MachineName; + static string m_type = "MSSQL"; + //数据库工厂接口 + const string CFGPATH = @"C:\DataConfig\host.cfg"; + const string INIPATH = @"C:\DataConfig\host.ini"; + const string DATALOGSOURCE = "Data Operations"; + const string DATALOGNAME = "Data Log"; + const int STRINGMAX = 255; + + static ILogger Log; + #region GetInstance + private static IDataFactory _ins; + + public static IDataFactory Instance + { + get + { + return _ins; + } + } + + public static string HostName + { + get { return m_host; } + } + + public static string ConnectString + { + get { return m_ConnStr; } + } + + public static string HdaPath + { + get { return m_Path; } + } + #endregion + /// + /// 数据库工厂构造函数 + /// + /// 数据库枚举 + static DataHelper() + { + var loggerFactory = new LoggerFactory(); + Func filter = (category, level) => true; + loggerFactory.AddProvider(new DebugLoggerProvider(filter)); + Log = loggerFactory.CreateLogger(DATALOGSOURCE); + try + { + if (File.Exists(INIPATH)) + { + var builder = new ConfigurationBuilder(); + var ibuild = builder.AddIniFile(INIPATH); + var root = ibuild.Build(); + var host = root.GetSection("HOST"); + m_host = host.GetSection("SERVER").Value; + var db = root.GetSection("DATABASE"); + m_ConnStr = db.GetSection("CONNSTRING").Value; + m_Path = db.GetSection("ARCHIVE").Value; + m_type = db.GetSection("TYPE").Value; + } + else if (File.Exists(CFGPATH)) + { + using (StreamReader objReader = new StreamReader(CFGPATH)) + { + m_host = objReader.ReadLine(); + m_ConnStr = objReader.ReadLine(); + m_Path = objReader.ReadLine(); + } + } + IPAddress addr; + if (string.IsNullOrEmpty(m_host) || !IPAddress.TryParse(m_host, out addr)) + { + m_host = Environment.MachineName; + } + _ins = new MysqlFactory(); + } + catch (Exception e) + { + AddErrorLog(e); + } + } + + public static DbParameter CreateParam(string paramName, SqlDbType dbType, object objValue, int size = 0, ParameterDirection direction = ParameterDirection.Input) + { + return _ins.CreateParam(paramName, dbType, objValue, size, direction); + } + + public static string DataTableToCsv(DataTable table) + { + //以半角逗号(即,)作分隔符,列为空也要表达其存在。 + //列内容如存在半角逗号(即,)则用半角引号(即"")将该字段值包含起来。 + //列内容如存在半角引号(即")则应替换成半角双引号("")转义,并用半角引号(即"")将该字段值包含起来。 + StringBuilder sb = new StringBuilder(); + DataColumn colum; + foreach (DataRow row in table.Rows) + { + for (int i = 0; i < table.Columns.Count; i++) + { + colum = table.Columns[i]; + if (i != 0) sb.Append(","); + var txt = row[colum] == null ? "" : row[colum].ToString(); + if (colum.DataType == typeof(string) && txt.Contains(",")) + { + sb.Append("\"" + txt.Replace("\"", "\"\"") + "\""); + } + else sb.Append(txt); + } + sb.AppendLine(); + } + return sb.ToString(); + } + + public static string ReaderToCsv(IDataReader reader) + { + StringBuilder sb = new StringBuilder(); + var colcount = reader.FieldCount; + while (reader.Read()) + { + for (int i = 0; i < colcount; i++) + { + if (i != 0) sb.Append(","); + var txt = reader[i] == null ? "" : reader[i].ToString(); + if (txt.Contains(",")) + { + sb.Append("\"" + txt.Replace("\"", "\"\"") + "\""); + } + else sb.Append(txt); + } + sb.AppendLine(); + } + return sb.ToString(); + } + + public static void AddErrorLog(Exception e) + { + string err = ""; + Exception exp = e; + while (exp != null) + { + err += string.Format("\n {0}", exp.Message); + exp = exp.InnerException; + } + err += string.Format("\n {0}", e.StackTrace); + Log.LogError(err); + } + + public static string GetNullableString(this DbDataReader reader, int index) + { + return reader.GetString(index); + } + + public static DateTime? GetNullableTime(this DbDataReader reader, int index) + { + return reader.GetDateTime(index); + } + + public static int GetTimeTick(this DbDataReader reader, int index) + { + var datetime = reader.GetDateTime(index); + var value = datetime.Subtract(new DateTime(1900, 1, 1)); + long num2 = value.Ticks - value.Days * 864000000000; + if (num2 < 0) + num2 += 864000000000; + int num3 = (int)(num2 / 10000.0 * 0.3 + 0.5); + if (num3 > 300 * 60 * 60 * 24 - 1) + num3 = 0; + return num3; + } + + public static object GetSqlValue(this DbDataReader reader, int index) + { + var mq = reader as MySqlDataReader; + if (mq != null) + { + return mq.GetValue(index); + } + return ""; + } + } + +} \ No newline at end of file diff --git a/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.csproj b/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.csproj new file mode 100644 index 0000000..085e74a --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataHelper/DataHelper.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + + + + true + + + + + + + + + + + + + + diff --git a/SCADA/Program/CoreApp/DataService/DataHelper/HDAIOHelper.cs b/SCADA/Program/CoreApp/DataService/DataHelper/HDAIOHelper.cs new file mode 100644 index 0000000..c2a3903 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataHelper/HDAIOHelper.cs @@ -0,0 +1,927 @@ +using DataService; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlTypes; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Runtime.InteropServices; +using System.Text; + +namespace DatabaseLib +{ + public class HDAIOHelper + { + static int[] dataLen = new int[] { 5, 5, 5, 5, 6, 6, 8, 8, 8, 8, 8, 8 }; + + static string m_Path = @"D:\HDA"; + + static HDAIOHelper() + { + m_Path = DataHelper.HdaPath; + } + + public static bool FindFile(DateTime date) + { + if (Directory.Exists(m_Path)) + { + return File.Exists(string.Concat(m_Path, "\\", date.Year.ToString(), "-", date.Month.ToString(), ".bin")); + } + return false; + } + + public static bool CreateFile(int year, int month) + { + string path = string.Concat(m_Path, "\\", year.ToString(), "-", month.ToString(), ".bin"); + try + { + if (Directory.Exists(m_Path)) + { + if (File.Exists(path)) + { + return true; + } + } + else + { + Directory.CreateDirectory(m_Path); + } + using (var stream = File.Create(path, 0x100)) + { + stream.Write(new byte[0x100], 0, 0x100); + return true; + } + } + catch (Exception err) + { + DataHelper.AddErrorLog(err); + return false; + } + } + + public static bool GetRangeFromDatabase(short? ID, ref DateTime start, ref DateTime end) + { + using (var reader = DataHelper.Instance.ExecuteReader("SELECT MIN(TIMESTAMP),MAX(TIMESTAMP) FROM LOG_HDATA" + (ID.HasValue ? " WHERE ID=" + ID.Value : ""))) + { + if (reader != null) + { + while (reader.Read()) + { + if (!reader.IsDBNull(0)) + start = reader.GetDateTime(0); + if (!reader.IsDBNull(1)) + end = reader.GetDateTime(1); + return true; + } + } + } + //start = end = DateTime.MinValue; + return false; + } + + public static void BackUpFile(DateTime date) + { + lock (typeof(HDAIOHelper)) + { + if (WriteToFile(date.AddDays(-1)) == 0) + { + DataHelper.Instance.ExecuteNonQuery(string.Format("DELETE FROM LOG_HDATA WHERE [TIMESTAMP]<='{0}';", date.ToShortDateString())); + } + } + } + + public static int WriteToFile(DateTime date)//每天凌晨写入昨天的数据到文件,可以考虑用服务或计划任务;数据库只保留当天的记录;调度程序负责删除过期记录;历史数据应支持合并 + { + int year = date.Year; int month = date.Month; int day = date.Day; + string path = string.Concat(m_Path, "\\", year.ToString(), "-", month.ToString(), ".bin"); + if (CreateFile(year, month))//如该月文件不存在,则创建;否则写入 + { + using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + stream.Seek(day * 8, SeekOrigin.Begin);//先读入索引区,定位到该日期的指针 + byte[] bits = new byte[8]; + stream.Read(bits, 0, 8);//如果该位置指针为>0的正数,说明该区域已有数据 + if (BitConverter.ToInt64(bits, 0) > 0) return -1; + } + using (var dataReader = DataHelper.Instance.ExecuteProcedureReader("WRITEHDATA", DataHelper.CreateParam("@DATE", SqlDbType.DateTime, date))) + { + if (dataReader == null) return -10; + else + { + dataReader.Read(); + int cont = dataReader.GetInt32(0);//读入标签数量 + if (cont == 0) return -2; + string path2 = path + ".temp"; + try + { + File.Copy(path, path2, true);//先把原文件全部复制到临时文件 + //Stopwatch sw = Stopwatch.StartNew(); + using (FileStream stream = File.Open(path2, FileMode.Open)) + { + //w.Seek(8 + day * 8, SeekOrigin.Begin); + //w.Seek(0x100, SeekOrigin.Begin); + long start = stream.Seek(0, SeekOrigin.End);//定位到文件末尾 + long end = 0; + using (BinaryWriter w = new BinaryWriter(stream)) + { + w.Write(new SqlDateTime(date).DayTicks);//写入日期 + w.Write(cont);///写入标签数量 + int count = dataReader.GetInt32(1); + w.Write(count); + HDataFormat[] list = new HDataFormat[count]; + if (dataReader.NextResult()) + { + int p = 0; + int x = 0; + while (dataReader.Read())//写入标签元数据 + { + short id = dataReader.GetInt16(0);//ID号 + byte type = dataReader.GetByte(1);//数据类型 + int cn = dataReader.GetInt32(2);//标签个数 + //list[x].ID = id; + list[x].Type = (DataType)type; + list[x].Count = cn; + //list[x].Offset = p; + w.Write(id); + w.Write(type); + w.Write(cn); + w.Write(p); + p += cn * dataLen[type]; + x++; + } + if (dataReader.NextResult()) + { + for (int i = 0; i < list.Length; i++) + { + int len = list[i].Count; + for (int j = 0; j < len; j++) + { + if (dataReader.Read()) + { + w.Write(dataReader.GetTimeTick(0)); + switch (list[i].Type) + { + case DataType.BOOL: + w.Write(dataReader.GetFloat(1) > 0); + break; + case DataType.BYTE: + w.Write((byte)dataReader.GetFloat(1)); + break; + case DataType.WORD: + case DataType.SHORT: + w.Write((short)dataReader.GetFloat(1)); + break; + case DataType.INT: + w.Write((int)dataReader.GetFloat(1)); + break; + case DataType.FLOAT: + w.Write(dataReader.GetFloat(1)); + break; + } + } + } + } + + } + } + end = stream.Position;//文件的结尾,总长度 + w.Seek((day - 1) * 8, SeekOrigin.Begin);//定位到索引区 + w.Write(start);//写入当日指针 + w.Write(end);//写入下一日指针 + //w.Close(); + } + } + File.Copy(path2, path, true); + } + catch (Exception err) + { + DataHelper.AddErrorLog(err); + return -3; + } + finally + { + if (File.Exists(path2)) + File.Delete(path2); + } + //dataReader.Close(); + return 0; + /*写入失败,则将备份文件还原;数据库不做删除动作,保留记录,次日服务检查数据文件是否存在,不存在则合并写入 + 可在服务内建XML文件保存失败记录的日期列表,以便还原;用File.Mov;定时间隔、开始时间也可XML定义。 + 先备份二进制归档库,再加载数据库数据,写入文件;如成功,删除数据库当日记录并删除备份文件 + sw.Stop(); + * if (sw.ElapsedTicks > 0) { } + */ + } + } + } + return -10; + } + + public static IEnumerable LoadFromFile(DateTime start, DateTime end, bool sdt = false) + { + //Stopwatch sw = Stopwatch.StartNew(); + //文件的组织格式:头文件:31,ln为间隔日期,position为指向日期段的指针,sizes为日期段的长度。 + //每日的抬头:按ID次序,包含每个TAG的数量,arr为每个日期所有的标签、每标签数量、数据类型、位置指针。 + //按时间排序,每个标签的值、时间戳。 + string path = string.Concat(m_Path, "\\", start.Year.ToString(), "-", start.Month.ToString(), sdt ? ".sdt" : ".bin"); + if (!File.Exists(path)) yield break; + int day1 = start.Day; + int startTicks = new SqlDateTime(start).TimeTicks; + int endTicks = new SqlDateTime(end).TimeTicks; + int ln = end.Day - day1 + 1; + using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryReader reader = new BinaryReader(stream); + long[] positions = new long[ln + 1]; + long[] sizes = new long[ln]; + stream.Seek((day1 - 1) * 8, SeekOrigin.Begin); + positions[0] = reader.ReadInt64(); + for (int i = 0; i < ln; i++) + { + positions[i + 1] = reader.ReadInt64(); + sizes[i] = positions[i + 1] - positions[i];//每一天数据的长度 + } + //reader.Close(); + HistoryData data = HistoryData.Empty; + using (MemoryMappedFile mapp = MemoryMappedFile.CreateFromFile(stream, Guid.NewGuid().ToString(), 0, MemoryMappedFileAccess.Read, + HandleInheritability.Inheritable, false)) + { + for (int k = 0; k < ln; k++) + { + if (positions[k] < 0x100 || sizes[k] <= 0 || positions[k] + sizes[k] > stream.Length) + continue; + using (MemoryMappedViewAccessor acc = mapp.CreateViewAccessor(positions[k], sizes[k], MemoryMappedFileAccess.Read)) + { + long pos = 0; + int day = acc.ReadInt32(pos); + pos += 8; + int count = acc.ReadInt32(pos); + pos += 4; + HDataFormat[] arr = new HDataFormat[count]; + for (int i = 0; i < count; i++) + { + arr[i].ID = acc.ReadInt16(pos); + pos += 2; + arr[i].Type = (DataType)acc.ReadByte(pos); + pos++; + arr[i].Count = acc.ReadInt32(pos);//4个字节是预留 + pos += 8; + } + long tempos = pos; + for (int i = 0; i < count; i++) + { + int con = arr[i].Count; + int j = 0; + pos = tempos + acc.ReadInt32(i * 11 + 19); + long pf = pos; + DataType type = arr[i].Type; + int len = dataLen[(int)type]; + if (k == 0) //判断是否为起始日期或结束日期 + { + int ind = BinarySearchTime(acc, pf, con, len, startTicks); + if (ind < 0) ind = ~ind; + j += ind; + pos += ind * len; + } + if (k == ln - 1) + { + int index = BinarySearchTime(acc, pf, con, len, endTicks); + con = index >= 0 ? index : ~index; + } + while (j++ < con) + { + data.ID = arr[i].ID; + data.TimeStamp = new SqlDateTime(day, acc.ReadInt32(pos)).Value; + pos += 4; + switch (type) + { + case DataType.BOOL: + data.Value.Boolean = acc.ReadBoolean(pos); + pos++; + break; + case DataType.BYTE: + data.Value.Byte = acc.ReadByte(pos); + pos++; + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = acc.ReadInt16(pos); + pos += 2; + break; + case DataType.INT: + data.Value.Int32 = acc.ReadInt32(pos); + pos += 4; + break; + case DataType.FLOAT: + data.Value.Single = acc.ReadSingle(pos); + pos += 4; + break; + } + yield return data; + } + } + } + } + } + } + yield break; + } + + public static IEnumerable LoadFromFile(DateTime start, DateTime end, short ID, bool sdt = false) + { + string path = string.Concat(m_Path, "\\", start.Year.ToString(), "-", start.Month.ToString(), sdt ? ".sdt" : ".bin");//bin-sdt + if (!File.Exists(path)) yield break; + int day1 = start.Day; + int startTicks = new SqlDateTime(start).TimeTicks;//开始日期部分的4位数据 + int endTicks = new SqlDateTime(end).TimeTicks; + int ln = end.Day - day1 + 1;//日期天数 + using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + long filelen = stream.Length;//文件长度 + BinaryReader reader = new BinaryReader(stream); + long[] positions = new long[ln];//每日数据指针(指向第一条数据,包括当日数据索引区) + stream.Seek((day1 - 1) * 8, SeekOrigin.Begin);///找到对应的开始日期索引位置 + + for (int i = 0; i < ln; i++) + { + positions[i] = reader.ReadInt64();//读入时间段内每日数据长度值 + } + long[] sizes = new long[ln]; + for (int i = 0; i < ln; i++) + { + if (positions[i] >= filelen) break;//如果读入长度超过文件大小则退出 + stream.Seek(positions[i] + 8, SeekOrigin.Begin);//定位文件指针到当日数据开头 + sizes[i] = reader.ReadInt32();//sizes为当日该标签数 + } + //reader.Close(); + HistoryData data = HistoryData.Empty; + //stream.Read(new byte[] + using (MemoryMappedFile mapp = MemoryMappedFile.CreateFromFile(stream, Guid.NewGuid().ToString(), filelen, MemoryMappedFileAccess.Read, + HandleInheritability.Inheritable, false)) + { + for (int k = 0; k < ln; k++)//先读入当日索引区 + { + if (positions[k] < 0x100 || sizes[k] <= 0 || positions[k] + sizes[k] > filelen) + continue; + //if (sizes[k] == 0) continue; + long pos = 0; + int count = 0; + int day = 0; + int len = 0; + DataType type = DataType.NONE; + using (MemoryMappedViewAccessor acc1 = mapp.CreateViewAccessor(positions[k], 12 + sizes[k] * 11, MemoryMappedFileAccess.Read))//12是头的长度,11是一个格式字段的长度 + { + day = acc1.ReadInt32(0);//当日日期部分 + int index = BinarySearch(acc1, (int)sizes[k], ID);//找到当天 指定标签的记录索引 + if (index >= 0) + index = index * 11 + 12;//如找到,则定位到当日数据的元数据(相对位移) + //sw.Stop(); + else continue; + byte tp = acc1.ReadByte(index + 2);//读入数据类型 + type = (DataType)tp; + len = dataLen[tp];//4,6,8分别为存储的标签长度,其中4字节是时间戳 + count = acc1.ReadInt32(index + 3);//读入数量 + pos = positions[k] + 12 + sizes[k] * 11 + acc1.ReadInt32(index + 7);//指针指向当日当前标签第一条记录 + } + using (MemoryMappedViewAccessor acc2 = mapp.CreateViewAccessor(pos, count * len, MemoryMappedFileAccess.Read))//重新从头定位文件指针到数据区 + { + pos = 0; + int j = 0; + if (k == 0)//判断是否为起始日期或结束日期 + { + int ind = BinarySearchTime(acc2, 0, count, len, startTicks);//根据时间排序方式二分法查找当日当前时间节点的数据,如为第一日 + if (ind < 0) ind = ~ind; + j += ind; + pos += ind * len; + } + if (k == ln - 1) + { + int ind = BinarySearchTime(acc2, 0, count, len, endTicks);//如果为最后一日的数据,则按结束时间定位 + count = ind >= 0 ? ind : ~ind; + } + while (j++ < count) + { + data.ID = ID; + data.TimeStamp = new SqlDateTime(day, acc2.ReadInt32(pos)).Value;//日期在前(4位) + pos += 4;//数据区也是4位 + switch (type) + { + case DataType.BOOL: + data.Value.Boolean = acc2.ReadBoolean(pos); + pos++; + break; + case DataType.BYTE: + data.Value.Byte = acc2.ReadByte(pos); + pos++; + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = acc2.ReadInt16(pos); + pos += 2; + break; + case DataType.INT: + data.Value.Int32 = acc2.ReadInt32(pos); + pos += 4; + break; + case DataType.FLOAT: + data.Value.Single = acc2.ReadSingle(pos); + pos += 4; + break; + } + yield return data; + } + } + } + } + reader.Close(); + } + yield break; + } + + public static IEnumerable LoadFromDatabase(DateTime start, DateTime end, short? ID = null) + { + using (var dataReader = DataHelper.Instance.ExecuteProcedureReader("READHDATA", + DataHelper.CreateParam("@STARTTIME", SqlDbType.DateTime, start), + DataHelper.CreateParam("@ENDTIME", SqlDbType.DateTime, end), + DataHelper.CreateParam("@ID", SqlDbType.Int, (object)ID ?? DBNull.Value))) + { + if (dataReader == null) yield break; + HistoryData data = HistoryData.Empty; + int itime = ID.HasValue ? 0 : 1; + int ivalue = ID.HasValue ? 1 : 2; + int itype = ID.HasValue ? 2 : 3; + while (dataReader.Read()) + { + data.ID = ID.HasValue ? ID.Value : dataReader.GetInt16(0); + data.TimeStamp = dataReader.GetDateTime(itime); + switch ((DataType)dataReader.GetByte(itype)) + { + case DataType.BOOL: + data.Value.Boolean = dataReader.GetFloat(ivalue) > 0 ? true : false; + break; + case DataType.BYTE: + data.Value.Byte = Convert.ToByte(dataReader.GetFloat(ivalue)); + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = Convert.ToInt16(dataReader.GetFloat(ivalue)); + break; + case DataType.INT: + data.Value.Int32 = Convert.ToInt32(dataReader.GetFloat(ivalue)); + break; + case DataType.FLOAT: + data.Value.Single = dataReader.GetFloat(ivalue); + break; + } + yield return data; + } + } + yield break; + } + + public static IEnumerable LoadFromDatabaseAtTime(short? ID, params DateTime[] timeStamps) + { + StringBuilder sql = new StringBuilder("SELECT "); + if (ID == null) sql.Append("ID,"); + sql.Append(" [TIMESTAMP],[VALUE],M.DATATYPE FROM LOG_HDATA L INNER JOIN META_TAG M ON L.ID=M.TAGID WHERE"); + if (ID != null) sql.Append(" ID=").Append(ID.Value).Append(" AND "); + sql.Append(" [TIMESTAMP] IN("); + for (int i = 0; i < timeStamps.Length; i++) + { + sql.Append("'").Append(timeStamps[i]).Append("',"); + } + using (var dataReader = DataHelper.Instance.ExecuteReader(sql.Append("1)").ToString())) + { + if (dataReader == null) yield break; + HistoryData data = HistoryData.Empty; + int itime = ID == null ? 0 : 1; + int ivalue = ID == null ? 1 : 2; + int itype = ID == null ? 2 : 3; + while (dataReader.Read()) + { + data.ID = ID == null ? dataReader.GetInt16(0) : ID.Value; + data.TimeStamp = dataReader.GetDateTime(itime); + switch ((DataType)dataReader.GetByte(itype)) + { + case DataType.BOOL: + data.Value.Boolean = dataReader.GetFloat(ivalue) > 0 ? true : false; + break; + case DataType.BYTE: + data.Value.Byte = Convert.ToByte(dataReader.GetFloat(ivalue)); + break; + case DataType.WORD: + case DataType.SHORT: + data.Value.Int16 = Convert.ToInt16(dataReader.GetFloat(ivalue)); + break; + case DataType.INT: + data.Value.Int32 = Convert.ToInt32(dataReader.GetFloat(ivalue)); + break; + case DataType.FLOAT: + data.Value.Single = dataReader.GetFloat(ivalue); + break; + } + yield return data; + } + } + yield break; + } + + private static int BinarySearch(MemoryMappedViewAccessor acc, int length, short value) + { + int i = 0; + int num2 = length - 1; + while (i <= num2) + { + int num3 = i + ((num2 - i) >> 1); + int num4 = acc.ReadInt16(12 + num3 * 11).CompareTo(value); + if (num4 == 0) + { + return num3; + } + if (num4 < 0) + { + i = num3 + 1; + } + else + { + num2 = num3 - 1; + } + } + return -1; + } + + private static int BinarySearchTime(MemoryMappedViewAccessor acc, long offset, int count, int len, int ticks) + { + int i = 0; + int num2 = count - 1; + while (i <= num2) + { + int num3 = i + ((num2 - i) >> 1); + int num4 = acc.ReadInt32(offset + num3 * len).CompareTo(ticks); + if (num4 == 0) + { + return num3; + } + if (num4 < 0) + { + i = num3 + 1; + } + else + { + num2 = num3 - 1; + } + } + return ~i; + } + + public static void SDTCompression(int year, int month, float E = 0.7f) + { + //Stopwatch sw = Stopwatch.StartNew(); + string path = string.Concat(m_Path, "\\", year.ToString(), "-", month.ToString()); + using (FileStream stream = File.Open(path + ".bin", FileMode.Open, FileAccess.Read, FileShare.Read)) + { + using (FileStream outstream = File.Create(path + ".sdt")) + { + outstream.Write(new byte[0x100], 0, 0x100); + BinaryWriter w = new BinaryWriter(outstream); + using (MemoryMappedFile mapp = MemoryMappedFile.CreateFromFile(stream, "map1", stream.Length, + MemoryMappedFileAccess.ReadWrite, HandleInheritability.Inheritable, false)) + { + int days = DateTime.DaysInMonth(year, month); + long[] ps = new long[days + 1]; + long[] ps1 = new long[days + 1]; + long[] sizes = new long[days]; + MemoryMappedViewAccessor acc1 = mapp.CreateViewAccessor(0, 8 * days); + long begin = 0; + ps[0] = acc1.ReadInt64(begin); + for (int i = 0; i < days; i++) + { + begin += 8; + ps[i + 1] = (i == days - 1 ? stream.Length : acc1.ReadInt64(begin)); + sizes[i] = ps[i + 1] - ps[i]; + } + acc1.Dispose(); + for (int i = 0; i < days; i++) + { + if (ps[i] < 0x100 || sizes[i] <= 0) + continue; + using (MemoryMappedViewAccessor acc = mapp.CreateViewAccessor(ps[i], sizes[i])) + { + ps1[i] = outstream.Position; + int len = acc.ReadInt32(8); + int len1 = len * 11 + 12; + HDataFormat[] list = new HDataFormat[len]; + w.Write(acc.ReadInt32(0)); + w.Write(acc.ReadInt32(4)); + w.Write(len); + outstream.Write(new byte[len1 - 12], 0, len1 - 12); + long pos = 12; + int off = 0; + for (int j = 0; j < len; j++) + { + short id; byte type; int count; int offset; + id = acc.ReadInt16(pos); + type = acc.ReadByte(pos + 2); + count = acc.ReadInt32(pos + 3); + offset = acc.ReadInt32(pos + 7); + list[j].ID = id; + list[j].Type = (DataType)type; + list[j].Offset = off;//此处可采取三次到五次抽样得到E和TLM + if (count < 3) + { + long pos2 = len1 + offset; + for (int m = 0; m < count; m++) + { + w.Write(acc.ReadInt32(pos2)); + pos2 += 4; + w.Write(acc.ReadSingle(pos2)); + pos2 += 4; + } + continue; + } + else + { + switch (list[j].Type) + { + case DataType.FLOAT: + { + int crt = 0; int net = 0; + float crv = 0; float nev = 0; + int maxt = 0; int mint = maxt; int sumt = 0; + float minv = 0; float maxv = minv; float sumv = 0; + int old_time = 0; int time = 0; + float mem = 0; float old_mem = 0; + long pp = len1 + offset; + long pos2 = pp + 16; + for (int c = 0; c < 9; c++) + { + crt = acc.ReadInt32(pp); + pp += 4; + crv = acc.ReadSingle(pp); + pp += 4; + if (c > 0) + { + float cv = crv - nev; + int ct = crt - net; + if (c == 1) + { + time = crt; + mem = crv; + maxt = mint = ct; + minv = maxv = cv; + } + else + { + if (cv > maxv) + maxv = cv; + if (cv < minv) + minv = cv; + if (ct > maxt) + maxt = ct; + if (ct < mint) + mint = ct; + } + sumv += cv; + sumt += ct; + } + else + { + old_mem = crv; + old_time = crt; + } + nev = crv; + net = crt; + } + int TLM = (sumt - maxt - mint) / 2; + float E1 = E * (sumv - maxv - minv) / 6; + int sum = 1; + //old_time = now_time = new_time = 0; + float timespan; + w.Write(old_time); + w.Write(old_mem); + float k1, k2, k; + timespan = time - old_time; + k = (mem - old_mem) / timespan; + k1 = k + (E1 / timespan); + k2 = 2 * k - k1; + for (int m = 2; m < count; m++) + { + if (timespan >= TLM || k < k2 || k > k1) + { + ++sum; + w.Write(old_time); + w.Write(old_mem); + k1 = k + (E1 / timespan); + k2 = 2 * k - k1; + } + old_time = time; + old_mem = mem; + time = acc.ReadInt32(pos2); + pos2 += 4; + mem = acc.ReadSingle(pos2); + pos2 += 4; + timespan = time - old_time; + k = (mem - old_mem) / timespan; + } + list[j].Count = sum; + off += sum * 8; + } + break; + case DataType.WORD: + case DataType.SHORT: + { + int crt = 0; int net = 0; + short crv = 0; short nev = 0; + int maxt = 0; int mint = maxt; int sumt = 0; + int minv = 0; int maxv = minv; int sumv = 0; + int old_time = 0; int time = 0; + short mem = 0; short old_mem = 0; + long pp = len1 + offset; + long pos2 = pp + 12; + for (int c = 0; c < 9; c++) + { + crt = acc.ReadInt32(pp); + pp += 4; + crv = acc.ReadInt16(pp); + pp += 2; + if (c > 0) + { + int cv = crv - nev; + int ct = crt - net; + if (c == 1) + { + time = crt; + maxt = mint = ct; + mem = crv; + minv = maxv = cv; + } + else + { + if (cv > maxv) + maxv = cv; + if (cv < minv) + minv = cv; + if (ct > maxt) + maxt = ct; + if (ct < mint) + mint = ct; + } + sumv += cv; + sumt += ct; + } + else + { + old_mem = crv; + old_time = crt; + } + nev = crv; + net = crt; + } + int TLM = (sumt - maxt - mint) / 2; + float E1 = E * (sumv - maxv - minv) / 6; + int sum = 1; + float timespan; + w.Write(old_time); + w.Write(old_mem); + float k1, k2, k; + timespan = time - old_time; + k = (mem - old_mem) / timespan; + k1 = k + (E1 / timespan); + k2 = 2 * k - k1; + for (int m = 2; m < count; m++) + { + if (timespan >= TLM || k < k2 || k > k1) + { + ++sum; + w.Write(old_time); + w.Write(old_mem); + k1 = k + (E1 / timespan); + k2 = 2 * k - k1; + } + old_time = time; + old_mem = mem; + time = acc.ReadInt32(pos2); + pos2 += 4; + mem = acc.ReadInt16(pos2); + pos2 += 2; + timespan = time - old_time; + k = (mem - old_mem) / timespan; + } + + list[j].Count = sum; + off += sum * 8; + } + break; + default: + { + byte[] buffer = new byte[count * dataLen[type]]; + stream.Seek(ps[i] + len1 + offset, SeekOrigin.Begin); + stream.Read(buffer, 0, buffer.Length); + outstream.Write(buffer, 0, buffer.Length); + list[j].Count = count; + off += buffer.Length; + } + break; + } + pos += 11; + } + } + outstream.Seek(ps1[i] + 12, SeekOrigin.Begin); + for (int j = 0; j < len; j++) + { + w.Write(list[j].ID); + w.Write((byte)list[j].Type); + w.Write(list[j].Count); + w.Write(list[j].Offset); + } + ps1[i + 1] = outstream.Seek(0, SeekOrigin.End); + } + + } + outstream.Seek(0, SeekOrigin.Begin); + for (int i = 0; i < days + 1; i++) + { + w.Write(ps1[i]); + } + } + } + } + } + + //遍历两个文件夹下所有历史记录文件;如日期无重复,则复制源路径下文件到目标路径;否则合并到一个文件 + public static unsafe bool Merge(string sourcePath, string targetPath) + { + return true; + } + + public static unsafe ushort ToFloat16(float f) + { + uint* i = (uint*)&f; + uint sign = (*i >> 31) & 0x1; + uint exponent = ((*i >> 23) & 0xff) - 0x7f; + uint mantissa = (*i) & 0x7fffff; + + exponent += 0x7; + uint ret = ((sign & 0x1) << 15); + ret |= ((exponent & 0xf) << 11); + ret |= ((mantissa >> 13) & 0x7ff); + return (ushort)ret; + } + + public static unsafe float ToFloat32(ushort f) + { + ushort* i = (ushort*)&f; + int sign = (*i >> 15) & 0x1; + int exponent = ((*i >> 11) & 0xf) - 0x7; + int mantissa = (*i) & 0x7ff; + + exponent += 0x7f; + int ret = ((sign & 0x1) << 31); + ret |= (exponent & 0xff) << 23; + ret |= (mantissa << 13) & 0x7fffff; + return *((float*)&ret); + } + + + public static float[] Interpolation(float[] dataIn, int n) + { + float[] dataOut = new float[n]; + int lenIn = dataIn.Length; + float[] divOut = new float[n]; + for (int i = 1; i < n; i++) + { + divOut[i] = divOut[i - 1] + lenIn / (float)n; + } + int k = 0; + for (int i = k; i < n; i++) + { + for (int j = 0; j < lenIn - 1; j++) + { + if (divOut[i] >= j && divOut[i] < j + 1) + { + dataOut[i] = (dataIn[j + 1] - dataIn[j]) * (divOut[i] - j) + dataIn[j]; + k = i; + } + } + } + return dataOut; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct HDataFormat + { + public short ID; + public DataType Type; + public int Count; + public int Offset; + + public HDataFormat(short id, DataType type, int count, int offset) + { + ID = id; + Type = type; + Count = count; + Offset = offset; + } + } +} \ No newline at end of file diff --git a/SCADA/Program/CoreApp/DataService/DataHelper/IDataFactory.cs b/SCADA/Program/CoreApp/DataService/DataHelper/IDataFactory.cs new file mode 100644 index 0000000..66260b9 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataHelper/IDataFactory.cs @@ -0,0 +1,32 @@ +using System.Data; +using System.Data.Common; + +namespace DatabaseLib +{ + public interface IDataFactory + { + bool BulkCopy(IDataReader reader, string tableName, string command = null); + void CallException(string message); + bool ConnectionTest(); + DbParameter CreateParam(string paramName, SqlDbType dbType, object objValue, int size = 0, ParameterDirection direction = ParameterDirection.Input); + DataRow ExecuteDataRowProcedure(string ProName, params DbParameter[] ParaName); + DataRowView ExecuteDataRowViewProcedure(string ProName, params DbParameter[] ParaName); + DataSet ExecuteDataset(string SQL); + DataSet ExecuteDataset(string[] SQLs, string[] TableNames); + DataSet ExecuteDataset(string SQL, string TableName); + DataSet ExecuteDataSetProcedure(string ProName, params DbParameter[] ParaName); + DataSet ExecuteDataSetProcedure(string ProName, ref int returnValue, params DbParameter[] ParaName); + DataTable ExecuteDataTable(string SQL); + DataTable ExecuteDataTableProcedure(string ProName, params DbParameter[] ParaName); + DataTable ExecuteDataTableProcedure(string ProName, ref int returnValue, DbParameter[] ParaName); + int ExecuteNonQuery(string[] SQLs); + int ExecuteNonQuery(string SQL); + int ExecuteNonQuery(string[] SQLs, object[][] Pars); + DbDataReader ExecuteProcedureReader(string sSQL, params DbParameter[] ParaName); + DbDataReader ExecuteReader(string sSQL); + object ExecuteScalar(string sSQL); + bool ExecuteStoredProcedure(string ProName); + int ExecuteStoredProcedure(string ProName, params DbParameter[] ParaName); + void FillDataSet(ref DataSet ds, string SQL, string TableName); + } +} \ No newline at end of file diff --git a/SCADA/Program/CoreApp/DataService/DataHelper/MysqlFactory.cs b/SCADA/Program/CoreApp/DataService/DataHelper/MysqlFactory.cs new file mode 100644 index 0000000..45cb93e --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataHelper/MysqlFactory.cs @@ -0,0 +1,778 @@ +using MySql.Data.MySqlClient; +using System; +using System.Data; +using System.Data.Common; +using System.IO; + +namespace DatabaseLib +{ + public class MysqlFactory : IDataFactory + { + public void CallException(string message) + { + DataHelper.AddErrorLog(new Exception(message)); + } + + public bool ConnectionTest() + { + //创建连接对象 + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + //myMySqlConnection.ConnectionTimeout = 1;//设置连接超时的时间 + try + { + //Open DataBase + //打开数据库 + m_Conn.Open(); + if (m_Conn.State == ConnectionState.Open) + { + return true; + } + } + catch (Exception e) + { + CallException(e.Message); + } + } + //myMySqlConnection is a MySqlConnection object + return false; + } + + public MySqlDbType ConvertType(SqlDbType dbType) + { + switch (dbType) + { + case SqlDbType.BigInt: + return MySqlDbType.Int64; + case SqlDbType.Binary: + return MySqlDbType.Binary; + case SqlDbType.Bit: + return MySqlDbType.Bit; + case SqlDbType.Date: + return MySqlDbType.Date; + case SqlDbType.SmallDateTime: + case SqlDbType.DateTime2: + case SqlDbType.DateTime: + return MySqlDbType.DateTime; + case SqlDbType.DateTimeOffset: + return MySqlDbType.Time; + case SqlDbType.Decimal: + return MySqlDbType.Decimal; + case SqlDbType.Float: + return MySqlDbType.Double; + case SqlDbType.Image: + return MySqlDbType.Binary; + case SqlDbType.Int: + return MySqlDbType.Int32; + case SqlDbType.Money: + return MySqlDbType.Float; + case SqlDbType.NText: + case SqlDbType.Text: + return MySqlDbType.Text; + case SqlDbType.Real: + return MySqlDbType.Float; + case SqlDbType.SmallInt: + return MySqlDbType.Int16; + case SqlDbType.Structured: + return MySqlDbType.Set; + case SqlDbType.Time: + return MySqlDbType.Time; + case SqlDbType.Timestamp: + return MySqlDbType.Timestamp; + case SqlDbType.TinyInt: + return MySqlDbType.Byte; + case SqlDbType.VarBinary: + return MySqlDbType.VarBinary; + case SqlDbType.Char: + case SqlDbType.NVarChar: + case SqlDbType.VarChar: + return MySqlDbType.VarChar; + default: + return MySqlDbType.VarChar; + } + } + + public DbParameter CreateParam(string paramName, SqlDbType dbType, object objValue, int size = 0, ParameterDirection direction = ParameterDirection.Input) + { + if (string.IsNullOrEmpty(paramName)) return null; + if (paramName[0] == '@') paramName = 'p' + paramName.TrimStart('@'); + MySqlParameter parameter = new MySqlParameter(paramName, ConvertType(dbType)); + if (size > 0) parameter.Size = size; + if (objValue == null) + { + if (direction == ParameterDirection.Output) + { + parameter.Direction = direction; + return parameter; + } + parameter.IsNullable = true; + parameter.Value = DBNull.Value; + return parameter; + } + parameter.Value = objValue; + return parameter; + } + #region ExecuteDataset //执行查询语句,返回一个记录集 + + /// + /// 返回记录集 + /// + /// 用于返回记录集的SQL语句 + /// 记录集 + public DataSet ExecuteDataset(string SQL) + { + DataSet ds = new DataSet(); + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + MySqlCommand cmd = new MySqlCommand(SQL, m_Conn); + da.SelectCommand = cmd; + da.Fill(ds); + } + catch (Exception e) + { + CallException(SQL + " " + e.Message); + } + } + return ds; + } + + + /// + /// 返回记录集 + /// + /// 用于返回记录集的SQL语句 + /// 映射表名 + /// 记录集 + public DataSet ExecuteDataset(string SQL, string TableName) + { + DataSet ds = new DataSet(); + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + MySqlCommand cmd = new MySqlCommand(SQL, m_Conn); + da.SelectCommand = cmd; + da.Fill(ds, TableName); + } + catch (Exception e) + { + CallException(SQL + " " + e.Message); + } + } + return ds; + } + + /// + /// 返回包含多个表的记录集 + /// + /// 用于返回记录集的SQL语句 + /// 映射表名 + /// 记录集 + + public DataSet ExecuteDataset(string[] SQLs, string[] TableNames) + { + DataSet ds = new DataSet(); + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + for (int i = 0; i < SQLs.Length; i++) + { + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + MySqlCommand cmd = new MySqlCommand(SQLs[i], m_Conn); + da.SelectCommand = cmd; + da.Fill(ds, TableNames[i]); + } + } + catch (Exception e) + { + CallException(SQLs + " " + e.Message); + } + } + return ds; + } + + #endregion ExecuteDataset + + /// + /// 返回表 + /// + /// 用于返回记录集的SQL语句 + /// 记录集 + public DataTable ExecuteDataTable(string SQL) + { + DataTable dt = new DataTable(); + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + MySqlCommand cmd = new MySqlCommand(SQL, m_Conn); + da.SelectCommand = cmd; + da.Fill(dt); + } + catch (Exception e) + { + CallException(SQL + " " + e.Message); + } + } + return dt; + } + + #region ExecuteNonQuery //执行非查询语句 + + /// + /// 执行一条INSERT、UPDATE、DELETE语句 + /// + /// T-SQL语句 + /// 返回影响的行数 + public int ExecuteNonQuery(string SQL) + { + int res = -1; + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + MySqlTransaction sqlT = null; MySqlBulkLoader loader = new MySqlBulkLoader(m_Conn); + //loader.Columns + try + { + using (MySqlCommand cmd = new MySqlCommand(SQL, m_Conn)) + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + cmd.Connection = m_Conn; + sqlT = m_Conn.BeginTransaction(); + cmd.Transaction = sqlT; + res = cmd.ExecuteNonQuery(); + sqlT.Commit(); + } + } + catch (Exception e) + { + if (sqlT != null) + sqlT.Rollback(); + CallException(SQL + " " + e.Message); + return -1; + } + return res; + } + } + + /// + /// 执行一组INSERT、UPDATE、DELETE语句 + /// + /// T-SQL语句 + /// 返回影响的行数 + public int ExecuteNonQuery(string[] SQLs) + { + int res = -1; + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + MySqlTransaction sqlT = null; + MySqlCommand cmd = new MySqlCommand(); + try + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + cmd.Connection = m_Conn; + sqlT = m_Conn.BeginTransaction(); + cmd.Transaction = sqlT; + for (int i = 0; i < SQLs.Length; i++) + { + cmd.CommandText = SQLs[i]; + res = cmd.ExecuteNonQuery(); + } + sqlT.Commit(); + } + catch (Exception e) + { + if (sqlT != null) + sqlT.Rollback(); + CallException(SQLs + " " + e.Message); + res = -1; + } + return res; + } + } + + /// + /// 执行一组INSERT、UPDATE、DELETE语句 + /// + /// T-SQL语句 + /// 执行参数 + /// 返回影响的行数 + public int ExecuteNonQuery(string[] SQLs, object[][] Pars) + { + int res = -1; + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + MySqlTransaction sqlT = null; + MySqlCommand cmd = new MySqlCommand(); + try + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + cmd.Connection = m_Conn; + sqlT = m_Conn.BeginTransaction(); + cmd.Transaction = sqlT; + for (int i = 0; i < SQLs.Length; i++) + { + cmd.CommandText = SQLs[i]; + cmd.Parameters.Clear(); + for (int j = 0; j < Pars[i].Length; j++) + { + cmd.Parameters.AddWithValue("@p" + j.ToString(), Pars[i][j]); + } + res = cmd.ExecuteNonQuery(); + } + sqlT.Commit(); + } + catch (Exception e) + { + if (sqlT != null) + sqlT.Rollback(); + CallException(SQLs + " " + e.Message); + res = -1; + } + return res; + } + } + + #endregion ExecuteNonQuery + + #region FillDataSet //填充一个记录集 + + /// + /// 用指定的SQL语句来填充一个记录集 + /// + /// 记录集 + /// SELECT语句 + /// 映射表名 + public void FillDataSet(ref DataSet ds, string SQL, string TableName) + { + try + { + MySqlConnection m_Conn; + m_Conn = new MySqlConnection(DataHelper.ConnectString); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + MySqlCommand cmd = new MySqlCommand(SQL, m_Conn); + da.SelectCommand = cmd; + da.Fill(ds, TableName); + } + catch (Exception e) + { + CallException(SQL + " " + e.Message); + } + } + + #endregion FillDataSet + + #region + // + /// 返回一个MySqlDataReader + /// + public DbDataReader ExecuteReader(string sSQL) + { + MySqlConnection connection = new MySqlConnection(DataHelper.ConnectString); + MySqlCommand command = new MySqlCommand(sSQL, connection); + if (connection.State == ConnectionState.Closed) + connection.Open(); + return command.ExecuteReader(CommandBehavior.CloseConnection); + } + + + public DbDataReader ExecuteProcedureReader(string sSQL, params DbParameter[] ParaName) + { + MySqlConnection connection = new MySqlConnection(DataHelper.ConnectString); + MySqlCommand command = new MySqlCommand(sSQL, connection); + command.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + command.Parameters.AddRange(ParaName); + } + try + { + if (connection.State == ConnectionState.Closed) + connection.Open(); + return command.ExecuteReader(CommandBehavior.CloseConnection); + } + catch (Exception e) + { + CallException(sSQL + " " + e.Message); + return null; + } + } + #endregion + + #region ExecuteScalar //执行查询,并返回查询所返回的结果集中第一行的第一列 + + /// + /// 执行查询,并返回查询所返回的结果集中第一行的第一列 + /// + /// SQL语句 + /// + public object ExecuteScalar(string sSQL) + { + MySqlTransaction sqlT = null; + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + MySqlCommand cmd = new MySqlCommand(sSQL, m_Conn); + try + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + sqlT = m_Conn.BeginTransaction(); + cmd.Transaction = sqlT; + var res = cmd.ExecuteScalar(); + sqlT.Commit(); + if (res == DBNull.Value) res = null; + return res; + } + catch (Exception e) + { + if (sqlT != null) + sqlT.Rollback(); + CallException(sSQL + " " + e.Message); + return null; + } + } + } + + #endregion ExecuteScalar + + #region ExecuteStoredProcedure //执行一个存储过程 + + /// + /// 执行一个带参数的存储过程 + /// + /// 存储过程名 + /// 参数名称 + /// 参数方向,Input参数是输入参数 InputOutput参数既能输入,也能输出 Output参数是输出参数 ReturnValue参数存储过程返回值。 + /// 参数对象数组 + /// 成功返回true,失败返回false + public int ExecuteStoredProcedure(string ProName, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn) + { + CommandType = CommandType.StoredProcedure + }; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + MySqlParameter param = new MySqlParameter(); + cmd.Parameters.Add(param); + param.Direction = ParameterDirection.ReturnValue; + if (m_Conn.State == ConnectionState.Closed) + { + m_Conn.Open(); + } + cmd.ExecuteNonQuery(); + return (int)param.Value; + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return -1; + } + } + } + + /// + /// 执行一个没有参数和返回值的存储过程(默认参数类型) + /// + /// 存储过程名 + /// 参数对象数组 + /// 成功返回true,失败返回false + public bool ExecuteStoredProcedure(string ProName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + cmd.ExecuteNonQuery(); + return true; + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return false; + } + } + } + + /// + /// 执行一个带参数的存储过程,并返回数据集 + /// + /// 存储过程名 + /// 参数名称 + /// 参数对象数组 + /// 执行过程中返回的数据集 + /// 成功返回true,失败返回false + public DataSet ExecuteDataSetProcedure(string ProName, ref int returnValue, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + DataSet ds = new DataSet(); + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + MySqlParameter param = new MySqlParameter { Direction = ParameterDirection.ReturnValue }; + cmd.Parameters.Add(param); + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(ds); + returnValue = (int)param.Value; + return ds; + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + public DataSet ExecuteDataSetProcedure(string ProName, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + DataSet ds = new DataSet(); + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(ds); + return ds; + } + + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + /// + /// 执行一个带参数的存储过程,并返回数据集 + /// + /// 存储过程名 + /// 参数名称 + /// 参数对象数组 + /// 执行过程中返回的数据集 + /// 成功返回true,失败返回false + /// + public DataTable ExecuteDataTableProcedure(string ProName, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + DataTable ds = new DataTable(); + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(ds); + return ds; + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + public DataTable ExecuteDataTableProcedure(string ProName, ref int returnValue, DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + DataTable ds = new DataTable(); + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + MySqlParameter param = new MySqlParameter { Direction = ParameterDirection.ReturnValue }; + cmd.Parameters.Add(param); + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(ds); + returnValue = (int)param.Value; + return ds; + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + /// + /// 执行一个带参数的存储过程,同时输出一行 + /// + /// 存储过程名 + /// 参数名称 + /// 参数对象数组 + /// 返回整数 + public DataRow ExecuteDataRowProcedure(string ProName, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + DataTable table = new DataTable(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(table); + if (table.Rows.Count > 0) + return table.Rows[0]; + else + return table.NewRow(); + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + /// + /// 执行一个带参数的存储过程,同时输出一行 + /// + /// 存储过程名 + /// 参数名称 + /// 参数对象数组 + /// 返回整数 + public DataRowView ExecuteDataRowViewProcedure(string ProName, params DbParameter[] ParaName) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + try + { + MySqlCommand cmd = new MySqlCommand(ProName, m_Conn); + cmd.CommandType = CommandType.StoredProcedure; + if (ParaName != null) + { + cmd.Parameters.AddRange(ParaName); + } + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + DataTable table = new DataTable(); + var da = MySqlClientFactory.Instance.CreateDataAdapter(); + da.SelectCommand = cmd; + da.Fill(table); + if (table.Rows.Count > 0) + return table.DefaultView[0]; + else + return table.DefaultView.AddNew(); + } + catch (Exception e) + { + CallException(ProName + " " + e.Message); + return null; + } + } + } + + public bool BulkCopy(IDataReader reader, string tableName, string command = null) + { + using (MySqlConnection m_Conn = new MySqlConnection(DataHelper.ConnectString)) + { + MySqlTransaction sqlT = null; + try + { + if (m_Conn.State == ConnectionState.Closed) + m_Conn.Open(); + sqlT = m_Conn.BeginTransaction(); + if (!string.IsNullOrEmpty(command)) + { + MySqlCommand cmd = new MySqlCommand(command, m_Conn); + cmd.Transaction = sqlT; + cmd.ExecuteNonQuery(); + } + string tmpPath = Path.GetTempFileName(); + string csv = DataHelper.ReaderToCsv(reader); + File.WriteAllText(tmpPath, csv); + MySqlBulkLoader copy = new MySqlBulkLoader(m_Conn) + { + FieldTerminator = ",", + FieldQuotationCharacter = '"', + EscapeCharacter = '"', + LineTerminator = "\r\n", + FileName = tmpPath, + NumberOfLinesToSkip = 0, + TableName = tableName, + }; + //copy.BatchSize = _capacity; + copy.Load();//如果写入失败,考虑不能无限增加线程数 + //Clear(); + sqlT.Commit(); + m_Conn.Close(); + File.Delete(tmpPath); + return true; + } + catch (Exception e) + { + if (sqlT != null) + sqlT.Rollback(); + m_Conn.Close(); + DataHelper.AddErrorLog(e); + return false; + } + } + } + + + #endregion ExecuteStoredProcedure + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService.sln b/SCADA/Program/CoreApp/DataService/DataService.sln new file mode 100644 index 0000000..f950486 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2005 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataService", "DataService\DataService.csproj", "{46BF803C-F153-4222-8FFC-9AC957F34EA5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GateWay", "GateWay\GateWay.csproj", "{99BEC8C4-2684-4BFD-897A-2340B6963120}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataHelper", "DataHelper\DataHelper.csproj", "{EE99C592-F4F2-4152-94CC-564ABA066C20}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModbusDriver", "ModbusDriver\ModbusDriver.csproj", "{79CDC563-AA30-48CA-8913-32454EDE76FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientDriver", "ClientDriver\ClientDriver.csproj", "{3D10E811-46FE-4EE7-A268-8B50C3B2D08F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Debug|x86.ActiveCfg = Debug|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Debug|x86.Build.0 = Debug|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Release|Any CPU.Build.0 = Release|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Release|x86.ActiveCfg = Release|Any CPU + {46BF803C-F153-4222-8FFC-9AC957F34EA5}.Release|x86.Build.0 = Release|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Debug|x86.ActiveCfg = Debug|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Debug|x86.Build.0 = Debug|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Release|Any CPU.Build.0 = Release|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Release|x86.ActiveCfg = Release|Any CPU + {99BEC8C4-2684-4BFD-897A-2340B6963120}.Release|x86.Build.0 = Release|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Debug|x86.Build.0 = Debug|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Release|Any CPU.Build.0 = Release|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Release|x86.ActiveCfg = Release|Any CPU + {EE99C592-F4F2-4152-94CC-564ABA066C20}.Release|x86.Build.0 = Release|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Debug|x86.Build.0 = Debug|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Release|Any CPU.Build.0 = Release|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Release|x86.ActiveCfg = Release|Any CPU + {79CDC563-AA30-48CA-8913-32454EDE76FA}.Release|x86.Build.0 = Release|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Debug|x86.Build.0 = Debug|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Release|Any CPU.Build.0 = Release|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Release|x86.ActiveCfg = Release|Any CPU + {3D10E811-46FE-4EE7-A268-8B50C3B2D08F}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {06245D70-C147-4613-91E1-E3990B520C63} + EndGlobalSection +EndGlobal diff --git a/SCADA/Program/CoreApp/DataService/DataService/Alarm.cs b/SCADA/Program/CoreApp/DataService/DataService/Alarm.cs new file mode 100644 index 0000000..523fadc --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/Alarm.cs @@ -0,0 +1,223 @@ +using System; +using System.ComponentModel; + +namespace DataService +{ + + public class AlarmItem : IComparable, INotifyPropertyChanged + { + int _condiId; + + Severity _severity; + SubAlarmType _alarmType; + DateTime _startTime; + TimeSpan _duration; + object _alarmValue; + string _alarmText; + string _source; + + public SubAlarmType SubAlarmType + { + get + { + return _alarmType; + } + set + { + _alarmType = value; + } + } + + public Severity Severity + { + get + { + return _severity; + } + set + { + _severity = value; + } + } + + public DateTime StartTime + { + get + { + return _startTime; + } + set + { + _startTime = value; + } + } + + public int ConditionId + { + get + { + return _condiId; + } + set + { + _condiId = value; + } + } + + public TimeSpan Duration + { + get + { + //return _endTime-_startTime; + return _duration; + } + set + { + _duration = value; + OnPropertyChanged("Duration"); + } + } + + public object AlarmValue + { + get + { + return _alarmValue; + } + set + { + _alarmValue = value; + } + } + + public string AlarmText + { + get + { + return _alarmText; + } + set + { + _alarmText = value; + } + } + + public string Source + { + get + { + return _source; + } + set + { + _source = value; + } + } + + public AlarmItem(DateTime time, string alarmText, object alarmValue, SubAlarmType type, Severity severity, int condId, string source) + { + this._startTime = time; + this._alarmType = type; + this._alarmText = alarmText; + this._alarmValue = alarmValue; + this._severity = severity; + this._condiId = condId; + this._source = source; + } + + public AlarmItem() + { + this._startTime = DateTime.Now; + this._alarmType = SubAlarmType.None; + this._alarmText = string.Empty; + this._severity = Severity.Normal; + this._condiId = -1; + this._source = string.Empty; + } + + #region IComparable Members + + public int CompareTo(AlarmItem other) + { + return this._startTime.CompareTo(other._startTime); + } + + #endregion + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged(string propertyName) + { + if (this.PropertyChanged != null) + { + + this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + } + + [Flags] + public enum AlarmType + { + None = 0, + Level = 1, + Dev = 2, + Dsc = 4, + ROC = 8, + Quality = 16, + Complex = 32, + WordDsc = 64 + } + + [Flags] + public enum SubAlarmType + { + None = 0, + LoLo = 1, + Low = 2, + High = 4, + HiHi = 8, + MajDev = 16, + MinDev = 32, + Dsc = 64, + + BadPV = 128, + MajROC = 256, + MinROC = 512 + } + + public enum Severity + { + Error = 7, + High = 6, + MediumHigh = 5, + Medium = 4, + MediumLow = 3, + Low = 2, + Information = 1, + Normal = 0 + } + + [Flags] + public enum ConditionState : byte + { + Acked = 4, + Actived = 2, + Enabled = 1 + } + + public enum EventType : byte + { + Simple = 1, + TraceEvent = 2, + ConditionEvent = 4, + } + + public enum ConditionType : byte + { + Absolute = 0, + Percent = 1 + } + +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/CacheReader.cs b/SCADA/Program/CoreApp/DataService/DataService/CacheReader.cs new file mode 100644 index 0000000..ac64220 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/CacheReader.cs @@ -0,0 +1,910 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; +using System.Text; + +namespace DataService +{ + public sealed class ByteCacheReader : ICache + { + byte[] _cache; + public Array Cache { get { return _cache; } } + + public int ByteCount + { + get { return 1; } + } + + int _size; + public int Size + { + get { return _size; } + set { _size = value; this._cache = new byte[_size]; } + } + + public ByteCacheReader() { } + + public ByteCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + return new ItemData(BitConverter.ToInt32(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadBit(DeviceAddress address) + { + return new ItemData((_cache[address.CacheIndex] & (1 << address.Bit)) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData(BitConverter.ToInt16(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData(_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size = 0xFF) + { + return new ItemData(Encoding.ASCII.GetString(_cache, address.CacheIndex, size), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadFloat(DeviceAddress address) + { + return new ItemData(BitConverter.ToSingle(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] |= (byte)(1 << address.Bit); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt16((IntPtr)(p1 + address.CacheIndex), value); + } + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), value); + } + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), *(int*)&value); + } + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Array.Copy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[size]; + Array.Copy(_cache, address.CacheIndex, bytes, 0, size); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Array.Copy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + } + + public sealed class NetByteCacheReader : ICache + { + byte[] _cache; + public Array Cache { get { return _cache; } } + + public int ByteCount + { + get { return 1; } + } + + int _size; + public int Size + { + get { return _size; } + set { _size = value; this._cache = new byte[_size]; } + } + + public NetByteCacheReader() { } + + public NetByteCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + return new ItemData(Utility.NetToInt32(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadBit(DeviceAddress address) + { + return new ItemData((_cache[address.CacheIndex] & (1 << address.Bit)) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData(Utility.NetToInt16(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData(_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size = 0xFF) + { + return new ItemData(Utility.ConvertToString(_cache, address.CacheIndex, size), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadFloat(DeviceAddress address) + { + return new ItemData(Utility.NetToSingle(_cache, address.CacheIndex), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] |= (byte)(1 << address.Bit); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt16((IntPtr)(p1 + address.CacheIndex), IPAddress.HostToNetworkOrder(value)); + } + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), IPAddress.HostToNetworkOrder(value)); + } + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + fixed (byte* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), IPAddress.HostToNetworkOrder(*(int*)(&value))); + } + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Array.Copy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[size]; + Array.Copy(_cache, address.CacheIndex, bytes, 0, size); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Array.Copy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + + } + + public sealed class ShortCacheReader : ICache + { + short[] _cache; + public Array Cache + { + get + { + return _cache; + } + } + + public int ByteCount + { + get { return 2; } + } + + int _size; + public int Size + { + get + { + return _size; + } + set + { + _size = value; + this._cache = new short[_size]; + } + } + + public ShortCacheReader() + { + } + + public ShortCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + int startIndex = address.CacheIndex; + int result; + if (startIndex == _cache.Length - 1) + { + result = _cache[startIndex]; + } + else + { + result = (_cache[startIndex + 1] << 16) | ((ushort)_cache[startIndex]); + } + return new ItemData(result, 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadBit(DeviceAddress address) + { + return new ItemData((_cache[address.CacheIndex] & (1 << address.Bit)) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData(_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData((byte)_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + byte[] buffer = new byte[size]; + Buffer.BlockCopy(_cache, 2 * address.CacheIndex, buffer, 0, size); + return new ItemData(Encoding.ASCII.GetString(buffer).Trim(), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + int startIndex = address.CacheIndex; + int result; + if (startIndex == _cache.Length - 1) + { + result = _cache[startIndex]; + } + else + { + result = (_cache[startIndex] << 16) | ((ushort)_cache[startIndex + 1]); + } + return new ItemData(*(((float*)&result)), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] |= (short)(1 << address.Bit); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + fixed (short* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), value); + } + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + fixed (short* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), *(int*)&value); + } + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Buffer.BlockCopy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[2 * size]; + Buffer.BlockCopy(_cache, address.CacheIndex, bytes, 0, bytes.Length); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Buffer.BlockCopy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + } + + public sealed class NetShortCacheReader : ICache + { + short[] _cache; + public Array Cache + { + get + { + return _cache; + } + } + + public int ByteCount + { + get { return 2; } + } + + int _size; + public int Size + { + get + { + return _size; + } + set + { + _size = value; + this._cache = new short[_size]; + } + } + + public NetShortCacheReader() + { + } + + public NetShortCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + int startIndex = address.CacheIndex; + int result; + if (startIndex == _cache.Length - 1) + { + result = _cache[startIndex]; + } + else + { + result = (IPAddress.HostToNetworkOrder(_cache[startIndex]) << 16) | ((ushort)IPAddress.HostToNetworkOrder(_cache[startIndex + 1])); + } + return new ItemData(result, 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadBit(DeviceAddress address) + { + return new ItemData((_cache[address.CacheIndex] & (1 << address.Bit.BitSwap())) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData(IPAddress.HostToNetworkOrder(_cache[address.CacheIndex]), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData((byte)IPAddress.HostToNetworkOrder(_cache[address.CacheIndex]), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadString(DeviceAddress address, ushort size) + { + short[] sarray = new short[size / 2]; + int index = address.CacheIndex; + for (int i = 0; i < sarray.Length; i++) + { + sarray[i] = IPAddress.HostToNetworkOrder(_cache[index + i]); + } + byte[] buffer = new byte[size]; + Buffer.BlockCopy(sarray, 0, buffer, 0, size); + return new ItemData(Encoding.ASCII.GetString(buffer).Trim(), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + int startIndex = address.CacheIndex; + int result; + if (startIndex == _cache.Length - 1) + { + result = _cache[startIndex]; + } + else + { + result = (IPAddress.HostToNetworkOrder(_cache[startIndex]) << 16) | ((ushort)IPAddress.HostToNetworkOrder(_cache[startIndex + 1])); + } + return new ItemData(*(((float*)&result)), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] |= (short)(1 << address.Bit.BitSwap()); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + fixed (short* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), IPAddress.HostToNetworkOrder(value)); + } + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + fixed (short* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), IPAddress.HostToNetworkOrder(*(int*)&value)); + } + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Buffer.BlockCopy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[2 * size]; + Buffer.BlockCopy(_cache, address.CacheIndex, bytes, 0, bytes.Length); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Buffer.BlockCopy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + } + + public sealed class IntCacheReader : ICache + { + int[] _cache; + + public Array Cache + { + get + { + return _cache; + } + } + + public int ByteCount + { + get { return 4; } + } + + int _size; + public int Size + { + get + { + return _size; + } + set + { + _size = value; + this._cache = new int[_size]; + } + } + + public IntCacheReader() + { + } + + public IntCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + + return new ItemData(_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadBit(DeviceAddress address) + { + return new ItemData((_cache[address.CacheIndex] & (1 << address.Bit)) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData((short)(_cache[address.CacheIndex]), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData((byte)_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + byte[] buffer = new byte[size]; + Buffer.BlockCopy(_cache, 4 * address.CacheIndex, buffer, 0, size); + return new ItemData(Encoding.ASCII.GetString(buffer).Trim(), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + int result = _cache[address.CacheIndex]; + return new ItemData(*(((float*)&result)), 0, QUALITIES.QUALITY_GOOD);//强制将4字节转换为浮点格式 + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] |= (1 << address.Bit); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + fixed (int* p1 = _cache) + { + Marshal.WriteInt32((IntPtr)(p1 + address.CacheIndex), *(int*)&value); + } + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Buffer.BlockCopy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[4 * size]; + Buffer.BlockCopy(_cache, address.CacheIndex, bytes, 0, bytes.Length); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Buffer.BlockCopy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + } + + public sealed class FloatCacheReader : ICache + { + float[] _cache; + + public Array Cache + { + get + { + return _cache; + } + } + + public int ByteCount + { + get { return 4; } + } + + int _size; + public int Size + { + get + { + return _size; + } + set + { + _size = value; + this._cache = new float[_size]; + } + } + + public FloatCacheReader() + { + } + + public FloatCacheReader(int size) + { + this.Size = size; + } + + public int GetOffset(DeviceAddress start, DeviceAddress end) + { + return start.Area == end.Area && start.DBNumber == end.DBNumber ? start.Start - end.Start : ushort.MaxValue; + } + + public ItemData ReadInt32(DeviceAddress address) + { + + return new ItemData((int)_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadBit(DeviceAddress address) + { + return new ItemData(((int)_cache[address.CacheIndex] & (1 << address.Bit)) != 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + return new ItemData((short)(_cache[address.CacheIndex]), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + return new ItemData((byte)_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + byte[] buffer = new byte[size]; + Buffer.BlockCopy(_cache, 4 * address.CacheIndex, buffer, 0, size); + return new ItemData(Encoding.ASCII.GetString(buffer).Trim(), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + return new ItemData(_cache[address.CacheIndex], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBit(DeviceAddress address, bool bit) + { + _cache[address.CacheIndex] = (int)_cache[address.CacheIndex] | (1 << address.Bit); + return 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + _cache[address.CacheIndex] = bits; + return 0; + } + + public unsafe int WriteInt16(DeviceAddress address, short value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteInt32(DeviceAddress address, int value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public unsafe int WriteFloat(DeviceAddress address, float value) + { + _cache[address.CacheIndex] = value; + return 0; + } + + public int WriteString(DeviceAddress address, string str) + { + byte[] b = Encoding.ASCII.GetBytes(str); + int index = address.CacheIndex; + Buffer.BlockCopy(_cache, index, b, 0, b.Length); + return 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + byte[] bytes = new byte[4 * size]; + Buffer.BlockCopy(_cache, address.CacheIndex, bytes, 0, bytes.Length); + return bytes; + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (bit != null && bit.Length > 0) + { + Buffer.BlockCopy(bit, 0, _cache, address.CacheIndex, bit.Length); + return 0; + } + return -1; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/ClientReader.cs b/SCADA/Program/CoreApp/DataService/DataService/ClientReader.cs new file mode 100644 index 0000000..1b1c7bb --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/ClientReader.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Net; +using System.Text; +using System.Threading; + +namespace DataService +{ + public class ClientReader : IDevice + { + string _ip; + public string ServerName + { + get { return _ip; } + } + + internal Socket tcpSynCl; + internal Socket tcpASynCl; + + public bool IsClosed + { + get + { + //return tcpASynCl.Poll(-1, SelectMode.SelectRead); + return !tcpSynCl.Connected || !tcpASynCl.Connected; + } + } + + private ushort _timeout = 0; + public int TimeOut + { + get { return _timeout; } + } + + + List _grps = new List(1); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + + public ClientReader(IDataServer server, string ip) + { + _server = server; + _ip = ip; + } + + public bool Connect() + { + try + { + int port = 1000; + IPAddress ip = IPAddress.Parse(_ip); + tcpASynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpASynCl.Connect(new IPEndPoint(ip, port)); + tcpASynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout); + tcpASynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout); + + tcpSynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpSynCl.Connect(new IPEndPoint(ip, port)); + tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, _timeout); + tcpSynCl.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _timeout); + return true; + } + catch (SocketException error) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + return false; + } + } + + public IGroup AddGroup(string name, ushort id, int updateRate, int timeOut = 0, float deadBand = 0f, bool active = false) + { + ClientGroup grp = new ClientGroup(id, name, updateRate, active, this); + _grps.Add(grp); + return grp; + } + + public int RemoveAllGroup() + { + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + return 1; + } + + public event ShutdownRequestEventHandler OnClose; + + public void Dispose() + { + if (tcpSynCl != null) + { + if (tcpSynCl.Connected) + { + try + { + tcpASynCl.Shutdown(SocketShutdown.Both); + tcpSynCl.Shutdown(SocketShutdown.Both); + } + catch { } + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs("SHUTDOWN")); + tcpSynCl.Close(); + tcpASynCl.Close(); + } + tcpSynCl = null; + tcpASynCl = null; + } + RemoveAllGroup(); + } + } + + public class ClientGroup : IGroup + { + public const byte fctHead = 0xAB; + public const byte fctReadSingle = 1; + public const byte fctReadMultiple = 2; + public const byte fctWriteSingle = 5; + public const byte fctWriteMultiple = 15; + + bool _active = false; + public bool IsActive + { + get + { + return _active; + } + set + { + _active = value; + if (value) + { + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(ReciveData), _tcpASynCl); + } + } + } + + protected ushort _id; + public ushort ID + { + get + { + return _id; + } + } + + protected int _updateRate; + public int UpdateRate + { + get + { + return _updateRate; + } + set + { + _updateRate = value; + } + } + + protected DeviceAddress _start; + public DeviceAddress Start + { + get + { + return _start; + } + } + + public int Size + { + get + { + return _items == null ? 0 : _items.Length; + } + } + + protected string _name; + public string Name + { + get + { + return _name; + } + } + + protected float _deadband; + public float DeadBand + { + get + { + return _deadband; + } + set + { + _deadband = value; + } + } + + protected ClientReader _plcReader; + public IDevice Parent + { + get + { + return _plcReader; + } + } + + protected ITag[] _items; + public IEnumerable Items + { + get { return _items; } + } + + IDataServer _server; + Socket _tcpSynCl, _tcpASynCl; + + byte[] tcpSynClBuffer; + + public ClientGroup(ushort id, string name, int updateRate, bool active, ClientReader plcReader) + { + this._id = id; + this._name = name; + this._updateRate = updateRate; + this._active = active; + this._plcReader = plcReader; + this._server = plcReader.Parent; + this._tcpASynCl = plcReader.tcpASynCl; + this._tcpSynCl = plcReader.tcpASynCl; + tcpSynClBuffer = new byte[_tcpASynCl.ReceiveBufferSize]; + } + + private byte[] ReadSingleData(DeviceAddress address, DataSource source = DataSource.Cache) + { + short ID = (short)address.Start; + byte type = (byte)address.VarType; + byte[] idbits = BitConverter.GetBytes(ID); + byte[] write_data = new byte[6] { fctHead, fctReadSingle, + source == DataSource.Cache?(byte)0:(byte)1, idbits[0], idbits[1], type }; + byte[] data = new byte[type < 4 ? 1 : type < 6 ? 2 : 4]; + SocketError error; + _tcpSynCl.Send(write_data, 0, 6, SocketFlags.None, out error); + int result = _tcpSynCl.Receive(tcpSynClBuffer, 0, data.Length + 3, SocketFlags.None, out error); + Array.Copy(tcpSynClBuffer, 3, data, 0, data.Length); + if (error == SocketError.Success) + return data; + else + { + throw new SocketException((int)error); + } + } + + + private int WriteSingleData(DeviceAddress address, byte[] value) + { + short ID = (short)address.Start; + byte type = (byte)address.VarType; + byte[] idbits = BitConverter.GetBytes(ID); + byte[] write_data = new byte[6] { fctHead, fctWriteSingle, 1, idbits[0], idbits[1], type }; + byte[] data = new byte[6 + value.Length]; + write_data.CopyTo(data, 0); + value.CopyTo(data, 6); + SocketError error; + _tcpSynCl.Send(data, 0, data.Length, SocketFlags.None, out error); + int result = _tcpSynCl.Receive(tcpSynClBuffer, 0, 2, SocketFlags.None, out error); + if (error == SocketError.Success) + return tcpSynClBuffer[1]; + else + { + throw new SocketException((int)error); + } + } + + + public void Init() + { + if (_items != null) + { + for (int i = 0; i < _items.Length; i++) + { + _items[i].Value = _items[i].Read(DataSource.Cache);//DataSource.Device + } + } + } + + private void ReciveData(object state) + { + if (state == null || !_active) return; + byte[] bytes = new byte[_tcpASynCl.ReceiveBufferSize]; + int result = 0; + SocketError error; + do + { + result = _tcpASynCl.Receive(bytes, 0, bytes.Length, SocketFlags.None, out error); + if (result > 5 && bytes[0] == 0xAB) + { + short len = BitConverter.ToInt16(bytes, 1); + short count = BitConverter.ToInt16(bytes, 3); + int j = 5; + DateTime time = DateTime.UtcNow; + Storage value = Storage.Empty; + for (int i = 0; i < count; i++) + { + short id = BitConverter.ToInt16(bytes, j); + j += 2; + ITag tag = GetItemByID(id); + if (tag != null) + { + DataType type = (DataType)bytes[j++]; + switch (type) + { + case DataType.BOOL: + value.Boolean = BitConverter.ToBoolean(bytes, j++); + break; + case DataType.BYTE: + value.Byte = bytes[j++]; + break; + case DataType.SHORT: + value.Int16 = BitConverter.ToInt16(bytes, j); + j += 2; + break; + case DataType.INT: + value.Int32 = BitConverter.ToInt32(bytes, j); + j += 4; + break; + case DataType.FLOAT: + value.Single = BitConverter.ToSingle(bytes, j); + j += 4; + break; + } + tag.Update(value, time, QUALITIES.QUALITY_GOOD); + } + else + { + byte type = bytes[j]; + j += (type < 4 ? 2 : type < 6 ? 3 : 5); + } + } + //Array.Clear(bytes, 0, count); + } + } + while (result > 0); + } + + public bool AddItems(ItemMetaData[] items) + { + int count = items.Length; + if (_items == null) _items = new ITag[count]; + for (int i = 0; i < count; i++) + { + ITag dataItem = null; + ItemMetaData meta = items[i]; + DeviceAddress addr = new DeviceAddress(0, 0, meta.ID, meta.Size, 0, meta.DataType); + switch (meta.DataType) + { + case DataType.BOOL: + dataItem = new BoolTag(meta.ID, addr, this); + break; + case DataType.BYTE: + dataItem = new ByteTag(meta.ID, addr, this); + break; + case DataType.WORD: + case DataType.SHORT: + dataItem = new ShortTag(meta.ID, addr, this); + break; + case DataType.TIME: + case DataType.INT: + dataItem = new IntTag(meta.ID, addr, this); + break; + case DataType.FLOAT: + dataItem = new FloatTag(meta.ID, addr, this); + break; + case DataType.STR: + dataItem = new StringTag(meta.ID, addr, this); + break; + default: + dataItem = new BoolTag(meta.ID, addr, this); + break; + } + _items[i] = dataItem; + _server.AddItemIndex(meta.Name, dataItem); + } + Array.Sort(_items); + Init(); + return true; + } + + public bool RemoveAll() + { + Array.Clear(_items, 0, _items.Length); + return true; + } + + public bool SetActiveState(bool active, params short[] items) + { + return true; + } + + public int FindItemByAddress(DeviceAddress addr) + { + return Array.BinarySearch(_items, new BoolTag(0, addr, null)); + } + + public ITag GetItemByID(short id) + { + return _server[id]; + } + + public int BatchRead(DataSource source, bool isSync, params ITag[] itemArray) + { + if (itemArray == null) return -1; + int len = itemArray.Length; + byte[] bt = new byte[4]; + byte[] data = new byte[3 + len * 2]; + int j=0; + data[j++] = fctHead; + data[j++] = fctReadMultiple; + data[j++] = source == DataSource.Cache ? (byte)0 : (byte)1; + bt = BitConverter.GetBytes(itemArray.Length); + data[j++] = bt[0]; + data[j++] = bt[1]; + data[j++] = bt[2]; + data[j++] = bt[3]; + for (int i = 0; i < len; i++) + { + ITag tag = itemArray[i]; + bt = BitConverter.GetBytes(tag.ID); + data[j++] = bt[0]; + data[j++] = bt[1]; + data[j++] = (byte)(tag.Address.DataSize >> 3); + } + SocketError error; + _tcpSynCl.Send(data, 0, data.Length, SocketFlags.None, out error); + int result = _tcpSynCl.Receive(tcpSynClBuffer, 0, tcpSynClBuffer.Length, SocketFlags.None, out error); + j = 2; + if (error == SocketError.Success) + { + DateTime time=DateTime.UtcNow; + Storage value=Storage.Empty; + for (int i = 0; i < len; i++) + { + ITag tag = itemArray[i]; + switch (tag.Address.VarType) + { + case DataType.BOOL: + value.Boolean = BitConverter.ToBoolean(tcpSynClBuffer, j++); + break; + case DataType.BYTE: + value.Byte = tcpSynClBuffer[j++]; + break; + case DataType.SHORT: + value.Int16 = BitConverter.ToInt16(tcpSynClBuffer, j); + j += 2; + break; + case DataType.INT: + value.Int32 = BitConverter.ToInt32(tcpSynClBuffer, j); + j += 4; + break; + case DataType.FLOAT: + value.Single = BitConverter.ToSingle(tcpSynClBuffer, j); + j += 4; + break; + } + tag.Update(value, time, QUALITIES.QUALITY_GOOD); + } + return 0; + } + else + { + throw new SocketException((int)error); + } + } + + public int BatchWrite(IDictionary items, bool isSync = true) + { + List list = new List(new byte[] { fctHead, fctWriteMultiple }); + list.AddRange(BitConverter.GetBytes(items.Count)); + foreach (var item in items) + { + ITag tag = item.Key; + list.AddRange(BitConverter.GetBytes(tag.ID)); + switch (tag.Address.VarType) + { + case DataType.BOOL: + list.Add((bool)item.Value ? (byte)1 : (byte)0); + break; + case DataType.BYTE: + list.Add((byte)item.Value); + break; + case DataType.SHORT: + list.AddRange(BitConverter.GetBytes((short)item.Value)); + break; + case DataType.INT: + list.AddRange(BitConverter.GetBytes((int)item.Value)); + break; + case DataType.FLOAT: + list.AddRange(BitConverter.GetBytes((float)item.Value)); + break; + } + } + SocketError error; + _tcpSynCl.Send(list.ToArray(), 0, list.Count, SocketFlags.None, out error); + int result = _tcpSynCl.Receive(tcpSynClBuffer, 0, 2, SocketFlags.None, out error); + if (error == SocketError.Success) + return tcpSynClBuffer[1]; + else + { + throw new SocketException((int)error); + } + } + + + public ItemData ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt32(data, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt16(data, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(data[0], 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + if (data == null) + return new ItemData(0.0f, 0, QUALITIES.QUALITY_BAD); + else + { + int value = BitConverter.ToInt32(data, 0); + return new ItemData(*(((float*)&value)), 0, QUALITIES.QUALITY_GOOD); + } + } + + public ItemData ReadBool(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(false, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToBoolean(data, address.Bit), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, DataSource source = DataSource.Cache) + { + var data = ReadSingleData(address, source); + return data == null ? new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD) : + new ItemData(Encoding.Default.GetString(data, 0, address.DataSize), 0, QUALITIES.QUALITY_GOOD); + } + + public int WriteInt32(DeviceAddress address, int value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteInt16(DeviceAddress address, short value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteFloat(DeviceAddress address, float value) + { + return WriteSingleData(address, BitConverter.GetBytes(value)); + } + + public int WriteString(DeviceAddress address, string value) + { + return WriteSingleData(address, Encoding.ASCII.GetBytes(value)); + } + + public int WriteBit(DeviceAddress address, bool value) + { + return WriteSingleData(address, new byte[] { (byte)(value ? 1 : 0) }); + } + + public int WriteBits(DeviceAddress address, byte value) + { + return WriteSingleData(address, new byte[] { value }); + } + + public event DataChangeEventHandler DataChange; + + public void Dispose() + { + RemoveAll(); + _items = null; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/Condition.cs b/SCADA/Program/CoreApp/DataService/DataService/Condition.cs new file mode 100644 index 0000000..1a0c23d --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/Condition.cs @@ -0,0 +1,1104 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace DataService +{ + public delegate void AlarmEventHandler(object sender, AlarmItem e); + + public interface IEvent + { + bool IsEnabled { get; set; } + bool IsAcked { get; set; } + bool IsActived { get; set; } + Severity Severity { get; } + EventType EventType { get; } + DateTime LastActive { get; set; } + string Comment { get; } + } + + public abstract class ICondition : IEvent, IDisposable, IComparable, IEquatable + { + public const string ALARMSTOP = "Alarm Clear"; + protected bool _enable, _ack, _active; + protected int _id; + protected DateTime _timeStamp; + protected SubAlarmType _tempType; + protected SubCondition[] _subConditions; + + public int ID + { + get + { + return _id; + } + set + { + _id = value; + } + } + + public bool IsEnabled + { + get + { + return _enable; + } + set + { + _enable = value; + } + } + + public bool IsActived + { + get + { + return _active; + } + set + { + _active = value; + } + } + + public bool IsAcked + { + get + { + return _ack; + } + set + { + _ack = value; + if (_ack) + { + _tempType = SubAlarmType.None; + _lastAckTime = DateTime.Now; + if (_ack && AlarmAck != null) + { + foreach (EventHandler deleg in AlarmAck.GetInvocationList()) + { + deleg.BeginInvoke(this, EventArgs.Empty, null, null); + } + } + } + } + } + + public abstract AlarmType AlarmType + { + get; + } + + public EventType EventType + { + get + { + return EventType.ConditionEvent; + } + } + + + protected ConditionType _conditionType; + public ConditionType ConditionType + { + get + { + return _conditionType; + } + set + { + _conditionType = value; + } + } + + protected DateTime _lastAckTime, _condLastActive, _lastInactive; + public DateTime LastAckTime + { + get + { + return _lastAckTime; + } + set + { + _lastAckTime = value; + } + } + + public DateTime SubCondLastActive + { + get + { + return _current.StartTime; + } + //set + //{ + // _subCondLastActive = value; + //} + } + + public DateTime LastActive + { + get + { + return _condLastActive; + } + set + { + _condLastActive = value; + } + } + + public DateTime LastInactive + { + get + { + return _lastInactive; + } + set + { + _lastInactive = value; + } + } + + protected AlarmItem _current; + + protected SubAlarmType _activeSub; + public SubAlarmType ActiveSubCondition + { + get + { + return _activeSub; + } + protected set + { + _activeSub = value; + } + } + + public Severity Severity + { + get + { + return _current.Severity; + } + } + + public string Message + { + get + { + return _current.AlarmText; + } + } + + public abstract string Value + { + get; + } + + protected float _para; + public float Para + { + get + { + return _para; + } + set + { + _para = value; + } + } + + protected float _deadBand; + public float DeadBand + { + get + { + return _deadBand; + } + set + { + _deadBand = value; + } + } + + protected int _delay; + public int Delay + { + get + { + return _delay; + } + set + { + _delay = value; + } + } + + protected string _comment; + public string Comment + { + get + { + return _comment; + } + set + { + _comment = value; + } + } + + protected string _source; + public string Source + { + get + { + return _source; + } + } + + public IList SubConditions + { + get + { + return _subConditions; + } + } + + public AlarmEventHandler AlarmActive; + public EventHandler AlarmAck; + + protected ICondition(int id, ConditionType conditionType, string source, string comment, float para, float deadBand, int delay) + { + this._id = id; + this._conditionType = conditionType; + this._para = para; + this._source = source; + this._comment = comment; + this._deadBand = deadBand; + this._delay = delay; + this._current = new AlarmItem(); + } + + public abstract bool AddSubCondition(SubCondition condition); + + public abstract bool RemoveSubCondition(SubCondition condition); + + protected abstract void OnActive(SubCondition condition, Storage value, DateTime timeStamp); + + protected abstract void OnInActive(Storage value); + + public int CompareTo(ICondition other) + { + int comp1 = ((int)this.Severity).CompareTo((int)other.Severity); + return comp1 == 0 ? this.LastActive.CompareTo(other.LastActive) : -comp1; + } + + public bool Equals(ICondition other) + { + if (other == null) return false; + return this._id == other._id; + } + + public virtual void Dispose() + { + _current = null; + AlarmAck = null; + AlarmActive = null; ; + } + } + + public abstract class SimpleCondition : ICondition, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public override string Value + { + get + { + return _tag == null ? null : _tag.ToString(); + } + } + + protected ITag _tag; + public ITag Tag + { + get + { + return _tag; + } + set + { + if (_tag != null) + _tag.ValueChanging -= CheckAlarm; + _tag = value; + if (_tag != null) + _tag.ValueChanging += CheckAlarm; + } + } + + protected SimpleCondition(int id, ConditionType conditionType, string source, string comment, float para, float deadBand, int delay) : + base(id, conditionType, source, comment, para, deadBand, delay) + { + } + + protected override void OnActive(SubCondition condition, Storage value, DateTime timeStamp) + { + if (condition.SubAlarmType != ActiveSubCondition) + { + if (condition.SubAlarmType != _tempType) + { + _timeStamp = timeStamp; + _tempType = condition.SubAlarmType; + } + if (_delay == 0 || (timeStamp - _timeStamp).TotalMilliseconds > _delay) + { + if (ActiveSubCondition == SubAlarmType.None) + { + _active = true; + _condLastActive = timeStamp; + } + _ack = false; + ActiveSubCondition = condition.SubAlarmType; + _current.Duration = timeStamp - SubCondLastActive; + _current = new AlarmItem(timeStamp, condition.Message, _tag.GetValue(value), ActiveSubCondition, condition.Severity, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + RaiseChanged("Value"); + } + } + else + { + RaiseChanged("Value"); + } + } + + protected override void OnInActive(Storage value) + { + if (ActiveSubCondition != SubAlarmType.None) + { + _active = false; + ActiveSubCondition = SubAlarmType.None; + _current.Duration = DateTime.Now - SubCondLastActive; + _current = new AlarmItem(DateTime.Now, string.Concat("【", _current.AlarmText, "】", ALARMSTOP), _tag.GetValue(value), SubAlarmType.None, Severity.Normal, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + } + } + + protected void RaiseChanged(string property) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(property)); + } + } + + public override void Dispose() + { + base.Dispose(); + PropertyChanged = null; + if (_tag != null) + _tag.ValueChanging -= CheckAlarm; + } + + protected abstract void CheckAlarm(object sender, ValueChangingEventArgs e); + + public override AlarmType AlarmType + { + get + { + return AlarmType.None; + } + } + + public override bool AddSubCondition(SubCondition condition) + { + return true; + } + + public override bool RemoveSubCondition(SubCondition condition) + { + return true; + } + } + + public sealed class ComplexCondition : ICondition, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public override string Value + { + get + { + return Convert.ToString(_current.AlarmValue); + } + } + + public ComplexCondition(int id, string source, string comment, float deadBand, int delay) : + base(id, ConditionType.Absolute, source, comment, 0f, deadBand, delay) + { + _subConditions = new SubCondition[1] + { + new SubCondition(SubAlarmType.Dsc) + }; + } + + protected override void OnActive(SubCondition condition, Storage value, DateTime timeStamp) + { + if (condition.SubAlarmType != ActiveSubCondition) + { + if (condition.SubAlarmType != _tempType) + { + _timeStamp = timeStamp; + _tempType = condition.SubAlarmType; + } + if (_delay == 0 || (timeStamp - _timeStamp).TotalMilliseconds > _delay) + { + if (ActiveSubCondition == SubAlarmType.None) + { + _active = true; + _condLastActive = timeStamp; + } + _ack = false; + ActiveSubCondition = condition.SubAlarmType; + _current.Duration = timeStamp - SubCondLastActive; + _current = new AlarmItem(timeStamp, condition.Message, true, ActiveSubCondition, condition.Severity, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs("Value")); + } + } + } + else + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs("Value")); + } + } + } + + protected override void OnInActive(Storage value) + { + if (ActiveSubCondition != SubAlarmType.None) + { + _active = false; + ActiveSubCondition = SubAlarmType.None; + _current.Duration = DateTime.Now - SubCondLastActive; + _current = new AlarmItem(DateTime.Now, string.Concat("【", _current.AlarmText, "】", ALARMSTOP), false, SubAlarmType.None, Severity.Normal, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + } + } + + public Action SetFunction(Delegate tagChanged) + { + var _func = tagChanged as Func; + if (_func != null) + { + return delegate + { + if (_enable) + { + SubCondition condition = _subConditions[0]; + if (condition.IsEnabled) + { + if (_func()) + { + OnActive(condition, Storage.Empty, DateTime.Now); + return; + } + } + OnInActive(Storage.Empty); + } + }; + } + else + return null; + } + + public override void Dispose() + { + base.Dispose(); + PropertyChanged = null; + } + + public override AlarmType AlarmType + { + get + { + return AlarmType.Complex; + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + _subConditions[0] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + _subConditions[0] = SubCondition.Empty; + return true; + default: + return false; + } + } + + } + + public sealed class LevelAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.Level; } + } + + public LevelAlarm(int id, string source, string comment, float deadBand = 0f, int delay = 0) : + base(id, ConditionType.Absolute, source, comment, 0, deadBand, delay) + { + _subConditions = new SubCondition[4] + { + new SubCondition(SubAlarmType.HiHi), + new SubCondition(SubAlarmType.High), + new SubCondition(SubAlarmType.LoLo), + new SubCondition(SubAlarmType.Low) + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + SubCondition condition; + float value = _tag.ScaleToValue(e.NewValue); + for (int i = 0; i < _subConditions.Length; i++) + { + if (_subConditions[i].IsEnabled) + { + condition = _subConditions[i]; + if (i < 2)//Hi Alarm + { + if (value > condition.Threshold) + { + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + else if (_deadBand > 0 && ActiveSubCondition == condition.SubAlarmType && value > condition.Threshold - _deadBand) + { + return; + } + } + else//Low Alarm + { + if (value < condition.Threshold) + { + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + else if (_deadBand > 0 && ActiveSubCondition == condition.SubAlarmType && value > condition.Threshold + _deadBand) + { + return; + } + } + } + } + OnInActive(e.NewValue); + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.HiHi: + _subConditions[0] = condition; + return true; + case SubAlarmType.High: + _subConditions[1] = condition; + return true; + case SubAlarmType.LoLo: + _subConditions[2] = condition; + return true; + case SubAlarmType.Low: + _subConditions[3] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.HiHi: + _subConditions[0] = SubCondition.Empty; + return true; + case SubAlarmType.High: + _subConditions[1] = SubCondition.Empty; + return true; + case SubAlarmType.LoLo: + _subConditions[2] = SubCondition.Empty; + return true; + case SubAlarmType.Low: + _subConditions[3] = SubCondition.Empty; + return true; + default: + return false; + } + } + } + + public sealed class DevAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.Dev; } + } + + public DevAlarm(int id, ConditionType conditionType, string source, string comment, float para, float deadBand = 0f, int delay = 0) : + base(id, conditionType, source, comment, para, deadBand, delay) + { + _subConditions = new SubCondition[2] + { + new SubCondition(SubAlarmType.MajDev), + new SubCondition(SubAlarmType.MinDev) + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + float value = _tag.ScaleToValue(e.NewValue) - _para; + SubCondition condition; + for (int i = 0; i < _subConditions.Length; i++) + { + if (_subConditions[i].IsEnabled) + { + condition = _subConditions[i]; + if (value > (_conditionType == ConditionType.Absolute ? condition.Threshold : _para * condition.Threshold)) + { + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + else if (_deadBand > 0 && ActiveSubCondition == condition.SubAlarmType && + ((_conditionType == ConditionType.Absolute && value > condition.Threshold - _deadBand) + || (_conditionType == ConditionType.Percent && value > _para * (condition.Threshold - _deadBand)))) + { + return; + } + } + } + OnInActive(e.NewValue); + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.MajDev: + _subConditions[0] = condition; + return true; + case SubAlarmType.MinDev: + _subConditions[1] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.MajDev: + _subConditions[0] = SubCondition.Empty; + return true; + case SubAlarmType.MinDev: + _subConditions[1] = SubCondition.Empty; + return true; + default: + return false; + } + } + } + + public sealed class DigitAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.Dsc; } + } + + public DigitAlarm(int id, string source = null, string comment = null, int delay = 0) : + base(id, ConditionType.Absolute, source, comment, 0, 0f, delay) + { + _subConditions = new SubCondition[1] + { + new SubCondition(SubAlarmType.Dsc) + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + SubCondition condition = _subConditions[0]; + if (condition.IsEnabled) + { + if (e.NewValue.Boolean == condition.Threshold > 0) + { + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + } + OnInActive(e.NewValue); + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + _subConditions[0] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + _subConditions[0] = SubCondition.Empty; + return true; + default: + return false; + } + } + } + + public sealed class WordDigitAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.WordDsc; } + } + + public WordDigitAlarm(int id, string source = null, string comment = null, int delay = 0) : + base(id, ConditionType.Absolute, source, comment, 0, 0f, delay) + { + _subConditions = new SubCondition[16]; + for (int i = 0; i < 16; i++) + { + _subConditions[i].SubAlarmType = SubAlarmType.Dsc; + _subConditions[i].Threshold = i; + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + short newvalue = e.NewValue.Int16; + short oldvlaue = e.OldValue.Int16; + if (newvalue == 0 && oldvlaue != 0) + { + OnInActive(e.NewValue); + return; + } + for (short i = 0; i < _subConditions.Length; i++) + { + SubCondition condition = _subConditions[i]; + if (condition.IsEnabled) + { + int mask = 1 << i; + int newval = mask & newvalue; + int oldval = mask & oldvlaue; + if (newval != 0 && oldval == 0) + { + OnActive(condition, new Storage { Int16 = i }, e.NewTimeStamp); + } + } + } + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + int index = (int)condition.Threshold; + if (index >= 0 && index < 16) + _subConditions[index] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.Dsc: + int index = (int)condition.Threshold; + if (index >= 0 && index < 16) + _subConditions[index] = SubCondition.Empty; + return true; + default: + return false; + } + } + + protected override void OnActive(SubCondition condition, Storage value, DateTime timeStamp) + { + if (ActiveSubCondition == SubAlarmType.None) + { + _active = true; + _condLastActive = timeStamp; + } + _ack = false; + ActiveSubCondition = condition.SubAlarmType; + _current = new AlarmItem(timeStamp, condition.Message, value.Int16, ActiveSubCondition, condition.Severity, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + RaiseChanged("Value"); + } + + protected override void OnInActive(Storage value) + { + if (ActiveSubCondition != SubAlarmType.None) + { + _active = false; + ActiveSubCondition = SubAlarmType.None; + _current.Duration = DateTime.Now - LastActive; + _current = new AlarmItem(DateTime.Now, string.Concat("【", Comment, "】", ALARMSTOP), 0, SubAlarmType.None, Severity.Normal, _id, _source); + if (AlarmActive != null) + { + foreach (AlarmEventHandler deleg in AlarmActive.GetInvocationList()) + { + deleg.BeginInvoke(this, _current, null, null); + } + } + } + } + } + + public sealed class QualitiesAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.Quality; } + } + + public QualitiesAlarm(int id, string source, string comment, int delay = 0) : + base(id, ConditionType.Absolute, source, comment, 0, 0f, delay) + { + _subConditions = new SubCondition[1] + { + new SubCondition(SubAlarmType.BadPV) + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + SubCondition condition = _subConditions[0]; + if (condition.IsEnabled) + { + if (e.Quality != QUALITIES.QUALITY_GOOD) + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + OnInActive(e.NewValue); + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.BadPV: + _subConditions[0] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.BadPV: + _subConditions[0] = SubCondition.Empty; + return true; + default: + return false; + } + } + } + + public sealed class ROCAlarm : SimpleCondition + { + public override AlarmType AlarmType + { + get { return AlarmType.ROC; } + } + + + public ROCAlarm(int id, string souce, string comment, float deadBand = 0f, int delay = 0) : + base(id, ConditionType.Percent, souce, comment, 0, deadBand, delay) + { + _subConditions = new SubCondition[2] + { + new SubCondition(SubAlarmType.MajROC), + new SubCondition(SubAlarmType.MinROC) + }; + } + + protected override void CheckAlarm(object sender, ValueChangingEventArgs e) + { + if (_enable) + { + float value = (float)((_tag.ScaleToValue(e.NewValue) - e.OldValue.Single) / (e.NewTimeStamp - e.OldTimeStamp).TotalMilliseconds); + SubCondition condition; + for (int i = 0; i < _subConditions.Length; i++) + { + if (_subConditions[i].IsEnabled) + { + condition = _subConditions[i]; + if (value > condition.Threshold) + { + OnActive(condition, e.NewValue, e.NewTimeStamp); + return; + } + else if (_deadBand > 0 && ActiveSubCondition == condition.SubAlarmType && value > condition.Threshold - _deadBand) + { + return; + } + } + } + OnInActive(e.NewValue); + } + } + + public override bool AddSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.MajROC: + _subConditions[0] = condition; + return true; + case SubAlarmType.MinROC: + _subConditions[1] = condition; + return true; + default: + return false; + } + } + + public override bool RemoveSubCondition(SubCondition condition) + { + switch (condition.SubAlarmType) + { + case SubAlarmType.MajROC: + _subConditions[0] = SubCondition.Empty; + return true; + case SubAlarmType.MinROC: + _subConditions[1] = SubCondition.Empty; + return true; + default: + return false; + } + } + } + + public struct SubCondition + { + public bool IsEnabled; + + public SubAlarmType SubAlarmType; + + public Severity Severity; + + public float Threshold; + + public string Message; + + public SubCondition(SubAlarmType type, float threshold = 0f, Severity severity = Severity.Normal, string message = "", bool enabled = true) + { + this.SubAlarmType = type; + this.Threshold = threshold; + this.Severity = severity; + this.Message = message; + this.IsEnabled = enabled; + } + + public static readonly SubCondition Empty = new SubCondition(SubAlarmType.None, 0f, Severity.Normal, "正常", false); + } + + public class CompareCondBySource : IComparer + { + public int Compare(ICondition x, ICondition y) + { + if (x == null || x.Source == null) + { + return y == null || y.Source == null ? 0 : 1; + } + else + { + return y == null || y.Source == null ? 1 : x.Source.CompareTo(y.Source); + } + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/DataService.csproj b/SCADA/Program/CoreApp/DataService/DataService/DataService.csproj new file mode 100644 index 0000000..fe04d3e --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/DataService.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.0 + + + + true + + + + + + + diff --git a/SCADA/Program/CoreApp/DataService/DataService/DeviceAddress.cs b/SCADA/Program/CoreApp/DataService/DataService/DeviceAddress.cs new file mode 100644 index 0000000..927ec42 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/DeviceAddress.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace DataService +{ + [StructLayout(LayoutKind.Sequential)] + public struct DeviceAddress : IComparable + { + public int Area; + public int Start; + public ushort DBNumber; + public ushort DataSize; + public ushort CacheIndex; + public byte Bit; + public DataType VarType; + + public DeviceAddress(int area, ushort dbnumber, ushort cIndex, int start, ushort size, byte bit, DataType type) + { + Area = area; + DBNumber = dbnumber; + CacheIndex = cIndex; + Start = start; + DataSize = size; + Bit = bit; + VarType = type; + } + + public static readonly DeviceAddress Empty = new DeviceAddress(0, 0, 0, 0, 0, 0, DataType.NONE); + + public int CompareTo(DeviceAddress other) + { + return this.Area > other.Area ? 1 : + this.Area < other.Area ? -1 : + this.DBNumber > other.DBNumber ? 1 : + this.DBNumber < other.DBNumber ? -1 : + this.Start > other.Start ? 1 : + this.Start < other.Start ? -1 : + this.Bit > other.Bit ? 1 : + this.Bit < other.Bit ? -1 : 0; + } + } + +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/Eval.cs b/SCADA/Program/CoreApp/DataService/DataService/Eval.cs new file mode 100644 index 0000000..14cb0eb --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/Eval.cs @@ -0,0 +1,716 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace DataService +{ + public class ExpressionEval : IDisposable + { + Expression _param1; + List _tagList = new List(); + public List TagList + { + get + { + return _tagList; + } + } + + IDataServer _server; + + public ExpressionEval(IDataServer server) + { + _server = server; + _param1 = Expression.Constant(this); + } + + public Delegate Eval(string expression) + { + if (string.IsNullOrEmpty(expression)) return null; + var lambda = ComplieRpnExp(RpnExpression(expression)); + if (lambda != null) return lambda.Compile(); + return null; + } + + public Delegate WriteEval(string expression) + { + if (_server == null || string.IsNullOrEmpty(expression)) return null; + if (_server[expression.ToUpper()] != null) + { + return new Func((object value) => { return WriteTag(expression, value); }); + } + return null; + } + + public Func WriteEval(string expression1, string expression2) + { + if (_server == null || string.IsNullOrEmpty(expression2)) return null; + if (_server[expression1.ToUpper()] != null) + { + var dele = Eval(expression2); + var funcbool = dele as Func; + if (funcbool != null) + return () => { return WriteTag(expression1, funcbool()); }; + var funcint = dele as Func; + if (funcint != null) + return () => { return WriteTag(expression1, funcint()); }; + var funcfloat = dele as Func; + if (funcfloat != null) + return () => { return WriteTag(expression1, funcfloat()); }; + var funcstring = dele as Func; + if (funcstring != null) + return () => { return WriteTag(expression1, funcstring()); }; + } + return null; + } + + public static bool ValidationExpression(string expression) + { + return true;//可加入正则表达式验证 + } + + /// + /// 操作符运算级别 + /// + /// 操作符 + /// 操作运算符,空格返回0,出错返回-1 + private static byte GetOperatorLevel(char strOperator) + { + switch (strOperator) + { + case '~': + return 10; + case '*': + case '/': + case '%': + return 9; + case '+': + case '-': + return 8; + case '>': + case '<': + return 7; + case '&': + return 6; + case '^': + return 5; + case '|': + return 4; + case '=': + case '!': + case '?': + return 3; + case '(': + return 2; + case ')': + return 1; + //case ':': + default: + return 0; + + } + } + + /// + /// 将中缀表达式转换为逆波兰表达式 + /// + /// 标准中缀表达式 + /// 标准逆波兰表达式 + public static List RpnExpression(string expression) + { + //加入结束标记 + //定义出栈和入栈堆栈 + string[] strNum = expression.Split('~', '%', '>', '<', '=', '!', '&', '|', '?', '#', '^', '+', '-', '*', '/', '(', ')'); + if (strNum.Length < 2) return new List() { expression }; + //操作运算符堆栈 + Stack oper = new Stack(); + //定义输出堆栈 + List output = new List(); + + //定义前缀表达式字符读取指针 + int i = 0; + + //定义当前读取数字数组指针 + int n = -1; + //定义操作运算符级别函数 + Operator op = new Operator(); + //输出堆栈的大小 + int intStackCount = 0; + + //从左到右读取前缀表达式 + while (i < expression.Length) + { + //读取一个字符 + char strChar = expression[i]; + //取字符的运算级别 + if (strChar == '#') { i++; continue; } + byte intLevel = GetOperatorLevel(strChar); + if (intLevel == 0) + //数字直接推入输出堆栈 + { + while (n++ < strNum.Length) + { + if (strNum[n] != "") + { + output.Add(strNum[n]); + i += strNum[n].Length; + //移动数组指针 + break; + } + } + } + else //操作字符根据运算字符级别推入运算符堆栈 + { + if (oper.Count == 0) + { + //运算符堆栈为空,直接推入堆栈 + oper.Push(new Operator(strChar, intLevel)); + //移动字符读取指针 + i++; + } + else + { + op = oper.Peek(); + if (intLevel > op.Level || intLevel == 2) + { + //运算字符比运算符堆栈最后的级别高或者运算符为'('直接推入运算符堆栈 + oper.Push(new Operator(strChar, intLevel)); + //移动字符读取指针 + i++; + } + else + { + //运算字符不高于运算符堆栈最后的级别,则将运算符堆栈出栈,直到比其高为止 + intStackCount = oper.Count; + for (int m = 0; m < intStackCount; m++) + { + op = oper.Peek(); + if (op.Level >= intLevel) + { + //将操作符出栈并压入输入堆栈 + char o = op.OperatorStack; + if (!(o == ')' || o == '(')) + { + output.Add(o.ToString()); + } + oper.Pop(); + if (op.Level == 2) + { + //如果操作符堆栈中最后的操作符为'('则停止出栈 + i++; + break; + } + } + else + { + //直到运算符已经高出运算符栈中最后的级别,则入栈 + oper.Push(new Operator(strChar, intLevel)); + i++; + break; + } + } + } + } + } + } + + intStackCount = oper.Count; + for (int m = 0; m < intStackCount; m++) + { + op = oper.Peek(); + output.Add(op.OperatorStack.ToString()); + oper.Pop(); + } + + return output; + } + + /// + /// 解逆波兰表达式 + /// + /// 标准逆波兰表达式 + /// 逆波兰表达式的解 + public LambdaExpression ComplieRpnExp(List strNum) + { + _tagList.Clear(); + //拆分逆波兰表达式 + int intLenth = strNum.Count; + if (intLenth == 0) return null; + //定义数字堆栈 + try + { + Stack number = new Stack(); + for (int i = 0; i < intLenth; i++) + { + string expr = strNum[i]; + switch (expr) + { + case "~": + if (number.Count > 0) + { + Expression left = number.Pop(); + number.Push(Expression.Not(left)); + } + break; + case "*": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.Multiply(left, right)); + } + break; + case "/": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.Divide(left, right)); + } + break; + case "%": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(int)) + left = Expression.Convert(left, typeof(int)); + if (right.Type != typeof(int)) + right = Expression.Convert(right, typeof(int)); + } + number.Push(Expression.Modulo(left, right)); + } + break; + case "+": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type == typeof(string) || right.Type == typeof(string)) + { + if (left.Type != typeof(string)) + left = Expression.Convert(left, typeof(object)); + if (right.Type != typeof(string)) + right = Expression.Convert(right, typeof(object)); + number.Push(Expression.Call(typeof(string).GetMethod("Concat", new Type[] { typeof(object), typeof(object) }), left, right)); + } + else + { + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.Add(left, right)); + } + } + break; + case "-": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.Subtract(left, right)); + } + break; + case ">": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.GreaterThan(left, right)); + } + break; + case "<": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.LessThan(left, right)); + } + break; + case "&": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + number.Push(Expression.And(left, right)); + } + break; + case "^": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + number.Push(Expression.ExclusiveOr(left, right)); + } + break; + case "|": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + number.Push(Expression.Or(left, right)); + } + break; + case "=": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.Equal(left, right)); + } + break; + case "!": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + number.Push(Expression.NotEqual(left, right)); + } + break; + case "?": + if (number.Count > 1) + { + Expression right = number.Pop(); + Expression left = number.Pop(); + if (left.Type != right.Type) + { + if (left.Type != typeof(float)) + left = Expression.Convert(left, typeof(float)); + if (right.Type != typeof(float)) + right = Expression.Convert(right, typeof(float)); + } + Expression test = number.Pop(); + number.Push(Expression.Condition(test, left, right)); + } + break; + default: + if (expr[0] == '@') + { + switch (expr.Substring(1).ToUpper()) + { + case "TIME": + { + Expression> f = () => DateTime.Now.ToShortTimeString(); + number.Push(f.Body); + } + goto lab1; + case "DATE": + { + Expression> f = () => DateTime.Now.ToShortDateString(); + number.Push(f.Body); + } + goto lab1; + case "DATETIME": + { + Expression> f = () => DateTime.Now.ToString(); + number.Push(f.Body); + } + goto lab1; + case "APP": + { + Expression> f = () => AppDomain.CurrentDomain.FriendlyName; + number.Push(f.Body); + } + goto lab1; + case "NAME": + { + Expression> f = () => Environment.MachineName; + number.Push(f.Body); + } + goto lab1; + case "PATH": + { + Expression> f = () => Environment.CurrentDirectory; + number.Push(f.Body); + } + goto lab1; + case "USER": + { + Expression> f = () => Environment.UserName; + number.Push(f.Body); + } + goto lab1; + case "REGION": + { + Expression> f = () => System.Globalization.CultureInfo.CurrentCulture.Name; + number.Push(f.Body); + } + goto lab1; + } + } + object result; + if (IsConstant(expr, out result)) + { + number.Push(Expression.Constant(result)); + } + else + { + number.Push(GetTagExpression(expr)); + } + lab1: + break; + } + } + + Expression d = number.Pop(); + return Expression.Lambda(d); + } + catch (Exception e) { return null; } + } + + MethodInfo _boolinfo = typeof(ExpressionEval).GetMethod("GetBool"); + MethodInfo _floatinfo = typeof(ExpressionEval).GetMethod("GetFloat"); + MethodInfo _intinfo = typeof(ExpressionEval).GetMethod("GetInt"); + MethodInfo _stringinfo = typeof(ExpressionEval).GetMethod("GetString"); + + public Expression GetTagExpression(string tagName) + { + if (_server == null) return Expression.Empty(); + ITag tag = _server[tagName]; + switch (tag.Address.VarType) + { + case DataType.BOOL: + return Expression.Call(_param1, _boolinfo, Expression.Constant(tagName)); + case DataType.BYTE: + case DataType.WORD: + case DataType.SHORT: + case DataType.TIME: + case DataType.INT: + return Expression.Call(_param1, _intinfo, Expression.Constant(tagName)); + case DataType.FLOAT: + return Expression.Call(_param1, _floatinfo, Expression.Constant(tagName)); + case DataType.STR: + return Expression.Call(_param1, _stringinfo, Expression.Constant(tagName)); + default: + return Expression.Empty(); + } + } + + public bool GetBool(string tagName) + { + if (_server == null) return false; + ITag tag = _server[tagName]; + switch (tag.Address.VarType) + { + case DataType.BOOL: + return tag.Value.Boolean; + case DataType.BYTE: + return Convert.ToBoolean(tag.Value.Byte); + case DataType.WORD: + case DataType.SHORT: + return Convert.ToBoolean(tag.Value.Int16); + case DataType.TIME: + case DataType.INT: + return Convert.ToBoolean(tag.Value.Int32); + case DataType.FLOAT: + return Convert.ToBoolean(tag.Value.Single); + case DataType.STR: + return Convert.ToBoolean(tag.ToString()); + default: + return false; + } + } + + public float GetFloat(string tagName) + { + if (_server == null) return 0f; + ITag tag = _server[tagName]; + return tag.ScaleToValue(tag.Value); + } + + public int GetInt(string tagName) + { + if (_server == null) return 0; + ITag tag = _server[tagName]; + switch (tag.Address.VarType) + { + case DataType.BOOL: + return tag.Value.Boolean ? 1 : 0; + case DataType.BYTE: + return (int)tag.Value.Byte; + case DataType.WORD: + case DataType.SHORT: + return (int)tag.Value.Int16; + case DataType.TIME: + case DataType.INT: + return tag.Value.Int32; + case DataType.FLOAT: + return Convert.ToInt32(tag.Value.Single); + case DataType.STR: + return int.Parse(tag.ToString()); + default: + return 0; + } + } + + public string GetString(string tagName) + { + return _server == null ? null : _server[tagName].ToString(); + } + + public int WriteTag(string tagName, object value) + { + if (_server == null || value == null) return -1; + ITag tag = _server[tagName]; + if (tag.Address.VarType == DataType.BOOL || tag.Address.VarType == DataType.STR) + return tag.Write(value); + else + { + float temp; + string str = value as string; + if (str == null) temp = Convert.ToSingle(value); + else + { + if (!float.TryParse(str, out temp)) + return -1; + } + return tag.Write(tag.ValueToScale(temp)); + } + } + + private bool IsConstant(string str, out object value) + { + if (str.Length > 1 & str[0] == '\'' && str[str.Length - 1] == '\'') + { + value = str.Trim('\''); + return true; + } + string upp = str.ToUpper(); + if (upp == "TRUE") + { + value = true; + return true; + } + else if (upp == "FALSE") + { + value = false; + return true; + } + if (_server != null) + { + var tag = _server[upp]; + if (tag != null) + { + if (!_tagList.Contains(tag)) + _tagList.Add(tag); + value = null; + return false; + } + } + int dotcount = 0; + for (int i = 0; i < str.Length; i++) + { + char opr = str[i]; + if (opr < '0' || opr > '9') + { + if (opr != '.') + { + value = str; + return true; + } + else + { + if (dotcount > 0) + { + value = str; + return true; + } + dotcount++; + } + } + } + //value = (dotcount == 0 ? int.Parse(str) : float.Parse(str)); + if (dotcount == 0) + value = int.Parse(str); + else value = float.Parse(str); + return true; + } + + public void Clear() + { + //_param1 = null; + _tagList.Clear(); + //_tagList = null; + } + + public void Dispose() + { + _param1 = null; + _tagList.Clear(); + _tagList = null; + _boolinfo = _floatinfo = _stringinfo = null; + } + } + + [StructLayout(LayoutKind.Sequential)] + /// + /// 操作符结构 + /// + public struct Operator + { + public char OperatorStack; + public byte Level; + public Operator(char OperatorStack, byte Level) + { + this.OperatorStack = OperatorStack; + this.Level = Level; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/ExtensionMethods.cs b/SCADA/Program/CoreApp/DataService/DataService/ExtensionMethods.cs new file mode 100644 index 0000000..09ab545 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/ExtensionMethods.cs @@ -0,0 +1,811 @@ +using System; +using System.Text; +using System.Net; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Data.SqlClient; + +namespace DataService +{ + public static class ExtMethods + { + public static string GetExceptionMsg(this Exception e) + { + string err = string.Empty; + Exception exp = e; + while (exp != null) + { + err += string.Format("\n {0}", exp.Message); + exp = exp.InnerException; + } + err += string.Format("\n {0}", e.StackTrace); + return err; + } + + public static bool ModifyItemName(this ITag tag, string name) + { + IDataServer server = tag.Parent.Server; + lock (server.SyncRoot) + { + int index = server.GetItemProperties(tag.ID); + if (index < 0) return false; + var meta = server.MetaDataList[index]; + if (meta.Name == name) return true; + server.MetaDataList[index] = new TagMetaData(meta.ID, meta.GroupID, name, meta.Address, meta.DataType, meta.Size, meta.Archive, meta.Maximum, meta.Minimum, meta.Cycle); + server.RemoveItemIndex(meta.Name); + server.AddItemIndex(name, tag); + return true; + } + } + + public static SubCondition FindSubConditon(this IAlarmServer server, string sourceName, SubAlarmType alarmType) + { + var conds = server.QueryConditions(sourceName); + if (conds == null) return SubCondition.Empty; + foreach (ICondition cond in conds) + { + SubCondition sub = cond.FindSubConditon(alarmType); + if (sub.SubAlarmType == alarmType) + return sub; + } + return SubCondition.Empty; + } + + public static SubCondition FindSubConditon(this ICondition cond, SubAlarmType alarmType) + { + var subs = cond.SubConditions; + if (subs != null && subs.Count > 0) + { + foreach (var sub in subs) + { + if (sub.SubAlarmType == alarmType) + { + return sub; + } + } + } + return SubCondition.Empty; + } + + public static bool HasScaling(this IDataServer server, string tagName) + { + ITag tag = server[tagName]; + if (tag == null) return false; + int scaleid = server.GetScaleByID(tag.ID); + return scaleid >= 0; + } + + public static bool HasAlarm(this IDataServer dserver, string sourceName) + { + IAlarmServer server = dserver as IAlarmServer; + if (server == null) return false; + List conds = server.ConditionList as List; + return conds == null || conds.Count == 0 ? false : conds.BinarySearch(new DigitAlarm(0, sourceName)) >= 0; + } + + public static bool HasSubCondition(this IDataServer dserver, string sourceName, SubAlarmType alarmType) + { + IAlarmServer server = dserver as IAlarmServer; + if (server == null) return false; + var conds = server.QueryConditions(sourceName); + if (conds == null) return false; + foreach (ICondition cond in conds) + { + var subs = cond.SubConditions; + if (subs != null && subs.Count > 0) + { + foreach (var sub in subs) + { + if (sub.SubAlarmType == alarmType) + { + return true; + } + } + } + } + return false; + } + + public static ItemData ReadValueEx(this IReaderWriter reader, DeviceAddress address) + { + switch (address.VarType) + { + case DataType.BOOL: + var bit = reader.ReadBit(address); + return new ItemData(bit.Value, bit.TimeStamp, bit.Quality); + case DataType.BYTE: + var bt = reader.ReadByte(address); + return new ItemData(bt.Value, bt.TimeStamp, bt.Quality); + case DataType.WORD: + case DataType.SHORT: + var sh = reader.ReadInt16(address); + return new ItemData(sh.Value, sh.TimeStamp, sh.Quality); + case DataType.TIME: + case DataType.INT: + var it = reader.ReadInt32(address); + return new ItemData(it.Value, it.TimeStamp, it.Quality); + case DataType.FLOAT: + var fl = reader.ReadFloat(address); + return new ItemData(fl.Value, fl.TimeStamp, fl.Quality); + case DataType.STR: + var str = reader.ReadString(address, address.DataSize); + return new ItemData(str.Value, str.TimeStamp, str.Quality); + } + return new ItemData(null, 0, QUALITIES.QUALITY_BAD); + } + + public static int WriteValueEx(this IReaderWriter writer, DeviceAddress address, object value) + { + switch (address.VarType) + { + case DataType.BOOL: + return writer.WriteBit(address, Convert.ToBoolean(value)); + case DataType.BYTE: + return writer.WriteBits(address, Convert.ToByte(value)); + case DataType.WORD: + case DataType.SHORT: + return writer.WriteInt16(address, Convert.ToInt16(value)); + case DataType.TIME: + case DataType.INT: + return writer.WriteInt32(address, Convert.ToInt32(value)); + case DataType.FLOAT: + return writer.WriteFloat(address, Convert.ToSingle(value)); + case DataType.STR: + return writer.WriteString(address, value.ToString()); + } + return -1; + } + + public static HistoryData[] BatchRead(DataSource source, params ITag[] itemArray) + { + int len = itemArray.Length; + HistoryData[] values = new HistoryData[len]; + for (int i = 0; i < len; i++) + { + itemArray[i].Refresh(source); + values[i].ID = itemArray[i].ID; + values[i].Value = itemArray[i].Value; + values[i].TimeStamp = itemArray[i].TimeStamp; + } + return values; + } + + public static int BatchWrite(IDictionary items) + { + int rev = 0; + foreach (var tag in items) + { + if (tag.Key.Write(tag.Value) < 0) + rev = -1; + } + return rev; + } + + public static List AssignFromPDU(this ICache cacheReader, int PDU, params DeviceAddress[] addrsArr) + { + List rangeList = new List(); + int count = addrsArr.Length; + if (count > 0) + { + //Array.Sort(addrsArr); + DeviceAddress start = addrsArr[0]; + start.Bit = 0; + int bitCount = cacheReader.ByteCount; + if (count > 1) + { + int cacheLength = 0;//缓冲区的大小 + int cacheIndexStart = 0; + int startIndex = 0; + DeviceAddress segmentEnd, tagAddress; + DeviceAddress segmentStart = start; + for (int j = 1, i = 1; i < count; i++, j++) + { + tagAddress = addrsArr[i];//当前变量地址 + int offset1 = cacheReader.GetOffset(tagAddress, segmentStart); + if (offset1 > (PDU / cacheReader.ByteCount)) + { + segmentEnd = addrsArr[i - 1]; + int len = cacheReader.GetOffset(segmentEnd, segmentStart); + len += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + tagAddress.CacheIndex = (ushort)(cacheIndexStart + len); + addrsArr[i] = tagAddress; + rangeList.Add(new PDUArea(segmentStart, len, startIndex, j)); + startIndex += j; j = 0; + cacheLength += len;//更新缓存长度 + cacheIndexStart = cacheLength; + segmentStart = tagAddress;//更新数据片段的起始地址 + } + else + { + tagAddress.CacheIndex = (ushort)(cacheIndexStart + offset1); + addrsArr[i] = tagAddress; + } + if (i == count - 1) + { + segmentEnd = addrsArr[i]; + int segmentLength = cacheReader.GetOffset(segmentEnd, segmentStart); + if (segmentLength > PDU / cacheReader.ByteCount) + { + segmentEnd = addrsArr[i - 1]; + segmentLength = segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + } + tagAddress.CacheIndex = (ushort)(cacheIndexStart + segmentLength); + addrsArr[i] = tagAddress; + segmentLength += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + rangeList.Add(new PDUArea(segmentStart, segmentLength, startIndex, j + 1)); + cacheLength += segmentLength; + } + } + cacheReader.Size = cacheLength; + } + else + cacheReader.Size = start.DataSize <= bitCount ? 1 : start.DataSize / bitCount;//改变Cache的Size属性值将创建Cache的内存区域 + } + return rangeList; + } + + //调用前应对地址数组排序(是否加锁?) + public static ItemData[] PLCReadMultiple(this IPLCDriver plc, ICache cache, DeviceAddress[] addrsArr) + { + if (addrsArr == null || cache == null || addrsArr.Length == 0) return null; + int len = addrsArr.Length; + ItemData[] items = new ItemData[len]; + int offset = 0; long now = DateTime.Now.ToFileTime(); + List areas = cache.AssignFromPDU(plc.PDU, addrsArr); + foreach (PDUArea area in areas) + { + byte[] rcvBytes = plc.ReadBytes(area.Start, (ushort)area.Len); + Buffer.BlockCopy(rcvBytes, 0, cache.Cache, offset, rcvBytes.Length); + offset += rcvBytes.Length / cache.ByteCount; + } + for (int i = 0; i < len; i++) + { + switch (addrsArr[i].VarType) + { + case DataType.BOOL: + items[i].Value.Boolean = cache.ReadBit(addrsArr[i]).Value; + break; + case DataType.BYTE: + items[i].Value.Byte = cache.ReadByte(addrsArr[i]).Value; + break; + case DataType.WORD: + case DataType.SHORT: + items[i].Value.Int16 = cache.ReadInt16(addrsArr[i]).Value; + break; + case DataType.TIME: + case DataType.INT: + items[i].Value.Int32 = cache.ReadInt32(addrsArr[i]).Value; + break; + case DataType.FLOAT: + items[i].Value.Single = cache.ReadFloat(addrsArr[i]).Value; + break; + case DataType.STR: + var item = cache.ReadString(addrsArr[i], addrsArr[i].DataSize); + break; + } + items[i].Quality = QUALITIES.QUALITY_GOOD; + items[i].TimeStamp = now; + } + return items; + } + + public static int PLCWriteMultiple(this IPLCDriver plc, ICache cache, DeviceAddress[] addrArr, object[] buffer, int limit) + { + if (cache == null || addrArr == null || buffer == null || addrArr.Length != buffer.Length) return -1; + if (addrArr.Length == 1) return plc.WriteValue(addrArr[0], buffer[0]); + lock (plc)//不锁定会有并发冲突问题;锁定也不能保障绝对安全,如有人现场操作会导致数据刷新 + { + List areas = cache.AssignFromPDU(plc.PDU, addrArr); + int offset = 0; + foreach (PDUArea area in areas) + { + byte[] rcvBytes = plc.ReadBytes(area.Start, (ushort)area.Len); + if (rcvBytes == null) return -1; + Buffer.BlockCopy(rcvBytes, 0, cache.Cache, offset, rcvBytes.Length); + offset += rcvBytes.Length / cache.ByteCount; + } + DeviceAddress start = addrArr[0]; + int startIndex = 0; + int endIndex = 0; + while (endIndex < addrArr.Length) + { + if (start.Area != addrArr[endIndex].Area || start.DBNumber != addrArr[endIndex].DBNumber || endIndex - startIndex >= limit) + { + for (int i = startIndex; i < endIndex; i++) + { + cache.WriteValue(addrArr[i], buffer[i]); + } + int c1 = start.CacheIndex; int c2 = addrArr[endIndex - 1].CacheIndex; + byte[] bytes = new byte[cache.ByteCount * (c2 - c1 + 1)]; + Buffer.BlockCopy(cache.Cache, c1, bytes, 0, bytes.Length); + if (plc.WriteBytes(start, bytes) < 0) return -1; + start = addrArr[endIndex]; + startIndex = endIndex; + } + endIndex++; + } + } + return 0; + } + /// + /// string RightFrom + /// + /// + /// + /// + public static string RightFrom(this string text, int index) + { + return text.Substring(index + 1, text.Length - index - 1); + } + + public static string Right(this string text, int length) + { + return text.Substring(text.Length - length, length); + } + + /// + /// Convert to Datetime + /// + /// + /// + + public static DateTime ToDateTime(this long filetime) + { + return filetime == 0 ? DateTime.Now : DateTime.FromFileTime(filetime); + } + + public static Type ToType(this DataType dataType) + { + switch (dataType) + { + case DataType.BOOL: + return typeof(bool); + case DataType.BYTE: + return typeof(byte); + case DataType.WORD: + return typeof(ushort); + case DataType.SHORT: + return typeof(short); + case DataType.INT: + case DataType.TIME: + return typeof(int); + case DataType.FLOAT: + return typeof(float); + case DataType.STR: + return typeof(string); + default: + return typeof(object); + } + } + + public static string ToFormatString(this int num, int len) + { + string str = num.ToString(); + int off = len - str.Length; + return off > 0 ? string.Concat(new string('0', off), str) : str; + } + + public static bool IsEquals(this byte[] b1, byte[] b2) + { + if (b1 == null || b2 == null) return false; + if (b1.Length != b2.Length) return false; + for (int i = 0; i < b1.Length; i++) + if (b1[i] != b2[i]) + return false; + return true; + } + + public static string ConvertToString(this byte[] bits) + { + char[] chars = new char[bits.Length]; + for (int i = 0; i < bits.Length; i++) + { + chars[i] = (char)bits[i]; + } + return new string(chars); + } + + public static byte[] ConvertToArray(this string bits) + { + var chars = bits.ToCharArray(); + byte[] arr = new byte[chars.Length]; + for (int i = 0; i < chars.Length; i++) + { + arr[i] = (byte)chars[i]; + } + return arr; + } + + public static int BitSwap(this byte bit) + { + return (bit < 8 ? bit + 8 : bit - 8); + } + + [Obsolete] + public static Storage ToStorage(this ITag tag, object obj) + { + Storage value = Storage.Empty; + var str = obj as string; + switch (tag.Address.VarType) + { + case DataType.BOOL: + value.Boolean = str == null ? Convert.ToBoolean(obj) : str == "0" ? false : str == "1" ? true : bool.Parse(str); + break; + case DataType.BYTE: + value.Byte = Convert.ToByte(obj); + break; + case DataType.WORD: + case DataType.SHORT: + value.Int16 = Convert.ToInt16(obj); + break; + case DataType.TIME: + case DataType.INT: + value.Int32 = Convert.ToInt32(obj); + break; + case DataType.FLOAT: + value.Single = Convert.ToSingle(obj); + break; + } + return value; + } + + public static byte[] ToByteArray(this ITag tag) + { + switch (tag.Address.VarType) + { + case DataType.BOOL: + return new byte[] { tag.Value.Boolean ? (byte)1 : (byte)0 }; + case DataType.BYTE: + return new byte[] { tag.Value.Byte }; + case DataType.WORD: + case DataType.SHORT: + return BitConverter.GetBytes(tag.Value.Int16); + case DataType.INT: + return BitConverter.GetBytes(tag.Value.Int32); + case DataType.FLOAT: + return BitConverter.GetBytes(tag.Value.Single); + case DataType.STR: + return Encoding.ASCII.GetBytes(tag.ToString()); + default: + return new byte[0]; + } + } + + public static byte[] ToByteArray(this ITag tag, Storage value) + { + switch (tag.Address.VarType) + { + case DataType.BOOL: + return new byte[] { value.Boolean ? (byte)1 : (byte)0 }; + case DataType.BYTE: + return new byte[] { value.Byte }; + case DataType.WORD: + case DataType.SHORT: + return BitConverter.GetBytes(value.Int16); + case DataType.INT: + return BitConverter.GetBytes(value.Int32); + case DataType.FLOAT: + return BitConverter.GetBytes(value.Single); + case DataType.STR: + return Encoding.ASCII.GetBytes(tag.ToString()); + default: + return new byte[0]; + } + } + + public static object GetValue(this ITag tag, Storage value) + { + switch (tag.Address.VarType) + { + case DataType.BOOL: + return value.Boolean; + case DataType.BYTE: + return value.Byte; + case DataType.WORD: + case DataType.SHORT: + return value.Int16; + case DataType.TIME: + case DataType.INT: + return value.Int32; + case DataType.FLOAT: + return value.Single; + case DataType.STR: + return tag.ToString(); + default: + return null; + } + } + + public static float ValueToScale(this ITag tag, float value) + { + IDataServer srv = tag.Parent.Server; + int ind = srv.GetScaleByID(tag.ID); + Scaling meta = ind < 0 ? Scaling.Empty : srv.ScalingList[ind]; + if (meta.ScaleType == ScaleType.None) + { + return value; + } + else + { + double temp = (value - meta.EULo) / (meta.EUHi - meta.EULo); + if (meta.ScaleType == ScaleType.SquareRoot) + temp = temp * temp; + return (meta.RawHi - meta.RawLo) * (float)temp + meta.RawLo; + } + } + + public static float ScaleToValue(this ITag tag, Storage value) + { + DataType type = tag.Address.VarType; + if (type == DataType.BOOL) return value.Boolean ? 1f : 0f; + IDataServer srv = tag.Parent.Server; + int ind = srv.GetScaleByID(tag.ID); + Scaling meta = ind < 0 ? Scaling.Empty : srv.ScalingList[ind]; + if (meta.ScaleType == ScaleType.None) + { + switch (type) + { + case DataType.BYTE: + return (float)value.Byte; + case DataType.WORD: + case DataType.SHORT: + return (float)value.Int16; + case DataType.INT: + return (float)value.Int32; + case DataType.FLOAT: + return value.Single; + case DataType.STR: + return float.Parse(tag.ToString()); + default: + return 0f; + } + } + else + { + double temp; + switch (type) + { + case DataType.BYTE: + temp = (value.Byte - meta.RawLo) / (meta.RawHi - meta.RawLo); + break; + case DataType.WORD: + case DataType.SHORT: + temp = (value.Int16 - meta.RawLo) / (meta.RawHi - meta.RawLo); + break; + case DataType.INT: + temp = (value.Int32 - meta.RawLo) / (meta.RawHi - meta.RawLo); + break; + case DataType.FLOAT: + temp = (value.Single - meta.RawLo) / (meta.RawHi - meta.RawLo); + break; + default: + return 0f; + } + if (meta.ScaleType == ScaleType.SquareRoot) + temp = Math.Sqrt(temp); + return (meta.EUHi - meta.EULo) * (float)temp + meta.EULo; + } + } + + public static float ScaleToValue(this ITag tag) + { + return ScaleToValue(tag, tag.Value); + } + + public static string GetTagName(this ITag tag) + { + IDataServer srv = tag.Parent.Server; + int ind = srv.GetItemProperties(tag.ID); + return ind < 0 ? null : srv.MetaDataList[ind].Name; + } + + public static string GetTagName(this IDataServer srv, short id) + { + int ind = srv.GetItemProperties(id); + return ind < 0 ? null : srv.MetaDataList[ind].Name; + } + + public static TagMetaData GetMetaData(this ITag tag) + { + IDataServer srv = tag.Parent.Server; + int index = srv.GetItemProperties(tag.ID); + return index < 0 ? new TagMetaData() : srv.MetaDataList[index]; + } + } + + public static class Utility + { + private static readonly ushort[] crcTable = { + 0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241, + 0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440, + 0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40, + 0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841, + 0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40, + 0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41, + 0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641, + 0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040, + 0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240, + 0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441, + 0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41, + 0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840, + 0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41, + 0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40, + 0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640, + 0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041, + 0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240, + 0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441, + 0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41, + 0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840, + 0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41, + 0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40, + 0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640, + 0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041, + 0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241, + 0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440, + 0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40, + 0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841, + 0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40, + 0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41, + 0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641, + 0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 + }; + + /// + /// Converts an array of bytes to an ASCII byte array + /// + /// The byte array + /// An array of ASCII byte values + public static string GetAsciiBytes(byte[] numbers) + { + string str = string.Empty; + for (int i = 0; i < numbers.Length; i++) + { + str += numbers[i].ToString("X2"); + } + return str; + } + + /// + /// Converts an array of UInt16 to an ASCII byte array + /// + /// The ushort array + /// An array of ASCII byte values + public static string GetAsciiBytes(ushort[] numbers) + { + string str = string.Empty; + for (int i = 0; i < numbers.Length; i++) + { + str += numbers[i].ToString("X4"); + } + return str; + } + + /// + /// Converts a network order byte array to an array of UInt16 values in host order + /// + /// The network order byte array + /// The host order ushort array + public static ushort[] NetworkBytesToHostUInt16(byte[] networkBytes) + { + if (networkBytes == null) + throw new ArgumentNullException("networkBytes"); + + if (networkBytes.Length % 2 != 0) + throw new FormatException("NetworkBytesNotEven"); + + ushort[] result = new ushort[networkBytes.Length / 2]; + + for (int i = 0; i < result.Length; i++) + result[i] = (ushort)IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkBytes, i * 2)); + + return result; + } + + /// + /// Converts a hex string to a byte array. + /// + /// The hex string + /// Array of bytes + public static byte[] HexToBytes(string hex) + { + if (string.IsNullOrEmpty(hex)) + throw new ArgumentNullException("hex"); + + if (hex.Length % 2 != 0) + throw new FormatException("HexCharacterCountNotEven"); + + byte[] bytes = new byte[hex.Length / 2]; + + for (int i = 0; i < bytes.Length; i++) + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + + return bytes; + } + + /// + /// Calculate Longitudinal Redundancy Check. + /// + /// The data used in LRC + /// LRC value + public static byte CalculateLrc(byte[] data, int len = 0) + { + if (data == null) + throw new ArgumentNullException("data"); + if (len == 0) len = data.Length; + byte lrc = 0; + for (int i = 0; i < len; i++) + { lrc += data[i]; } + + lrc = (byte)((lrc ^ 0xFF) + 1); + + return lrc; + } + + /// + /// Calculate Cyclical Redundancy Check + /// + /// The data used in CRC + /// CRC value + public static byte[] CalculateCrc(byte[] data, int len = 0) + { + if (data == null) + throw new ArgumentNullException("data"); + if (len == 0) len = data.Length; + ushort crc = ushort.MaxValue; + for (int i = 0; i < len; i++) + { + byte tableIndex = (byte)(crc ^ data[i]); + crc >>= 8; + crc ^= crcTable[tableIndex]; + } + + return BitConverter.GetBytes(crc); + } + + public static bool CheckSumCRC(byte[] frame) + { + int len = frame.Length; + byte[] chk = CalculateCrc(frame, len - 2); + return (chk[0] == frame[len - 2] && chk[1] == frame[len - 1]); + } + + public static unsafe short NetToInt16(byte[] value, int startIndex) + { + if (value == null || startIndex > value.Length - 2) + { + throw new NotImplementedException(); + } + fixed (byte* numRef = &(value[startIndex])) + { + return (short)((numRef[0] << 8) | numRef[1]); + } + } + + public static unsafe int NetToInt32(byte[] value, int startIndex) + { + if (value == null || startIndex > value.Length - 4) + { + throw new NotImplementedException(); + } + fixed (byte* numRef = &(value[startIndex])) + { + return (int)((numRef[0] << 24) | (numRef[1] << 16) | (numRef[2] << 8) | numRef[3]); + } + } + + public static unsafe float NetToSingle(byte[] value, int startIndex) + { + int a = NetToInt32(value, startIndex); + return *(float*)&a; + } + + public static string ConvertToString(byte[] bytes, int start = 0, int len = 0) + { + //西门子300、400 + if (bytes == null || bytes.Length == 0) + return string.Empty; + var klen = bytes[start + 1]; + return Encoding.ASCII.GetString(bytes, start + 2, klen).Trim((char)0); + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/HistoryData.cs b/SCADA/Program/CoreApp/DataService/DataService/HistoryData.cs new file mode 100644 index 0000000..fd00199 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/HistoryData.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +namespace DataService +{ + public struct HistoryData : IComparable + { + public short ID; + public QUALITIES Quality; + public Storage Value; + public DateTime TimeStamp; + + public HistoryData(short id, QUALITIES qualitie, Storage value, DateTime timeStamp) + { + ID = id; + Quality = qualitie; + Value = value; + TimeStamp = timeStamp; + } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is HistoryData) + { + return this == (HistoryData)obj; + } + else return false; + } + + public override int GetHashCode() + { + return ID.GetHashCode() ^ TimeStamp.GetHashCode(); + } + + + public static bool operator ==(HistoryData x, HistoryData y) + { + return x.ID == y.ID && x.TimeStamp == y.TimeStamp; + } + + public static bool operator !=(HistoryData x, HistoryData y) + { + return x.ID != y.ID || x.TimeStamp != y.TimeStamp; + } + + public static readonly HistoryData Empty = new HistoryData(); + + public int CompareTo(HistoryData other) + { + int comp = this.TimeStamp.CompareTo(other.TimeStamp); + return comp == 0 ? this.ID.CompareTo(other.ID) : comp; + } + } + + public struct FileData : IComparable + { + public short ID; + public Storage Value; + public string Text; + + public FileData(short id, Storage value, string text) + { + ID = id; + Value = value; + Text = text; + } + + public int CompareTo(FileData other) + { + return this.ID.CompareTo(other.ID); + } + } + + public class CompareHistoryData : IComparer + { + public int Compare(HistoryData x, HistoryData y) + { + int c1 = x.TimeStamp.CompareTo(y.TimeStamp); + return c1 == 0 ? x.ID.CompareTo(y.ID) : c1; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/IGroup.cs b/SCADA/Program/CoreApp/DataService/DataService/IGroup.cs new file mode 100644 index 0000000..a3928d4 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/IGroup.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; + +namespace DataService +{ + public interface IGroup : IDisposable + { + bool IsActive { get; set; } + short ID { get; } + int UpdateRate { get; set; } + float DeadBand { get; set; } + string Name { get; set; } + IDriver Parent { get; } + IDataServer Server { get; } + IEnumerable Items { get; } + bool AddItems(IList items); + bool AddTags(IEnumerable tags); + bool RemoveItems(params ITag[] items); + bool SetActiveState(bool active, params short[] items); + ITag FindItemByAddress(DeviceAddress addr); + HistoryData[] BatchRead(DataSource source, bool isSync, params ITag[] itemArray); + int BatchWrite(SortedDictionary items, bool isSync = true); + ItemData ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache); + ItemData ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache); + ItemData ReadByte(DeviceAddress address, DataSource source = DataSource.Cache); + ItemData ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache); + ItemData ReadBool(DeviceAddress address, DataSource source = DataSource.Cache); + ItemData ReadString(DeviceAddress address, DataSource source = DataSource.Cache); + int WriteInt32(DeviceAddress address, int value); + int WriteInt16(DeviceAddress address, short value); + int WriteFloat(DeviceAddress address, float value); + int WriteString(DeviceAddress address, string value); + int WriteBit(DeviceAddress address, bool value); + int WriteBits(DeviceAddress address, byte value); + event DataChangeEventHandler DataChange; + } + + public class DataChangeEventArgs : EventArgs + { + public DataChangeEventArgs(int transactionID, IList pValues) + { + this.TransactionID = transactionID; + this.Values = pValues; + } + + public int TransactionID; + public IList Values; + } + + public class WriteCompleteEventArgs : EventArgs + { + public WriteCompleteEventArgs(int transactionID, short[] pIds, int[] errors) + { + this.TransactionID = transactionID; + this.Values = pIds; + this.Errors = errors; + } + + public int TransactionID; + public short[] Values; + public int[] Errors; + } + + public delegate void DataChangeEventHandler(object sender, DataChangeEventArgs e); + + public delegate void ReadCompleteEventHandler(object sender, DataChangeEventArgs e); + + public delegate void WriteCompleteEventHandler(object sender, WriteCompleteEventArgs e); + +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/IReader.cs b/SCADA/Program/CoreApp/DataService/DataService/IReader.cs new file mode 100644 index 0000000..2e28be0 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/IReader.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace DataService +{ + public interface IReaderWriter + { + byte[] ReadBytes(DeviceAddress address, ushort size); + ItemData ReadInt32(DeviceAddress address); + ItemData ReadInt16(DeviceAddress address); + ItemData ReadByte(DeviceAddress address); + ItemData ReadString(DeviceAddress address, ushort size); + ItemData ReadFloat(DeviceAddress address); + ItemData ReadBit(DeviceAddress address); + ItemData ReadValue(DeviceAddress address); + + int WriteBytes(DeviceAddress address, byte[] bit); + int WriteBit(DeviceAddress address, bool bit); + int WriteBits(DeviceAddress address, byte bits); + int WriteInt16(DeviceAddress address, short value); + int WriteInt32(DeviceAddress address, int value); + int WriteFloat(DeviceAddress address, float value); + int WriteString(DeviceAddress address, string str); + int WriteValue(DeviceAddress address, object value); + } + + + public interface ICache : IReaderWriter + { + int Size { get; set; } + int ByteCount { get; } + Array Cache { get; } + int GetOffset(DeviceAddress start, DeviceAddress end); + } + + public interface IMultiReadWrite + { + int Limit { get; } + ItemData[] ReadMultiple(DeviceAddress[] addrsArr); + int WriteMultiple(DeviceAddress[] addrArr, object[] buffer); + } + + public interface IDriver : IDisposable + { + short ID { get; } + string Name { get; } + string ServerName { get; set; }//可以考虑增加一个附加参数,Sever只定义本机名 + bool IsClosed { get; } + int TimeOut { get; set; } + IEnumerable Groups { get; } + IDataServer Parent { get; } + bool Connect(); + IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false); + bool RemoveGroup(IGroup group); + event ShutdownRequestEventHandler OnClose; + } + + public interface IPLCDriver : IDriver, IReaderWriter + { + int PDU { get; } + DeviceAddress GetDeviceAddress(string address); + string GetAddress(DeviceAddress address); + } + + public interface IFileDriver : IDriver, IReaderWriter + { + string FileName { get; set; } + FileData[] ReadAll(short groupId); + //bool RecieveData(string data); + } + + public class ShutdownRequestEventArgs : EventArgs + { + public ShutdownRequestEventArgs(string reson) + { + shutdownReason = reson; + } + public string shutdownReason; + } + + public delegate void ShutdownRequestEventHandler(object sender, ShutdownRequestEventArgs e); +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/IServer.cs b/SCADA/Program/CoreApp/DataService/DataService/IServer.cs new file mode 100644 index 0000000..4ad8815 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/IServer.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; + +namespace DataService +{ + //历史数据、报警数据的提交和数据均从服务器 + public interface IHDAServer : IDisposable + { + IEnumerable ReadAtTime(params DateTime[] timeStamps); + IEnumerable ReadAtTime(short ID, params DateTime[] timeStamps); + IEnumerable ReadRaw(DateTime start, DateTime end); + IEnumerable ReadRaw(DateTime start, DateTime end, short ID); + //IEnumerable ReadProcessed(DateTime start, DateTime end, HDAAggregate aggregates, params short[] aggregateIDs); + } + + public interface IAlarmServer : IDisposable + { + int DisableCondition(string sourceName, AlarmType type); + int EnableCondition(string sourceName, AlarmType type); + int RemoveConditon(string sourceName, AlarmType type); + int RemoveConditons(string sourceName); + int AckConditions(params ICondition[] conditions); + IList QueryConditions(string sourceName); + IEnumerable AlarmList { get; } + IList ConditionList { get; } + IList ActivedConditionList { get; } + } + + public interface IDataServer : IDisposable + { + ITag this[short id] { get; } + ITag this[string name] { get; } + ExpressionEval Eval { get; } + Object SyncRoot { get; }//对所有涉及集合更改项目使用;包括IGROUP的ADDITEMS + IList MetaDataList { get; } + IList ScalingList { get; } + IEnumerable Drivers { get; } + IEnumerable BrowseItems(BrowseType browseType, string tagName, DataType dataType); + IDriver AddDriver(short id, string name, string server, int timeOut, + string assembly, string className, string spare1, string spare2); + IGroup GetGroupByName(string name); + int GetScaleByID(short id); + int GetItemProperties(short id);//返回的是元数据在元数据列表的索引 + bool RemoveDriver(IDriver device); + bool AddItemIndex(string key, ITag value); + bool RemoveItemIndex(string key); + void ActiveItem(bool active, params ITag[] items); + int BatchWrite(Dictionary tags, bool sync); + HistoryData[] BatchRead(DataSource source, bool sync, params ITag[] itemArray); + } + + public class FCTCOMMAND + { + public const byte fctHead = 0xAB;//报头可加密,如报头不符,则不进行任何操作;客户端Socket发送报警请求,封装于Server + public const byte fctHdaIdRequest = 30; + public const byte fctHdaRequest = 31; + public const byte fctAlarmRequest = 32; + public const byte fctOrderChange = 33; + public const byte fctReset = 34; + public const byte fctXMLHead = 0xEE;//xml协议 + public const byte fctReadSingle = 1; + public const byte fctReadMultiple = 2; + public const byte fctWriteSingle = 5; + public const byte fctWriteMultiple = 15; + } + + public enum HDAAggregate + { + HDANoAggregate, + HDAInterpolative, + HDATotal, + HDAAverage, + HDATimeAverage, + HDACount, + HDAStDev, + HDAMinimumActualTime, + HDAMinimum, + HDAMaximumActualTime, + HDAMaximum, + HDAStart, + HDAEnd, + HDADelta, + HDARegSlope, + HDARegConst, + HDARegDev, + HDAVariance, + HDARange, + HDADurationGood, + HDADurationBad, + HDAPercentGood, + HDAPercentBad, + HDAWorstQuality, + HDAAnnotations, + } + + public enum BrowseType + { + Branch = 1, + Leaf = 2, + Flat = 3 + } +} \ No newline at end of file diff --git a/SCADA/Program/CoreApp/DataService/DataService/ITag.cs b/SCADA/Program/CoreApp/DataService/DataService/ITag.cs new file mode 100644 index 0000000..a08c8df --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/ITag.cs @@ -0,0 +1,633 @@ +using System; + +namespace DataService +{ + public abstract class ITag : IComparable + { + protected short _id; + public short ID + { + get + { + return _id; + } + } + + protected bool _active = true; + public bool Active + { + get + { + return _active; + } + set + { + _group.SetActiveState(value, _id); + _active = value; + } + } + + protected QUALITIES _quality; + public QUALITIES Quality + { + get + { + return _quality; + } + } + + protected Storage _value; + public Storage Value + { + get + { + return _value; + } + } + + protected DateTime _timeStamp = DateTime.MinValue; + public DateTime TimeStamp + { + get + { + return _timeStamp; + } + } + + protected DeviceAddress _plcAddress; + public DeviceAddress Address + { + get + { + return _plcAddress; + } + set + { + _plcAddress = value; + } + } + + protected IGroup _group; + public IGroup Parent + { + get + { + return _group; + } + } + + protected ITag(short id, DeviceAddress address, IGroup group) + { + _id = id; + _group = group; + _plcAddress = address; + } + + public void Update(Storage newvalue, DateTime timeStamp, QUALITIES quality) + { + if (_timeStamp > timeStamp) return;//如果时间戳更旧或值未改变 + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(quality, _value, newvalue, _timeStamp, timeStamp)); + } + _timeStamp = timeStamp; + _quality = quality; + if (quality == QUALITIES.QUALITY_GOOD) + { + _value = newvalue; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(_value)); + } + } + } + + public abstract bool Refresh(DataSource source = DataSource.Device); + + public abstract Storage Read(DataSource source = DataSource.Cache); + + protected abstract int InnerWrite(Storage value); + + public abstract int Write(object value); + + public int Write(Storage value, bool bForce) + { + DateTime time = DateTime.Now; + _timeStamp = time; + if (bForce) + { + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(QUALITIES.QUALITY_GOOD, _value, value, _timeStamp, time)); + } + } + int result = InnerWrite(value); + if (bForce || result != 0) + { + var data = Read(DataSource.Device); + if (data != value) + { + time = DateTime.Now; + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(QUALITIES.QUALITY_GOOD, _value, data, _timeStamp, time)); + } + _value = data; + _timeStamp = time; + return result; + } + } + return 0; + } + + public ValueChangingEventHandler ValueChanging; + + public ValueChangedEventHandler ValueChanged; + + #region IComparable Members + + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + public int CompareTo(ITag other) + { + return _plcAddress.CompareTo(other._plcAddress); + } + + #endregion + } + + public sealed class BoolTag : ITag + { + public BoolTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + #region IDevice Members + + public override bool Refresh(DataSource source = DataSource.Cache) + { + var _newvalue = _group.ReadBool(_plcAddress, source); + if (_newvalue.Value != _value.Boolean) + { + Storage value = Storage.Empty; + value.Boolean = _newvalue.Value; + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _value = value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + Storage value = Storage.Empty; + value.Boolean = _group.ReadBool(_plcAddress, source).Value; + return value; + } + + public override int Write(object value) + { + if (value == null) return -1; + bool temp = _value.Boolean; + var str = value as string; + if (str == null) + temp = Convert.ToBoolean(value); + else if (!Boolean.TryParse(str, out temp)) + return -1; + _timeStamp = DateTime.Now; + return _group.WriteBit(_plcAddress, temp); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteBit(_plcAddress, value.Boolean); + } + + #endregion + + public override string ToString() + { + return _value.Boolean.ToString(); + } + } + + public sealed class ByteTag : ITag + { + + public ByteTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + + #region IDevice Members + public override bool Refresh(DataSource source = DataSource.Device) + { + var _newvalue = _group.ReadByte(_plcAddress, source); + if (_newvalue.Value != _value.Byte) + { + Storage value = Storage.Empty; + value.Byte = _newvalue.Value; + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _value = value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + Storage value = Storage.Empty; + value.Byte = _group.ReadByte(_plcAddress, source).Value; + return value; + } + + public override int Write(object value) + { + byte temp = _value.Byte; + var str = value as string; + if (str == null) + temp = Convert.ToByte(value); + else if (!Byte.TryParse(str, out temp)) + return -1; + _timeStamp = DateTime.Now; + return _group.WriteBits(_plcAddress, temp); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteBits(_plcAddress, value.Byte); + } + + #endregion + + public override string ToString() + { + return _value.Byte.ToString(); + } + } + + public sealed class ShortTag : ITag + { + + public ShortTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + + #region IDevice Members + public override bool Refresh(DataSource source = DataSource.Device) + { + var _newvalue = _group.ReadInt16(_plcAddress, source); + if (_newvalue.Value != _value.Int16) + { + Storage value = Storage.Empty; + value.Int16 = _newvalue.Value; + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _value = value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + Storage value = Storage.Empty; + value.Int16 = _group.ReadInt16(_plcAddress, source).Value; + return value; + } + + public override int Write(object value) + { + var temp = _value.Int16; + var str = value as string; + if (str == null) + temp = Convert.ToInt16(value); + else if (!short.TryParse(str, out temp)) + return -1; + _timeStamp = DateTime.Now; + return _group.WriteInt16(_plcAddress, temp); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteInt16(_plcAddress, value.Int16); + } + + #endregion + + public override string ToString() + { + return _value.Int16.ToString(); + } + } + + public sealed class IntTag : ITag + { + + public IntTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + #region IDevice Members + public override bool Refresh(DataSource source = DataSource.Device) + { + var _newvalue = _group.ReadInt32(_plcAddress, source); + if (_newvalue.Value != _value.Int32) + { + Storage value = Storage.Empty; + value.Int32 = _newvalue.Value; + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _value = value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + Storage value = Storage.Empty; + value.Int32 = _group.ReadInt32(_plcAddress, source).Value; + return value; + } + + public override int Write(object value) + { + var temp = _value.Int32; + var str = value as string; + if (str == null) + temp = Convert.ToInt32(value); + else if (!int.TryParse(str, out temp)) + return -1; + _timeStamp = DateTime.Now; + return _group.WriteInt32(_plcAddress, temp); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteInt32(_plcAddress, value.Int32); + } + + #endregion + + public override string ToString() + { + return _value.Int32.ToString(); + } + } + + public sealed class FloatTag : ITag + { + + public FloatTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + + #region IDevice Members + public override bool Refresh(DataSource source = DataSource.Device) + { + var _newvalue = _group.ReadFloat(_plcAddress, source); + if (_newvalue.Value != _value.Single) + { + Storage value = Storage.Empty; + value.Single = _newvalue.Value; + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _value = value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + Storage value = Storage.Empty; + value.Single = _group.ReadFloat(_plcAddress, source).Value; + return value; + } + + public override int Write(object value) + { + var temp = _value.Single; + var str = value as string; + if (str == null) + temp = Convert.ToSingle(value); + else if (!float.TryParse(str, out temp)) + return -1; + _timeStamp = DateTime.Now; + return _group.WriteFloat(_plcAddress, temp); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteFloat(_plcAddress, value.Single); + } + + #endregion + + public override string ToString() + { + return _value.Single.ToString(); + } + } + + public sealed class StringTag : ITag + { + string _str; + public string String + { + get + { + return _str; + } + set + { + _str = value; + } + } + + public StringTag(short id, DeviceAddress addr, IGroup group) + : base(id, addr, group) + { + } + + + #region IDevice Members + public override bool Refresh(DataSource source = DataSource.Device) + { + var _newvalue = _group.ReadString(_plcAddress, source); + if (_newvalue.Value != _str) + { + DateTime time = _newvalue.TimeStamp.ToDateTime(); + if (ValueChanging != null) + { + ValueChanging(this, new ValueChangingEventArgs(_newvalue.Quality, _value, _value, _timeStamp, time)); + } + _timeStamp = time; + _quality = _newvalue.Quality; + if (_quality == QUALITIES.QUALITY_GOOD) + { + _str = _newvalue.Value; + if (ValueChanged != null) + { + ValueChanged(this, new ValueChangedEventArgs(_value)); + } + } + return true; + } + return false; + } + + public override Storage Read(DataSource source = DataSource.Cache) + { + var value = _group.ReadString(_plcAddress, source); + if (value.Quality == QUALITIES.QUALITY_GOOD) + _str = value.Value; + return Storage.Empty; + } + + public override int Write(object value) + { + if (value == null) return -1; + var str = (value is String) ? (String)value : value.ToString(); + _timeStamp = DateTime.Now; + return _group.WriteString(_plcAddress, str); + } + + protected override int InnerWrite(Storage value) + { + return _group.WriteString(_plcAddress, _str); + } + + #endregion + + public override string ToString() + { + return _str ?? string.Empty; + } + } + + public enum DataSource + { + Cache = 1, + Device = 2 + } + + public enum DataType : byte + { + NONE = 0, + BOOL = 1, + BYTE = 3, + SHORT = 4, + WORD = 5, + TIME = 6, + INT = 7, + FLOAT = 8, + SYS = 9, + STR = 11 + } + + public delegate void ValueChangingEventHandler(object sender, ValueChangingEventArgs e); + public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e); + + public class ValueChangingEventArgs : EventArgs + { + public ValueChangingEventArgs(QUALITIES quality, T oldValue, T newValue, DateTime oldTime, DateTime newTime) + { + this.Quality = quality; + this.OldValue = oldValue; + this.NewValue = newValue; + this.OldTimeStamp = oldTime; + this.NewTimeStamp = newTime; + } + + public QUALITIES Quality; + public T OldValue; + public T NewValue; + public DateTime OldTimeStamp; + public DateTime NewTimeStamp; + } + + public class ValueChangedEventArgs : EventArgs + { + public ValueChangedEventArgs(Storage value) + { + this.Value = value; + } + + public Storage Value; + } + +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/PLCGroup.cs b/SCADA/Program/CoreApp/DataService/DataService/PLCGroup.cs new file mode 100644 index 0000000..07bbd7e --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/PLCGroup.cs @@ -0,0 +1,684 @@ +using System; +using System.Collections.Generic; +using System.Timers; + +namespace DataService +{ + public class PLCGroup : IGroup + { + protected Timer _timer; + + protected bool _isActive; + public virtual bool IsActive + { + get + { + return _isActive; + } + set + { + _isActive = value; + if (value) + { + //_timer.Start((uint)_updateRate, true); + //_timer.Timer += new EventHandler(timer_Timer); + _timer.Interval = _updateRate; + _timer.Elapsed += new ElapsedEventHandler(timer_Timer); + _timer.Start(); + } + else + { + _timer.Elapsed -= new ElapsedEventHandler(timer_Timer); + _timer.Stop(); + } + } + } + + protected short _id; + public short ID + { + get + { + return _id; + } + } + + protected int _updateRate; + public int UpdateRate + { + get + { + return _updateRate; + } + set + { + _updateRate = value; + } + } + + protected string _name; + public string Name + { + get + { + return _name; + } + set + { + _name = value; + } + } + + protected float _deadband; + public float DeadBand + { + get + { + return _deadband; + } + set + { + _deadband = value; + } + } + + + protected ICache _cacheReader; + + protected IPLCDriver _plcReader; + public IDriver Parent + { + get + { + return _plcReader; + } + } + + protected List _changedList; + public List ChangedList + { + get + { + return _changedList; + } + } + + + protected List _items; + public IEnumerable Items + { + get { return _items; } + } + + protected IDataServer _server; + public IDataServer Server + { + get + { + return _server; + } + } + + protected List _rangeList = new List(); + + protected PLCGroup() + { + } + + public PLCGroup(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._changedList = new List(); + this._cacheReader = new ByteCacheReader(); + this._timer = new Timer(); + } + + public bool AddItems(IList items) + { + int count = items.Count; + if (_items == null) _items = new List(count); + lock (_server.SyncRoot) + { + for (int i = 0; i < count; i++) + { + ITag dataItem = null; + TagMetaData meta = items[i]; + if (meta.GroupID == this._id) + { + DeviceAddress addr = _plcReader.GetDeviceAddress(meta.Address); + if (addr.DataSize == 0) addr.DataSize = meta.Size; + if (addr.VarType == DataType.NONE) addr.VarType = meta.DataType; + if (addr.VarType != DataType.BOOL) addr.Bit = 0; + switch (meta.DataType) + { + case DataType.BOOL: + dataItem = new BoolTag(meta.ID, addr, this); + break; + case DataType.BYTE: + dataItem = new ByteTag(meta.ID, addr, this); + break; + case DataType.WORD: + case DataType.SHORT: + dataItem = new ShortTag(meta.ID, addr, this); + break; + case DataType.TIME: + case DataType.INT: + dataItem = new IntTag(meta.ID, addr, this); + break; + case DataType.FLOAT: + dataItem = new FloatTag(meta.ID, addr, this); + break; + case DataType.STR: + dataItem = new StringTag(meta.ID, addr, this); + break; + } + if (dataItem != null) + { + //dataItem.Active = meta.Active; + _items.Add(dataItem); + _server.AddItemIndex(meta.Name, dataItem); + } + } + } + } + _items.TrimExcess(); + _items.Sort(); + UpdatePDUArea(); + return true; + } + + public bool AddTags(IEnumerable tags) + { + if (_items == null) + { + _items = new List(); + } + foreach (ITag tag in tags) + { + if (tag != null) + { + _items.Add(tag); + } + } + _items.TrimExcess(); + _items.Sort(); + UpdatePDUArea(); + return true; + } + + public bool RemoveItems(params ITag[] items) + { + foreach (var item in items) + { + _server.RemoveItemIndex(item.GetTagName()); + _items.Remove(item); + } + UpdatePDUArea(); + return true; + } + + protected void UpdatePDUArea() + { + int count = _items.Count; + if (count > 0) + { + DeviceAddress _start = _items[0].Address; + _start.Bit = 0; + int bitCount = _cacheReader.ByteCount; + if (count > 1) + { + int cacheLength = 0;//缓冲区的大小 + int cacheIndexStart = 0; + int startIndex = 0; + DeviceAddress segmentEnd = DeviceAddress.Empty; + DeviceAddress tagAddress = DeviceAddress.Empty; + DeviceAddress segmentStart = _start; + for (int j = 1, i = 1; i < count; i++, j++) + { + tagAddress = _items[i].Address;//当前变量地址 + int offset1 = _cacheReader.GetOffset(tagAddress, segmentStart); + if (offset1 > (_plcReader.PDU - tagAddress.DataSize) / bitCount) + { + segmentEnd = _items[i - 1].Address; + int len = _cacheReader.GetOffset(segmentEnd, segmentStart); + len += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + tagAddress.CacheIndex = (ushort)(cacheIndexStart + len); + _items[i].Address = tagAddress; + _rangeList.Add(new PDUArea(segmentStart, len, startIndex, j)); + startIndex += j; j = 0; + cacheLength += len;//更新缓存长度 + cacheIndexStart = cacheLength; + segmentStart = tagAddress;//更新数据片段的起始地址 + } + else + { + tagAddress.CacheIndex = (ushort)(cacheIndexStart + offset1); + _items[i].Address = tagAddress; + } + if (i == count - 1) + { + segmentEnd = _items[i].Address; + int segmentLength = _cacheReader.GetOffset(segmentEnd, segmentStart); + if (segmentLength > (_plcReader.PDU - segmentEnd.DataSize) / bitCount) + { + segmentEnd = _items[i - 1].Address; + segmentLength = segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + } + tagAddress.CacheIndex = (ushort)(cacheIndexStart + segmentLength); + _items[i].Address = tagAddress; + segmentLength += segmentEnd.DataSize <= bitCount ? 1 : segmentEnd.DataSize / bitCount; + _rangeList.Add(new PDUArea(segmentStart, segmentLength, startIndex, j + 1)); + cacheLength += segmentLength; + } + } + _cacheReader.Size = cacheLength; + } + else + _cacheReader.Size = _start.DataSize <= bitCount ? 1 : _start.DataSize / bitCount;//改变Cache的Size属性值将创建Cache的内存区域 + } + } + + public ITag FindItemByAddress(DeviceAddress addr) + { + int index = _items.BinarySearch(new BoolTag(0, addr, null)); + return index < 0 ? null : _items[index]; + } + + public bool SetActiveState(bool active, params short[] items) + { + return false; + } + + object sync = new object(); + protected void timer_Timer(object sender, EventArgs e) + { + if (_isActive) + { + lock (sync) + { + Poll(); + if (_changedList.Count > 0) + Update(); + } + } + else + return; + } + + protected virtual int Poll() + { + if (_plcReader.IsClosed) return -1; + byte[] cache = (byte[])_cacheReader.Cache; + int offset = 0; + foreach (PDUArea area in _rangeList) + { + byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 + if (rcvBytes == null) + { + //_plcReader.Connect(); + return -1; + } + else + { + int index = area.StartIndex;//index指向_items中的Tag元数据 + int count = index + area.Count; + while (index < count) + { + DeviceAddress addr = _items[index].Address; + int iByte = addr.CacheIndex; + int iByte1 = iByte - offset; + if (addr.VarType == DataType.BOOL) + { + int tmp = rcvBytes[iByte1] ^ cache[iByte]; + 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 + { + ushort size = addr.DataSize; + for (int i = 0; i < size; i++) + { + if (rcvBytes[iByte1 + i] != cache[iByte + i]) + { + _changedList.Add(index); + break; + } + } + index++; + } + } + for (int j = 0; j < rcvBytes.Length; j++) + cache[j + offset] = rcvBytes[j];//将PLC读取的数据写入到CacheReader中 + } + offset += rcvBytes.Length; + } + return 1; + } + + protected void Update() + { + DateTime dt = DateTime.Now; + if (DataChange != null) + { + HistoryData[] values = new HistoryData[_changedList.Count]; + int i = 0; + foreach (int index in _changedList) + { + ITag item = _items[index]; + var itemData = item.Read(); + if (item.Active) + item.Update(itemData, dt, QUALITIES.QUALITY_GOOD); + if (_deadband == 0 || (item.Address.VarType == DataType.FLOAT && + (Math.Abs(itemData.Single / item.Value.Single - 1) > _deadband))) + { + values[i].ID = item.ID; + values[i].Quality = item.Quality; + values[i].Value = itemData; + values[i].TimeStamp = item.TimeStamp; + i++; + } + } + foreach (DataChangeEventHandler deleg in DataChange.GetInvocationList()) + { + deleg.BeginInvoke(this, new DataChangeEventArgs(1, values), null, null); + } + } + else + { + foreach (int index in _changedList) + { + ITag item = _items[index]; + if (item.Active) + item.Update(item.Read(), dt, QUALITIES.QUALITY_GOOD); + } + } + _changedList.Clear(); + } + + public void Dispose() + { + if (_timer != null) + _timer.Dispose(); + //if (_items != null) + // _items.Clear(); + } + + public virtual HistoryData[] BatchRead(DataSource source, bool isSync, params ITag[] itemArray) + { + int len = itemArray.Length; + HistoryData[] values = new HistoryData[len]; + if (source == DataSource.Device) + { + IMultiReadWrite multi = _plcReader as IMultiReadWrite; + if (multi != null) + { + Array.Sort(itemArray); + var itemArr = multi.ReadMultiple(Array.ConvertAll(itemArray, x => x.Address)); + for (int i = 0; i < len; i++) + { + values[i].ID = itemArray[i].ID; + values[i].Value = itemArr[i].Value; + values[i].TimeStamp = itemArr[i].TimeStamp.ToDateTime(); + itemArray[i].Update(itemArr[i].Value, values[i].TimeStamp, itemArr[i].Quality); + } + } + else + { + for (int i = 0; i < len; i++) + { + itemArray[i].Refresh(source); + values[i].ID = itemArray[i].ID; + values[i].Value = itemArray[i].Value; + values[i].Quality = itemArray[i].Quality; + values[i].TimeStamp = itemArray[i].TimeStamp; + } + } + } + else + { + for (int i = 0; i < len; i++) + { + values[i].ID = itemArray[i].ID; + values[i].Value = itemArray[i].Value; + values[i].Quality = itemArray[i].Quality; + values[i].TimeStamp = itemArray[i].TimeStamp; + } + } + return values; + } + + public virtual int BatchWrite(SortedDictionary items, bool isSync = true) + { + int len = items.Count; + int rev = 0; + IMultiReadWrite multi = _plcReader as IMultiReadWrite; + if (multi != null) + { + DeviceAddress[] addrs = new DeviceAddress[len]; + object[] objs = new object[len]; + int i = 0; + foreach (var item in items) + { + addrs[i] = item.Key.Address; + objs[i] = item.Value; + i++; + } + rev = multi.WriteMultiple(addrs, objs); + } + else + { + foreach (var tag in items) + { + if (tag.Key.Write(tag.Value) < 0) + rev = -1; + } + } + if (DataChange != null && rev >= 0) + { + HistoryData[] data = new HistoryData[len]; + int i = 0; + foreach (var item in items) + { + ITag tag = item.Key; + data[i].ID = tag.ID; + data[i].TimeStamp = tag.TimeStamp; + data[i].Quality = tag.Quality; + data[i].Value = item.Key.ToStorage(item.Value); + i++; + } + foreach (DataChangeEventHandler deleg in DataChange.GetInvocationList()) + { + deleg.BeginInvoke(this, new DataChangeEventArgs(1, data), null, null); + } + } + return rev; + } + + public ItemData ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache) + { + return source == DataSource.Cache ? _cacheReader.ReadInt32(address) : _plcReader.ReadInt32(address); + } + + public ItemData ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache) + { + return source == DataSource.Cache ? _cacheReader.ReadInt16(address) : _plcReader.ReadInt16(address); + } + + public ItemData ReadByte(DeviceAddress address, DataSource source = DataSource.Cache) + { + return source == DataSource.Cache ? _cacheReader.ReadByte(address) : _plcReader.ReadByte(address); + } + + public ItemData ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache) + { + return source == DataSource.Cache ? _cacheReader.ReadFloat(address) : _plcReader.ReadFloat(address); + } + + public ItemData ReadBool(DeviceAddress address, DataSource source = DataSource.Cache) + { + return source == DataSource.Cache ? _cacheReader.ReadBit(address) : _plcReader.ReadBit(address); + } + + public ItemData ReadString(DeviceAddress address, DataSource source = DataSource.Cache) + { + ushort siz = address.DataSize; + return source == DataSource.Cache ? _cacheReader.ReadString(address, siz) : + _plcReader.ReadString(address, siz); + } + + public int WriteInt32(DeviceAddress address, int value) + { + int rs = _plcReader.WriteInt32(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,new Storage{Int32=value}, DateTime.Now) + })); + } + } + return rs; + } + + public int WriteInt16(DeviceAddress address, short value) + { + int rs = _plcReader.WriteInt16(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,new Storage{Int16=value}, DateTime.Now) + })); + } + } + return rs; + } + + public int WriteFloat(DeviceAddress address, float value) + { + int rs = _plcReader.WriteFloat(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,new Storage{Single=value}, DateTime.Now) + })); + } + } + return rs; + } + + public int WriteString(DeviceAddress address, string value) + { + int rs = _plcReader.WriteString(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,Storage.Empty, DateTime.Now) + })); + } + } + return rs; + } + + public int WriteBit(DeviceAddress address, bool value) + { + int rs = _plcReader.WriteBit(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,new Storage{Boolean=value}, DateTime.Now) + })); + } + } + return rs; + } + + public int WriteBits(DeviceAddress address, byte value) + { + int rs = _plcReader.WriteBits(address, value); + if (rs >= 0) + { + if (DataChange != null) + { + ITag tag = GetTagByAddress(address); + if (tag != null) + DataChange(this, new DataChangeEventArgs(1, new HistoryData[1] + { + new HistoryData(tag.ID,QUALITIES.QUALITY_GOOD,new Storage{Byte=value}, DateTime.Now) + })); + } + } + return rs; + } + + private ITag GetTagByAddress(DeviceAddress addr) + { + int index = _items.BinarySearch(new BoolTag(0, addr, null)); + return index < 0 ? null : _items[index]; + } + + public event DataChangeEventHandler DataChange; + } + + + public struct PDUArea + { + public DeviceAddress Start; + public int Len; + public int StartIndex; + public int Count; + + public PDUArea(DeviceAddress start, int len, int startIndex, int count) + { + Start = start; + Len = len; + StartIndex = startIndex; + Count = count; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/DataService/Storage.cs b/SCADA/Program/CoreApp/DataService/DataService/Storage.cs new file mode 100644 index 0000000..1c2df0b --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/Storage.cs @@ -0,0 +1,95 @@ +using System.Runtime.InteropServices; +using System; + +namespace DataService +{ + [StructLayout(LayoutKind.Explicit, Size = 4)] + public struct Storage + { + // Fields + [FieldOffset(0)] + public bool Boolean; + [FieldOffset(0)] + public byte Byte; + [FieldOffset(0)] + public short Int16; + [FieldOffset(0)] + public int Int32; + [FieldOffset(0)] + public float Single; + + public static readonly Storage Empty ; + + static Storage() + { + Empty = new Storage(); + } + + public override bool Equals(object obj) + { + if (obj == null) return false; + Type type = obj.GetType(); + if (type == typeof(Storage)) + return this.Int32 == ((Storage)obj).Int32; + else + { + if (type == typeof(Int32)) + return this.Int32 == (Int32)obj; + if (type == typeof(Int16)) + return this.Int16 == (Int16)obj; + if (type == typeof(Byte)) + return this.Byte == (Byte)obj; + if (type == typeof(Boolean)) + return this.Boolean == (Boolean)obj; + if (type == typeof(Single)) + return this.Single == (Single)obj; + if (type == typeof(String)) + return this.ToString() == obj.ToString(); + } + return false; + } + + public override int GetHashCode() + { + return Int32.GetHashCode(); + } + + public static bool operator ==(Storage x, Storage y) + { + return x.Int32 == y.Int32; + } + + public static bool operator !=(Storage x, Storage y) + { + return x.Int32 != y.Int32; + } + } + + public enum QUALITIES : short + { + // Fields + LIMIT_CONST = 3, + LIMIT_HIGH = 2, + LIMIT_LOW = 1, + //LIMIT_MASK = 3, + //LIMIT_OK = 0, + QUALITY_BAD = 0, + QUALITY_COMM_FAILURE = 0x18, + QUALITY_CONFIG_ERROR = 4, + QUALITY_DEVICE_FAILURE = 12, + QUALITY_EGU_EXCEEDED = 0x54, + QUALITY_GOOD = 0xc0, + QUALITY_LAST_KNOWN = 20, + QUALITY_LAST_USABLE = 0x44, + QUALITY_LOCAL_OVERRIDE = 0xd8, + QUALITY_MASK = 0xc0, + QUALITY_NOT_CONNECTED = 8, + QUALITY_OUT_OF_SERVICE = 0x1c, + QUALITY_SENSOR_CAL = 80, + QUALITY_SENSOR_FAILURE = 0x10, + QUALITY_SUB_NORMAL = 0x58, + QUALITY_UNCERTAIN = 0x40, + QUALITY_WAITING_FOR_INITIAL_DATA = 0x20, + STATUS_MASK = 0xfc, + } +} \ No newline at end of file diff --git a/SCADA/Program/CoreApp/DataService/DataService/TagMetaData.cs b/SCADA/Program/CoreApp/DataService/DataService/TagMetaData.cs new file mode 100644 index 0000000..b8f9fec --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/DataService/TagMetaData.cs @@ -0,0 +1,108 @@ +using System; +using System.Runtime.InteropServices; + +namespace DataService +{ + [StructLayout(LayoutKind.Sequential)] + public struct TagMetaData : IComparable + { + public bool Archive; + + public DataType DataType; + + public ushort Size; + + public short ID; + + public short GroupID; + + public float Maximum; + + public float Minimum; + + public int Cycle; + + public string Address; + + public string Name; + + public TagMetaData(short id, short grpId, string name, string address, + DataType type, ushort size, bool archive = false, float max = 0, float min = 0, int cycle = 0) + { + ID = id; + GroupID = grpId; + Name = name; + Address = address; + DataType = type; + Size = size; + Archive = archive; + Maximum = max; + Minimum = min; + Cycle = cycle; + } + + public int CompareTo(TagMetaData other) + { + return this.ID.CompareTo(other.ID); + } + + public override string ToString() + { + return Name; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct Scaling : IComparable + { + public short ID; + + public ScaleType ScaleType; + + public float EUHi; + + public float EULo; + + public float RawHi; + + public float RawLo; + + public Scaling(short id, ScaleType type, float euHi, float euLo, float rawHi, float rawLo) + { + ID = id; + ScaleType = type; + EUHi = euHi; + EULo = euLo; + RawHi = rawHi; + RawLo = rawLo; + } + + public int CompareTo(Scaling other) + { + return ID.CompareTo(other.ID); + } + + public static readonly Scaling Empty = new Scaling { ScaleType = ScaleType.None }; + } + + public struct ItemData + { + public T Value; + public long TimeStamp; + public QUALITIES Quality; + + public ItemData(T value, long timeStamp, QUALITIES quality) + { + Value = value; + TimeStamp = timeStamp; + Quality = quality; + } + } + + public enum ScaleType : byte + { + None = 0, + Linear = 1, + SquareRoot = 2 + } +} diff --git a/SCADA/Program/CoreApp/DataService/GateWay/DAService.cs b/SCADA/Program/CoreApp/DataService/GateWay/DAService.cs new file mode 100644 index 0000000..521595b --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/GateWay/DAService.cs @@ -0,0 +1,1838 @@ +using ClientDriver; +using DatabaseLib; +using DataService; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using Microsoft.Extensions.Logging.Debug; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using System.Xml; + +namespace BatchCoreService +{ + + public class DAService : IDataServer, IAlarmServer + { + const int PORT = 6543; + + const char SPLITCHAR = '.'; + const string SERVICELOGSOURCE = "DataProcess"; + const string SERVICELOGNAME = "DataProcess"; + const string PATH = @"C:\DataConfig\"; + const string FILENAME = "server.xml"; + + + //可配置参数,从XML文件读取 + int DELAY = 3000; + int MAXHDACAP = 10000; + int ALARMLIMIT = 1000; + int CYCLE = 60000; + int CYCLE2 = 600000; + int SENDTIMEOUT = 60000; + //int SENDSIZE = ushort.MaxValue; + int HDALEN = 1024 * 1024; + int MAXLOGSIZE = 1024; + int HDADELAY = 3600 * 1000; + int ALARMDELAY = 3600 * 1000; + int ARCHIVEINTERVAL = 100; + + static ILogger Log; + + private System.Timers.Timer timer1 = new System.Timers.Timer(); + private System.Timers.Timer timer2 = new System.Timers.Timer(); + private System.Timers.Timer timer3 = new System.Timers.Timer(); + private DateTime _hdastart = DateTime.Now; + private DateTime _alarmstart = DateTime.Now; + + #region DAServer(标签数据服务器) + public ITag this[short id] + { + get + { + int index = GetItemProperties(id); + if (index >= 0) + { + return this[_list[index].Name]; + } + return null; + } + } + + public ITag this[string name] + { + get + { + if (string.IsNullOrEmpty(name)) return null; + ITag dataItem; + _mapping.TryGetValue(name.ToUpper(), out dataItem); + return dataItem; + } + } + + List _list; + public IList MetaDataList + { + get + { + return _list; + } + } + + public IList ScalingList + { + get + { + return _scales; + } + } + + object _syncRoot; + public object SyncRoot + { + get + { + if (this._syncRoot == null) + { + Interlocked.CompareExchange(ref this._syncRoot, new object(), null); + } + return this._syncRoot; + } + } + + bool _hasHda = false; + List _hda; + Dictionary _archiveTimes = new Dictionary(); + + Socket tcpServer = null; + + Dictionary _socketThreadList; + public Dictionary SocketList + { + get + { + return _socketThreadList; + } + } + + Dictionary _mapping; + + List _scales; + + SortedList _drivers; + public IEnumerable Drivers + { + get { return _drivers.Values; } + } + + CompareCondBySource _compare; + + ExpressionEval reval; + public ExpressionEval Eval + { + get + { + return reval; + } + } + + private object _myLock = new object(); + Dictionary _archiveList = null;//是否需要lock + public Dictionary ArchiveList + { + get + { + lock (_myLock) + { + if (_archiveList == null) + { + var list = MetaDataList.Where(x => x.Archive).Select(y => y.ID);//&& x.DataType != DataType.BOOL + if (list != null && list.Count() > 0) + { + string sql = "SELECT TAGID,DESCRIPTION FROM META_TAG WHERE TAGID IN(" + string.Join(",", list) + ");"; + using (var reader = DataHelper.Instance.ExecuteReader(sql)) + { + if (reader != null) + { + _archiveList = new Dictionary(); + while (reader.Read()) + { + _archiveList.Add(reader.GetInt16(0), reader.GetNullableString(1)); + } + } + } + } + } + } + return _archiveList; + } + } + + public DAService() + { + var loggerFactory = new LoggerFactory(); + Func filter = (category, level) => true; + loggerFactory.AddProvider( new ConsoleLoggerProvider(filter, false)); + loggerFactory.AddProvider(new DebugLoggerProvider(filter)); + Log = loggerFactory.CreateLogger(SERVICELOGSOURCE); + InitServerByXml(); + _scales = new List(); + _drivers = new SortedList(); + _alarmList = new List(ALARMLIMIT + 10); + reval = new ExpressionEval(this); + _hda = new List(); + InitServerByDatabase(); + InitConnection(); + _socketThreadList = new Dictionary(); + InitHost(); + + timer1.Elapsed += timer1_Elapsed; + timer2.Elapsed += timer2_Elapsed; + timer3.Elapsed += timer3_Elapsed; + timer1.Interval = CYCLE; + timer1.Enabled = true; + timer1.Start(); + timer2.Interval = CYCLE2; + timer2.Enabled = true; + timer2.Start(); + if (_hasHda) + { + foreach (var item in _archiveTimes.Values) + { + if (item != null) + { + timer3.Interval = ARCHIVEINTERVAL; + timer3.Enabled = true; + timer3.Start(); + return; + } + } + } + } + + public void Dispose() + { + lock (this) + { + try + { + if (timer1 != null) + timer1.Dispose(); + if (timer2 != null) + timer2.Dispose(); + if (timer3 != null) + timer3.Dispose(); + if (_drivers != null) + { + foreach (var driver in Drivers) + { + driver.OnClose -= this.reader_OnClose; + driver.Dispose(); + } + foreach (var condition in _conditionList) + { + if (condition != null) + condition.AlarmActive -= cond_SendAlarm; + } + + if (_hasHda) + { + Flush(); + //hda.Clear(); + } + SaveAlarm(); + foreach (var socket in _socketThreadList.Values) + { + socket.Dispose(); + } + if (tcpServer != null && tcpServer.Connected) + tcpServer.Disconnect(false); + + _mapping.Clear(); + _conditionList.Clear(); + reval.Dispose(); + } + } + catch (Exception e) + { + AddErrorLog(e); + } + } + } + + public void AddErrorLog(Exception e) + { + Log.LogError(e.GetExceptionMsg()); + } + + private void timer1_Elapsed(object sender, ElapsedEventArgs e) + { + foreach (IDriver d in Drivers) + { + if (d.IsClosed) + { + d.Connect();//t.IsAlive可加入判断;如线程异常,重新启动。 + } + } + } + + private void timer2_Elapsed(object sender, ElapsedEventArgs e) + { + if (HDADELAY > 0 && _hda.Count > 0 && (DateTime.Now - _hdastart).TotalMilliseconds > HDADELAY) + { + lock (_hdaRoot) + { + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.SaveCachedData), _hda.ToArray()); + _hda.Clear(); + } + } + if (ALARMDELAY > 0 && _alarmList.Count > 0 && (DateTime.Now - _alarmstart).TotalMilliseconds > ALARMDELAY) + SaveAlarm(); + DateTime today = DateTime.Today; + try + { + if (e.SignalTime > today.AddHours(2)) + { + DateTime startTime = DateTime.MinValue; + DateTime endTime = DateTime.MaxValue; + HDAIOHelper.GetRangeFromDatabase(null, ref startTime, ref endTime); + if (startTime >= today || startTime == DateTime.MinValue) + { + return; + } + bool success = true; + if (endTime < today && _hda.Count > 0 && _hda[0].TimeStamp < today) + { + success = SaveRange(endTime, today); + } + if (success) + { + startTime = startTime.Date.AddDays(1); + endTime = endTime.Date.AddDays(1); + if (endTime >= today) endTime = today; + while (startTime <= endTime) + { + HDAIOHelper.BackUpFile(startTime); + startTime = startTime.AddDays(1); + } + } + } + } + catch (Exception err) + { + AddErrorLog(err); + } + } + + private void timer3_Elapsed(object sender, ElapsedEventArgs e) + { + var now = e.SignalTime; + List tempData = new List(); + foreach (var archive in _archiveTimes) + { + var archiveTime = archive.Value; + if (archiveTime != null && (now - archiveTime.LastTime).TotalMilliseconds > archiveTime.Cycle) + { + var tag = this[archive.Key]; + if (tag != null && tag.TimeStamp > archiveTime.LastTime) + { + tempData.Add(new HistoryData(tag.ID, tag.Quality, tag.Value, now)); + archive.Value.LastTime = now; + } + } + } + if (tempData.Count > 0) + { + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.OnUpdate), tempData); + } + //var result = from item in _archiveTimes where item.Value.Cycle > 0 && (now - item.Value.LastTime).Milliseconds > item.Value.Cycle select item.Key; + } + + #region 初始化(标签数据服务器) + void InitConnection() + { + foreach (IDriver reader in _drivers.Values) + { + reader.OnClose += new ShutdownRequestEventHandler(reader_OnClose); + if (reader.IsClosed) + { + //if (reader is IFileDriver) + reader.Connect(); + } + foreach (IGroup grp in reader.Groups) + { + grp.DataChange += new DataChangeEventHandler(grp_DataChange); + //可在此加入判断,如为ClientDriver发出,则变化数据毋须广播,只需归档。 + grp.IsActive = grp.IsActive; + } + } + //此处需改进,与Condition采用相同的处理方式,可配置 + } + + void InitServerByDatabase() + { + using (var dataReader = DataHelper.Instance.ExecuteProcedureReader("InitServer", DataHelper.CreateParam("@TYPE", SqlDbType.Int, 0))) + { + if (dataReader == null) return;// Stopwatch sw = Stopwatch.StartNew(); + while (dataReader.Read()) + { + AddDriver(dataReader.GetInt16(0), dataReader.GetNullableString(1), + dataReader.GetNullableString(2), dataReader.GetInt32(3), dataReader.GetNullableString(4), dataReader.GetNullableString(5), + dataReader.GetNullableString(6), dataReader.GetNullableString(7)); + } + + dataReader.NextResult(); + dataReader.Read(); + int count = dataReader.GetInt32(0); + _list = new List(count); + _mapping = new Dictionary(count); + dataReader.NextResult(); + while (dataReader.Read()) + { + var meta = new TagMetaData(dataReader.GetInt16(0), dataReader.GetInt16(1), dataReader.GetString(2), dataReader.GetString(3), (DataType)dataReader.GetByte(4), + (ushort)dataReader.GetInt16(5), dataReader.GetBoolean(6), dataReader.GetFloat(7), dataReader.GetFloat(8), dataReader.GetInt32(9)); + _list.Add(meta); + if (meta.Archive) + { + _archiveTimes.Add(meta.ID, meta.Cycle == 0 ? null : new ArchiveTime(meta.Cycle, DateTime.MinValue)); + } + //Advise(DDETOPIC, meta.Name); + } + _list.Sort(); + dataReader.NextResult(); + while (dataReader.Read()) + { + IDriver dv; + _drivers.TryGetValue(dataReader.GetInt16(0), out dv); + if (dv != null) + { + IGroup grp = dv.AddGroup(dataReader.GetString(1), dataReader.GetInt16(2), dataReader.GetInt32(3), + dataReader.GetFloat(4), dataReader.GetBoolean(5)); + if (grp != null) + grp.AddItems(_list); + } + } + dataReader.NextResult(); + while (dataReader.Read()) + { + ITag tag = this[dataReader.GetNullableString(0)]; + if (tag != null) + { + tag.ValueChanged += OnValueChanged; + } + } + dataReader.NextResult(); + _conditions = new List(); + _conditionList = new List(); + while (dataReader.Read()) + { + int id = dataReader.GetInt32(0); + AlarmType type = (AlarmType)dataReader.GetInt32(2); + ICondition cond; + string source = dataReader.GetString(1); + if (_conditions.Count > 0) + { + cond = _conditions[_conditions.Count - 1]; + if (cond.ID == id) + { + cond.AddSubCondition(new SubCondition((SubAlarmType)dataReader.GetInt32(9), dataReader.GetFloat(10), + (Severity)dataReader.GetByte(11), dataReader.GetString(12), dataReader.GetBoolean(13))); + continue; + } + } + switch (type) + { + case AlarmType.Complex: + cond = new ComplexCondition(id, source, dataReader.GetString(6), dataReader.GetFloat(7), dataReader.GetInt32(8)); + break; + case AlarmType.Level: + cond = new LevelAlarm(id, source, dataReader.GetString(6), dataReader.GetFloat(7), dataReader.GetInt32(8)); + break; + case AlarmType.Dev: + cond = new DevAlarm(id, (ConditionType)dataReader.GetByte(4), source, dataReader.GetString(6), + dataReader.GetFloat(5), dataReader.GetFloat(7), dataReader.GetInt32(8)); + break; + case AlarmType.ROC: + cond = new ROCAlarm(id, source, dataReader.GetString(6), dataReader.GetFloat(7), dataReader.GetInt32(8)); + break; + case AlarmType.Quality: + cond = new QualitiesAlarm(id, source, dataReader.GetString(6)); + break; + case AlarmType.WordDsc: + cond = new WordDigitAlarm(id, source, dataReader.GetString(6), dataReader.GetInt32(8)); + break; + default: + cond = new DigitAlarm(id, source, dataReader.GetString(6), dataReader.GetInt32(8)); + break; + } + cond.AddSubCondition(new SubCondition((SubAlarmType)dataReader.GetInt32(9), dataReader.GetFloat(10), + (Severity)dataReader.GetByte(11), dataReader.GetString(12), dataReader.GetBoolean(13))); + + cond.IsEnabled = dataReader.GetBoolean(3); + var simpcond = cond as SimpleCondition; + if (simpcond != null) + { + simpcond.Tag = this[source]; + } + else + { + var complexcond = cond as ComplexCondition; + if (complexcond != null) + { + var action = complexcond.SetFunction(reval.Eval(source)); + if (action != null) + { + ValueChangedEventHandler handle = (s1, e1) => { action(); }; + foreach (ITag tag in reval.TagList) + { + tag.ValueChanged += handle;// tag.Refresh(); + } + } + } + } + cond.AlarmActive += new AlarmEventHandler(cond_SendAlarm); + //_conditions.Add(cond);// UpdateCondition(cond); + _conditions.Add(cond); + } + dataReader.NextResult(); + while (dataReader.Read()) + { + _scales.Add(new Scaling(dataReader.GetInt16(0), (ScaleType)dataReader.GetByte(1), + dataReader.GetFloat(2), dataReader.GetFloat(3), dataReader.GetFloat(4), dataReader.GetFloat(5))); + } + } + if (_archiveTimes.Count > 0) + { + _hasHda = true; + _hda.Capacity = MAXHDACAP; + } + reval.Clear(); + _scales.Sort(); + _compare = new CompareCondBySource(); + _conditions.Sort(_compare); + } + + void InitHost() + { + /*对关闭状态的判断,最好用心跳检测;冗余切换,可广播冗余命令,包含新主机名、数据库连接、IP地址等。 + * 服务启动时,向整个局域网UDP广播加密的主机名、连接字符串等信息 + */ + //socketThreadList = new Dictionary(); + tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + IPEndPoint LocalPort = new IPEndPoint(IPAddress.Any, PORT); + tcpServer.Bind(LocalPort); + tcpServer.Listen(100); + ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptWorkThread)); + } + + void InitServerByXml() + { + string path = PATH + '\\' + FILENAME; + if (File.Exists(path)) + { + try + { + using (var reader = XmlTextReader.Create(path)) + { + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Server": + { + if (reader.MoveToAttribute("MaxLogSize")) + int.TryParse(reader.Value, out MAXLOGSIZE); + } + break; + case "Data": + { + if (reader.MoveToAttribute("TestCycle")) + int.TryParse(reader.Value, out CYCLE); + if (reader.MoveToAttribute("SendTimeout")) + int.TryParse(reader.Value, out SENDTIMEOUT); + } + break; + case "Hda": + { + if (reader.MoveToAttribute("MaxHdaCap")) + { + int.TryParse(reader.Value, out MAXHDACAP); + } + if (reader.MoveToAttribute("HdaLen")) + int.TryParse(reader.Value, out HDALEN); + if (reader.MoveToAttribute("WriteCycle")) + int.TryParse(reader.Value, out CYCLE2); + if (reader.MoveToAttribute("Delay")) + int.TryParse(reader.Value, out HDADELAY); + if (reader.MoveToAttribute("Interval")) + int.TryParse(reader.Value, out ARCHIVEINTERVAL); + } + break; + case "Alarm": + { + if (reader.MoveToAttribute("AlarmLimit")) + int.TryParse(reader.Value, out ALARMLIMIT); + if (reader.MoveToAttribute("Delay")) + int.TryParse(reader.Value, out ALARMDELAY); + } + break; + } + } + } + } + } + catch (Exception err) + { + AddErrorLog(err); + } + } + } + #endregion + + void AcceptWorkThread(object state) + { + while (true) + { + //if (tcpServer.Poll(0, SelectMode.SelectRead)) + Socket s_Accept = tcpServer.Accept(); + //IPAddress addr = (s_Accept.RemoteEndPoint as IPEndPoint).Address; + s_Accept.SendTimeout = SENDTIMEOUT; + IPAddress addr = (s_Accept.RemoteEndPoint as IPEndPoint).Address; + try + { + if (!_socketThreadList.ContainsKey(addr)) + _socketThreadList.Add(addr, s_Accept); + } + catch (Exception err) + { + AddErrorLog(err); + } + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(ReceiveWorkThread), s_Accept); + } + } + + void ReceiveWorkThread(object obj) + { + Socket s_Receive = (Socket)obj; + IPAddress addr = null; + try + { + addr = (s_Receive.RemoteEndPoint as IPEndPoint).Address; + } + catch (Exception err) + { + AddErrorLog(err); + return; + } + byte[] buffer = new byte[s_Receive.ReceiveBufferSize]; // 创建接收缓冲 + while (true) + { + try + { + if (addr == null || !_socketThreadList.ContainsKey(addr)) return; + /*if (!s_Receive.Connected) return; + 关于数据传输协议:命令可分为:订单指令(订单类型,增删改标记可各用一个字段,路径ID用GUID,路径状态包括暂停、继续 + 、终止、启动);可返回客户端一个可行的路径设备链、ERP交换数据指令(包含DATASET),冗余切换指令等) + */ + int ReceiveCount = s_Receive.Receive(buffer); + + if (buffer[0] == FCTCOMMAND.fctHead) + { + //buffer[0]是协议头,1是指令号,2是读方式(缓存还是设备),3、4是ID,5是长度,后接变量值 + byte command = buffer[1]; + switch (command) + { + case FCTCOMMAND.fctReadSingle: + { + //DataSource source = buffer[2] == 0 ? DataSource.Cache : DataSource.Device; + short id = BitConverter.ToInt16(buffer, 3); + byte length = buffer[5]; + byte[] send = new byte[5 + length]; + for (int i = 0; i < 5; i++) + { + send[i] = buffer[i]; + } + ITag tag = this[id]; + if (tag != null) + { + Storage value = buffer[2] == 0 ? tag.Value : tag.Read(DataSource.Device); + byte[] bt = tag.ToByteArray(value); + for (int k = 0; k < bt.Length; k++) + { + send[5 + k] = bt[k]; + } + } + else + { + //出错处理,可考虑返回一个DATATYPE.NONE类型 + } + s_Receive.Send(send); + } + break; + case FCTCOMMAND.fctReadMultiple: + { + //buffer[0]是协议头,1是指令号,2是读方式(缓存还是设备),3、4是变量数,后接变量值 + //DataSource source = buffer[2] == 0 ? DataSource.Cache : DataSource.Device; + byte[] send = new byte[s_Receive.SendBufferSize]; + send[0] = FCTCOMMAND.fctHead; + short count = BitConverter.ToInt16(buffer, 3);//要读取的变量数 + int j = 5; int l = 5; + if (buffer[2] == 0) + { + for (int i = 0; i < count; i++) + { + short id = BitConverter.ToInt16(buffer, l); + send[j++] = buffer[l++]; + send[j++] = buffer[l++]; + ITag tag = this[id]; + if (tag != null) + { + byte[] bt = tag.ToByteArray(); + var length = (byte)bt.Length; + send[j++] = length; + for (int k = 0; k < length; k++) + { + send[j + k] = bt[k]; + } + j += length; + } + else + {//类型后跟长度 + send[j++] = 0; + } + } + } + else + { + Dictionary> dict = new Dictionary>(); + for (int i = 0; i < count; i++) + { + short id = BitConverter.ToInt16(buffer, l); + l += 2; + ITag tag = this[id]; + if (tag != null) + { + IGroup grp = tag.Parent; + if (!dict.ContainsKey(grp)) + dict.Add(grp, new List { tag }); + else + dict[grp].Add(tag); + } + } + foreach (var dev in dict) + { + var list = dev.Value; + var array = dev.Key.BatchRead(DataSource.Device, true, list.ToArray()); + if (array == null) continue; + for (int i = 0; i < list.Count; i++) + { + byte[] bt = list[i].ToByteArray(array[i].Value); + var length = (byte)bt.Length; + send[j++] = length; + for (int k = 0; k < bt.Length; k++) + { + send[j + k] = bt[k]; + } + j += length; + } + } + } + s_Receive.Send(send, 0, j, SocketFlags.None); + } + break; + case FCTCOMMAND.fctWriteSingle: + { + //buffer[0]是协议头,1是指令号,2是写方式(缓存还是设备),3、4是ID,5是长度 + short id = BitConverter.ToInt16(buffer, 3); + byte rs = 0; + ITag tag = this[id]; + if (tag != null)//此处应考虑万一写失败,是否需要更新值 + { + if (tag.Address.VarType == DataType.STR) + { + StringTag strTag = tag as StringTag; + if (strTag != null) + { + string txt = Encoding.ASCII.GetString(buffer, 6, buffer[5]).Trim((char)0); + rs = (byte)tag.Write(txt); + if (rs == 0) + strTag.String = txt; + } + } + else + { + Storage value = Storage.Empty; + switch (tag.Address.VarType) + { + case DataType.BOOL: + value.Boolean = BitConverter.ToBoolean(buffer, 6); + break; + case DataType.BYTE: + value.Byte = buffer[6]; + break; + case DataType.WORD: + case DataType.SHORT: + value.Int16 = BitConverter.ToInt16(buffer, 6); + break; + case DataType.TIME: + case DataType.INT: + value.Int32 = BitConverter.ToInt32(buffer, 6); + break; + case DataType.FLOAT: + value.Single = BitConverter.ToSingle(buffer, 6); + break; + default: + break; + } + rs = (byte)tag.Write(value, false); + } + } + else + { + rs = 0xFF;//此处长度应注意;如无此变量,应返回一个错误代码 + } + s_Receive.Send(new byte[] { FCTCOMMAND.fctWriteSingle, rs }, 0, 2, SocketFlags.None);//应返回一个错误代码; + } + break; + case FCTCOMMAND.fctWriteMultiple: + { //int BatchWrite(IDictionary items, bool isSync = true); + int count = BitConverter.ToInt16(buffer, 2); + int j = 4; byte rs = 0; + Dictionary> dict = new Dictionary>(); + for (int i = 0; i < count; i++) + { + short id = BitConverter.ToInt16(buffer, j); + j += 2; + byte length = buffer[j++]; + ITag tag = this[id]; + IGroup grp = tag.Parent; + SortedDictionary values; + if (!dict.ContainsKey(grp)) + { + values = new SortedDictionary(); + dict.Add(grp, values); + } + else + values = dict[grp]; + if (tag != null) + { + switch (tag.Address.VarType) + { + case DataType.BOOL: + values.Add(tag, BitConverter.ToBoolean(buffer, j)); + break; + case DataType.BYTE: + values.Add(tag, buffer[j]); + break; + case DataType.WORD: + case DataType.SHORT: + values.Add(tag, BitConverter.ToInt16(buffer, j)); + break; + case DataType.TIME: + case DataType.INT: + values.Add(tag, BitConverter.ToInt32(buffer, j)); + break; + case DataType.FLOAT: + values.Add(tag, BitConverter.ToSingle(buffer, j)); + break; + case DataType.STR: + values.Add(tag, Encoding.ASCII.GetString(buffer, j, length).Trim((char)0)); + break; + } + } + j += length; + } + foreach (var dev in dict) + { + if (dev.Key.BatchWrite(dev.Value) < 0) rs = 0xFF; + } + s_Receive.Send(new byte[] { FCTCOMMAND.fctWriteMultiple, rs }, 0, 2, SocketFlags.None); + } + break; + case FCTCOMMAND.fctAlarmRequest://刷新报警数据 + { + if (_alarmList.Count > 0) + { + long startTime = BitConverter.ToInt64(buffer, 2); + long endTime = BitConverter.ToInt64(buffer, 10); + if (_alarmstart > DateTime.FromFileTime(startTime) || DateTime.FromFileTime(endTime) > _alarmstart) + { + SaveAlarm(); + } + } + s_Receive.Send(new byte[] { FCTCOMMAND.fctAlarmRequest, 0 }, 0, 2, SocketFlags.None); + } + break; + case FCTCOMMAND.fctReset://重置连接 + { + byte[] iparry = new byte[4]; + Array.Copy(buffer, 2, iparry, 0, 4); + IPAddress ipaddr = new IPAddress(iparry); + if (_socketThreadList.Count > 0 && _socketThreadList.ContainsKey(ipaddr)) + { + var scok = _socketThreadList[ipaddr]; + _socketThreadList.Remove(ipaddr); + if (scok != null) + { + scok.Dispose(); + } + } + } + break; + case FCTCOMMAND.fctHdaRequest: + { + DateTime start = DateTime.FromFileTime(BitConverter.ToInt64(buffer, 2)); + DateTime end = DateTime.FromFileTime(BitConverter.ToInt64(buffer, 10)); + try + { + SendHData(GetHData(start, end), new byte[HDALEN], s_Receive); + } + catch (Exception err) + { + AddErrorLog(err); + } + s_Receive.Send(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 24, SocketFlags.None); + } + break; + case FCTCOMMAND.fctHdaIdRequest://优先读取本地HDA文件夹下的二进制归档文件 + { + DateTime start = DateTime.FromFileTime(BitConverter.ToInt64(buffer, 2)); + DateTime end = DateTime.FromFileTime(BitConverter.ToInt64(buffer, 10)); + short ID = BitConverter.ToInt16(buffer, 18); + try + { + SendHData(GetHData(start, end, ID), new byte[HDALEN], s_Receive, this[ID]); + } + catch (Exception err) + { + AddErrorLog(err); + } + s_Receive.Send(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 24, SocketFlags.None); + } + break; + } + } + } + catch (SocketException ex) + { + var err = ex.SocketErrorCode; + if (err == SocketError.ConnectionAborted || err == SocketError.HostDown || err == SocketError.NetworkDown || err == SocketError.Shutdown || err == SocketError.ConnectionReset) + { + s_Receive.Dispose(); + if (addr != null) + _socketThreadList.Remove(addr); + //s_Receive.Dispose(); + } + AddErrorLog(ex); + } + catch (Exception ex) + { + AddErrorLog(ex); + } + } + } + + #region 历史数据归档查询 + private IEnumerable GetHData(DateTime start, DateTime end, short ID) + { + if (start > end) yield break; + DateTime now = DateTime.Now; + if (start > now) yield break; + if (end > now) end = now; + if (now.Date > start.Date) + { + DateTime tempstart = DateTime.MinValue; + DateTime tempend = end; + HDAIOHelper.GetRangeFromDatabase(ID, ref tempend, ref tempstart); + if (tempend > end) tempend = end; + if (tempend > start) + { + int eyear = tempend.Year; + int syear = start.Year; + int emonth = tempend.Month; + int smonth = start.Month; + int year = syear; + while (year <= eyear) + { + int month = (year == syear ? smonth : 1); + while (month <= (year == eyear ? emonth : 12)) + { + var result = HDAIOHelper.LoadFromFile((year == syear && month == smonth ? start : new DateTime(year, month, 1)), + (year == eyear && month == emonth ? tempend : new DateTime(year, month, 1).AddMonths(1).AddMilliseconds(-2)), ID);//考虑按月遍历 + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + month++; + } + year++; + } + } + } + var tempdata = _hda.ToArray(); + DateTime ftime = (tempdata.Length > 0 ? tempdata[0].TimeStamp : DateTime.Now); + if (start < ftime) + { + var result = HDAIOHelper.LoadFromDatabase(start, ftime > end ? end : ftime, ID);//范围冲突 + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + } + if (end > ftime) + { + var result = tempdata.Where(x => x.ID == ID && x.TimeStamp >= ftime && x.TimeStamp <= end); + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + } + yield break; + } + + private IEnumerable GetHData(DateTime start, DateTime end) + { + if (start > end) yield break; + DateTime now = DateTime.Now; + if (start > now) yield break; + if (end > now) end = now; + if (now.Date > start.Date) + { + DateTime tempstart = DateTime.MinValue; + DateTime tempend = end; + HDAIOHelper.GetRangeFromDatabase(null, ref tempend, ref tempstart); + if (tempend > start) + { + int eyear = tempend.Year; + int syear = start.Year; + int emonth = tempend.Month; + int smonth = start.Month; + int year = syear; + while (year <= eyear) + { + int month = (year == syear ? smonth : 1); + while (month <= (year == eyear ? emonth : 12)) + { + var result = HDAIOHelper.LoadFromFile((year == syear && month == smonth ? start : new DateTime(year, month, 1)), + (year == eyear && month == emonth ? tempend : new DateTime(year, month, 1).AddMonths(1).AddMilliseconds(-2)));//考虑按月遍历 + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + month++; + } + year++; + } + } + } + var tempdata = _hda.ToArray(); + DateTime ftime = (tempdata.Length > 0 ? tempdata[0].TimeStamp : DateTime.Now); + if (start < ftime) + { + var result = HDAIOHelper.LoadFromDatabase(start, ftime > end ? end : ftime);//范围冲突 + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + } + if (end > ftime) + { + var result = tempdata.Where(x => x.TimeStamp >= ftime && x.TimeStamp <= end); + if (result != null) + { + foreach (var data in result) + { + yield return data; + } + } + } + yield break; + } + + private void SendHData(IEnumerable result, byte[] buffer, Socket socket, ITag tag) + { + if (result == null || tag == null || socket == null || !socket.Connected) return; + int index = 0; + int len = buffer.Length; + int size = tag.Address.DataSize; + foreach (var data in result) + { + if (index + 8 + size >= len) + { + //s_Receive.BeginSend(tempbuffer, 0, index, SocketFlags.None, null, null); + socket.Send(buffer, index, SocketFlags.None); + index = 0; + } + byte[] bits = tag.ToByteArray(data.Value); + bits.CopyTo(buffer, index); + index += size; + bits = BitConverter.GetBytes(data.TimeStamp.ToFileTime()); + bits.CopyTo(buffer, index); + index += 8; + } + socket.Send(buffer, index, SocketFlags.None); + } + + private void SendHData(IEnumerable result, byte[] buffer, Socket socket) + { + if (result == null || socket == null || !socket.Connected) return; + int index = 0; + int len = buffer.Length; + short tempid = short.MinValue; + ITag tag = null; + byte[] idarray = new byte[2]; + foreach (var data in result) + { + if (tempid != data.ID) + { + tempid = data.ID; + idarray = BitConverter.GetBytes(tempid); + tag = this[tempid]; + } + if (tag == null) continue; + int size = tag.Address.DataSize; + if (index + 10 + size >= len) + { + //s_Receive.BeginSend(tempbuffer, 0, index, SocketFlags.None, null, null);这里有一个同步的问题,发生ID号错位。 + socket.Send(buffer, index, SocketFlags.None); + index = 0; + } + idarray.CopyTo(buffer, index); + index += 2; + byte[] bits = tag.ToByteArray(data.Value); + bits.CopyTo(buffer, index); + index += size; + bits = BitConverter.GetBytes(data.TimeStamp.ToFileTime()); + bits.CopyTo(buffer, index); + index += 8; + } + socket.Send(buffer, index, SocketFlags.None); + } + + private object _hdaRoot = new object(); + public void Flush() + { + lock (_hdaRoot) + { + if (_hda.Count == 0) return; + if (DataHelper.Instance.BulkCopy(new HDASqlReader(_hda, this), "Log_HData", + string.Format("DELETE FROM Log_HData WHERE [TIMESTAMP]>'{0}'", _hda[0].TimeStamp.ToString()))) + { + _hda.Clear(); + _hdastart = DateTime.Now; + } + } + } + + public bool SaveRange(DateTime startTime, DateTime endTime) + { + var tempdata = _hda.ToArray(); + if (tempdata.Length == 0) return true; + return DataHelper.Instance.BulkCopy(new HDASqlReader(GetData(tempdata, startTime, endTime), this), "Log_HData", + string.Format("DELETE FROM Log_HData WHERE [TIMESTAMP] BETWEEN '{0}' AND '{1}'", startTime, endTime)); + } + + public void OnUpdate(object stateInfo) + { + lock (_hdaRoot) + { + var tempData = (List)stateInfo; + _hda.AddRange(tempData); + if (_hda.Count >= MAXHDACAP) + { + //Reverse(data); + DateTime start = _hda[0].TimeStamp; + //_array.CopyTo(data, 0); + if (DataHelper.Instance.BulkCopy(new HDASqlReader(_hda, this), "Log_HData", + string.Format("DELETE FROM Log_HData WHERE [TIMESTAMP]>'{0}'", start.ToString()))) + _hdastart = DateTime.Now; + else ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.SaveCachedData), _hda.ToArray()); + _hda.Clear(); + } + } + } + + public void SaveCachedData(object stateInfo) + { + var tempData = (HistoryData[])stateInfo; + if (tempData.Length == 0) return; + DateTime startTime = tempData[0].TimeStamp; + DateTime endTime = tempData[tempData.Length - 1].TimeStamp; + //Thread.Sleep(TimeSpan.FromMinutes(10)); + int count = 0; + while (true) + { + if (count >= 5) return; + if (DataHelper.Instance.BulkCopy(new HDASqlReader(tempData, this), "Log_HData", + string.Format("DELETE FROM Log_HData WHERE [TIMESTAMP] BETWEEN '{0}' AND '{1}'", + startTime, endTime))) + { + stateInfo = null; + _hdastart = DateTime.Now; + } + count++; + Thread.Sleep(CYCLE2); + } + } + + public IEnumerable GetData(HistoryData[] hdaarray, DateTime startTime, DateTime endTime) + { + //if (hdaarray.Length == 0) yield break; + foreach (var data in hdaarray) + { + if (data.TimeStamp >= startTime) + { + if (data.TimeStamp <= endTime) + yield return data; + else + yield break; + } + } + } + #endregion + + void OnValueChanged(object sender, ValueChangedEventArgs e) + { + var tag = sender as ITag; + DataHelper.Instance.ExecuteStoredProcedure("AddEventLog", + DataHelper.CreateParam("@StartTime", SqlDbType.DateTime, tag.TimeStamp), + DataHelper.CreateParam("@Source", SqlDbType.NVarChar, tag.ID.ToString(), 50), + DataHelper.CreateParam("@StartTime", SqlDbType.NVarChar, tag.ToString(), 50)); + } + + public HistoryData[] BatchRead(DataSource source, bool sync, params ITag[] itemArray) + { + int count = itemArray.Length; + HistoryData[] data = new HistoryData[count]; + Dictionary> dict = new Dictionary>(); + for (int i = 0; i < count; i++) + { + short id = itemArray[i].ID; + ITag tag = this[id]; + if (tag != null) + { + IGroup grp = tag.Parent; + if (!dict.ContainsKey(grp)) + dict.Add(grp, new List { tag }); + else + dict[grp].Add(tag); + } + } + int j = 0; + foreach (var dev in dict) + { + var list = dev.Value; + var array = dev.Key.BatchRead(source, sync, list.ToArray()); + if (array == null) continue; + Array.Copy(array, 0, data, j, array.Length); + j += array.Length; + } + return data; + } + + public int BatchWrite(Dictionary tags, bool sync) + { + int rs = -1; + Dictionary> dict = new Dictionary>(); + foreach (var item in tags) + { + var tag = this[item.Key]; + if (tag != null) + { + IGroup grp = tag.Parent; + SortedDictionary values; + if (!dict.ContainsKey(grp)) + { + values = new SortedDictionary(); + if (tag.Address.VarType != DataType.BOOL && tag.Address.VarType != DataType.STR) + { + values.Add(tag, tag.ValueToScale(Convert.ToSingle(item.Value))); + } + else + values.Add(tag, item.Value); + dict.Add(grp, values); + } + else + { + values = dict[grp]; + if (tag.Address.VarType != DataType.BOOL && tag.Address.VarType != DataType.STR) + { + values.Add(tag, tag.ValueToScale(Convert.ToSingle(item.Value))); + } + else + values.Add(tag, item.Value); + } + } + else Log.LogError(string.Format("变量{0}不在变量表中,无法下载", item.Key)); + } + foreach (var dev in dict) + { + rs = dev.Key.BatchWrite(dev.Value, sync); + } + return rs; + } + + void grp_DataChange(object sender, DataChangeEventArgs e) + { + var data = e.Values; + var now = DateTime.Now; + if (_hasHda) + { + ArchiveTime archiveTime; + List tempData = new List(20); + for (int i = 0; i < data.Count; i++) + { + if (_archiveTimes.TryGetValue(data[i].ID, out archiveTime) && archiveTime == null && data[i].TimeStamp != DateTime.MinValue) + { + tempData.Add(data[i]); + } + } + if (tempData.Count > 0) + { + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.OnUpdate), tempData); + } + } + if (_socketThreadList != null && _socketThreadList.Count > 0) + { + IPAddress addr = null; + var grp = sender as ClientGroup; + if (grp != null) + addr = grp.RemoteAddress; + ThreadPool.UnsafeQueueUserWorkItem(new WaitCallback(this.SendData), new TempCachedData(addr, data)); + } + } + //此处发生内存泄漏;需要试验CLRProfile确定泄漏原因;改回原方法测试;看是否解决队列堵塞问题。对于客户端Grp,要过滤掉 + private void SendData(object obj) + { + var tempdata = obj as TempCachedData; + var data = tempdata.Data; + byte[] sendBuffer = new byte[8192]; + sendBuffer[0] = FCTCOMMAND.fctHead; + sendBuffer[1] = FCTCOMMAND.fctReadMultiple; + //bytes[2] = 0; + int len = data.Count; + short j = 5; + for (int i = 0; i < len; i++) + { + short id = data[i].ID; + byte[] dt = BitConverter.GetBytes(id); + sendBuffer[j++] = dt[0]; + sendBuffer[j++] = dt[1]; + switch (_list[GetItemProperties(id)].DataType) + { + case DataType.BOOL: + sendBuffer[j++] = 1; + sendBuffer[j++] = data[i].Value.Boolean ? (byte)1 : (byte)0; + break; + case DataType.BYTE: + sendBuffer[j++] = 1; + sendBuffer[j++] = data[i].Value.Byte; + break; + case DataType.WORD: + case DataType.SHORT: + { + sendBuffer[j++] = 2; + byte[] bt = BitConverter.GetBytes(data[i].Value.Int16); + sendBuffer[j++] = bt[0]; + sendBuffer[j++] = bt[1]; + } + break; + case DataType.TIME: + case DataType.INT: + { + sendBuffer[j++] = 4; + byte[] bt = BitConverter.GetBytes(data[i].Value.Int32); + sendBuffer[j++] = bt[0]; + sendBuffer[j++] = bt[1]; + sendBuffer[j++] = bt[2]; + sendBuffer[j++] = bt[3]; + } + break; + case DataType.FLOAT: + { + sendBuffer[j++] = 4; + byte[] bt = BitConverter.GetBytes(data[i].Value.Single); + sendBuffer[j++] = bt[0]; + sendBuffer[j++] = bt[1]; + sendBuffer[j++] = bt[2]; + sendBuffer[j++] = bt[3]; + } + break; + case DataType.STR: + { + byte[] bt = Encoding.ASCII.GetBytes(this[data[i].ID].ToString()); + sendBuffer[j++] = (byte)bt.Length; + for (int k = 0; k < bt.Length; k++) + { + sendBuffer[j++] = bt[k]; + } + } + break; + default: + break; + } + Array.Copy(BitConverter.GetBytes((data[i].TimeStamp == DateTime.MinValue ? DateTime.Now : data[i].TimeStamp).ToFileTime()), 0, sendBuffer, j, 8); + j += 8; + } + byte[] dt1 = BitConverter.GetBytes(j); + sendBuffer[3] = dt1[0]; + sendBuffer[4] = dt1[1]; + SocketError err; + //bytes.CopyTo(bytes2, 0); + List sockets = new List(_socketThreadList.Count); + foreach (var socket in _socketThreadList) + { + if (!socket.Key.Equals(tempdata.Address)) + sockets.Add(socket.Value); + } + data = null; + obj = null; + tempdata = null; + foreach (var socket in sockets) + { + try + { + socket.Send(sendBuffer, 0, j, SocketFlags.None, out err); + if (err == SocketError.ConnectionAborted || err == SocketError.HostDown || + err == SocketError.NetworkDown || err == SocketError.Shutdown) + { + _socketThreadList.Remove((socket.RemoteEndPoint as IPEndPoint).Address); + } + } + catch (Exception ex1) + { + AddErrorLog(ex1); + } + } + } + + public IDriver AddDriver(short id, string name, string server, int timeOut, + string assembly, string className, string spare1, string spare2) + { + if (_drivers.ContainsKey(id)) + return _drivers[id]; + IDriver dv = null; + try + { + Assembly ass = Assembly.LoadFrom(assembly); + var dvType = ass.GetType(className); + if (dvType != null) + { + //dv = new ModbusDriver.ModbusTCPReader(this, id, name, server, timeOut); + dv = Activator.CreateInstance(dvType, new object[] { this, id, name, server, timeOut, spare1, spare2 }) as IDriver; + if (dv != null) + _drivers.Add(id, dv); + } + } + catch (Exception e) + { + AddErrorLog(e); + } + return dv; + } + + public bool RemoveDriver(IDriver device) + { + lock (SyncRoot) + { + if (_drivers.Remove(device.ID)) + { + device.Dispose(); + device = null; + return true; + } + return false; + } + } + + void reader_OnClose(object sender, ShutdownRequestEventArgs e) + { + Log.LogWarning(e.shutdownReason); + //AddErrorLog(new Exception(e.shutdownReason)); + } + + public bool AddItemIndex(string key, ITag value) + { + key = key.ToUpper(); + if (_mapping.ContainsKey(key)) + return false; + _mapping.Add(key, value); + return true; + } + + public bool RemoveItemIndex(string key) + { + return _mapping.Remove(key.ToUpper()); + } + + + object _alarmsync = new object(); + + string[] itemList = null; + public IEnumerable BrowseItems(BrowseType browseType, string tagName, DataType dataType) + { + lock (SyncRoot) + { + if (_list.Count == 0) yield break; + int len = _list.Count; + if (itemList == null) + { + itemList = new string[len]; + for (int i = 0; i < len; i++) + { + itemList[i] = _list[i].Name; + } + Array.Sort(itemList); + } + int ii = 0; + bool hasTag = !string.IsNullOrEmpty(tagName); + bool first = true; + string str = hasTag ? tagName + SPLITCHAR : string.Empty; + if (hasTag) + { + ii = Array.BinarySearch(itemList, tagName); + if (ii < 0) first = false; + //int strLen = str.Length; + ii = Array.BinarySearch(itemList, str); + if (ii < 0) ii = ~ii; + } + //while (++i < len && temp.Length >= strLen && temp.Substring(0, strLen) == str) + do + { + if (first && hasTag) + { + first = false; + yield return tagName; + } + string temp = itemList[ii]; + if (hasTag && !temp.StartsWith(str, StringComparison.Ordinal)) + break; + if (dataType == DataType.NONE || _mapping[temp].Address.VarType == dataType) + { + bool b3 = true; + if (browseType != BrowseType.Flat) + { + string curr = temp + SPLITCHAR; + int index = Array.BinarySearch(itemList, ii, len - ii, curr); + if (index < 0) index = ~index; + b3 = itemList[index].StartsWith(curr, StringComparison.Ordinal); + if (browseType == BrowseType.Leaf) + b3 = !b3; + } + if (b3) + yield return temp; + } + } while (++ii < len); + } + } + + public int GetScaleByID(short Id) + { + if (_scales == null || _scales.Count == 0) return -1; + return _scales.BinarySearch(new Scaling { ID = Id }); + } + + public IGroup GetGroupByName(string name) + { + if (string.IsNullOrEmpty(name)) return null; + foreach (IDriver device in Drivers) + { + foreach (IGroup grp in device.Groups) + { + if (grp.Name == name) + return grp; + } + } + return null; + } + + public void ActiveItem(bool active, params ITag[] items) + { + Dictionary> dict = new Dictionary>(); + for (int i = 0; i < items.Length; i++) + { + List list = null; + ITag item = items[i]; + dict.TryGetValue(item.Parent, out list); + if (list != null) + { + list.Add(item.ID); + } + else + dict.Add(item.Parent, new List { item.ID }); + + } + foreach (var grp in dict) + { + grp.Key.SetActiveState(active, grp.Value.ToArray()); + } + } + + public int GetItemProperties(short id) + { + return _list.BinarySearch(new TagMetaData { ID = id }); + } + #endregion + + #region Condition & Alarm(报警和条件) + List _conditions; + List _conditionList; + + List _alarmList; + public IEnumerable AlarmList + { + get + { + return _alarmList; + } + } + + public IList ActivedConditionList + { + get + { + return _conditionList; + } + } + + public IList ConditionList + { + get + { + return _conditions; + } + } + + void cond_SendAlarm(object sender, AlarmItem e) + { + lock (_alarmsync) + { + int index2 = _conditions.BinarySearch(new DigitAlarm(0, e.Source), _compare); + if (index2 > -1) + { + var cond = _conditions[index2]; + _conditionList.Remove(cond); + if (e.SubAlarmType != SubAlarmType.None) + { + _conditionList.Add(cond); + } + } + + if (_alarmList.Count < ALARMLIMIT) + { + _alarmList.Add(e); + } + else + { + SaveAlarm(); + _alarmList.Add(e); + } + } + /*应加入判断,是否需要更新数据库(if(Index>=ALARMLIMIT){Save(); Index=0;}else Index++; + * 客户端查询前先发送一个查询报警(alarmQuery)请求,包含起始时间参数,服务器从判断是否要将缓存写入数据库, + * 待服务器返回就绪后,客户端再从数据库查询报警记录。 + */ + } + + private bool SaveAlarm() + { + if (_alarmList.Count == 0) return true; + if (DataHelper.Instance.BulkCopy(new AlarmDataReader(_alarmList), "Log_Alarm", null)) + { + _alarmList.Clear(); + _alarmstart = DateTime.Now; + return true; + } + return false; + } + + public ICondition GetCondition(string tagName, AlarmType type) + { + ITag tag = this[tagName]; + if (tag == null) return null; + short id = tag.ID; + int index = _conditions.BinarySearch(new DigitAlarm(0, tagName)); + if (index < 0) return null; + int ind1 = index - 1; + ICondition cond = _conditions[index]; + while (index < _conditions.Count && cond.Source == tagName) + { + cond = _conditions[index++]; + if (cond.AlarmType == type) + { + return cond; + } + } + while (ind1 >= 0 && cond.Source == tagName) + { + cond = _conditions[ind1--]; + if (cond.AlarmType == type) + { + return cond; + } + } + return null; + } + + public IList QueryConditions(string sourceName) + { + if (_conditions == null || sourceName == null) return null; + ITag tag = this[sourceName]; + if (tag == null) return null; + int index = _conditions.BinarySearch(new DigitAlarm(0, sourceName)); + if (index < 0) return null; + List condList = new List(); + ICondition cond = _conditions[index]; + int ind1 = index - 1; + while (cond.Source == sourceName) + { + condList.Add(cond); + if (++index < _conditions.Count) + cond = _conditions[index]; + else + break; + } + while (ind1 >= 0) + { + if (cond.Source == sourceName) + condList.Add(cond); + } + return condList; + } + + public int DisableCondition(string sourceName, AlarmType type) + { + var cond = GetCondition(sourceName, type); + if (cond != null) + { + cond.IsEnabled = false; + return 1; + } + return -1; + } + + public int EnableCondition(string sourceName, AlarmType type) + { + var cond = GetCondition(sourceName, type); + if (cond != null) + { + cond.IsEnabled = true; + return 1; + } + return -1; + } + + public int RemoveConditon(string sourceName, AlarmType type) + { + var cond = GetCondition(sourceName, type); + if (cond != null) + { + _conditions.Remove(cond); + return 1; + } + return -1; + } + + public int RemoveConditons(string sourceName) + { + ITag tag = this[sourceName]; + if (_conditions == null || tag == null) return -1; + int index = _conditions.BinarySearch(new DigitAlarm(0, sourceName)); + if (index < 0) return index; + int ind1 = index - 1; + ICondition cond = _conditions[index]; + List li = new List(); + while (cond.Source == sourceName) + { + li.Add(index); + if (++index < _conditions.Count) + cond = _conditions[index]; + else + break; + } + while (ind1 >= 0) + { + cond = _conditions[ind1--]; + if (cond.Source == sourceName) + li.Add(ind1); + } + if (li.Count == 0) return -1; + for (int i = li.Count - 1; i >= 0; i--) + { + _conditions.RemoveAt(i); + } + return 1; + } + + public int AckConditions(params ICondition[] conditions) + { + if (conditions == null || conditions.Length == 0) return -1; + foreach (ICondition cond in conditions) + { + cond.IsAcked = true; + cond.LastAckTime = DateTime.Now; + } + return 1; + } + #endregion + } + + class TempCachedData + { + IPAddress _addr; + public IPAddress Address + { + get { return _addr; } + } + + IList _data; + public IList Data + { + get { return _data; } + } + + public TempCachedData(IPAddress addr, IList data) + { + _addr = addr; + _data = data; + } + } + + internal sealed class ArchiveTime + { + public int Cycle; + public DateTime LastTime; + public ArchiveTime(int cycle, DateTime last) + { + Cycle = cycle; + LastTime = last; + } + } +} diff --git a/SCADA/Program/CoreApp/DataService/GateWay/GateWay.csproj b/SCADA/Program/CoreApp/DataService/GateWay/GateWay.csproj new file mode 100644 index 0000000..58c9ec4 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/GateWay/GateWay.csproj @@ -0,0 +1,22 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + diff --git a/SCADA/Program/CoreApp/DataService/GateWay/Program.cs b/SCADA/Program/CoreApp/DataService/GateWay/Program.cs new file mode 100644 index 0000000..d73799c --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/GateWay/Program.cs @@ -0,0 +1,17 @@ +using BatchCoreService; +using System; + +namespace GateWay +{ + class Program + { + static void Main(string[] args) + { + //Console.WriteLine("Hello World!"); + DAService dataService = new DAService(); + Console.ReadLine(); + } + + + } +} diff --git a/SCADA/Program/CoreApp/DataService/GateWay/SqlMapping.cs b/SCADA/Program/CoreApp/DataService/GateWay/SqlMapping.cs new file mode 100644 index 0000000..c7a9ec8 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/GateWay/SqlMapping.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections.Generic; +using System.Data; +using DataService; + +namespace BatchCoreService +{ + internal class AlarmDataReader : IDataReader + { + IEnumerator _enumer; + + public AlarmDataReader(IEnumerable list) + { + this._enumer = list.GetEnumerator(); + } + + #region IDataReader Members + + public void Close() + { + + } + public int Depth + { + get { return 0; } + } + + public DataTable GetSchemaTable() + { + DataTable table = new DataTable("AlarmItem"); + table.Columns.Add("StartTime", typeof(DateTime)); + table.Columns.Add("Source", typeof(string)); + table.Columns.Add("ConditionId", typeof(int)); + table.Columns.Add("AlarmText", typeof(string)); + table.Columns.Add("AlarmValue", typeof(object)); + table.Columns.Add("Duration", typeof(int)); + table.Columns.Add("Severity", typeof(int)); + table.Columns.Add("SubAlarmType", typeof(int)); + return table; + } + public bool IsClosed + { + get { return false; } + } + + public bool NextResult() + { + return false; + } + + public bool Read() + { + return _enumer.MoveNext(); + } + + public int RecordsAffected + { + get { throw new NotImplementedException(); } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + } + + #endregion + + #region IDataRecord Members + + public int FieldCount + { + get { return 8; } + } + + public bool GetBoolean(int i) + { + return (bool)GetValue(i); + } + + public byte GetByte(int i) + { + return (byte)GetValue(i); + } + + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } + + public char GetChar(int i) + { + return (char)GetValue(i); + } + + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } + + public IDataReader GetData(int i) + { + throw new NotImplementedException(); + } + + public string GetDataTypeName(int i) + { + throw new NotImplementedException(); + } + + public DateTime GetDateTime(int i) + { + return (DateTime)GetValue(i); + } + + public decimal GetDecimal(int i) + { + return (decimal)GetValue(i); + } + + public double GetDouble(int i) + { + return (double)GetValue(i); + } + + public Type GetFieldType(int i) + { + switch (i) + { + case 0: + return typeof(DateTime); + case 1: + return typeof(string); + case 2: + return typeof(int); + case 3: + return typeof(string); + case 4: + return typeof(object); + case 5: + return typeof(int); + case 6: + return typeof(int); + case 7: + return typeof(int); + default: + return typeof(string); + } + } + + public float GetFloat(int i) + { + return (float)GetValue(i); + } + + public Guid GetGuid(int i) + { + return (Guid)GetValue(i); + } + + public short GetInt16(int i) + { + return (short)GetValue(i); + } + public int GetInt32(int i) + { + return (int)GetValue(i); + } + + public long GetInt64(int i) + { + return (long)GetValue(i); + } + + public string GetName(int i) + { + switch (i) + { + case 0: + return "StartTime"; + case 1: + return "Source"; + case 2: + return "ConditionId"; + case 3: + return "AlarmText"; + case 4: + return "AlarmValue"; + case 5: + return "Duration"; + case 6: + return "Severity"; + case 7: + return "SubAlarmType"; + default: + return ""; + } + } + + public int GetOrdinal(string name) + { + switch (name) + { + case "StartTime": + return 0; + case "Source": + return 1; + case "ConditionId": + return 2; + case "AlarmText": + return 3; + case "AlarmValue": + return 4; + case "Duration": + return 5; + case "Severity": + return 6; + case "SubAlarmType": + return 7; + default: + return -1; + } + } + + public string GetString(int i) + { + return (string)GetValue(i); + } + + public object GetValue(int i) + { + switch (i) + { + case 0: + return _enumer.Current.StartTime; + case 1: + return _enumer.Current.Source; + case 2: + return _enumer.Current.ConditionId; + case 3: + return _enumer.Current.AlarmText; + case 4: + return _enumer.Current.AlarmValue; + case 5: + return _enumer.Current.Duration.Seconds; + case 6: + return _enumer.Current.Severity; + case 7: + return _enumer.Current.SubAlarmType; + default: + return null; + } + } + + public int GetValues(object[] values) + { + throw new NotImplementedException(); + } + + public bool IsDBNull(int i) + { + switch (i) + { + case 0: + return _enumer.Current.StartTime == DateTime.MinValue; + case 1: + return string.IsNullOrEmpty(_enumer.Current.Source); + case 2: + return _enumer.Current.ConditionId == 0; + case 3: + return string.IsNullOrEmpty(_enumer.Current.AlarmText); + case 4: + return _enumer.Current.AlarmValue == null; + case 5: + case 6: + case 7: + default: + return false; + } + } + + public object this[string name] + { + get + { + return GetValue(GetOrdinal(name)); + } + } + + public object this[int i] + { + get + { + return GetValue(i); + } + } + + #endregion + } + + internal class HDASqlReader : IDataReader + { + IEnumerator _enumer; + IDataServer _server; + + public HDASqlReader(IEnumerable list, IDataServer server) + { + this._enumer = list.GetEnumerator(); + _server = server; + } + + #region IDataReader Members + + public void Close() + { + + } + + public int Depth + { + get { return 0; } + } + + public DataTable GetSchemaTable() + { + DataTable table = new DataTable("Log_HData"); + table.Columns.Add("ID", typeof(short)); + table.Columns.Add("TimeStamp", typeof(DateTime)); + table.Columns.Add("Value", typeof(float)); + return table; + } + public bool IsClosed + { + get { return false; } + } + + public bool NextResult() + { + return false; + } + + public bool Read() + { + return _enumer.MoveNext(); + } + + public int RecordsAffected + { + get { throw new NotImplementedException(); } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + } + + #endregion + + #region IDataRecord Members + + public int FieldCount + { + get { return 3; } + } + + public bool GetBoolean(int i) + { + return (bool)GetValue(i); + } + + public byte GetByte(int i) + { + return (byte)GetValue(i); + } + + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } + + public char GetChar(int i) + { + return (char)GetValue(i); + } + + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } + + public IDataReader GetData(int i) + { + return this; + } + + public string GetDataTypeName(int i) + { + throw new NotImplementedException(); + } + + public DateTime GetDateTime(int i) + { + return (DateTime)GetValue(i); + } + + public decimal GetDecimal(int i) + { + return (decimal)GetValue(i); + } + + public double GetDouble(int i) + { + return (double)GetValue(i); + } + + public Type GetFieldType(int i) + { + switch (i) + { + case 0: + return typeof(Int16); + case 1: + return typeof(DateTime); + case 2: + return typeof(Single); + default: + return typeof(Int32); + } + } + + public float GetFloat(int i) + { + return Convert.ToSingle(GetValue(i)); + } + + public Guid GetGuid(int i) + { + return (Guid)GetValue(i); + } + + public short GetInt16(int i) + { + return (short)GetValue(i); + } + public int GetInt32(int i) + { + return (int)GetValue(i); + } + + public long GetInt64(int i) + { + return (long)GetValue(i); + } + + public string GetName(int i) + { + switch (i) + { + case 0: + return "ID"; + case 1: + return "TimeStamp"; + case 2: + return "Value"; + default: + return string.Empty; + } + } + + public int GetOrdinal(string name) + { + switch (name) + { + case "ID": + return 0; + case "TimeStamp": + return 1; + case "Value": + return 2; + default: + return -1; + } + } + + public string GetString(int i) + { + return (string)GetValue(i); + } + + public object GetValue(int i) + { + switch (i) + { + case 0: + return _enumer.Current.ID; + case 1: + return _enumer.Current.TimeStamp; + case 2: + var index = _server.GetItemProperties(_enumer.Current.ID); + if (index < 0) return 0f; + switch (_server.MetaDataList[index].DataType) + { + case DataType.FLOAT: + var ff = _enumer.Current.Value.Single; + return ff > -2E-38 && ff < 2E-38 ? 0f : ff; + case DataType.BOOL: + return _enumer.Current.Value.Boolean ? 1f : 0f; + case DataType.INT: + return _enumer.Current.Value.Int32; + case DataType.WORD: + case DataType.SHORT: + return _enumer.Current.Value.Int16; + case DataType.BYTE: + return _enumer.Current.Value.Byte; + default: + return 0f; + } + default: + return 0f; ; + } + } + + public int GetValues(object[] values) + { + throw new NotImplementedException(); + } + + public bool IsDBNull(int i) + { + return false; + } + + public object this[string name] + { + get + { + return GetValue(GetOrdinal(name)); + } + } + + public object this[int i] + { + get + { + return GetValue(i); + } + } + + #endregion + } +} diff --git a/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusDriver.csproj b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusDriver.csproj new file mode 100644 index 0000000..aa8334b --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusDriver.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + + + + true + + + + + + + + + + + diff --git a/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusRTUDriver.cs b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusRTUDriver.cs new file mode 100644 index 0000000..dd5f896 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusRTUDriver.cs @@ -0,0 +1,563 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Text; +using System.Timers; +using DataService; +using RJCP.IO.Ports; + +namespace ModbusDriver +{ + [Description("Modbus RTU协议")] + public sealed class ModbusRTUReader : IPLCDriver + { + short _id; + public short ID + { + get + { + return _id; + } + } + + private SerialPortStream _serialPort; + public int PDU + { + get { return 0x100; } + } + + public DeviceAddress GetDeviceAddress(string address) + { + DeviceAddress dv = DeviceAddress.Empty; + if (string.IsNullOrEmpty(address)) + return dv; + switch (address[0]) + { + case '0': + { + dv.Area = Modbus.fctReadCoil; + int st; + int.TryParse(address, out st); + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '1': + { + dv.Area = Modbus.fctReadDiscreteInputs; + int st; + int.TryParse(address.Substring(1), out st); + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '4': + { + int index = address.IndexOf('.'); + dv.Area = Modbus.fctReadHoldingRegister; + 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)); + dv.Start--; + } + break; + case '3': + { + int index = address.IndexOf('.'); + dv.Area = Modbus.fctReadInputRegister; + 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)); + dv.Start--; + } + break; + } + return dv; + } + + public string GetAddress(DeviceAddress address) + { + return string.Empty; + } + + string _name; + public string Name + { + get + { + return _name; + } + } + + string _port; + public string ServerName + { + get { return _port; } + set { _port = value; } + } + + public bool IsClosed + { + get + { + return _serialPort.IsOpen == false; + } + } + + private int _timeOut; + public int TimeOut + { + get { return _timeOut; } + set { _timeOut = value; } + } + + List _grps = new List(); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + + public ModbusRTUReader(IDataServer server, short id, string name, string remote = null, int timeOut = 10000, string port = "COM1", string baudRate = "9600") + { + _id = id; + _name = name; + _server = server; + _port = port; + _serialPort = new SerialPortStream(port); + _timeOut = timeOut; + _serialPort.ReadTimeout = _timeOut; + _serialPort.WriteTimeout = _timeOut; + _serialPort.BaudRate = int.Parse(baudRate); + _serialPort.DataBits = 8; + _serialPort.Parity = Parity.Even; + _serialPort.StopBits = StopBits.One; + } + + public bool Connect() + { + try + { + _serialPort.Open(); + return true; + } + catch (IOException error) + { + if (OnClose != null) + { + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + } + return false; + } + } + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) + { + ModbusRtuGroup grp = new ModbusRtuGroup(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() + { + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + _serialPort.Close(); + } + + private byte[] CreateReadHeader(int startAddress, ushort length, byte function) + { + byte[] data = new byte[8]; + data[0] = (byte)_id; // Slave id high byte + data[1] = function; // Message size + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)length); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteSingleCoils(int startAddress, bool OnOff) + { + byte[] data = new byte[8]; + data[0] = (byte)_id; // Slave id high byte + data[1] = Modbus.fctWriteSingleCoil; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + if (OnOff) data[4] = 0xFF; + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteMultipleCoils(int startAddress, ushort numBits, byte[] values) + { + int len = values.Length; + byte[] data = new byte[len + 9]; + data[0] = (byte)_id; // Slave id high byte + data[1] = Modbus.fctWriteMultipleCoils; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)numBits); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + data[6] = (byte)len; + Array.Copy(values, 0, data, 7, len); + byte[] arr = Utility.CalculateCrc(data, len + 7); + data[len + 7] = arr[0]; + data[len + 8] = arr[1]; + return data; + } + + public byte[] WriteSingleRegister(int startAddress, byte[] values) + { + byte[] data = new byte[8]; + data[0] = (byte)_id; // Slave id high byte + data[1] = Modbus.fctWriteSingleRegister; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + data[4] = values[0]; + data[5] = values[1]; + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteMultipleRegister(int startAddress, byte[] values) + { + int len = values.Length; + if (len % 2 > 0) len++; + byte[] data = new byte[len + 9]; + data[0] = (byte)_id; // Slave id high byte + data[1] = Modbus.fctWriteMultipleRegister; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)(len >> 1)); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + data[6] = (byte)len; + Array.Copy(values, 0, data, 7, len); + byte[] arr = Utility.CalculateCrc(data, len + 7); + data[len + 7] = arr[0]; + data[len + 8] = arr[1]; + return data; + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + int area = address.Area; + try + { + byte[] header = area == Modbus.fctReadCoil ? CreateReadHeader(address.Start * 16, (ushort)(16 * size), (byte)area) : + CreateReadHeader(address.Start, size, (byte)area); + _serialPort.Write(header, 0, header.Length); + byte[] frameBytes = new byte[size * 2 + 5]; + byte[] data = new byte[size * 2]; + int numBytesRead = 0; + while (numBytesRead != size) + numBytesRead += _serialPort.Read(frameBytes, numBytesRead, size - numBytesRead); + if (frameBytes[0] == (byte)_id && Utility.CheckSumCRC(frameBytes)) + { + Array.Copy(frameBytes, 3, data, 0, size); + return data; + } + return null; + } + catch (Exception e) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(e.Message)); + return null; + } + } + + public ItemData ReadInt32(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 2); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt32(bit, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt16(bit, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(bit[0], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + byte[] bit = ReadBytes(address, size); + return bit == null ? new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD) : + new ItemData(Encoding.ASCII.GetString(bit), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadFloat(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 2); + return bit == null ? new ItemData(0f, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToSingle(bit, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadBit(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(false, 0, QUALITIES.QUALITY_BAD) : + new ItemData((bit[0] & (1 << (address.Bit))) > 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + var data = WriteMultipleRegister(address.Start, bit); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteBit(DeviceAddress address, bool bit) + { + var data = WriteSingleCoils(address.Start + address.Bit, bit); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + var data = WriteSingleRegister(address.Start, new byte[] { bits }); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteInt16(DeviceAddress address, short value) + { + var data = WriteSingleRegister(address.Start, BitConverter.GetBytes(value)); + _serialPort.Write(data, 0, data.Length); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteInt32(DeviceAddress address, int value) + { + var data = WriteMultipleRegister(address.Start, BitConverter.GetBytes(value)); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteFloat(DeviceAddress address, float value) + { + var data = WriteMultipleRegister(address.Start, BitConverter.GetBytes(value)); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return (chr & 0x80) > 0 ? -1 : 0; + } + + public int WriteString(DeviceAddress address, string str) + { + var data = WriteMultipleRegister(address.Start, Encoding.ASCII.GetBytes(str)); + _serialPort.Write(data, 0, data.Length); + _serialPort.ReadByte(); + var chr = _serialPort.ReadByte(); + return chr == (byte)_id ? -1 : 0; + } + + public int WriteValue(DeviceAddress address, object value) + { + return this.WriteValueEx(address, value); + } + + public event ShutdownRequestEventHandler OnClose; + } + + public sealed class ModbusRtuGroup : PLCGroup + { + public ModbusRtuGroup(short id, string name, int updateRate, bool active, ModbusRTUReader 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 ShortCacheReader(); + } + + protected override unsafe int Poll() + { + short[] cache = (short[])_cacheReader.Cache; + int k = 0; + foreach (PDUArea area in _rangeList) + { + byte[] rcvBytes = _plcReader.ReadBytes(area.Start, (ushort)area.Len);//从PLC读取数据 + if (rcvBytes == null) + { + _plcReader.Connect(); + return -1; + } + 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 - k; + 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 + k] = prcv[j]; + }//将PLC读取的数据写入到CacheReader中 + } + k += len; + } + } + return 1; + } + + } + + + public sealed class Modbus + { + public const byte fctReadCoil = 1; + public const byte fctReadDiscreteInputs = 2; + public const byte fctReadHoldingRegister = 3; + public const byte fctReadInputRegister = 4; + public const byte fctWriteSingleCoil = 5; + public const byte fctWriteSingleRegister = 6; + public const byte fctWriteMultipleCoils = 15; + public const byte fctWriteMultipleRegister = 16; + public const byte fctReadWriteMultipleRegister = 23; + + /// 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/CoreApp/DataService/ModbusDriver/ModbusRTU_TCP.cs b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusRTU_TCP.cs new file mode 100644 index 0000000..e8243f0 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusRTU_TCP.cs @@ -0,0 +1,462 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Sockets; +using System.Text; +using DataService; + +namespace ModbusDriver +{ + [Description("Modbus RTU_TCP协议")] + public sealed class ModbusRTU_TCPReader : IPLCDriver, IMultiReadWrite + { + private int _timeout; + + private Socket tcpSynCl; + private byte[] tcpSynClBuffer = new byte[0xFF]; + + short _id; + public short ID + { + get + { + return _id; + } + } + + string _name; + public string Name + { + get + { + return _name; + } + } + + int _slave = 1; + string _ip; + public string ServerName + { + get { return _ip; } + set { _ip = value; } + } + + public bool IsClosed + { + get + { + return tcpSynCl == null || tcpSynCl.Connected == false; + } + } + + public int TimeOut + { + get { return _timeout; } + set { _timeout = value; } + } + + List _grps = new List(20); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + + public ModbusRTU_TCPReader(IDataServer server, short id, string name, string ip, int timeOut = 500, string spare1 = "1", string spare2 = null) + { + _id = id;//id + _name = name; + _server = server; + _ip = ip; + _timeout = timeOut; + if (!string.IsNullOrEmpty(spare1)) + _slave = int.Parse(spare1); + } + + public bool Connect() + { + int port = 7000; + try + { + if (tcpSynCl != null) + tcpSynCl.Close(); + //IPAddress ip = IPAddress.Parse(_ip); + // ---------------------------------------------------------------- + // Connect synchronous client + tcpSynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpSynCl.SendTimeout = _timeout; + tcpSynCl.ReceiveTimeout = _timeout; + tcpSynCl.NoDelay = true; + tcpSynCl.Connect(_ip, port); + return true; + } + catch (SocketException error) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + return false; + } + } + + private byte[] CreateReadHeader(int startAddress, ushort length, byte function) + { + byte[] data = new byte[8]; + data[0] = (byte)_slave; // Slave id high byte + data[1] = function; // Message size + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)length); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteSingleCoils(int startAddress, bool OnOff) + { + byte[] data = new byte[8]; + data[0] = (byte)_slave; // Slave id high byte + data[1] = Modbus.fctWriteSingleCoil; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + if (OnOff) data[4] = 0xFF; + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteMultipleCoils(int startAddress, ushort numBits, byte[] values) + { + int len = values.Length; + byte[] data = new byte[len + 9]; + data[0] = (byte)_slave; // Slave id high byte + data[1] = Modbus.fctWriteMultipleCoils; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)numBits); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + data[6] = (byte)len; + Array.Copy(values, 0, data, 7, len); + byte[] arr = Utility.CalculateCrc(data, len + 7); + data[len + 7] = arr[0]; + data[len + 8] = arr[1]; + return data; + } + + public byte[] WriteSingleRegister(int startAddress, byte[] values) + { + byte[] data = new byte[8]; + data[0] = (byte)_slave; // Slave id high byte + data[1] = Modbus.fctWriteSingleRegister; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + data[4] = values[0]; + data[5] = values[1]; + byte[] arr = Utility.CalculateCrc(data, 6); + data[6] = arr[0]; + data[7] = arr[1]; + return data; + } + + public byte[] WriteMultipleRegister(int startAddress, byte[] values) + { + int len = values.Length; + if (len % 2 > 0) len++; + byte[] data = new byte[len + 9]; + data[0] = (byte)_slave; // Slave id high byte + data[1] = Modbus.fctWriteMultipleRegister; // Function code + byte[] _adr = BitConverter.GetBytes((short)startAddress); + data[2] = _adr[0]; // Start address + data[3] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes((short)(len >> 1)); + data[4] = _length[0]; // Number of data to read + data[5] = _length[1]; // Number of data to read + data[6] = (byte)len; + Array.Copy(values, 0, data, 7, len); + byte[] arr = Utility.CalculateCrc(data, len + 7); + data[len + 7] = arr[0]; + data[len + 8] = arr[1]; + return data; + } + + public int PDU + { + get { return 252; } + //get { return 256; } + } + + public DeviceAddress GetDeviceAddress(string address) + { + DeviceAddress dv = DeviceAddress.Empty; + if (string.IsNullOrEmpty(address)) + return dv; + switch (address[0]) + { + case '0': + { + dv.Area = Modbus.fctReadCoil; + int st; + int.TryParse(address, out st); + //dv.Start = (st / 16) * 16;//??????????????????? + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '1': + { + dv.Area = Modbus.fctReadDiscreteInputs; + int st; + int.TryParse(address.Substring(1), out st); + //dv.Start = (st / 16) * 16;//??????????????????? + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '4': + { + int index = address.IndexOf('.'); + dv.Area = Modbus.fctReadHoldingRegister; + 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)); + dv.Start--; + } + break; + case '3': + { + int index = address.IndexOf('.'); + dv.Area = Modbus.fctReadInputRegister; + 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)); + dv.Start--; + } + break; + } + return dv; + } + + public string GetAddress(DeviceAddress address) + { + return string.Empty; + } + + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) + { + ModbusTcpGroup grp = new ModbusTcpGroup(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 (tcpSynCl != null) + { + if (tcpSynCl.Connected) + { + try { tcpSynCl.Shutdown(SocketShutdown.Both); } + catch { } + tcpSynCl.Close(); + } + tcpSynCl = null; + } + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + int area = address.Area; + try + { + if (!tcpSynCl.Connected) return null; + byte[] header = area == Modbus.fctReadCoil ? CreateReadHeader(address.Start * 16, (ushort)(16 * size), (byte)area) : + CreateReadHeader(address.Start, size, (byte)area); + tcpSynCl.Send(header, 0, header.Length, SocketFlags.None);//是否存在lock的问题? + byte[] frameBytes = new byte[size * 2 + 3]; + int result = tcpSynCl.Receive(frameBytes, 0, frameBytes.Length, SocketFlags.None); + byte[] data = new byte[size * 2]; + if (frameBytes[0] == (byte)_slave) + { + Array.Copy(frameBytes, 3, data, 0, data.Length); + return data; + } + else return new byte[0]; + } + catch (Exception e) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(e.Message)); + return null; + } + } + + public ItemData ReadInt32(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 2); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt32(bit, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadInt16(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToInt16(bit, 0), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadByte(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(0, 0, QUALITIES.QUALITY_BAD) : + new ItemData(bit[0], 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadString(DeviceAddress address, ushort size) + { + byte[] bit = ReadBytes(address, size); + return bit == null ? new ItemData(string.Empty, 0, QUALITIES.QUALITY_BAD) : + new ItemData(Encoding.ASCII.GetString(bit), 0, QUALITIES.QUALITY_GOOD); + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 2); + return bit == null ? new ItemData(0f, 0, QUALITIES.QUALITY_BAD) : + new ItemData(BitConverter.ToSingle(bit, 0), 0, QUALITIES.QUALITY_GOOD); + //int value = BitConverter.ToInt32(bit, 0); + //return new ItemData(*(((float*)&value)), 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadBit(DeviceAddress address) + { + byte[] bit = ReadBytes(address, 1); + return bit == null ? new ItemData(false, 0, QUALITIES.QUALITY_BAD) : + new ItemData((bit[0] & (1 << (address.Bit))) > 0, 0, QUALITIES.QUALITY_GOOD); + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteMultipleRegister(address.Start, bit); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteBit(DeviceAddress address, bool bit) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteSingleCoils(address.Start + address.Bit, bit); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteSingleRegister(address.Start, new byte[] { bits }); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteInt16(DeviceAddress address, short value) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteSingleRegister(address.Start, BitConverter.GetBytes(value)); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteInt32(DeviceAddress address, int value) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteMultipleRegister(address.Start, BitConverter.GetBytes(value)); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteFloat(DeviceAddress address, float value) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteMultipleRegister(address.Start, BitConverter.GetBytes(value)); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -1 : 0; + } + + public int WriteString(DeviceAddress address, string str) + { + if (!tcpSynCl.Connected) return -1; + var data = WriteMultipleRegister(address.Start, Encoding.ASCII.GetBytes(str)); + tcpSynCl.Send(data, 0, data.Length, SocketFlags.None);//是否存在lock的问题? + int result = tcpSynCl.Receive(tcpSynClBuffer, 0, 0xFF, SocketFlags.None); + return (tcpSynClBuffer[1] & 0x80) > 0 ? -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); + } + } +} + diff --git a/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusTCPReader.cs b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusTCPReader.cs new file mode 100644 index 0000000..9f9a234 --- /dev/null +++ b/SCADA/Program/CoreApp/DataService/ModbusDriver/ModbusTCPReader.cs @@ -0,0 +1,634 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Timers; +using DataService; + +namespace ModbusDriver +{ + [Description("Modbus TCP协议")] + public sealed class ModbusTCPReader : IPLCDriver, IMultiReadWrite + { + private int _timeout; + + private Socket tcpSynCl; + private byte[] tcpSynClBuffer = new byte[0xFF]; + + short _id; + public short ID + { + get + { + return _id; + } + } + + string _name; + public string Name + { + get + { + return _name; + } + } + + string _ip; + public string ServerName + { + get { return _ip; } + set { _ip = value; } + } + + public bool IsClosed + { + get + { + return tcpSynCl == null || tcpSynCl.Connected == false; + } + } + + public int TimeOut + { + get { return _timeout; } + set { _timeout = value; } + } + + byte _slaveId;//设备ID 单元号 字节号 + /// + /// 设备ID 单元号 字节号 + /// + public byte SlaveId + { + get { return _slaveId; } + set { _slaveId = value; } + } + + List _grps = new List(20); + public IEnumerable Groups + { + get { return _grps; } + } + + IDataServer _server; + public IDataServer Parent + { + get { return _server; } + } + + public ModbusTCPReader(IDataServer server, short id, string name, string ip, int timeOut = 500, string spare1 = null, string spare2 = null) + { + _id = id; + _name = name; + _server = server; + _ip = ip; + _timeout = timeOut; + byte.TryParse(spare1, out _slaveId); + } + + public bool Connect() + { + int port = 502; + try + { + if (tcpSynCl != null) + tcpSynCl.Close(); + //IPAddress ip = IPAddress.Parse(_ip); + // ---------------------------------------------------------------- + // Connect synchronous client + tcpSynCl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + tcpSynCl.SendTimeout = _timeout; + tcpSynCl.ReceiveTimeout = _timeout; + tcpSynCl.NoDelay = true; + tcpSynCl.Connect(_ip, port); + return true; + } + catch (SocketException error) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(error.Message)); + return false; + } + } + + private byte[] CreateReadHeader(int id, int startAddress, ushort length, byte function) + { + byte[] data = new byte[12]; + data[0] = 0; // Slave id high byte + data[1] = 0; // Slave id low byte + data[5] = 6; // Message size + data[6] = (byte)id; // Slave address + data[7] = function; // Function code + byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress)); + data[8] = _adr[0]; // Start address + data[9] = _adr[1]; // Start address + byte[] _length = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)length)); + data[10] = _length[0]; // Number of data to read + data[11] = _length[1]; // Number of data to read + return data; + } + + private byte[] CreateWriteHeader(int id, int startAddress, ushort numData, ushort numBytes, byte function) + { + byte[] data = new byte[numBytes + 11]; + data[0] = 0; // Slave id high byte + data[1] = 0; // Slave id low byte+ + byte[] _size = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)(5 + numBytes))); + data[4] = _size[0]; // Complete message size in bytes + data[5] = _size[1]; // Complete message size in bytes + data[6] = (byte)id; // Slave address + data[7] = function; // Function code + byte[] _adr = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress)); + data[8] = _adr[0]; // Start address + data[9] = _adr[1]; // Start address + if (function >= Modbus.fctWriteMultipleCoils) + { + byte[] _cnt = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)numData)); + data[10] = _cnt[0]; // Number of bytes + data[11] = _cnt[1]; // Number of bytes + data[12] = (byte)(numBytes - 2); + } + return data; + } + + private byte[] WriteSyncData(byte[] write_data) + { + short id = BitConverter.ToInt16(write_data, 0); + if (IsClosed) CallException(id, write_data[7], Modbus.excExceptionConnectionLost); + else + { + 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; + + 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); + } + // ------------------------------------------------------------ + // Read response data + else + { + data = new byte[tcpSynClBuffer[8]]; + Array.Copy(tcpSynClBuffer, 9, data, 0, tcpSynClBuffer[8]); + } + return data; + } + catch (SocketException) + { + CallException(id, write_data[7], Modbus.excExceptionConnectionLost); + } + } + return null; + } + + public byte[] WriteSingleCoils(int id, int startAddress, bool OnOff) + { + byte[] data; + data = CreateWriteHeader(id, startAddress, 1, 1, Modbus.fctWriteSingleCoil); + if (OnOff == true) data[10] = 255; + else data[10] = 0; + return WriteSyncData(data); + } + + public byte[] WriteMultipleCoils(int id, int startAddress, ushort numBits, byte[] values) + { + byte numBytes = Convert.ToByte(values.Length); + byte[] data; + data = CreateWriteHeader(id, startAddress, numBits, (byte)(numBytes + 2), Modbus.fctWriteMultipleCoils); + Array.Copy(values, 0, data, 13, numBytes); + return WriteSyncData(data); + } + + public byte[] WriteSingleRegister(int id, int startAddress, byte[] values) + { + byte[] data; + data = CreateWriteHeader(id, startAddress, 1, 1, Modbus.fctWriteSingleRegister); + data[10] = values[0]; + data[11] = values[1]; + return WriteSyncData(data); + } + + public byte[] WriteMultipleRegister(int id, int startAddress, byte[] values) + { + ushort numBytes = Convert.ToUInt16(values.Length); + if (numBytes % 2 > 0) numBytes++; + byte[] data; + + data = CreateWriteHeader(id, startAddress, Convert.ToUInt16(numBytes / 2), Convert.ToUInt16(numBytes + 2), Modbus.fctWriteMultipleRegister); + Array.Copy(values, 0, data, 13, values.Length); + return WriteSyncData(data); + } + + public int PDU + { + get { return 252; } + //get { return 256; } + } + + public DeviceAddress GetDeviceAddress(string address) + { + DeviceAddress dv = DeviceAddress.Empty; + if (string.IsNullOrEmpty(address)) + return dv; + dv.Area = _slaveId; + switch (address[0]) + { + case '0': + { + dv.DBNumber = Modbus.fctReadCoil; + int st; + int.TryParse(address, out st); + //dv.Start = (st / 16) * 16;//??????????????????? + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '1': + { + dv.DBNumber = Modbus.fctReadDiscreteInputs; + int st; + int.TryParse(address.Substring(1), out st); + //dv.Start = (st / 16) * 16;//??????????????????? + dv.Bit = (byte)(st % 16); + st /= 16; + dv.Start = st; + } + break; + case '4': + { + int index = address.IndexOf('.'); + dv.DBNumber = Modbus.fctReadHoldingRegister; + 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)); + dv.Start--; + } + break; + case '3': + { + int index = address.IndexOf('.'); + dv.DBNumber = Modbus.fctReadInputRegister; + 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)); + dv.Start--; + } + break; + } + return dv; + } + + public string GetAddress(DeviceAddress address) + { + return string.Empty; + } + + + public IGroup AddGroup(string name, short id, int updateRate, float deadBand = 0f, bool active = false) + { + ModbusTcpGroup grp = new ModbusTcpGroup(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 (tcpSynCl != null) + { + if (tcpSynCl.Connected) + { + try { tcpSynCl.Shutdown(SocketShutdown.Both); } + catch { } + tcpSynCl.Close(); + } + tcpSynCl = null; + } + foreach (IGroup grp in _grps) + { + grp.Dispose(); + } + _grps.Clear(); + } + + internal string GetErrorString(byte exception) + { + switch (exception) + { + case Modbus.excIllegalFunction: + return "Constant for ModbusModbus.exception illegal function."; + case Modbus.excIllegalDataAdr: + return "Constant for ModbusModbus.exception illegal data address."; + case Modbus.excIllegalDataVal: + return "Constant for ModbusModbus.exception illegal data value."; + case Modbus.excSlaveDeviceFailure: + return "Constant for ModbusModbus.exception slave device failure."; + case Modbus.excAck: + return "Constant for ModbusModbus.exception acknowledge."; + case Modbus.excSlaveIsBusy: + return "Constant for ModbusModbus.exception slave is busy/booting up."; + case Modbus.excGatePathUnavailable: + return "Constant for ModbusModbus.exception gate path unavailable."; + case Modbus.excExceptionNotConnected: + return "Constant for ModbusModbus.exception not connected."; + case Modbus.excExceptionConnectionLost: + return "Constant for ModbusModbus.exception connection lost."; + case Modbus.excExceptionTimeout: + return "Constant for ModbusModbus.exception response timeout."; + case Modbus.excExceptionOffset: + return "Constant for ModbusModbus.exception wrong offset."; + case Modbus.excSendFailt: + return "Constant for ModbusModbus.exception send failt."; + } + return string.Empty; + } + + internal void CallException(int id, byte function, byte exception) + { + if (tcpSynCl == null) return; + if (exception == Modbus.excExceptionConnectionLost && IsClosed == false) + { + if (OnClose != null) + OnClose(this, new ShutdownRequestEventArgs(GetErrorString(exception))); + } + } + + public byte[] ReadBytes(DeviceAddress address, ushort size) + { + int area = address.DBNumber; + return area < 2 ? WriteSyncData(CreateReadHeader(address.Area, address.Start * 16, (ushort)(16 * size), (byte)area)) + : WriteSyncData(CreateReadHeader(address.Area, address.Start, size, (byte)area)); + } + + public ItemData ReadInt32(DeviceAddress address) + { + byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 2, (byte)address.DBNumber)); + 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); + } + + public ItemData ReadInt16(DeviceAddress address) + { + byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 1, (byte)address.DBNumber)); + 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); + } + + public ItemData ReadByte(DeviceAddress address) + { + byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 1, (byte)address.DBNumber)); + 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(address.Area, address.Start, size, (byte)address.DBNumber)); + 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);//是否考虑字节序问题? + } + + public unsafe ItemData ReadFloat(DeviceAddress address) + { + byte[] data = WriteSyncData(CreateReadHeader(address.Area, address.Start, 2, (byte)address.DBNumber)); + 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); + } + } + + public ItemData ReadBit(DeviceAddress address) + { + byte[] data = address.DBNumber > 2 ? WriteSyncData(CreateReadHeader(address.Area, address.Start, 1, (byte)address.DBNumber)) : + WriteSyncData(CreateReadHeader(address.Area, address.Start * 16 + address.Bit, 1, (byte)address.DBNumber)); + 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); + } + } + } + + public ItemData ReadValue(DeviceAddress address) + { + return this.ReadValueEx(address); + } + + public int WriteBytes(DeviceAddress address, byte[] bit) + { + var data = address.DBNumber > 2 ? WriteMultipleRegister(address.Area, address.Start, bit) + : WriteMultipleCoils(address.Area, address.Start, (ushort)(8 * bit.Length), bit);//应考虑到 + return data == null ? -1 : 0; + } + + public int WriteBit(DeviceAddress address, bool bit) + { + if (address.DBNumber < 3) + { + var data = WriteSingleCoils(address.Area, address.Start + address.Bit, bit); + return data == null ? -1 : 0; + } + return -1; + } + + public int WriteBits(DeviceAddress address, byte bits) + { + var data = WriteSingleRegister(address.Area, address.Start, new byte[] { bits }); + return data == null ? -1 : 0; + } + + public int WriteInt16(DeviceAddress address, short value) + { + var data = WriteSingleRegister(address.Area, address.Start, BitConverter.GetBytes(value)); + return data == null ? -1 : 0; + } + + public int WriteInt32(DeviceAddress address, int value) + { + var data = WriteMultipleRegister(address.Area, address.Start, BitConverter.GetBytes(value)); + return data == null ? -1 : 0; + } + + public int WriteFloat(DeviceAddress address, float value) + { + var data = WriteMultipleRegister(address.Area, address.Start, BitConverter.GetBytes(value)); + return data == null ? -1 : 0; + } + + public int WriteString(DeviceAddress address, string str) + { + var data = WriteMultipleRegister(address.Area, address.Start, Encoding.ASCII.GetBytes(str)); + 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 ModbusTcpGroup : PLCGroup + { + public ModbusTcpGroup(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 int 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(); + return -1; + } + 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; + } + } + return 1; + } + + } +}