開源純C#工控網關+組態軟件(五)從網關到人機界面

1、   引子git

以前都在講網關,很多網友關注如何實現界面。想了解下位機變量變化,是怎樣一步步觸發人機界面動畫的。github

 

這個步步觸發,實質上是變量組(Group)的批量數據變化(DataChange)事件,引起了變量(Tag)的值更新(ValueChanged)事件,最終觸發了圖元的動畫腳本(Action)。這是一個連鎖反應。數據庫

簡言之,界面是一批叫Tag乘客,從網關坐TLV協議的列車,到了上位機車站下車,在ClientService這個舞臺上,用各自的樂器(ITagReader)演奏了一出交響樂。數組

2、   承上啓下的核心對象:Tag架構

Tag(標籤或者叫變量)是整個項目的核心對象。所謂核心對象,就是它無所不在,是動態的,流動的,就像血液融匯貫通。動畫

實質上,Tag對下位機,就是一個個傳感器的數據、一個個開關信號;對上位機,就是一個個按鈕、儀表盤、電機。加密

Tag在變量管理器(TagConfig)產生,在系統初始化時分配,存在於人機界面程序和網關服務的各個角落,它們的值和時間戳在不斷的變化。spa

對上位機設計者,用到的是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事件,以驅動人機界面。

3、   上下位機鏈接的紐帶: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;//寫多個變量
    }

4、   人機界面的驅動引擎: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,圖元動畫的幕後指揮。

5、   圖元動畫的幕後指揮: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能夠是讓電機報警,顏色變爲閃爍的紅色;也能夠是點亮一盞燈,或打開一座閥門。下文會詳細闡述。

從網關到人機界面流程:

 

6、   下面的計劃

寫一系列帖子,把架構、原理講清楚。大體以下:

  • 網關層接口概述
  • 上下位機通信原理
  • 如何實現一個設備驅動
  • 從網關到人機界面
  • 如何設計圖元
  • VS插件模塊及原理
  • 歸檔模塊及文件格式
  • 如何進行功能擴展
  • 組態變量表達式實現

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

相關文章
相關標籤/搜索