1、 网关的功能:承上启下git
最近有点忙,更新慢了。感谢园友们给予的支持,如今github上已经有。目标是最好的开源组态,看来又近一步^^github
以前有提到网关是物联网的关键环节,它的做用就是承上启下。数组
下位机有下位机的语言,上位机有上位机的思路。网关就是一个翻译,把下位机的语言转成通用语,再告诉上位机该怎么作。缓存
这个翻译的过程,应该保证:安全
要实现这些指标,仍是颇有挑战的。服务器
2、 通信蓝本:OPC规范网络
网关看上去是个很玄奥的东西,网上找来不少相关资料,发现都集中到一个点:OPC规范。架构
OPC就如一盏明灯,照亮了前进的方向。OPC规范是你们手笔,集中了业界专家的智慧,站在巨人的肩膀上,能够少走不少弯路。异步
OPC规范定义了数据的采集、归档、报警以及完整的接口示范。函数
OPC数据采集规范,包含了这样一些重要概念:
总之,信息量很丰富,启发很大,个人网关程序很大程度上都参考了OPC规范。
可是OPC有它固有的缺陷:依赖微软的COM组件技术,不能跨平台,同时COM是一种过期的技术,在不一样主机上通信的配置十分繁琐且不安全。
所以,我改造实现了本身的类OPC服务器。基本通信过程是:批量轮询下位机-与上个周期的数据比对-提取变化的数据-批量推送给上位机。
3、 与下位机通信:批量轮询
下位机通信的特色:
轮询就是网关做为主机,按期请求下位机的数据。若是实现批量请求,减小往返,轮询的效率并不低。几千个变量轮询周期500毫秒(西门子),无压力。
轮询是以组(Group)为单位的。Group都继承自IGroup 接口:
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<ITag> Items { get; } bool AddItems(IList<TagMetaData> items); bool AddTags(IEnumerable<ITag> 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<ITag, object> items, bool isSync = true); ItemData<int> ReadInt32(DeviceAddress address, DataSource source = DataSource.Cache); ItemData<short> ReadInt16(DeviceAddress address, DataSource source = DataSource.Cache); ItemData<byte> ReadByte(DeviceAddress address, DataSource source = DataSource.Cache); ItemData<float> ReadFloat(DeviceAddress address, DataSource source = DataSource.Cache); ItemData<bool> ReadBool(DeviceAddress address, DataSource source = DataSource.Cache); ItemData<string> 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; }
其中,UpdateRate就是轮询周期。DeadBand是死区。死区表明敏感度,设的小敏感度高,但也带来更多的噪声。
每一个Group的变量可支持单独读写(如各ReadXXX,WriteXXX方法),也支持批量推送(DataChange事件)。对下位机的轮询,都是以组为单位,每一个组在激活状态下按照本身的轮询周期,采集、推送数据,互不干扰。
每一个Group包含特性类似的一组变量:有相同的轮询周期、激活属性(须要轮询或无需轮询)、读写属性(均为只读、读/写或只写),须要的话能够同时使能或同时屏蔽。
由于部分变量无需随时监控,能够将其划入一组,不刷新(轮询);有些变量变化很快,须要高频扫描;有些变化很慢也不须要时时查看,能够几分钟轮询一次。将变量有效分组能够提升对重点监控变量的读写效率,减小对下位机资源的占用。
网关若是有多个客户端相连,各自须要的数据又不尽相同,由网关统必定期轮询,再批量推送给客户端是很高效的。
就好比开超市,南来北往的客人(客户端)需求各异,但超市(网关)来统一采购(轮询),不用客户跟各个批发市场(下位机)直接定货,集中来我这购物(订阅Tag)就行。
虽然当前主流PLC不都支持订阅-推送模式,但这是历史潮流。AB Controllogix、新的西门子S71200-1500,都支持标签地址,也就是直接推送变化的标签(Tag)数据。
将来考虑制定一个新的接口支持这一模式。
4、 与上位机通信:订阅-推送
上位机通讯的特色主要为:
所以,向订阅客户只推送变化的数据,无疑是一种高效的办法。只要数据发生变化,立刻推送给客户端,既保证了实时性,又保证了推送数据的最小化。
就像打报修电话,送修单位(下位机)不必定时时刻刻有人上门;电话打过去,总台(网关)记下号码,有人上门(下位机有数据变化)了通知(推送)您。没人你也不用几秒钟打一个催(轮询),你烦你们烦。
推送是只推送变化的变量。要知道哪些变量变化了,必需要缓存上一次变量的数据加以比对。所以,须要缓存每次轮询的数据。
网关在对下位机的轮询过程当中,将订阅的变量都扫描了一遍;这些数据都存在内存的变量缓存:ICache:
public interface ICache : IReaderWriter { int Size { get; set; } int ByteCount { get; } Array Cache { get; } int GetOffset(DeviceAddress start, DeviceAddress end); }
首先,ICache 继承了IReaderWriter,也就是缓存类也具备可读写性,以方便比对。同时还有一个Cache属性,这就是内存区域:映射、储存下位机的变量。
根据下位机的地址最小单元、字节序的不一样,对应的ICache也相应不一样。例如ModbusTcp是16位寄存器,网络字节序,相应的Cache也是16位数组,按照网络字节序读写;AB PLC对应的Cache则是32位数组。
由于下位机可能有不少个,存储地址也是不连续的;但经过对下位机地址DeviceAddress的排序,最终下位机地址映射到一块连续的内存地址,经过DeviceAddress的CacheIndex(缓存索引)相关联。
每次轮询,即调用IReaderWriter的ReadBytes方法依次读入下位机变量区域;读入的值与Cache中缓存的数据比对; 全部变化的部分加入一个ChangedList表,存储变化的CacheIndex。
这些功能在PLCGroup定时器内的Poll函数实现。
protected void timer_Timer(object sender, EventArgs e) { if (_isActive) { lock (sync) { Poll(); if (_changedList.Count > 0) Update(); } } else return; }
比较以后,如发现ChangedList的数量大于0,说明有变量数值更新,执行Update方法,根据CacheIndex找到全部变化的变量,经过IGroup 接口的DataChange事件打包推送出去。
具体的订阅-推送过程,是利用套接字(Socket)在DAService类实现的。套接字顾名思义,就是一条电话线:各客户端向网关服务器发送请求,创建一个长链接:只要客户不挂电话,就一直连着。客户始终监听电话;只要服务器数据有变化,立马有话务员及时告知,客户响应。在这里我实现了一个自定义的TLV(Type-Length-Value)协议,将变化的数据打包发送,客户端拆包,分解出变量的ID、实时值、时间戳等信息,并转换为图元的动画,后文再详细阐述。
5、 下面的计划
写一系列帖子,把架构、原理讲清楚。大体以下:
github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275