the code for calling register_callback is more readable and more explicit now Change-Id: I7a6a236d7f50b1ad391c1d49e3fb48f2580aa875 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22564 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
239 lines
8.9 KiB
Python
239 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# *****************************************************************************
|
|
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Module authors:
|
|
# Alexander Lenz <alexander.lenz@frm2.tum.de>
|
|
#
|
|
# *****************************************************************************
|
|
|
|
|
|
import secop.client
|
|
from secop.gui.modulectrl import ModuleCtrl
|
|
from secop.gui.nodectrl import NodeCtrl
|
|
from secop.gui.paramview import ParameterView
|
|
from secop.gui.qt import QInputDialog, QMainWindow, QMessageBox, \
|
|
QObject, QTreeWidgetItem, pyqtSignal, pyqtSlot, QBrush, QColor
|
|
from secop.gui.util import loadUi, Value
|
|
from secop.lib import formatExtendedTraceback
|
|
|
|
ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1
|
|
ITEM_TYPE_GROUP = QTreeWidgetItem.UserType + 2
|
|
ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 3
|
|
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 4
|
|
|
|
|
|
class QSECNode(QObject):
|
|
newData = pyqtSignal(str, str, object) # module, parameter, data
|
|
stateChange = pyqtSignal(str, bool, str) # node name, online, connection state
|
|
unhandledMsg = pyqtSignal(str) # message
|
|
logEntry = pyqtSignal(str)
|
|
|
|
def __init__(self, uri, parent=None):
|
|
QObject.__init__(self, parent)
|
|
self.conn = conn = secop.client.SecopClient(uri)
|
|
conn.validate_data = True
|
|
self.log = conn.log
|
|
self.contactPoint = conn.uri
|
|
conn.connect()
|
|
self.equipmentId = conn.properties['equipment_id']
|
|
self.nodename = '%s (%s)' % (self.equipmentId, conn.uri)
|
|
self.modules = conn.modules
|
|
self.properties = self.conn.properties
|
|
self.protocolVersion = conn.secop_version
|
|
conn.register_callback(None, self.updateEvent, self.nodeStateChange, self.unhandledMessage)
|
|
|
|
# provide methods from old baseclient for making other gui code work
|
|
|
|
def getParameters(self, module):
|
|
return self.modules[module]['parameters']
|
|
|
|
def getCommands(self, module):
|
|
return self.modules[module]['commands']
|
|
|
|
def getModuleProperties(self, module):
|
|
return self.modules[module]['properties']
|
|
|
|
def getProperties(self, module, parameter):
|
|
props = self.modules[module]['parameters'][parameter]
|
|
if 'unit' in props['datainfo']:
|
|
props['unit'] = props['datainfo']['unit']
|
|
return self.modules[module]['parameters'][parameter]
|
|
|
|
def setParameter(self, module, parameter, value):
|
|
self.conn.setParameter(module, parameter, value)
|
|
|
|
def getParameter(self, module, parameter):
|
|
return self.conn.getParameter(module, parameter, True)
|
|
|
|
def execCommand(self, module, command, arg):
|
|
try:
|
|
return self.conn.execCommand(module, command, arg)
|
|
except Exception as e:
|
|
return 'ERROR: %r' % e, {}
|
|
|
|
def queryCache(self, module):
|
|
return {k: Value(*self.conn.cache[(module, k)])
|
|
for k in self.modules[module]['parameters']}
|
|
|
|
def syncCommunicate(self, action, ident='', data=None):
|
|
reply = self.conn.request(action, ident, data)
|
|
return secop.client.encode_msg_frame(*reply).decode('utf-8')
|
|
|
|
def decode_message(self, msg):
|
|
# decode_msg needs bytes as input
|
|
return secop.client.decode_msg(msg.encode('utf-8'))
|
|
|
|
def _getDescribingParameterData(self, module, parameter):
|
|
return self.modules[module]['parameters'][parameter]
|
|
|
|
def updateEvent(self, module, parameter, value, timestamp, readerror):
|
|
self.newData.emit(module, parameter, Value(value, timestamp, readerror))
|
|
|
|
def nodeStateChange(self, online, state):
|
|
self.stateChange.emit(self.nodename, online, state)
|
|
|
|
def unhandledMessage(self, *msg):
|
|
self.unhandledMsg.emit('%s %s %r' % msg)
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self, hosts, parent=None):
|
|
super(MainWindow, self).__init__(parent)
|
|
|
|
loadUi(self, 'mainwindow.ui')
|
|
|
|
self.toolBar.hide()
|
|
|
|
self.splitter.setStretchFactor(0, 1)
|
|
self.splitter.setStretchFactor(1, 70)
|
|
self.splitter.setSizes([50, 500])
|
|
|
|
self._nodes = {}
|
|
self._nodeCtrls = {}
|
|
self._moduleCtrls = {}
|
|
self._paramCtrls = {}
|
|
self._topItems = {}
|
|
self._currentWidget = self.splitter.widget(1).layout().takeAt(0)
|
|
|
|
# add localhost (if available) and SEC nodes given as arguments
|
|
for host in hosts:
|
|
try:
|
|
self._addNode(host)
|
|
except Exception as e:
|
|
print(formatExtendedTraceback())
|
|
print('error in addNode: %r' % e)
|
|
|
|
@pyqtSlot()
|
|
def on_actionAdd_SEC_node_triggered(self):
|
|
host, ok = QInputDialog.getText(self, 'Add SEC node',
|
|
'Enter SEC node hostname:')
|
|
|
|
if not ok:
|
|
return
|
|
|
|
try:
|
|
self._addNode(host)
|
|
except Exception as e:
|
|
QMessageBox.critical(self.parent(),
|
|
'Connecting to %s failed!' % host, str(e))
|
|
|
|
def on_validateCheckBox_toggled(self, state):
|
|
print("validateCheckBox_toggled", state)
|
|
|
|
def on_visibilityComboBox_activated(self, level):
|
|
if level in ['user', 'admin', 'expert']:
|
|
print("visibility Level now:", level)
|
|
|
|
def on_treeWidget_currentItemChanged(self, current, previous):
|
|
if current.type() == ITEM_TYPE_NODE:
|
|
self._displayNode(current.text(0))
|
|
elif current.type() == ITEM_TYPE_GROUP:
|
|
self._displayGroup(current.parent().text(0), current.text(0))
|
|
elif current.type() == ITEM_TYPE_MODULE:
|
|
self._displayModule(current.parent().text(0), current.text(0))
|
|
elif current.type() == ITEM_TYPE_PARAMETER:
|
|
self._displayParameter(current.parent().parent().text(0),
|
|
current.parent().text(0), current.text(0))
|
|
|
|
def _removeSubTree(self, toplevel_item):
|
|
self.treeWidget.invisibleRootItem().removeChild(toplevel_item)
|
|
|
|
def _set_node_state(self, nodename, online, state):
|
|
node = self._nodes[nodename]
|
|
if online:
|
|
self._topItems[node].setBackground(0, QBrush(QColor('white')))
|
|
else:
|
|
self._topItems[node].setBackground(0, QBrush(QColor('orange')))
|
|
# TODO: make connection state be a separate row
|
|
node.contactPoint = '%s (%s)' % (node.conn.uri, state)
|
|
if nodename in self._nodeCtrls:
|
|
self._nodeCtrls[nodename].contactPointLabel.setText(node.contactPoint)
|
|
|
|
def _addNode(self, host):
|
|
|
|
# create client
|
|
node = QSECNode(host, parent=self)
|
|
nodename = node.nodename
|
|
|
|
self._nodes[nodename] = node
|
|
|
|
# fill tree
|
|
nodeItem = QTreeWidgetItem(None, [nodename], ITEM_TYPE_NODE)
|
|
|
|
for module in sorted(node.modules):
|
|
moduleItem = QTreeWidgetItem(nodeItem, [module], ITEM_TYPE_MODULE)
|
|
for param in sorted(node.getParameters(module)):
|
|
paramItem = QTreeWidgetItem(moduleItem, [param],
|
|
ITEM_TYPE_PARAMETER)
|
|
paramItem.setDisabled(False)
|
|
|
|
self.treeWidget.addTopLevelItem(nodeItem)
|
|
self._topItems[node] = nodeItem
|
|
node.stateChange.connect(self._set_node_state)
|
|
|
|
def _displayNode(self, node):
|
|
ctrl = self._nodeCtrls.get(node, None)
|
|
if ctrl is None:
|
|
ctrl = self._nodeCtrls[node] = NodeCtrl(self._nodes[node])
|
|
self._nodes[node].unhandledMsg.connect(ctrl._addLogEntry)
|
|
|
|
self._replaceCtrlWidget(ctrl)
|
|
|
|
def _displayModule(self, node, module):
|
|
ctrl = self._moduleCtrls.get((node, module), None)
|
|
if ctrl is None:
|
|
ctrl = self._moduleCtrls[(node, module)] = ModuleCtrl(self._nodes[node], module)
|
|
|
|
self._replaceCtrlWidget(ctrl)
|
|
|
|
def _displayParameter(self, node, module, parameter):
|
|
ctrl = self._paramCtrls.get((node, module, parameter), None)
|
|
if ctrl is None:
|
|
ctrl = ParameterView(self._nodes[node], module, parameter)
|
|
self._paramCtrls[(node, module, parameter)] = ctrl
|
|
|
|
self._replaceCtrlWidget(ctrl)
|
|
|
|
def _replaceCtrlWidget(self, new):
|
|
old = self.splitter.widget(1).layout().takeAt(0)
|
|
if old:
|
|
old.widget().hide()
|
|
self.splitter.widget(1).layout().addWidget(new)
|
|
new.show()
|
|
self._currentWidget = new
|