Completed
Push — master ( 52b12e...458b63 )
by Olivier
59s
created

Window.connect()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 10
rs 9.4285
1
#! /usr/bin/env python3
2
3
import sys
4
from datetime import datetime
5
from enum import Enum
6
7
from PyQt5.QtCore import pyqtSignal, QTimer, Qt, QObject, QSettings, QModelIndex, QMimeData, QCoreApplication
8
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon
9
from PyQt5.QtWidgets import QMainWindow, QWidget, QApplication, QAbstractItemView, QMenu, QAction
10
11
from opcua import ua
12
13
from uaclient.uaclient import UaClient
14
from uaclient.mainwindow_ui import Ui_MainWindow
15
from uawidgets import resources
16
from uawidgets.attrs_widget import AttrsWidget
17
from uawidgets.tree_widget import TreeWidget
18
from uawidgets.refs_widget import RefsWidget
19
20
21
class DataChangeHandler(QObject):
22
    data_change_fired = pyqtSignal(object, str, str)
23
24
    def datachange_notification(self, node, val, data):
25
        if data.monitored_item.Value.SourceTimestamp:
26
            dato = data.monitored_item.Value.SourceTimestamp.isoformat()
27
        elif data.monitored_item.Value.ServerTimestamp:
28
            dato = data.monitored_item.Value.ServerTimestamp.isoformat()
29
        else:
30
            dato = datetime.now().isoformat()
31
        self.data_change_fired.emit(node, str(val), dato)
32
33
34
class EventHandler(QObject):
35
    event_fired = pyqtSignal(object)
36
37
    def event_notification(self, event):
38
        self.event_fired.emit(event)
39
40
41
class EventUI(object):
42
43 View Code Duplication
    def __init__(self, window, uaclient):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
44
        self.window = window
45
        self.uaclient = uaclient
46
        self._handler = EventHandler()
47
        self._subscribed_nodes = []  # FIXME: not really needed
48
        self.model = QStandardItemModel()
49
        self.window.ui.evView.setModel(self.model)
50
        self.window.ui.actionSubscribeEvent.triggered.connect(self._subscribe)
51
        self.window.ui.actionUnsubscribeEvents.triggered.connect(self._unsubscribe)
52
        # context menu
53
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeEvent)
54
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeEvents)
55
        self._handler.event_fired.connect(self._update_event_model, type=Qt.QueuedConnection)
56
57
        # accept drops
58
        self.model.canDropMimeData = self.canDropMimeData
59
        self.model.dropMimeData = self.dropMimeData
60
61
    def canDropMimeData(self, mdata, action, row, column, parent):
62
        return True
63
64
    def dropMimeData(self, mdata, action, row, column, parent):
65
        node = self.uaclient.client.get_node(mdata.text())
66
        print("SUB 1", mdata.text(), node)
67
        self._subscribe(node)
68
        return True
69
70
71
    def clear(self):
72
        self._subscribed_nodes = []
73
        self.model.clear()
74
75
    def _subscribe(self, node=None):
76
        print("SUB", node)
77
        if not node:
78
            node = self.window.get_current_node()
79
            if node is None:
80
                return
81
        if node in self._subscribed_nodes:
82
            print("allready subscribed to event for node: ", node)
83
            return
84
        print("Subscribing to events for ", node)
85
        self.window.ui.evDockWidget.raise_()
86
        try:
87
            self.uaclient.subscribe_events(node, self._handler)
88
        except Exception as ex:
89
            self.window.show_error(ex)
90
            raise
91
        else:
92
            self._subscribed_nodes.append(node)
93
94
    def _unsubscribe(self):
95
        node = self.window.get_current_node()
96
        if node is None:
97
            return
98
        self._subscribed_nodes.remove(node)
99
        self.uaclient.unsubscribe_events(node)
100
101
    def _update_event_model(self, event):
102
        self.model.appendRow([QStandardItem(str(event))])
103
104
105
class DataChangeUI(object):
106
107 View Code Duplication
    def __init__(self, window, uaclient):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
108
        self.window = window
109
        self.uaclient = uaclient
110
        self._subhandler = DataChangeHandler()
111
        self._subscribed_nodes = []
112
        self.model = QStandardItemModel()
113
        self.window.ui.subView.setModel(self.model)
114
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)
115
116
        self.window.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
117
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(self._unsubscribe)
118
119
        # populate contextual menu
