gui: support proper formatting of values
- use Datatype.format_value to convert all values - frappy.client.ProxyClient: use CacheItem instead of 3-tuple - CacheItem has built in formatting - adapt gui to use it instead of stopgap As it is now easy to convert to string including values, it may be better to move the unit in the modulewidget into the value field. This would simplyfy the code. Change-Id: I5c06da4a24706fcbc83ebcbf8c0ea6a8eb6d7890 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30680 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
31b1a916f5
commit
349c510555
@ -33,7 +33,7 @@ from threading import Event, RLock, current_thread
|
||||
import frappy.errors
|
||||
import frappy.params
|
||||
from frappy.datatypes import get_datatype
|
||||
from frappy.lib import mkthread
|
||||
from frappy.lib import mkthread, formatExtendedStack
|
||||
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from frappy.protocol.interface import decode_msg, encode_msg_frame
|
||||
from frappy.protocol.messages import COMMANDREQUEST, \
|
||||
@ -99,10 +99,64 @@ class CallbackObject:
|
||||
"""
|
||||
|
||||
|
||||
class CacheItem(tuple):
|
||||
"""cache entry
|
||||
|
||||
includes formatting information
|
||||
inheriting from tuple: compatible with old previous version of cache
|
||||
"""
|
||||
def __new__(cls, value, timestamp=None, readerror=None, datatype=None):
|
||||
if readerror:
|
||||
assert isinstance(readerror, Exception)
|
||||
else:
|
||||
try:
|
||||
value = datatype.import_value(value)
|
||||
except (KeyError, ValueError, AttributeError):
|
||||
readerror = ValueError('can not import %r as %r' % (value, datatype))
|
||||
value = None
|
||||
obj = tuple.__new__(cls, (value, timestamp, readerror))
|
||||
try:
|
||||
obj.format_value = datatype.format_value
|
||||
except AttributeError:
|
||||
obj.format_value = lambda value, unit=None: str(value)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self[0]
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
return self[1]
|
||||
|
||||
@property
|
||||
def readerror(self):
|
||||
return self[2]
|
||||
|
||||
def __str__(self):
|
||||
"""format value without unit"""
|
||||
if self[2]: # readerror
|
||||
return repr(self[2])
|
||||
return self.format_value(self[0], unit='') # skip unit
|
||||
|
||||
def formatted(self):
|
||||
"""format value with using unit"""
|
||||
return self.format_value(self[0])
|
||||
|
||||
def __repr__(self):
|
||||
args = (self.value,)
|
||||
if self.timestamp:
|
||||
args += (self.timestamp,)
|
||||
if self.readerror:
|
||||
args += (self.readerror,)
|
||||
return 'CacheItem%s' % repr(args)
|
||||
|
||||
|
||||
class ProxyClient:
|
||||
"""common functionality for proxy clients"""
|
||||
|
||||
CALLBACK_NAMES = ('updateEvent', 'descriptiveDataChange', 'nodeStateChange', 'unhandledMessage')
|
||||
CALLBACK_NAMES = ('updateEvent', 'updateItem', 'descriptiveDataChange',
|
||||
'nodeStateChange', 'unhandledMessage')
|
||||
online = False # connected or reconnecting since a short time
|
||||
state = 'disconnected' # further possible values: 'connecting', 'reconnecting', 'connected'
|
||||
log = None
|
||||
@ -133,7 +187,19 @@ class ProxyClient:
|
||||
cbdict[key].append(cbfunc)
|
||||
|
||||
# immediately call for some callback types
|
||||
if cbname == 'updateEvent':
|
||||
if cbname == 'updateItem':
|
||||
if key is None:
|
||||
for (mname, pname), data in self.cache.items():
|
||||
cbfunc(mname, pname, data)
|
||||
else:
|
||||
data = self.cache.get(key, None)
|
||||
if data:
|
||||
cbfunc(*key, data) # case single parameter
|
||||
else: # case key = module
|
||||
for (mname, pname), data in self.cache.items():
|
||||
if mname == key:
|
||||
cbfunc(mname, pname, data)
|
||||
elif cbname == 'updateEvent':
|
||||
if key is None:
|
||||
for (mname, pname), data in self.cache.items():
|
||||
cbfunc(mname, pname, *data)
|
||||
@ -176,17 +242,13 @@ class ProxyClient:
|
||||
return bool(cblist)
|
||||
|
||||
def updateValue(self, module, param, value, timestamp, readerror):
|
||||
if readerror:
|
||||
assert isinstance(readerror, Exception)
|
||||
else:
|
||||
try:
|
||||
# try to import (needed for enum, scaled, blob)
|
||||
datatype = self.modules[module]['parameters'][param]['datatype']
|
||||
value = datatype.import_value(value)
|
||||
except (KeyError, ValueError):
|
||||
if self.log:
|
||||
self.log.warning('cannot assign %r to %s:%s', value, module, param)
|
||||
self.cache[(module, param)] = (value, timestamp, readerror)
|
||||
entry = CacheItem(value, timestamp, readerror,
|
||||
self.modules[module]['parameters'][param]['datatype'])
|
||||
self.cache[(module, param)] = entry
|
||||
self.callback(None, 'updateItem', module, param, entry)
|
||||
self.callback(module, 'updateItem', module, param, entry)
|
||||
self.callback((module, param), 'updateItem', module, param, entry)
|
||||
# TODO: change clients to use updateItem instead of updateEvent
|
||||
self.callback(None, 'updateEvent', module, param, value, timestamp, readerror)
|
||||
self.callback(module, 'updateEvent', module, param, value, timestamp, readerror)
|
||||
self.callback((module, param), 'updateEvent', module, param, value, timestamp, readerror)
|
||||
|
@ -24,7 +24,6 @@
|
||||
from frappy.gui.qt import QObject, pyqtSignal
|
||||
|
||||
import frappy.client
|
||||
from frappy.gui.util import Value
|
||||
|
||||
|
||||
class QSECNode(QObject):
|
||||
@ -48,7 +47,7 @@ class QSECNode(QObject):
|
||||
self.properties = self.conn.properties
|
||||
self.protocolVersion = conn.secop_version
|
||||
self.log.debug('SECoP Version: %s', conn.secop_version)
|
||||
conn.register_callback(None, self.updateEvent, self.nodeStateChange,
|
||||
conn.register_callback(None, self.updateItem, self.nodeStateChange,
|
||||
self.unhandledMessage)
|
||||
|
||||
# provide methods from old baseclient for making other gui code work
|
||||
@ -84,7 +83,7 @@ class QSECNode(QObject):
|
||||
return self.conn.execCommand(module, command, argument)
|
||||
|
||||
def queryCache(self, module):
|
||||
return {k: Value(*self.conn.cache[(module, k)])
|
||||
return {k: self.conn.cache[(module, k)]
|
||||
for k in self.modules[module]['parameters']}
|
||||
|
||||
def syncCommunicate(self, action, ident='', data=None):
|
||||
@ -100,8 +99,8 @@ class QSECNode(QObject):
|
||||
# print(module, parameter, self.modules[module]['parameters'])
|
||||
return self.modules[module]['parameters'][parameter]
|
||||
|
||||
def updateEvent(self, module, parameter, value, timestamp, readerror):
|
||||
self.newData.emit(module, parameter, Value(value, timestamp, readerror))
|
||||
def updateItem(self, module, parameter, item):
|
||||
self.newData.emit(module, parameter, item)
|
||||
|
||||
def nodeStateChange(self, online, state):
|
||||
self.stateChange.emit(self.nodename, online, state)
|
||||
|
@ -24,10 +24,7 @@ class ModuleItem(QTreeWidgetItem):
|
||||
self._hasTarget = 'target' in parameters
|
||||
#if self._hasTarget:
|
||||
# self.setFlags(self.flags() | Qt.ItemIsEditable)
|
||||
if 'value' in parameters:
|
||||
props = node.getProperties(self.module, 'value')
|
||||
self._unit = props.get('unit', '')
|
||||
else:
|
||||
if 'status' not in parameters:
|
||||
self.setIcon(self.display['status'], ModuleItem.icons['clear'])
|
||||
|
||||
self.setText(0, self.module)
|
||||
@ -72,16 +69,14 @@ class ModuleItem(QTreeWidgetItem):
|
||||
if parameter not in self.display:
|
||||
return
|
||||
if parameter == 'status':
|
||||
self.setIcon(self.display[parameter], ModuleItem.statusIcon(value.value[0].value))
|
||||
self.setText(self.display['status/text'], value.value[1])
|
||||
else:
|
||||
# TODO: stopgap
|
||||
if value.readerror:
|
||||
strvalue = str(value)
|
||||
self.setIcon(self.display[parameter], ModuleItem.statusIcon(400)) # 400=ERROR
|
||||
self.setText(self.display['status/text'], str(value.readerror))
|
||||
else:
|
||||
strvalue = ('%g' if isinstance(value.value, float)
|
||||
else '%s') % (value.value,)
|
||||
self.setText(self.display[parameter], '%s %s' % (strvalue, self._unit))
|
||||
self.setIcon(self.display[parameter], ModuleItem.statusIcon(value.value[0].value))
|
||||
self.setText(self.display['status/text'], value.value[1])
|
||||
else:
|
||||
self.setText(self.display[parameter], value.formatted())
|
||||
|
||||
def disconnected(self):
|
||||
self.setIcon(self.display['status'], ModuleItem.icons['unknown'])
|
||||
@ -92,7 +87,6 @@ class ModuleItem(QTreeWidgetItem):
|
||||
def hasTarget(self):
|
||||
return self._hasTarget
|
||||
|
||||
|
||||
def _rebuildAdvanced(self, advanced):
|
||||
if advanced:
|
||||
self.addChildren(self.params)
|
||||
|
@ -259,13 +259,7 @@ class ModuleWidget(QWidget):
|
||||
if mod != self._name:
|
||||
return
|
||||
if param in self._paramDisplays:
|
||||
# TODO: stopgap
|
||||
if val.readerror:
|
||||
strvalue = str(val)
|
||||
else:
|
||||
strvalue = ('%g' if isinstance(val.value, float)
|
||||
else '%s') % (val.value,)
|
||||
self._paramDisplays[param].setText(strvalue)
|
||||
self._paramDisplays[param].setText(str(val))
|
||||
|
||||
def _addParam(self, param, row):
|
||||
paramProps = self._node.getProperties(self._name, param)
|
||||
|
@ -77,12 +77,7 @@ class GenericParameterWidget(ParameterWidget):
|
||||
self.setLineEdit.text())
|
||||
|
||||
def updateValue(self, value):
|
||||
fmtstr = getattr(self._datatype, 'fmtstr', '%s')
|
||||
if value.readerror:
|
||||
value = str(value)
|
||||
else:
|
||||
value = fmtstr % (value.value,)
|
||||
self.currentLineEdit.setText(value)
|
||||
self.currentLineEdit.setText(str(value))
|
||||
|
||||
|
||||
class EnumParameterWidget(GenericParameterWidget):
|
||||
|
@ -32,26 +32,6 @@ uipath = path.dirname(__file__)
|
||||
def loadUi(widget, uiname, subdir='ui'):
|
||||
uic.loadUi(path.join(uipath, subdir, uiname), widget)
|
||||
|
||||
class Value:
|
||||
def __init__(self, value, timestamp=None, readerror=None):
|
||||
self.value = value
|
||||
self.timestamp = timestamp
|
||||
self.readerror = readerror
|
||||
|
||||
def __str__(self):
|
||||
"""for display"""
|
||||
if self.readerror:
|
||||
return str('!!' + str(self.readerror) + '!!')
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
args = (self.value,)
|
||||
if self.timestamp:
|
||||
args += (self.timestamp,)
|
||||
if self.readerror:
|
||||
args += (self.readerror,)
|
||||
return 'Value%s' % repr(args)
|
||||
|
||||
|
||||
def is_light_theme(palette):
|
||||
background = palette.window().color().lightness()
|
||||
|
Loading…
x
Reference in New Issue
Block a user