You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
686 lines
24 KiB
686 lines
24 KiB
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)
|
|
{
|
|
if (_updateRate <= 0) _updateRate = 100;
|
|
_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<int> _changedList;
|
|
public List<int> ChangedList
|
|
{
|
|
get
|
|
{
|
|
return _changedList;
|
|
}
|
|
}
|
|
|
|
|
|
protected List<ITag> _items;
|
|
public IEnumerable<ITag> Items
|
|
{
|
|
get { return _items; }
|
|
}
|
|
|
|
protected IDataServer _server;
|
|
public IDataServer Server
|
|
{
|
|
get
|
|
{
|
|
return _server;
|
|
}
|
|
}
|
|
|
|
protected List<PDUArea> _rangeList = new List<PDUArea>();
|
|
|
|
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<int>();
|
|
this._cacheReader = new ByteCacheReader();
|
|
this._timer = new Timer();
|
|
}
|
|
|
|
public bool AddItems(IList<TagMetaData> items)
|
|
{
|
|
int count = items.Count;
|
|
if (_items == null) _items = new List<ITag>(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<ITag> tags)
|
|
{
|
|
if (_items == null)
|
|
{
|
|
_items = new List<ITag>();
|
|
}
|
|
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
|
|
{
|
|
var size= _start.DataSize <= bitCount ? 1 : _start.DataSize / bitCount;
|
|
_rangeList.Add(new PDUArea(_start, size, 0, 1));
|
|
_cacheReader.Size = size;//改变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)
|
|
{
|
|
_changedList.Clear();
|
|
Poll();
|
|
if (_changedList.Count > 0)
|
|
Update();
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
protected virtual void Poll()
|
|
{
|
|
if (_plcReader.IsClosed) return;
|
|
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();
|
|
continue;
|
|
}
|
|
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 (iByte1 + i < rcvBytes.Length && 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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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<ITag, object> 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<int> ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache)
|
|
{
|
|
return source == DataSource.Cache ? _cacheReader.ReadInt32(address) : _plcReader.ReadInt32(address);
|
|
}
|
|
|
|
public ItemData<short> ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache)
|
|
{
|
|
return source == DataSource.Cache ? _cacheReader.ReadInt16(address) : _plcReader.ReadInt16(address);
|
|
}
|
|
|
|
public ItemData<byte> ReadByte(DeviceAddress address, DataSource source = DataSource.Cache)
|
|
{
|
|
return source == DataSource.Cache ? _cacheReader.ReadByte(address) : _plcReader.ReadByte(address);
|
|
}
|
|
|
|
public ItemData<float> ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache)
|
|
{
|
|
return source == DataSource.Cache ? _cacheReader.ReadFloat(address) : _plcReader.ReadFloat(address);
|
|
}
|
|
|
|
public ItemData<bool> ReadBool(DeviceAddress address, DataSource source = DataSource.Cache)
|
|
{
|
|
return source == DataSource.Cache ? _cacheReader.ReadBit(address) : _plcReader.ReadBit(address);
|
|
}
|
|
|
|
public ItemData<string> 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;
|
|
}
|
|
}
|
|
}
|
|
|