120
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeDataChange)
121
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeDataChange)
122
123
        # handle subscriptions
124
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)
125
        
126
        # accept drops
127
        self.model.canDropMimeData = self.canDropMimeData
128
        self.model.dropMimeData = self.dropMimeData
129
130
    def canDropMimeData(self, mdata, action, row, column, parent):
131
        return True
132
133
    def dropMimeData(self, mdata, action, row, column, parent):
134
        node = self.uaclient.client.get_node(mdata.text())
135
        self._subscribe(node)
136
        return True
137
138
    def clear(self):
139
        self._subscribed_nodes = []
140
        self.model.clear()
141
142
    def _subscribe(self, node=None):
143
        if node is None:
144
            node = self.window.get_current_node()
145
            if node is None:
146
                return
147
        if node in self._subscribed_nodes:
148
            print("allready subscribed to node: ", node)
149
            return
150
        self.model.setHorizontalHeaderLabels(["DisplayName", "Value", "Timestamp"])
151
        text = str(node.get_display_name().Text)
152
        row = [QStandardItem(text), QStandardItem("No Data yet"), QStandardItem("")]
153
        row[0].setData(node)
154
        self.model.appendRow(row)
155
        self._subscribed_nodes.append(node)
156
        self.window.ui.subDockWidget.raise_()
157
        try:
158
            self.uaclient.subscribe_datachange(node, self._subhandler)
159
        except Exception as ex:
160
            self.window.show_error(ex)
161
            idx = self.model.indexFromItem(row[0])
162
            self.model.takeRow(idx.row())
163
            raise
164
165
    def _unsubscribe(self):
166
        node = self.window.get_current_node()
167
        if node is None:
168
            return
169
        self.uaclient.unsubscribe_datachange(node)
170
        self._subscribed_nodes.remove(node)
171
        i = 0
172
        while self.model.item(i):
173
            item = self.model.item(i)
174
            if item.data() == node:
175
                self.model.removeRow(i)
176
            i += 1
177
178
    def _update_subscription_model(self, node, value, timestamp):
179
        i = 0
180
        while self.model.item(i):
181
            item = self.model.item(i)
182
            if item.data() == node:
183
                it = self.model.item(i, 1)
184
                it.setText(value)
185
                it_ts = self.model.item(i, 2)
186
                it_ts.setText(timestamp)
187
            i += 1
188
189
190
class Window(QMainWindow):
191
192
    def __init__(self):
193
        QMainWindow.__init__(self)
194
        self.ui = Ui_MainWindow()
195
        self.ui.setupUi(self)
196
        self.setWindowIcon(QIcon(":/network.svg"))
197
198
        # fix stuff imposible to do in qtdesigner
199
        # remove dock titlebar for addressbar
200
        w = QWidget()
201
        self.ui.addrDockWidget.setTitleBarWidget(w)
202
        # tabify some docks
203
        self.tabifyDockWidget(self.ui.evDockWidget, self.ui.subDockWidget)
204
        self.tabifyDockWidget(self.ui.subDockWidget, self.ui.refDockWidget)
205
206
        # we only show statusbar in case of errors
207
        self.ui.statusBar.hide()
208
209
        # setup QSettings for application and get a settings object
210
        QCoreApplication.setOrganizationName("FreeOpcUa")
211
        QCoreApplication.setApplicationName("OpcUaClient")
212
        self.settings = QSettings()
213
214
        self._address_list = self.settings.value("address_list", ["opc.tcp://localhost:4840", "opc.tcp://localhost:53530/OPCUA/SimulationServer/"])
215
        self._address_list_max_count = int(self.settings.value("address_list_max_count", 10))
216
217
        # init widgets
218
        for addr in self._address_list:
219
            self.ui.addrComboBox.insertItem(-1, addr)
220
221
        self.uaclient = UaClient()
222
223
        self.tree_ui = TreeWidget(self.ui.treeView)
224
        self.tree_ui.error.connect(self.show_error)
225
        self.refs_ui = RefsWidget(self.ui.refView)
226
        self.refs_ui.error.connect(self.show_error)
227
        self.attrs_ui = AttrsWidget(self.ui.attrView)
228
        self.attrs_ui.error.connect(self.show_error)
229
        self.datachange_ui = DataChangeUI(self, self.uaclient)
230
        self.event_ui = EventUI(self, self.uaclient)
231
232
        self.ui.treeView.activated.connect(self.show_refs)
