89

开源纯C#工控网关+组态软件(四)上下位机通讯原理 - 老坏猫

 6 years ago
source link: http://www.cnblogs.com/evilcat/p/7743970.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

开源纯C#工控网关+组态软件(四)上下位机通讯原理

一、   网关的功能:承上启下

最近有点忙,更新慢了。感谢园友们给予的支持,现在github上已经有43917-20171027164326851-577399482.png。目标是最好的开源组态,看来又近一步^^

之前有提到网关是物联网的关键环节,它的作用就是承上启下

下位机有下位机的语言,上位机有上位机的思路。网关就是一个翻译,把下位机的语言转成通用语,再告诉上位机该怎么做。

这个翻译的过程,应该保证:

  1. 实时性。如果太慢,上下位机明显不合拍,就会出问题。
  2. 精确性。信号不能频繁丢失、丢步、跳步;不能有太大误差;也不会带入太多干扰和噪音。
  3. 稳定性。如发生故障,如通讯断开,要能自动重连;要足够强壮,不会动辄崩溃;发生意外崩溃,要能自动重启;要有容错机制和错误日志;等等。

要实现这些指标,还是很有挑战的。

二、   通讯蓝本:OPC规范

网关看上去是个很玄奥的东西,网上找来很多相关资料,发现都集中到一个点:OPC规范。

OPC就如一盏明灯,照亮了前进的方向。OPC规范是大家手笔,集中了业界专家的智慧,站在巨人的肩膀上,可以少走很多弯路。

OPC规范定义了数据的采集、归档、报警以及完整的接口示范。

OPC数据采集规范,包含了这样一些重要概念:

  1. 数据读写方式包括下位机批量推送数据、上位机单独读写数据。
  2. 可以异步读写,也可以同步读写。
  3. 数据包括元数据(数据的属性,如数据类型、长度、名称等)和过程数据(ID、数值、时间戳、质量戳)。
  4. 包含驱动(Driver)-组(Group)-变量(Item)的三级架构。可以对变量按需分组,有的组只读,有的组需要1秒刷新一次,有的只要5分钟就可以,要加以区别以提高效率。
  5. 要能判断驱动程序是否断线、要提供断线或关闭的事件供应用程序处理。

  

43917-20171027165220523-1456865607.png

总之,信息量很丰富,启发很大,我的网关程序很大程度上都参考了OPC规范。

但是OPC有它固有的缺陷:依赖微软的COM组件技术,不能跨平台,同时COM是一种过时的技术,在不同主机上通讯的配置十分繁琐且不安全。

因此,我改造实现了自己的类OPC服务器。基本通讯过程是:批量轮询下位机-与上个周期的数据比对-提取变化的数据-批量推送给上位机

三、   与下位机通讯:批量轮询

  • 下位机的特点-为什么要采用轮询

下位机通讯的特点:

  1. 下位机很多采用主-从模式。即主机发送的信息可以传送到各个从机或指定的从机,而各个从机的信息只能发送给主机。主机采用查询方式接收发送数据,从机采用中断方式接收发送数据。这种模式天然适合轮询。
  2. 下位机多数只有一个通信口,有些还是串口,天然不适合推送模式。
  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)数据。

未来考虑制定一个新的接口支持这一模式。

四、   与上位机通讯:订阅-推送

  •  上位机的特点-为什么要采用订阅-推送

上位机通信的特点主要为:

  1. 要及时、准确了解下位机的消息。无论是监控画面、还是报警、提示人工操作这些,越实时越好,越准确越好。如果采用请求-响应模式,请求的周期决定实时性不会太好。请求频繁网关压力大,反之实时性差。
  2. 一个网关可能要拖好几个上位机,工段多的,可能要开十几个显示屏同时监控。因此,每个上位机都向网关请求数据,流量会陡然上升,网络会阻塞,网关压力会很大。
  3. 大部分上位机需要的数据不会经常变化,尤其是一些开关量。如果每次反复请求,浪费资源,浪费时间。
  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、实时值、时间戳等信息,并转换为图元的动画,后文再详细阐述。

  • 上下位机通讯流程图:
43917-20171027211037086-96628891.png

 五、   下面的计划

写一系列帖子,把架构、原理讲清楚。大致如下:

  • 网关层接口概述
  • 上下位机通讯原理
  • 如何实现一个设备驱动
  • 如何设计图元
  • VS插件模块及原理
  • 归档模块及文件格式
  • 如何进行功能扩展
  • 组态变量表达式实现

github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK