工業物聯網之從 Modbus 到 Web 資料視覺效果

來源:互聯網
上載者:User

標籤:gif   海量   ===   建議   line   dex   demo   mes   any   

前言

  工業物聯網是一個範圍很大的概念,本文從資料視覺效果的角度介紹了一個最小化的工業物聯網平台,從 Modbus 資料擷取到前端資料視覺效果呈現的基本實現思路。這裡面主要涉及基於 Modbus 通訊規約的資料擷取、後台即時資料處理、前端即時資料接收、前端即時資料可視化顯示。物聯網平台架構主要參考了圖撲物聯工業物聯網平台,並從中提取了部分功能進行介紹,前端資料視覺效果採用的是HT for Web。

  由於內容比較多,具體實現上涉及到前端工程師、後台工程師、資料擷取工程師等多個開發角色的參與,所以本文重點介紹實現思路和 WebSocket 訊息推送的實現,其它環節的具體實現細節作者會在其它文章中進行詳細介紹。

 

一、物聯網平台架構

  物聯網平台主要是B/S模式,工業物聯網平台大都採用的是微服務架構,本文主要涉及兩個微服務:前置資料擷取服務和 Web 即時訊息推送服務。

  前置資料擷取服務主要用於現場裝置、儀器、儀錶、感應器即時資料的採集,IoTopo工業物聯網平台支援MQTT和透傳雲解析兩種方式,透傳雲解析支援 Modbus 通訊規約。

  即時資料採集到平台後,需要推送到瀏覽器端進行顯示,Web 即時訊息推送服務採用 Web Socket 進行即時資料推送,可以確保資料的即時性和高效性。

 

  前端可視化技術採用的是HT for Web, HT for Web 是基於HTML5標準的公司專屬應用程式圖形介面一站式解決方案,其包含萬用群組件、拓撲組件和3D渲染引擎等豐富的圖形介面開發類庫。雖然 HT for Web 是商業軟體但其提供的一站式解決方案可以極大縮短產品開發週期、減少研發成本、補齊我們在 Web 圖形介面可視化技術上的短板。

 

二、Modbus 資料擷取

Modbus是一種串列通訊協定,是Modicon公司(現在的施耐德電氣Schneider Electric)於1979年為使用可程式化邏輯控制器(PLC)通訊而發表。Modbus已經成為工業領域通訊協定的業界標準,並且現在是工業電子裝置之間常用的串連方式。Modbus比其他通訊協定使用的更廣泛的主要原因有:

  1. 公開發表並且無著作權要求
  2. 易於部署和維護
  3. 對供應商來說,修改移動本地的位元或位元組沒有很多限制

Modbus允許多個 (大約240個) 裝置串連在同一個網路上進行通訊,舉個例子,一個由測量溫度和濕度的裝置,並且將結果發送給電腦。在資料擷取與監視控制系統(SCADA)中,Modbus通常用來串連監控電腦和遠程終端控制系統(RTU)。

目前主流的編輯語言都有 Modbus 開發庫,由於 Modbus 相對比較簡單,很多企業也選擇自行開發實現。Modbus 資料擷取屬於後台通訊,資料擷取到平台後首先會進行資料清理和預先處理,過濾掉冗餘和無效資料,形成即時資料。平台擷取到即時資料後一般會做 3 項工作:

1. 推送到 Web 前端進行顯示

2. 儲存到時序資料庫

3. 判斷是否產生警示

 

 

三、將即時資料推送到 Web 前端

  基於 Web 的即時資料推送需要用到 WebSocket,初學者可以學習阮一峰老師的 WebSocket 教程。我們基於 WebSocket 封裝了一套訊息傳輸協議,類似於一個訊息中介軟體,前端部分可以訂閱即時資料。考慮到海量即時資料的推送需求,將即時資料分為平台級、網站級、裝置級,前端在訂閱即時資料時,可以通過訊息主題規則訂閱不同層級的資料。平台側在收到訂閱請求時,可以主動推送一次即時資料。這樣可以確保資料視覺效果介面在訂閱即時資料成功後,第一時間顯示出正確的介面。

  下面給出一個簡化的 WebSocket 訊息協議的用戶端代碼,大家可以在些基礎上進行改造以適合自己的業務情境。

  訊息主題Regex,用來匹配訊息主題:

