zoukankan      html  css  js  c++  java
  • 开源纯C#工控网关+组态软件(五)从网关到人机界面

    一、   引子

    之前都在讲网关,不少网友关注如何实现界面。想了解下位机变量变化,是怎样一步步触发人机界面动画的。

     

    这个步步触发,实质上是变量组(Group)的批量数据变化(DataChange)事件,引发了变量(Tag)的值更新(ValueChanged)事件,最终触发了图元的动画脚本(Action)。这是一个连锁反应。

    简言之,界面是一批叫Tag乘客,从网关坐TLV协议的列车,到了上位机车站下车,在ClientService这个舞台上,用各自的乐器(ITagReader)演奏了一出交响乐。

    二、   承上启下的核心对象:Tag

    Tag(标签或者叫变量)是整个项目的核心对象。所谓核心对象,就是它无所不在,是动态的,流动的,就像血液融汇贯通。

    实质上,Tag对下位机,就是一个个传感器的数据、一个个开关信号;对上位机,就是一个个按钮、仪表盘、电机。

    Tag在变量管理器(TagConfig)产生,在系统初始化时分配,存在于人机界面程序和网关服务的各个角落,它们的值和时间戳在不断的变化。

    对上位机设计者,用到的是Tag的名字、Tag的数据类型;对下位机设计者,看到的是Tag的地址、Tag的长度。对变量报警和数据归档,需要知道Tag的时间戳。

    所有的Tag继承于ITag接口。Tag的类型就是数据的类型,有FloatTag(浮点型)、BoolTag(逻辑型)、还有整型、字符型。不同类型Tag的读写对应IReaderWriter接口的ReadXXX/WriteXXX方法。

    Tag可以主动去读(Read)写(Write),也可以被动的刷新(Update),强制刷新(Refresh)。

    Tag的Read方法是调用所属Group、最终是调用所属IDriver的ReadXXX方法从下位机读入数据。但Tag的主要应用场景是被动刷新触发ValueChanged事件,以驱动人机界面。

    三、   上下位机连接的纽带:TLV协议

    前文已经阐述了网关如何通过轮询下位机、推送批量数据给上位机。上位机需要将推送来的数据流解析为一堆变化的Tag,以驱动整个人机界面和控制逻辑。

    网关和上位机之间通讯,我这里使用了一个自定义的简单的TLV协议(Tag-Length-Value),承载于Socket。

    这个协议包括两部分:

    •  数据推送:将网关一端变化的Tag打包封装,传输给客户端;客户端拆包,还原为一堆Tag。具体流程为:
    1. 网关的DataChange事件调用SendData方法,将变化的Tag打包为HistoryData数组(包含变量ID、值、时间戳);
    2. Socket将HistoryData数组转换为字节流推送给客户端;
    3. 客户端的ClientDriver 包含ReciveData方法,将字节流还原为HistoryData数组并触发客户端DataChange事件;
    4. 客户端的DataChange事件将HistoryData数组转换为Tag数组,并调用Tag的Update,触发ValueChanging和ValueChanged事件。
    •  指令:客户端主动向网关发送指令,一般用来读、写特定变量或一批变量,还可以查询历史归档、查询报警等。指令格式如下:

          指令码FCTCOMMAND:包含各种命令;

          参数:如读入时间段内所有归档数据,则需要起始时间、结束时间;读入变量,则需要变量ID。

          返回值:网关接收指令并返回数据,也是字节流。  

        public class FCTCOMMAND
        {
            public const byte fctHead = 0xAB;//报头可加密,如报头不符,则不进行任何操作;客户端Socket发送报警请求,封装于Server
            public const byte fctHdaIdRequest = 30;//按变量ID读入历史数据
            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;//写多个变量
        }

    四、   人机界面的驱动引擎:ClientService

    人机界面客户端的 ClientService与网关的DAService如出一辙:都具有相类似的结构,继承了IDataServer, IAlarmServer,都从同一个数据库加载驱动、组、变量、报警:

    客户端的:

    public sealed class DAServer : IDataServer, IAlarmServer, IHDAServer

    网关的:

    public class DAService : IDataExchangeService, IDataServer, IAlarmServer

    只是多了一个IHDAServer,具有查询历史数据的功能,而历史数据归档是网关的功能。

    因此,ClientService也带有自己的驱动ClientDriverClientDriver也带有自己的组ClientGroup

    注意的是,ClientDriver是上位机唯一的Driver,ClientGroup也是ClientDriver唯一的Group。这是因为上位机无需和各类型下位机打交道,与它打交道的唯一对象就是网关本身。

    因此,人机界面的各类操作指令,如按按钮、读归档数据、查询报警等,最终都反映成TLV协议指令发送给网关,并得到反馈。

    而人机界面图元的动画,都是来自网关推送的Tag,触发ValueChanged事件;事件的订阅者,就是图元对应的ITagReader,图元动画的幕后指挥。

    五、   图元动画的幕后指挥:ITagReader

    ITagReader接口为所有图元组件继承,它的功能就是将Tag与动画绑定。先看下结构:

        public interface ITagReader : ITagLink
        {
            string TagReadText { get; set; }
            string[] GetActions();
            Action SetTagReader(string key, Delegate tagChanged);
            IList<ITagLink> Children { get; }
        }

    TagReadText属性,就是与图元动画关联的变量表达式:形如Tag1*2+Tag2*5>10。我实现了一个自定义表达式编译器Eval,可以解析表达式语法,分离出Tag1和Tag2。这段代码在Example-WindowHelper-BindingControl

    接着,图元组件订阅Tag1和Tag2的ValueChanged事件。

    如果值发生变化,这个事件内部会执行SetTagReader,计算表达式的结果,如满足条件,将向界面发送指令。

    例如变量表达式为Tag1*2+Tag2*5>10,此时若Tag1=1,Tag2=2,满足条件,最终会触发一个动画脚本:Action。这个Action可以是让电机报警,颜色变为闪烁的红色;也可以是点亮一盏灯,或打开一座阀门。下文会详细阐述。

    从网关到人机界面流程:

     

    六、   下面的计划

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

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

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

  • 相关阅读:
    [CF538F]A Heap of Heaps(主席树)
    [BZOJ1901][luogu2617]Dynamic Rankings(树状数组+主席树)
    [BZOJ3932][CQOI2015]任务查询系统(差分+主席树)
    [BZOJ2588]Count on a tree(LCA+主席树)
    [BZOJ2733][HNOI2012] 永无乡(线段树合并)
    [BZOJ1604][Usaco2008 Open]Cow Neighborhoods 奶牛的邻居 (Treap+单调队列)
    【贪心】POJ2376-Cleaning Shifts
    【穷竭】POJ3187-Backward Digit Sums
    【枚举+贪心】POJ2718-Smallest Difference
    【BFS】POJ3669-Meteor Shower
  • 原文地址:https://www.cnblogs.com/evilcat/p/7782265.html
Copyright © 2011-2022 走看看