移动端获取OPC数据的新方法

OPC的二套架构,Classic和UA,都是在智能手机诞生前就产生的,天生地就没有考虑移动端的应用,所使用的协议不适合在移动端上直接运用。但是在工厂,总部或者出差在外,总有那么种需求,就是无论走到哪里,也想随时随地地获取相关的工厂信息,若有报警能够及时处理等等。OPC Classic所遵循的COM协议,手机上无法直接相连;OPC UA所提供的OPC.TCP或者HTTP协议,都是基于SOA架构的Web Service接口,不能简单地拿来直接使用,要通过UA端口发现过程,然后给端口设置防火墙,进行证书安装等诸多烦琐事宜,总之不是为移动端天生的。移动端使用的主要协议比如HTTP,非常直接了当,没有证书安装和端口发现等多余步骤,都要求直接连上能用。看到有人把UA .NET客户端C#程序移植到移动端,再进行证书的安装配置,那叫一个费劲🥵。能不能用手机自带的原生协议来获得OPC的数据?在前文里为大家提供了一个新的思路来获取OPC的数据,即使用移动端自带的WebSocket,顺着这个思路,能否从手机直接获取OPC数据?下面用iPhone作为移动端,给出一个原型示范。 

 

抛开iPhone开发使用的Swift的语言自身特性,此示范基本遵循MVC的框架,先来看看View即前端的代码, 

import SwiftUI

struct ContentView: View {
    @State var buttonLabel = "Disconnect"
    var myModel : ViewModel
    
    @ObservedObject var socket:WebSocketController 
    
    init(){
        myModel = ViewModel()
        socket = WebSocketController(alertWrapper:myModel.alertWrapper)
    }
    
var body: some View {
      
    VStack(spacing: 1) {
        List(socket.alertWrapper){ myAlertWrapper in
            Text(myAlertWrapper.alert)
        }
        
        Divider()
        
        Button(buttonLabel, action: {
            if self.buttonLabel == "Connect" {
                self.socket.connect()
            } else {
                self.socket.disconnect()
            }
            self.buttonLabel = self.buttonLabel == "Connect" ? "Disconnect" : "Connect"

        }).padding()
  }
}

前端代码不复杂,它定义了Model和Controller,即ViewModel和WebSocketController的类并获得实例,然后是View的本身定义,即有一个List和Button在View上。再然后是List的填充和Button按下时的反应,一目了然。下面看一下Model, 

struct AlertWrapper: Identifiable {
  let id = UUID()
  var alert: String
}

class ViewModel{
    var alertWrapper = [AlertWrapper] ()
}

Modle也不复杂,按Swift的要求,定义个符合Identifiable接口要求的结构,然后定义个包含此结构阵列的类。下面是Controller相关的代码, 

class myDelegate: NSObject, URLSessionWebSocketDelegate {
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        }
        
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
    }
}

class WebSocketController : ObservableObject {
    @Published var alertWrapper : [AlertWrapper]
    
    private var session: URLSession
    var socket: URLSessionWebSocketTask!
    
    init(alertWrapper : [AlertWrapper]){
        self.alertWrapper = alertWrapper
        
        self.session = URLSession(configuration: .default, delegate: myDelegate(), delegateQueue: OperationQueue()) 
        self.connect()        
    }

    func disconnect() {
        self.socket.cancel(with: .normalClosure, reason: nil) 
    }
    
    func connect() {
        self.socket = session.webSocketTask(with: URL(string: "ws://testServer/OPC/main.opc")!) 
        self.listen()
        self.socket.resume()
        alertWrapper.removeAll()
        
        self.socket.send(.string("browse"), completionHandler: { error in
            if let error = error {
                    print("Failed with Error \(error.localizedDescription)")
                } else {
                    self.socket.send(.string("subscribe:Random.Int1")){_ in }
                }
        })
    }

    func listen() {
       self.socket.receive { [weak self] (result) in
          guard let self = self else { return }
      
          switch result {
              case .failure(let error):
                print(error)
        
                return
              case .success(let message):
        
                switch message {       
                case .string(let str):
                    print(str)
            
                    DispatchQueue.main.async {
                        self.alertWrapper.append(AlertWrapper(alert:str))
                    }
                case .data(_):
                    break
                @unknown default:
                    break
                }
              }
      
          self.listen()
        }
      }
}

Controller稍微复杂些。在这个Controller里要关注的是connect()函数,注意这里使用的是ws而不是wss,即没有加密的http请求,为演示需要。同样在这个函数里依序发出了二个命令,先是个顶层的浏览命令browse,然后是一个订阅命令,订阅点Random.Int1来获得更新数值。另外,connect()还调用了listen()函数——它是一个异步监听函数,负责分析获取服务端的返回值,送回UI进行异步更新并继续监听。 注意一点,listen()函数里没有任何的刷新或polling请求,完全是依靠服务端的push。把如上的APP部署到实际使用的iPhone运行后,得到了下图🙂

 

第一行显示的是支持的OPC协议,第二行是当前状态信息,第三行是顶层浏览命令的返回结果,以后各行都是订阅命令的返回结果。总之,在移动端实现了OPC数值的实时获得,概念上证明了它的可行性。下面是最新的iOS应用截图,对应的源程序完全开源,感兴趣的可以到此下载🤝

​​​​​​​

这种方法的最大益处就是避免了手机电池因快速刷新而耗尽的窘境,同时节约带宽,不做无谓的消耗,不做轮询,只等新值自动推送给移动端。这虽然是个概念证明,但大框架已经具备,比如服务端使用IIS自带的用户验证,使用IIS的证书进行HTTPS/WSS协议的互联网上的数据安全传输,移动端自身不需要证书的安装等等。下篇介绍一下随着智能工业互联网(AIOT)的兴起,对工厂数据的渴望引发的Python获取OPC数据浪潮进行探究。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值