手机获取OPC数据的新方法 点击:781 | 回复:3



OPC那点事

    
  • 精华:0帖
  • 求助:0帖
  • 帖子:11帖 | 41回
  • 年度积分:0
  • 历史总积分:52
  • 注册:2015年4月25日
发表于:2022-06-30 03:07:51
楼主

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运行后,得到了下图🙂


IMG_2418.PNG

第一行显示的是支持的OPC协议,第二行是当前状态信息,第三行是顶层浏览命令的返回结果,以后各行都是订阅命令的返回结果。总之,在移动端实现了OPC数值的实时获得,概念上证明了它的可行性。在此抛砖引玉,有兴趣进行OPC移动端开发的朋友请私信🤝

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




楼主最近还看过



savingxu

  • 精华:0帖
  • 求助:0帖
  • 帖子:2帖 | 214回
  • 年度积分:22
  • 历史总积分:358
  • 注册:2017年8月09日
发表于:2022-09-19 13:08:01
1楼

不错不错 做个标记

lxkd888999

  • 精华:0帖
  • 求助:0帖
  • 帖子:0帖 | 33回
  • 年度积分:0
  • 历史总积分:83
  • 注册:2011年7月13日
发表于:2022-09-19 14:34:54
2楼

不错不错 做个标记

OPC那点事

  • 精华:0帖
  • 求助:0帖
  • 帖子:11帖 | 41回
  • 年度积分:0
  • 历史总积分:52
  • 注册:2015年4月25日
发表于:2022-10-10 00:17:03
3楼

已经开源在此,WebSocket4OPC,最新的界面如下,

IMG_2548.PNGIMG_2551.PNGIMG_2552.PNGIMG_2553.PNG


热门招聘
相关主题

官方公众号

智造工程师