1. 程式人生 > >基於 HTML5 的 PID-進料系統視覺化介面

基於 HTML5 的 PID-進料系統視覺化介面

前言

  隨著工業物聯網和網際網路技術的普及和發展,人工填料的方式已經逐漸被機械裝置取代。工業廠商減小誤操作、提升裝置安全以及追求高效率等製造特點對裝置的要求愈加高標準、嚴要求。同時機械生產以後還需遵從整個專案流程的規範管理,如何實行管理與交接也是一大嚴峻的挑戰。因此,整個生產流程中還應該制定一套關於管理流程的視覺化介面。

  在工業過程控制中,按被控物件的實時資料採集的資訊與給定值比較產生的誤差的比例、積分和微分進行控制的控制系統,簡稱 PID 控制系統。PID 控制生產環境具有適應性強,魯棒性強,使用方便等特點。進料系統則涉及到超高壓技術,在流水線系統中廣泛應用,能夠實現裝置半自動化或自動化送料作業,解決傳統進料方式計量不準、工作環境汙染以及工人勞動強度高等問題,從而實現高效的流水線加工。結合 PID 和自動化部署,可以為電力、機械、冶金、化工、食品、紡織等工業或者民用行業供需。本篇文章通過搭建危險廢物進料系統的 2D 場景以及資料介面展示,幫助我們瞭解如何使用 HT 實現一個視覺化的 PID 控制進料系統。

  專案地址預覽: 基於 HTML5  的 PID-進料系統視覺化介面 http://www.hightopo.com/demo/PID-feed-system/

 

效果預覽

  整體協作場景

  抓鬥操作場景

  進料場景

程式碼構建

  搭建場景

  該文主要實現的是 2D 場景,我們需要用到拓撲元件的相關 api 搭建基礎場景:

1 dataModel = new ht.DataModel(); //資料容器,用來存取資料節點Node
2 graphView = new ht.graph.GraphView(dataModel); //拓撲元件
3 graphView.addToDOM(); //將元件新增到body中

  上述程式碼新增元件到body中所用的是 addToDom 方法,HT元件一般會嵌入BorderPane、SplitView和TabView等容器中使用,而最外層的HT元件則需要使用者手工將 getView() 返回的底層 div 元素新增到頁面的 DOM 元素中,這裡需要注意的是,當父容器大小變化時,如果父容器是 BorderPane 和 SplitView 等這些HT預定義的容器元件,則HT的容器會自動遞迴呼叫孩子元件 invalidate 函式通知更新。但如果父容器是原生的 html 元素, 則 HT 元件無法獲知需要更新,因此最外層的 HT 元件一般需要監聽 window 的視窗大小變化事件,呼叫最外層元件 invalidate 函式進行更新。

  為了最外層元件載入填充滿視窗的方便性,HT 的所有元件都有 addToDOM 函式,其實現邏輯如下,其中 iv 是 invalidate 的簡寫:

 1 addToDom = function(){
 2     var self = this,
 3         view = self.getView(), //獲取元件的底層div
 4         style = view.style;
 5     document.body.appendChild(view); //將元件底層div新增到body中
 6     style.left = '0'; //預設所有元件的position都設定為absolute絕對定位
 7     style.right = '0';
 8     style.top = '0';
 9     style.bottom = '0';
10     window.addEventListener('resize',function(){ self.iv(); },false); //視窗改變大小,呼叫重新整理函式
11 }

  將檢視預設方法重置:

1 graphView.setPannable(false); //禁用通過滑鼠拖拽進行平移操作
2 graphView.setRectSelectable(false); //禁用拓撲上進行框選操作
3 graphView.setMovableFunc(()=>{false}); //禁用移動過濾器函式

  在 2D 編輯器上建立 2D 圖形會生成 JSON 檔案,引入生成場景需要進行反序列化:

