本小节继续演示如何在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 APPviews文件中增加一个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端代码

   这里djangoFlask的差别就是无须新建一个新的项目,当前项目我们就可以通过重构tank4C9.html页面代码来使用websocket实时推送功能。

  重构后tank4C9.html代码如下: 

<html>
<head><title></title>
    <script src="https://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最后那个“/”,这个点是djangoflask的一个差别,否则我们创建这个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. 调试运行效果

Python工业互联网监控项目实战3—websocket to UI-LMLPHP

5. 小结

  本小节我们通过websocket的主动推送方式,完成了实时监控画面从后台服务端主动推送到UI端的技术架构迭代,这个过程我们也演示了项目迭代的方式,迭代推进项目功能点的好处非常明显也就是在一个版本满足需求的前提下,可以相对从容的采用新的技术和方案升级产品改进性能。

  例子我们保留了原来的getCollectorData url,实际的项目开发也是通过增加新推送方法的方式来组织进行的,这样新的升级也同时满足原有ajax模式的后台访问方式。从而避免升级过程中,前后台升级版本不一致导致原有页面不能正常访问,避免系统已发布就“崩溃”的“灾难”问题。

  这里多说一下敏捷编程下的“小步快跑,快速迭代”模式下,一些团队遇到的问题就是一开始极简设计满足当下要求,然后在不断功能迭代过程中项目产品架构技术快速老化,可是团队还是不断的增加功能点,而没有人员关心技术架构优化和调整。最终,导致问题越积越多,架构越来越难用,产品构建越来越慢,最后等待一次彻底的项目“重构”。一些“好的”项目应该在过程中逐步演化代码结构来满足不断扩张的功能需求。

  敏捷编程的前提是要有一套体系来做保证的,需求管理、代码重构、单元测试等等,比如:代码重构在敏捷编程项目过程中就非常重要,一开始简单满足需求,一旦发现引入新的需求代码不能很好的满足需求的变化时,引入好的设计模式,采用代码重构的方式来优化代码结构,并通过回归单元测试来保证新的代码结构能够正常通过原来的单元测试。盲目的采用敏捷编程又没有采用它有效管理的一整套机制,最后陷入项目泥潭的,只能说是没有理解好“敏捷”的核心要素罢了。

 

04-20 10:56