1 const matchWildcard = function(str, rule) {2     return new RegExp(‘^‘ + rule.split(‘*‘).join(‘.*‘) + ‘$‘).test(str)3 }

  WebSocket 用戶端,支援訊息主題訂閱、取消訊息主題訂閱、同一個訊息主題支援多個訂閱者:

  1 class WebSocketClient {  2     constructor() {  3         this.ws = null  4         this.opts = {  5             debug: false,  6             autoReconnect: true,  7             reconnectInterval: 10000,  8             subscriber: {},  9         } 10         this.opened = false 11     } 12  13     connect() { 14         if (!this.opened) { 15             return 16         } 17  18         const url = ‘ws://www.iotopo.com/msg/v1‘ 19         console.debug(‘websocket connect‘, url) 20  21         let ws = this.ws = new WebSocket(url) 22         ws.onmessage = event => { 23             if (this.opts.debug) { 24                 console.log(event) 25             } 26             let data = JSON.parse(event.data) 27  28             for (let topic in this.opts.subscriber) { 29                 if (matchWildcard(data.topic, topic)) { 30                     let listeners = this.opts.subscriber[topic] 31                     if (Array.isArray(listeners)) { 32                         listeners.forEach(cb => { 33                             if (typeof cb === ‘function‘) { 34                                 cb(data.payload) 35                             } 36                         }) 37                     } 38                 } 39             } 40         } 41         ws.onopen = e => { 42             if (this.opts.debug) { 43                 console.log(e) 44             } 45             // 執行訂閱請求 46             for (let topic in this.opts.subscriber) { 47                 this._sendSubscribe(topic) 48             } 49             if (typeof this.opts.onopen === ‘function‘) { 50                 this.opts.onopen(e) 51             } 52         } 53         ws.onclose = e => { 54             if (this.opts.debug) { 55                 console.log(e) 56             } 57             if (typeof this.opts.onclose === ‘function‘) { 58                 this.opts.onclose(e) 59             } 60             if (this.opened && this.opts.autoReconnect) { 61                 setTimeout(() => { 62                     this.connect() 63                 }, this.opts.reconnectInterval) 64             } 65         } 66         ws.onerror = e => { 67             if (this.opts.debug) { 68                 console.log(e) 69             } 70             if (typeof this.opts.onerror === ‘function‘) { 71                 this.opts.onerror(e) 72             } 73         } 74     } 75  76     open(opts) { 77         if (!this.opened) { 78             Object.assign(this.opts, opts || {}) 79             this.opened = true 80             this.connect() 81         } 82     } 83  84     close() { 85         this.opened = false 86         if (this.ws !== null) { 87             this.ws.close() 88         } 89         this.ws = null 90     } 91  92     isOpened() { 93         return this.opened 94     } 95  96     isConnected() { 97         return this.ws !== null 98     } 99 100     _sendSubscribe(topic) {101         if (this.ws === null) {102             return Error(‘websocet not opened‘)103         }104         if (typeof topic !== ‘string‘) {105             return Error(‘topic should be a string value‘)106         }107 108         if (this.ws.readyState === WebSocket.OPEN) {109             let msg = {110                 type: ‘subscribe‘,111                 topic: topic,112             }113             this.ws.send(JSON.stringify(msg))114         } else {115             return Error(‘websocet not connected‘)116         }117     }118 119     subscribe(topic, cb) {120         if (this.opts.debug) {121             console.log(‘subscribe:‘, topic)122         }123         let listeners = this.opts.subscriber[topic]124         if (!Array.isArray(listeners)) {125             listeners = [126                 cb127             ]128             this.opts.subscriber[topic] = listeners129         } else {130             listeners.push(cb)131         }132         this._sendSubscribe(topic)133 134         return { topic, cb }135     }136 137     unsubscribe({topic, cb}) {138         if (this.opts.debug) {139             console.log(‘unsubscribe:‘, topic)140         }141 142         if (this.ws === null) {143             return Error(‘websocet not opened‘)144         }145 146         if (typeof topic !== ‘string‘) {147             return Error(‘topic should be a string value‘)148         }149 150         let listeners = this.opts.subscriber[topic]151         if (cb) {152             if (Array.isArray(listeners)) {153                 let idx = listeners.indexOf(cb)154                 if (idx >= 0) {155                     listeners.splice(idx, 1)156                 }157             }158         } else {159             delete this.opts.subscriber[topic]160         }161 162         if (Array.isArray(listeners) && listeners == 0) {163             if (this.ws.readyState === WebSocket.OPEN) {164                 let msg = {165                     type: ‘unsubscribe‘,166                     topic: topic,167                 }168                 this.ws.send(JSON.stringify(msg))169             } else {170                 return Error(‘websocet not connected‘)171             }172         }173     }174 }

  用法舉例:

 1 // 初始化用戶端 2 const ws = new WebSocketClient() 3 // 與 WebSocket 伺服器建議串連 4 ws.open({ 5     debug: false 6 }) 7 // 訂閱訊息 8 ws.subscribe(‘/foo/bar/*‘, function(msg) { 9     console.log(‘recv ws msg:‘, msg)10 })

 

四、資料視覺效果介面實現

  基於 HT for Web 可以簡單快速地搭建一個符合 HTML5 標準的可視化圖形介面,通過 WebSocket 訂閱即時資料,然後驅動圖形介面的變化。資料驅動圖形介面變化的實現方式很多,基本方法是採用資料繫結的方式,具體可以參考 HT for Web 的官方文檔。

在後面的文章中,作者會介紹一種基於 HT for Web 實現的業務資料和圖形資料分離的資料繫結方法。

 

線上示範地址

工業物聯網之從 Modbus 到 Web 資料視覺效果

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.