隨着 工業物聯網 和 互聯網技術 的普及和發展,人工填料的方式已經逐漸被機械設備取代。工業廠商減小誤操作、提升設備安全以及追求高效率等制造特點對設備的要求愈加高標准、嚴要求。同時機械生產以后還需遵從整個項目流程的規范管理,如何實行管理與交接也是一大嚴峻的挑戰。因此,整個生產流程中還應該制定一套關於管理流程的可視化界面。
在工業過程控制中,按被控對象的實時數據采集的信息與給定值比較產生的誤差的比例、積分和微分進行控制的控制系統,簡稱 PID 控制系統。PID 控制生產環境具有適應性強,魯棒性強,使用方便等特點。進料系統則涉及到超高壓技術,在流水線系統中廣泛應用,能夠實現設備半自動化或自動化送料作業,解決傳統進料方式計量不准、工作環境污染以及工人勞動強度高等問題,從而實現高效的流水線加工。結合 PID 和自動化部署,可以為電力、機械、冶金、化工、食品、紡織等工業或者民用行業供需。Hightopo(以下簡稱 HT )的 HT for Web 產品上提供了豐富的 2D 組態,本篇文章通過搭建危險廢物進料系統的 2D 場景以及數據界面展示,幫助我們了解如何使用 HT 實現一個可視化的 PID 控制進料系統。
// 數據容器,用來存取數據節點 Node dataModel = new ht.DataModel(); // 拓撲組件 graphView = new ht.graph.GraphView(dataModel); // 將組件添加到 body 中 graphView.addToDOM();
上述代碼添加組件到body中所用的是 addToDOM() 方法,HT組件一般會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的HT組件則需要用戶手工將 getView() 返回的底層 div 元素添加到頁面的 DOM 元素中,這里需要注意的是,當父容器大小變化時,如果父容器是 BorderPane 和 SplitView 等這些HT預定義的容器組件,則HT的容器會自動遞歸調用孩子組件 invalidate 函數通知更新。但如果父容器是原生的 html 元素, 則 HT 組件無法獲知需要更新,因此最外層的 HT 組件一般需要監聽 window 的窗口大小變化事件,調用最外層組件 invalidate() 函數進行更新。
為了最外層組件加載填充滿窗口的方便性,HT 的所有組件都有 addToDOM() 函數,其實現邏輯如下,其中 iv() 是 invalidate() 的簡寫:
addToDom = function(){ var self = this, // 獲取組件的底層 div view = self.getView(), style = view.style; // 將組件底層 div 添加到 body 中 document.body.appendChild(view); // 默認所有組件的 position 都設置為 absolute 絕對定位 style.left = '0'; style.right = '0'; style.top = '0'; style.bottom = '0'; // 窗口改變大小,調用刷新函數 window.addEventListener('resize',function(){ self.iv(); }, false); }
將視圖默認方法重置:
// 禁用通過鼠標拖拽進行平移操作 graphView.setPannable(false); // 禁用拓撲上進行框選操作 graphView.setRectSelectable(false); // 禁用移動過濾器函數 graphView.setMovableFunc(()=>{false});
在 2D 編輯器上創建 2D 圖形會生成 JSON 文件,引入生成場景需要進行反序列化:
ht.Default.xhrLoad('displays/industry/PID-進料系統.json',function(text){ // 解析為 JSON 對象 var json = ht.Default.parse(text); // 反序列化為場景 dataModel.deserialize(json); })
在 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 對象:
{ "c": "ht.Node", "i": 407, "p": { "displayName": "抓手的結", "parent": { "__i": 403 }, "tag": "gripKnot", "image": "symbols/symbol factory/垃圾處理/抓手的結.json", "position": { "x": -569.62125, "y": -117.05025 }, "width": 50, "height": 25 }, "s": { "select.width": 0 } }
var gripRightPaw = dataModel.getDataByTag('gripRightPaw'); var girpLeftPaw = dataModel.getDataByTag('grapLeftPaw'); var gripKnot = dataModel.getDataByTag('gripKnot');
// 循環水流動 function animation() { var lineJson = {}; var name = ''; var speed = 20, lastTime = Date.now(); // 循環獲取水流 tag,並設置初始化 shape.dash.offset 為0 for (var i = 1; i <= 9; i++ ) { if (i != 8) { name = 'line'+i; lineJson[name] = 0; } } ht.Default.startAnim({ duration: 5000, action: function () { var time = Date.now(), deltaTime = (time - lastTime) / 1000; for (var tags in lineJson) { if (tags.split('e')[1] % 2) { lineJson[tags] += deltaTime * speed; } else { lineJson[tags] -= deltaTime * speed; } var lines = dataModel.getDataByTag(tags); lines.setStyle('shape.dash.offset',lineJson[tags]); } lastTime = time }, finishFunc: function () { animation(); // TODO... 也可以在這里異步調用下一個動畫 } }) }
該例首先根據已創建的循環水流(已綁定 tag 標簽)通過 for 循環以及 dataModel. getDataByTag 動態獲取 Data 節點,通過標簽名攜帶的數字判斷水流方向,最終使用 Data.setStyle(可以簡寫為 Data.s ) 設置虛線部分的偏移距離。
上面的循環水流為例,如果 lineJson[tags] += value (定值) ,當用戶放大視圖時圖元數量減少,會多調用幾次 anim 中的 action 函數,流動速度增快,縮小同理。因此采用 value = speed * deltaTime 的解決方式,解決視圖在不同縮放 zoom 的情況下播放速度不一致的問題,具體原理如下:
// global var lastTime = Date.now(); // 距離 var distance = 0; // 速度 var speed = 20; // action ht.Default.startAnim({ duration:5000, action:function(){ var time = Date.now(); var deltaTime = (time - lastTime) / 1000; distance += speed * deltaTime; lastTime = time; }, finishFunc:function(){//TODO} })
dataModel.addScheleTask({ // 調度間隔 interval, // 調度開始之前的動作 beforeAction(){}, // 調度任務 action(){}, // 調度結束之后的動作 afterAction(){} })
也可以使用 callLater 進行實現,ht 內置函數封裝了非常多關於動畫有趣且實操性強的 api ,有興趣可以進入官網 ( https://www.hightopo.com )進行了解和學習,也可以線上申請 framework 的試用包。
// 創建面板對象 var fp = new ht.widget.FormPane(); fp.setWidth(200); fp.setHeight(100); // 面板行高 fp.setRowHeight(30); fp.setPadding(16); // 節點設置類名后可以直接在 style 中設置屬性,說白了 fp.getView() 就是一個普通的 DOM 節點 fp.getView().className = 'main'; // 通過 addRow 方法添加文本以及進度條等內容 fp.addRow([{ id:'text', element:'Current Speed === 20', align:'center' }],[0.1]); fp.addRow([{ id:'speed', // 進度條 slider:{ min:0, max:100, // 當前進度值 value:20, step:1, // value改變時觸發函數 onValueChanged(){ var speed = fp.v('speed'); fp.v('text','Current Speed === ' + speed); } } }],[0.1]); document.body.appendChild(fp.getView());
此時,我們只要把之前定義的 speed 指向 fp.v('speed') ,就可以簡單地實現數據視圖綁定:
function animation(fp){ var lineJson = {}; var name = ''; var lastTime = Date.now(); var speed; for (var i = 1; i <= 9; i++ ) { if (i != 8) { name = 'line'+i; lineJson[name] = 0; } } ht.Default.startAnim({ duration: 5000, action: function () { speed = fp.v('speed'); var time = Date.now(), deltaTime = (time - lastTime) / 1000; for (var tags in lineJson) { if (tags.split('e')[1] % 2) { lineJson[tags] += deltaTime * speed; } else { lineJson[tags] -= deltaTime * speed; } var lines = dataModel.getDataByTag(tags); lines.setStyle('shape.dash.offset',lineJson[tags]); } lastTime = time; }, finishFunc: function () { animation(fp); } }) }
另一種是通過 HT 的矢量圖形庫,矢量圖形采用點、線或多邊形的圖形描述方式,解決了 png 、jpg 等格式圖片在縮放過程中出現失真現象。創建矢量圖形可以通過常規編輯器如 webstorm、webstorm 通過代碼編寫,也可以通過 HT-2D 編輯器直接創建圖形,基本上不需要操作代碼就可以簡單地創建出圖形,有學過 3dmax 或者 CAD 制圖的同學對此應該都不陌生。在編輯器的不斷完善下,內部已經有許多優秀的圖標和組件案例,這邊就可以直接引用一些小案例,首先需要創建一張圖紙,然后直接拉取一個自制圖標,類似 legend 的效果都是繪線畫出來的,更改文字部分就可以直接看到效果了。
關鍵的還是功能性組件,圖標展示顯示界面,功能性組件支持事件的觸發,首先在控件里面拉取 slider 圖標,然后到組件欄拉取 slider 組件,設置控件的最大值、最小值和默認值等一系列參數。
可以得知我們即將改變的值有兩個,一個是 slider ,一個是文本的值,默認20,我們給這兩個 Data 對象綁定唯一標簽,分別為 sliderValue 以及 textValue,先通過進度條的當前值改變文本的值:
var sliderValue = dataModel.getDataByTag('sliderValue'); var textValue = dataModel.getDataByTag('textValue'); // value 改變觸發事件 sliderValue.a('ht.onChange',function(){ textValue.a('textValue',sliderValue.a('ht.value')); })
然后animation拿到進度條的當前值,指向speed:
function animation(data) { var lineJson = {}; var name = ''; var lastTime = Date.now(); var speed; for (var i = 1; i <= 9; i++ ) { if (i != 8) { name = 'line'+i; lineJson[name] = 0; } } ht.Default.startAnim({ duration: 5000, action: function () { speed = data.a('ht.value'); var time = Date.now(), deltaTime = (time - lastTime) / 1000; for (var tags in lineJson) { if (tags.split('e')[1] % 2) { lineJson[tags] += deltaTime * speed; } else { lineJson[tags] -= deltaTime * speed; } var lines = dataModel.getDataByTag(tags); lines.setStyle('shape.dash.offset',lineJson[tags]); } lastTime = time; }, finishFunc: function () { animation(data); } }) }
當然也可以自定義多個 slider 分別控制不同的動畫,具體如何實現還是全憑需求而定。
不局限於 2D 可視化場景,與 3D 相關生產環境的可視化場景模擬也有許多案例,如下:
3D水泥工廠工藝流程:http://www.hightopo.com/demo/CementFactory/
3D高爐煉鐵工業流程:http://www.hightopo.com/demo/large-screen-puddling/
通過可視化系統的維護,很大程度上節省了人力的資源和時間的成本,並且通過豐富的 2D 組態也可以呈現出一套完整的工藝流程。信息網絡的發展是 工業4.0 的趨勢化,而工業管控和數據可視化的集成,也是 工業互聯網 的核心所在,這可以實現將一些復雜的行業標准通過一系列簡單清晰的動畫加上實時數據的反饋,搭建出一個工業領域上具有代表性的行業流程標准的可視化運維系統。當然,HT 通過自身地不斷完善,產品上不僅僅擁有可使用戶輕松上手的 2D 組態,在 3D 組態上更有許多有趣好玩的手法可供用戶搭建!
2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這里你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA
同時,你也可以查看更多案例及效果:https://www.hightopo.com/demos/index.html