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数值的实时获得,概念上证明了它的可行性。在此抛砖引玉,有兴趣进行OPC移动端开发的朋友请私信🤝
这种方法的最大益处就是避免了手机电池因快速刷新而耗尽的窘境,同时节约带宽,不做无谓的消耗,不做轮询,只等新值自动推送给移动端。这虽然是个概念证明,但大框架已经具备,比如服务端使用IIS自带的用户验证,使用IIS的证书进行HTTPS/WSS协议的互联网上的数据安全传输,移动端自身不需要证书的安装等等。下篇介绍一下随着智能工业互联网(AIOT)的兴起,对工厂数据的渴望引发的Python获取OPC数据浪潮进行探究。
楼主最近还看过