Python工業互聯網監控項目實戰3—websocket to UI
本小節繼續演示如何在Django項目中採用早期websocket技術原型來實現把OPC服務端數據實時推送到UI端,讓監控頁面在另一種技術方式下,實時顯示現場設備的工藝數據變化情況。本例我們仍然採用比較輕量級的dwebsocket組件。
1. 安裝dwebsocket組件
安裝命令:pip install dwebsocket
1.1. dwebsocket使用方法
如果你想為一個單獨的視圖處理一個websocket連接可以使用accept_websocket裝飾器,它會將標準的HTTP請求路由到視圖中。使用require_websocke裝飾器只允許使用WebSocket連接,會拒絕正常的HTTP請求。
在設置中添加設置MIDDLEWARE_CLASSES=dwebsocket.middleware.WebSocketMiddleware這樣會拒絕單獨的視圖使用websocket,必須加上accept_websocket 裝飾器。
設置WEBSOCKET_ACCEPT_ALL=True可以允許每一個單獨的視圖實用websockets
1.2. 常用方法和屬性
1.request.is_websocket() 如果是個websocket請求返回True,如果是個普通的http請求返回False,可以用這個方法區分它們。
2.request.websocket 在一個websocket請求建立之後,這個請求將會有一個websocket屬性,用來給客戶端提供一個簡單的api通訊,如果request.is_websocket()是False,這個屬性將是None。
3.WebSocket.wait() 返回一個客戶端發送的信息,在客戶端關閉連接之前他不會返回任何值,這種情況下,方法將返回None
4.WebSocket.read() 如果沒有從客戶端接收到新的消息,read方法會返回一個新的消息,如果沒有,就不返回。這是一個替代wait的非阻塞方法
5.WebSocket.count_messages() 返回消息隊列數量
6.WebSocket.has_messages() 如果有新消息返回True,否則返回False
7.WebSocket.send(message) 向客戶端發送消息
8.WebSocket.__iter__() websocket迭代器
dwebsocket使用起來比較簡單,增加一個簡單的服務端url和重構UI代碼;UI代碼創建一個websocket連接並在onmessage 事件里處理返回的數據即可,不用花費多大的代價就能快速讓監控頁面升級到一個新的方式下,下面看代碼演進吧。
2.重構服務端代碼——增加一個推送的websocket url
使用accept_websocket裝飾器在Collector APP的views文件中增加一個pushCollector的方法,實現UI端連接上服務端後,服務端使用websocket主動向UI界面推送實時設備工藝數據,函數代碼如下:
from dwebsocket.decorators import accept_websocket import OpenOPC @accept_websocket def pushCollectorData(request): tank4C9={ 'DeviceId': 1, 'DeviceName':'1#反應罐', 'Status': 0, #設備運行狀態 'OverheadFlow':0 ,#'頂流量', 'ButtomsFlow': 0, #'低流量' 'Power': 0, #功率 } Collector={ 'CollectorId': 1, 'CollectorName':'1#採集器', 'Status': 0, 'DeviceList':[tank4C9], } Collector={ 'CollectorId': 1, 'CollectorName':'1#採集器', 'Status': 0, 'DeviceList':[tank4C9], } if request.is_websocket(): try: while True: opc = OpenOPC.client() opc.connect('Matrikon.OPC.Simulation') tank4C9['OverheadFlow']= opc['Random.Int1'] tank4C9['ButtomsFlow']= opc['Random.Int2'] tank4C9['Power']= opc['Random.Int4'] opc.close() request.websocket.send(\ json.dumps( {"rows":[Collector],'total':1})) time.sleep(2) finally: client.disconnect()
解讀:上文代碼與原來的主要差別就是從被動刷新(UI請求後)讀去opc服務的tag位號值,變成間隔time.sleep(2)秒讀取數據後通過request.websocket.send到UI端。
3. 重構UI端代碼
這裡django與Flask的差別就是無須新建一個新的項目,當前項目我們就可以通過重構tank4C9.html頁面代碼來使用websocket實時推送功能。
重構後tank4C9.html代碼如下:
<html> <head><title></title> <script src="//lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script> </head> <body> <div> Status: <strong id="divStatus">0</strong> </div> <div> OverheadFlow: <strong id="divOverheadFlow">0</strong> </div> <div> ButtomsFlow: <strong id="divButtomsFlow">0</strong> </div> <div> Power: <strong id="divPower">0</strong> </div> <div> pushCount: <strong id="divpushCount">0</strong> </div> <script> //JQuery 代碼入口 $(document).ready(function(){ if ("WebSocket" in window) { //連接server--TagCurValue var ws = new WebSocket("ws://127.0.0.1:8090/pushCollector/"); ws.onmessage = function (evt) { // 接收數據 d = JSON.parse(evt.data); collector= d.rows[0] for (i = 0; i < collector.DeviceList.length; i++){ device = collector.DeviceList[i] $("#divStatus").html(device.Status); $("#divOverheadFlow").html(device.OverheadFlow); $("#divButtomsFlow").html(device.ButtomsFlow); $("#divPower").html(device.Power); $("#divpushCount").html(device.Count); } }; } }); </script> </body> </html>
解讀:UI端代碼通過ws.onmessage事件更新頁面顯示,對照上一張的ajax輪詢模式的代碼,代碼的主體結構和功能並沒有大的變化,只是採用了一種的新的數據傳遞方式而已。
注意:
var ws = new WebSocket(“ws://127.0.0.1:8090/pushCollector/”);
pushCollector/ url最後那個「/」,這個點是django與flask的一個差別,否則我們創建這個websocket時會收到301錯誤提示!
4. 發佈pushCollectorData url
項目的urls發佈這新的url接口地址,這例我們保留原來的getCollectorData,代碼如下:
# Uncomment next two lines to enable admin: #from django.contrib import admin from django.urls import path from Collector import views urlpatterns = [ # Uncomment the next line to enable the admin: #path('admin/', admin.site.urls) path('tank4C9/', views.tank4C9), path('getCollectorData/', views.getCollectorData), path('pushCollectorData/', views.pushCollectorData), ]
4. 調試運行效果
5. 小結
本小節我們通過websocket的主動推送方式,完成了實時監控畫面從後台服務端主動推送到UI端的技術架構迭代,這個過程我們也演示了項目迭代的方式,迭代推進項目功能點的好處非常明顯也就是在一個版本滿足需求的前提下,可以相對從容的採用新的技術和方案升級產品改進性能。
例子我們保留了原來的getCollectorData url,實際的項目開發也是通過增加新推送方法的方式來組織進行的,這樣新的升級也同時滿足原有ajax模式的後台訪問方式。從而避免升級過程中,前後台升級版本不一致導致原有頁面不能正常訪問,避免系統已發佈就「崩潰」的「災難」問題。
這裡多說一下敏捷編程下的「小步快跑,快速迭代」模式下,一些團隊遇到的問題就是一開始極簡設計滿足當下要求,然後在不斷功能迭代過程中項目產品架構技術快速老化,可是團隊還是不斷的增加功能點,而沒有人員關心技術架構優化和調整。最終,導致問題越積越多,架構越來越難用,產品構建越來越慢,最後等待一次徹底的項目「重構」。一些「好的」項目應該在過程中逐步演化代碼結構來滿足不斷擴張的功能需求。
敏捷編程的前提是要有一套體系來做保證的,需求管理、代碼重構、單元測試等等,比如:代碼重構在敏捷編程項目過程中就非常重要,一開始簡單滿足需求,一旦發現引入新的需求代碼不能很好的滿足需求的變化時,引入好的設計模式,採用代碼重構的方式來優化代碼結構,並通過回歸單元測試來保證新的代碼結構能夠正常通過原來的單元測試。盲目的採用敏捷編程又沒有採用它有效管理的一整套機制,最後陷入項目泥潭的,只能說是沒有理解好「敏捷」的核心要素罷了。