1 ht.Default.xhrLoad('displays/industry/PID-進料系統.json',function(text){
2     var json = ht.Default.parse(text); //解析為JSON物件
3     dataModel.deserialize(json); //反序列化為場景
4 })

  在 HT 中,Data 型別物件構造時內部會自動被賦予一個 id 屬性,可通過 data.getId() 和 data.setId( id ) 獲取和設定,Data 物件新增到 DataModel 之後不允許修改 id 值,可通過 dataModel.getDataById (id ) 快速查詢 Data 物件。但是一般建議 id 屬性由 HT 自動分配,使用者業務意義的唯一標示可存在 tag 屬性上,通過 Data#setTag( tag ) 函式允許任意動態改變 tag 值,通過DataModel#getDataByTag(tag) 可查詢到對應的 Data 物件,並支援通過 DataModel#removeDataByTag( tag ) 刪除 Data 物件。我們這邊通過在 JSON 中設定 Data 物件的 tag 屬性,在程式碼中通過 dataModel.getDataByTag( tag ) 函式來獲取該 Data 物件:

 1 {
 2     "c": "ht.Node",
 3     "i": 407,
 4     "p": {
 5         "displayName": "抓手的結",
 6         "parent": {
 7             "__i": 403
 8         },
 9         "tag": "gripKnot",
10         "image": "symbols/symbol factory/垃圾處理/抓手的結.json",
11         "position": {
12             "x": -569.62125,
13             "y": -117.05025
14         },
15         "width": 50,
16         "height": 25
17     },
18     "s": {
19         "select.width": 0
20     }
21 },
1 var gripRightPaw = dataModel.getDataByTag('gripRightPaw');
2 var girpLeftPaw = dataModel.getDataByTag('grapLeftPaw');
3 var gripKnot = dataModel.getDataByTag('gripKnot');

  展開動畫

  HT 對動畫封裝了 ht.Default.startAnim 函式,通過設定 duration 獲取動畫時長, action 函式裡為執行的動畫屬性,以及 finishFunc 動畫執行後的回撥函式,該案例共置8個動畫,包含自驅動以及非同步動畫。下面舉第八個動畫(迴圈水流動)為例來理解 ht 內建動畫效果:

 1 //迴圈水流動
 2 function animation() {
 3     var lineJson = {};  
 4     var name = ''; 
 5     var speed = 20,
 6         lastTime = Date.now();
 7     //迴圈獲取水流 tag,並設定初始化 shape.dash.offset 為0
 8     for (var i = 1; i <= 9; i++ ) {
 9         if (i != 8) {
10             name = 'line'+i;
11             lineJson[name] = 0;
12         }
13     }
14     ht.Default.startAnim({
15         duration: 5000,
16         action: function () {
17             var time = Date.now(),
18                deltaTime = (time - lastTime) / 1000;
19             for (var tags in lineJson) {  
20                 if (tags.split('e')[1] % 2) {
21                     lineJson[tags] += deltaTime * speed;
22                 } else {
23                     lineJson[tags] -= deltaTime * speed;
24                 }
25                 var lines = dataModel.getDataByTag(tags);
26                 lines.setStyle('shape.dash.offset',lineJson[tags]);
27             }
28             lastTime = time
29         },
30         finishFunc: function () {
31             animation();
32             //TODO... 也可以在這裡非同步呼叫下一個動畫
33         }
34     })
35 }

  該例首先根據已建立的迴圈水流(已繫結 tag 標籤)通過 for 迴圈以及 dataModel. getDataByTag 動態獲取 Data 節點,通過標籤名攜帶的數字判斷水流方向,最終使用 Data.setStyle(可以簡寫為 Data.s ) 設定虛線部分的偏移距離。

  上面的迴圈水流為例,如果 lineJson[tags] += value (定值) ,當用戶放大檢視時圖元數量減少,會多呼叫幾次 anim 中的 action 函式,流動速度增快,縮小同理。因此採用 value = speed * deltaTime 的解決方式,解決檢視在不同縮放 zoom 的情況下播放速度不一致的問題,具體原理如下:

 1 //global
 2 var lastTime = Date.now();
 3 var distance = 0; //距離
 4 var speed = 20; //速度
 5 //action
 6 ht.Default.startAnim({
 7     duration:5000,
 8     action:function(){
 9         var time = Date.now();
10         var deltaTime = (time - lastTime) / 1000; 
11         distance += speed * deltaTime;
12         lastTime = time;
13     },
14     finishFunc:function(){//TODO}
15 })

    ht 實現動畫不僅可以使用 startAnim 來驅動,也可以採用按排程 addScheduleTask 進行實現,程式碼如下:

1 dataModel.addScheleTask({
2     interval, //排程間隔
3     beforeAction(){}, //排程開始之前的動作
4     action(){}, //排程任務
5     afterAction(){} //排程結束之後的動作
6 })    

  也可以使用 callLater 進行實現,ht 內建函式封裝了非常多關於動畫有趣且實操性強的 api ,有興趣可以進入官網 ( https://www.hightopo.com )進行了解和學習,也可以線上申請 framework 的試用包。如果想要了解更多HT封裝的動畫進行操作,可以參考 https://www.cnblogs.com/xhload3d/p/9222549.html 等其他文章。

  可操作

  當然,HT 也汲取了訂閱-釋出模式的天然優勢,通過驅動資料更改檢視,更加直觀地感受到資料與檢視的繫結過程。以下提供2種 HT 提供的可操作介面,第一種是通過建立面板元件, HT 內部提供了包含 formPane 、borderPane、TablePane 等一系列通用面板元件,此處我們以  formPane 為例,首先在 index.html 主頁面中引入 ht-form.js ,該檔案封裝了 formPane 面板的 api ,相關虛擬碼如下:

 1 var fp = new ht.widget.FormPane(); //建立面板物件
 2 fp.setWidth(200); 
 3 fp.setHeight(100); 
 4 fp.setRowHeight(30); //面板行高
 5 fp.setPadding(16); 
 6 fp.getView().className = 'main'; //節點設定類名後可以直接在 style 中設定屬性,說白了 fp.getView() 就是一個普通的 DOM 節點
 7 fp.addRow([{ //通過 addRow 方法新增文字以及進度條等內容
 8     id:'text',
 9     element:'Current Speed === 20',
10     align:'center'
11 }],[0.1]);
12 fp.addRow([{
13     id:'speed',
14     slider:{ //進度條
15         min:0,
16         max:100,
17         value:20, //當前進度值
18         step:1,
19         onValueChanged(){ value改變時觸發函式
20             var speed = fp.v('speed');
21             fp.v('text','Current Speed === ' + speed);
22         }
23     }
24 }],[0.1]);
25 document.body.appendChild(fp.getView());

  此時,我們只要把之前定義的 speed 指向 fp.v('speed') ,就可以簡單地實現資料檢視繫結:

 1 function animation(fp){
 2     var lineJson = {};
 3     var name = ''; 
 4     var lastTime = Date.now();
 5     var speed;
 6     for (var i = 1; i <= 9; i++ ) {
 7         if (i != 8) {
 8             name = 'line'+i;
 9             lineJson[name] = 0;
10         }
11     }
12     ht.Default.startAnim({
13         duration: 5000,
14         action: function () {
15             speed = fp.v('speed'); 
16             var time = Date.now(),
17                 deltaTime = (time - lastTime) / 1000;
18             for (var tags in lineJson) {  
19                 if (tags.split('e')[1] % 2) {
20                     lineJson[tags] += deltaTime * speed;
21                 } else {
22                     lineJson[tags] -= deltaTime * speed;
23                 }
24                 var lines = dataModel.getDataByTag(tags);
25                 lines.setStyle('shape.dash.offset',lineJson[tags]);
26             }
27             lastTime = time;
28         },
29         finishFunc: function () {
30             animation(fp);
31         }    
32     })
33 }

 

  另一種是通過 HT 的向量圖形庫,向量圖形採用點、線或多邊形的圖形描述方式,解決了 png 、jpg 等格式圖片在縮放過程中出現失真現象。建立向量圖形可以通過常規編輯器如 webstorm、webstorm 通過程式碼編寫,也可以通過 HT-2D 編輯器直接建立圖形,基本上不需要操作程式碼就可以簡單地創建出圖形,有學過 3dmax 或者 CAD 製圖的同學對此應該都不陌生。在編輯器的不斷完善下,內部已經有許多優秀的圖示和元件案例,這邊就可以直接引用一些小案例,首先需要建立一張圖紙,然後直接拉取一個自制圖示,類似 legend 的效果都是繪線畫出來的,更改文字部分就可以直接看到效果了。

  關鍵的還是功能性元件,圖示展示顯示介面,功能性元件支援事件的觸發,首先在控制元件裡面拉取 slider 圖示,然後到元件欄拉取 slider 元件,設定控制元件的最大值、最小值和預設值等一系列引數。

  可以得知我們即將改變的值有兩個,一個是 slider ,一個是文字的值,預設20,我們給這兩個 Data 物件繫結唯一標籤,分別為 sliderValue 以及 textValue,先通過進度條的當前值改變文字的值:

1 var sliderValue = dataModel.getDataByTag('sliderValue');
2 var textValue = dataModel.getDataByTag('textValue');
3 sliderValue.a('ht.onChange',function(){  //value改變觸發事件
4   textValue.a('textValue',sliderValue.a('ht.value'));
5 })

  然後animation拿到進度條的當前值,指向speed:

 1 function animation(data) {
 2     var lineJson = {};  
 3     var name = ''; 
 4     var lastTime = Date.now();
 5     var speed;
 6     for (var i = 1; i <= 9; i++ ) {
 7         if (i != 8) {
 8             name = 'line'+i;
 9             lineJson[name] = 0;
10         }
11     }
12     ht.Default.startAnim({
13         duration: 5000,
14         action: function () {
15             speed = data.a('ht.value');
16             var time = Date.now(),
17                 deltaTime = (time - lastTime) / 1000;
18             for (var tags in lineJson) {  
19                 if (tags.split('e')[1] % 2) {
20                     lineJson[tags] += deltaTime * speed;
21                 } else {
22                     lineJson[tags] -= deltaTime * speed;
23                 }
24                 var lines = dataModel.getDataByTag(tags);
25                 lines.setStyle('shape.dash.offset',lineJson[tags]);
26             }
27             lastTime = time;
28         },
29         finishFunc: function () {
30             animation(data);
31         }
32     })
33 }

 

  當然也可以自定義多個 slider 分別控制不同的動畫,具體如何實現還是全憑需求而定。

  

  不侷限於 2D 視覺化場景,與 3D 相關生產環境的視覺化場景模擬也有許多案例,如下:

  3D水泥工廠工藝流程:http://www.hightopo.com/demo/CementFactory/

 

  3D高爐鍊鐵工業流程:http://www.hightopo.com/demo/large-screen-puddling/