233
        self.ui.treeView.clicked.connect(self.show_refs)
234
        self.ui.actionCopyPath.triggered.connect(self.tree_ui.copy_path)
235
        self.ui.actionCopyNodeId.triggered.connect(self.tree_ui.copy_nodeid)
236
        # add items to context menu
237
        self.ui.treeView.addAction(self.ui.actionCopyPath)
238
        self.ui.treeView.addAction(self.ui.actionCopyNodeId)
239
240
        self.ui.treeView.activated.connect(self.show_attrs)
241
        self.ui.treeView.clicked.connect(self.show_attrs)
242
        self.ui.attrRefreshButton.clicked.connect(self.show_attrs)
243
244
        self.resize(int(self.settings.value("main_window_width", 800)), int(self.settings.value("main_window_height", 600)))
245
        data = self.settings.value("main_window_state", None)
246
        if data:
247
            self.restoreState(data)
248
249
        self.ui.connectButton.clicked.connect(self.connect)
250
        self.ui.disconnectButton.clicked.connect(self.disconnect)
251
        # self.ui.treeView.expanded.connect(self._fit)
252
253
        self.ui.actionConnect.triggered.connect(self.connect)
254
        self.ui.actionDisconnect.triggered.connect(self.disconnect)
255
256
        self.ui.modeComboBox.addItem("None")
257
        self.ui.modeComboBox.addItem("Sign")
258
        self.ui.modeComboBox.addItem("SignAndEncrypt")
259
260
        self.ui.policyComboBox.addItem("None")
261
        self.ui.policyComboBox.addItem("Basic128RSA15")
262
        self.ui.policyComboBox.addItem("Basic256")
263
        self.ui.policyComboBox.addItem("Basic256SHA256")
264
265
    def show_refs(self, idx):
266
        node = self.get_current_node(idx)
267
        if node:
268
            self.refs_ui.show_refs(node)
269
    
270
    def show_attrs(self, idx):
271
        if not isinstance(idx, QModelIndex):
272
            idx = None
273
        node = self.get_current_node(idx)
274
        if node:
275
            self.attrs_ui.show_attrs(node)
276
277
    def show_error(self, msg, level=1):
278
        print("showing error: ", msg, level)
279
        self.ui.statusBar.show()
280
        self.ui.statusBar.setStyleSheet("QStatusBar { background-color : red; color : black; }")
281
        self.ui.statusBar.showMessage(str(msg))
282
        QTimer.singleShot(1500, self.ui.statusBar.hide)
283
284
    def get_current_node(self, idx=None):
285
        return self.tree_ui.get_current_node(idx)
286
287
    def get_uaclient(self):
288
        return self.uaclient
289
290
    def connect(self):
291
        uri = self.ui.addrComboBox.currentText()
292
        try:
293
            self.uaclient.connect(uri)
294
        except Exception as ex:
295
            self.show_error(ex)
296
            raise
297
298
        self._update_address_list(uri)
299
        self.tree_ui.set_root_node(self.uaclient.client.get_root_node())
300
301
    def _update_address_list(self, uri):
302
        if uri == self._address_list[0]:
303
            return
304
        if uri in self._address_list:
305
            self._address_list.remove(uri)
306
        self._address_list.insert(0, uri)
307
        if len(self._address_list) > self._address_list_max_count:
308
            self._address_list.pop(-1)
309
310
    def disconnect(self):
311
        try:
312
            self.uaclient.disconnect()
313
        except Exception as ex:
314
            self.show_error(ex)
315
            raise
316
        finally:
317
            self.tree_ui.clear()
318
            self.refs_ui.clear()
319
            self.attrs_ui.clear()
320
            self.datachange_ui.clear()
321
            self.event_ui.clear()
322
323
    def closeEvent(self, event):
324
        self.attrs_ui.save_state()
325
        self.refs_ui.save_state()
326
        self.settings.setValue("main_window_width", self.size().width())
327
        self.settings.setValue("main_window_height", self.size().height())
328
        self.settings.setValue("main_window_state", self.saveState())
329
        self.settings.setValue("address_list", self._address_list)
330
        self.disconnect()
331
        event.accept()
332
333
334
def main():
335
    app = QApplication(sys.argv)
336
    client = Window()
337
    client.show()
338
    sys.exit(app.exec_())
339
340
341
if __name__ == "__main__":
342
    main()
343