big rework to comply to current spec

- adapt to release(v2018-11-07)
- remove duplicate errors.py
- adapt tests

Change-Id: I383bb571f9808c72b37c12fbe55042011c4c0084
Reviewed-on: https://forge.frm2.tum.de/review/19397
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber
2018-11-21 17:10:11 +01:00
parent 87261382cf
commit 29a5b5c49e
25 changed files with 770 additions and 424 deletions

View File

@ -40,9 +40,9 @@ timeout=900
pollinterval.export=False
# some parameter grouping
p.group='pid'
i.group='pid'
d.group='pid'
p.group=pid
i.group=pid
d.group=pid
value.unit='K'
value.unit=K

View File

@ -47,7 +47,7 @@ from secop.lib.parsing import parse_time, format_time
#from secop.protocol.encoding import ENCODERS
#from secop.protocol.framing import FRAMERS
#from secop.protocol.messages import *
from secop.protocol.errors import EXCEPTIONS
from secop.errors import EXCEPTIONS
class TCPConnection(object):
@ -344,25 +344,18 @@ class Client(object):
def _getDescribingParameterData(self, module, parameter):
return self._getDescribingModuleData(module)['accessibles'][parameter]
def _decode_list_to_ordereddict(self, data):
# takes a list of 2*N <key>, <value> entries and
# return an orderedDict from it
result = OrderedDict()
while len(data) > 1:
key = data.pop(0)
value = data.pop(0)
result[key] = value
return result
def _decode_substruct(self, specialkeys=[], data={}): # pylint: disable=W0102
# take a dict and move all keys which are not in specialkeys
# into a 'properties' subdict
# specialkeys entries are converted from list to ordereddict
try:
result = {}
for k in specialkeys:
result[k] = self._decode_list_to_ordereddict(data.pop(k, []))
result[k] = OrderedDict(data.pop(k, []))
result['properties'] = data
return result
except Exception as err:
raise RuntimeError('Error decoding substruct of descriptive data: %r\n%r' % (err, data))
def _issueDescribe(self):
_, _, describing_data = self._communicate('describe')

View File

@ -33,7 +33,7 @@ except NameError:
from base64 import b64encode, b64decode
from secop.lib.enum import Enum
from secop.errors import ProgrammingError, ParsingError
from secop.errors import ProgrammingError, ProtocolError
from secop.parse import Parser
@ -92,9 +92,6 @@ class FloatRange(DataType):
self.max = None if maxval is None else float(maxval)
# note: as we may compare to Inf all comparisons would be false
if (self.min or float(u'-inf')) <= (self.max or float(u'+inf')):
if minval is None and maxval is None:
self.as_json = [u'double']
else:
self.as_json = [u'double', minval, maxval]
else:
raise ValueError(u'Max must be larger then min!')
@ -141,14 +138,13 @@ class FloatRange(DataType):
class IntRange(DataType):
"""Restricted int type"""
def __init__(self, minval=None, maxval=None):
self.min = int(minval) if minval is not None else minval
self.max = int(maxval) if maxval is not None else maxval
if self.min is not None and self.max is not None and self.min > self.max:
def __init__(self, minval=-16777216, maxval=16777216):
self.min = int(minval)
self.max = int(maxval)
if self.min > self.max:
raise ValueError(u'Max must be larger then min!')
if self.min is None and self.max is None:
self.as_json = [u'int']
else:
if None in (self.min, self.max):
raise ValueError(u'Limits can not be None!')
self.as_json = [u'int', self.min, self.max]
def validate(self, value):
@ -193,7 +189,7 @@ class EnumType(DataType):
return [u'enum'] + [dict((m.name, m.value) for m in self._enum.members)]
def __repr__(self):
return "EnumType(%r, %s" % (self._enum.name, ', '.join('%s=%d' %(m.name, m.value) for m in self._enum.members))
return u"EnumType(%r, %s)" % (self._enum.name, ', '.join(u'%s=%d' %(m.name, m.value) for m in self._enum.members))
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -208,7 +204,7 @@ class EnumType(DataType):
try:
return self._enum[value]
except KeyError:
raise ValueError('%r is not a member of enum %r' % (value, self._enum))
raise ValueError(u'%r is not a member of enum %r' % (value, self._enum))
def from_string(self, text):
return self.validate(text)
@ -217,26 +213,21 @@ class EnumType(DataType):
class BLOBType(DataType):
minsize = None
maxsize = None
def __init__(self, maxsize=None, minsize=0):
def __init__(self, minsize=0, maxsize=None):
# if only one argument is given, use exactly that many bytes
# if nothing is given, default to 255
if maxsize is None:
raise ValueError(u'BLOBType needs a maximum number of Bytes count!')
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
self.minsize = minsize
self.maxsize = maxsize
if minsize < 0:
maxsize = minsize or 255
minsize = maxsize
minsize = int(minsize)
maxsize = int(maxsize)
self.minsize, self.maxsize = min(minsize, maxsize), max(minsize, maxsize)
if self.minsize < 0:
raise ValueError(u'sizes must be bigger than or equal to 0!')
if minsize:
self.as_json = [u'blob', maxsize, minsize]
else:
self.as_json = [u'blob', maxsize]
self.as_json = [u'blob', minsize, maxsize]
def __repr__(self):
if self.minsize:
return u'BLOB(%s, %s)' % (
unicode(self.minsize) if self.minsize else u'unspecified',
unicode(self.maxsize) if self.maxsize else u'unspecified')
return u'BLOB(%s)' % (unicode(self.minsize) if self.minsize else u'unspecified')
return u'BLOB(%s, %s)' % (unicode(self.minsize), unicode(self.maxsize))
def validate(self, value):
"""return the validated (internal) value or raise"""
@ -271,25 +262,19 @@ class StringType(DataType):
minsize = None
maxsize = None
def __init__(self, maxsize=255, minsize=0):
def __init__(self, minsize=0, maxsize=None):
if maxsize is None:
raise ValueError(u'StringType needs a maximum bytes count!')
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
if minsize < 0:
raise ValueError(u'sizes must be >= 0')
if minsize:
self.as_json = [u'string', maxsize, minsize]
else:
self.as_json = [u'string', maxsize]
self.minsize = minsize
self.maxsize = maxsize
maxsize = minsize or 255
minsize = 0
minsize = int(minsize)
maxsize = int(maxsize)
self.minsize, self.maxsize = min(minsize, maxsize), max(minsize, maxsize)
if self.minsize < 0:
raise ValueError(u'sizes must be bigger than or equal to 0!')
self.as_json = [u'string', minsize, maxsize]
def __repr__(self):
if self.minsize:
return u'StringType(%s, %s)' % (
unicode(self.minsize) or u'unspecified', unicode(self.maxsize) or u'unspecified')
return u'StringType(%s)' % unicode(self.maxsize)
return u'StringType(%s, %s)' % (unicode(self.minsize), unicode(self.maxsize))
def validate(self, value):
"""return the validated (internal) value or raise"""
@ -358,24 +343,27 @@ class BoolType(DataType):
class ArrayOf(DataType):
minsize = None
maxsize = None
def __init__(self, subtype, maxsize=None, minsize=0):
self.subtype = subtype
subtype = None
def __init__(self, subtype, minsize=0, maxsize=None):
# one argument -> exactly that size
# argument default to 10
if maxsize is None:
maxsize = minsize or 10
minsize = maxsize
if not isinstance(subtype, DataType):
raise ValueError(
u'ArrayOf only works with DataType objs as first argument!')
u'ArrayOf only works with a DataType as first argument!')
self.subtype = subtype
if maxsize is None:
raise ValueError(u'ArrayOf needs a maximum size')
minsize = int(minsize)
maxsize = int(maxsize)
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
if minsize < 0:
raise ValueError(u'sizes must be > 0')
if maxsize < 1:
raise ValueError(u'Maximum size must be >= 1!')
# if only one arg is given, it is maxsize!
if minsize:
self.as_json = [u'array', subtype.as_json, maxsize, minsize]
else:
self.as_json = [u'array', subtype.as_json, maxsize]
self.as_json = [u'array', minsize, maxsize, subtype.as_json]
self.minsize = minsize
self.maxsize = maxsize
@ -410,7 +398,7 @@ class ArrayOf(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError(u'trailing garbage: %r' % rem)
raise ProtocolError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -424,7 +412,7 @@ class TupleOf(DataType):
raise ValueError(
u'TupleOf only works with DataType objs as arguments!')
self.subtypes = subtypes
self.as_json = [u'tuple', [subtype.as_json for subtype in subtypes]]
self.as_json = [u'tuple'] + [subtype.as_json for subtype in subtypes]
def __repr__(self):
return u'TupleOf(%s)' % u', '.join([repr(st) for st in self.subtypes])
@ -454,7 +442,7 @@ class TupleOf(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError(u'trailing garbage: %r' % rem)
raise ProtocolError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -512,49 +500,40 @@ class StructOf(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError(u'trailing garbage: %r' % rem)
raise ProtocolError(u'trailing garbage: %r' % rem)
return self.validate(dict(value))
class CommandType(DataType):
IS_COMMAND = True
argtype = None
resulttype = None
def __init__(self, argtypes=tuple(), resulttype=None):
for arg in argtypes:
if not isinstance(arg, DataType):
raise ValueError(u'CommandType: Argument types must be DataTypes!')
def __init__(self, argtype=None, resulttype=None):
if argtype is not None:
if not isinstance(argtype, DataType):
raise ValueError(u'CommandType: Argument type must be a DataType!')
if resulttype is not None:
if not isinstance(resulttype, DataType):
raise ValueError(u'CommandType: result type must be DataTypes!')
self.argtypes = argtypes
raise ValueError(u'CommandType: Result type must be a DataType!')
self.argtype = argtype
self.resulttype = resulttype
if resulttype is not None:
self.as_json = [u'command',
[t.as_json for t in argtypes],
resulttype.as_json]
else:
self.as_json = [u'command',
[t.as_json for t in argtypes],
None] # XXX: or NoneType ???
if argtype:
argtype = argtype.as_json
if resulttype:
resulttype = resulttype.as_json
self.as_json = [u'command', argtype, resulttype]
def __repr__(self):
argstr = u', '.join(repr(arg) for arg in self.argtypes)
argstr = repr(self.argtype) if self.argtype else ''
if self.resulttype is None:
return u'CommandType(%s)' % argstr
return u'CommandType(%s)->%s' % (argstr, repr(self.resulttype))
def validate(self, value):
"""return the validated arguments value or raise"""
try:
if len(value) != len(self.argtypes):
raise ValueError(
u'Illegal number of Arguments! Need %d arguments.' %
len(self.argtypes))
# validate elements and return
return [t.validate(v) for t, v in zip(self.argtypes, value)]
except Exception as exc:
raise ValueError(u'Can not validate %s: %s' % (repr(value), unicode(exc)))
"""return the validated argument value or raise"""
return self.argtype.validate(value)
def export_value(self, value):
raise ProgrammingError(u'values of type command can not be transported!')
@ -565,7 +544,7 @@ class CommandType(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError(u'trailing garbage: %r' % rem)
raise ProtocolError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -584,7 +563,7 @@ class LimitsType(StructOf):
class Status(TupleOf):
# shorten initialisation and allow acces to status enumMembers from status values
def __init__(self, enum):
TupleOf.__init__(self, EnumType(enum), StringType(255))
TupleOf.__init__(self, EnumType(enum), StringType())
self.enum = enum
def __getattr__(self, key):
enum = TupleOf.__getattr__(self, 'enum')
@ -595,17 +574,17 @@ class Status(TupleOf):
# XXX: derive from above classes automagically!
DATATYPES = dict(
bool=BoolType,
int=lambda _min=None, _max=None: IntRange(_min, _max),
double=lambda _min=None, _max=None: FloatRange(_min, _max),
blob=lambda _max=None, _min=0: BLOBType(_max, _min),
string=lambda _max=None, _min=0: StringType(_max, _min),
array=lambda subtype, _max=None, _min=0: ArrayOf(get_datatype(subtype), _max, _min),
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
enum=lambda kwds: EnumType('', **kwds),
struct=lambda named_subtypes: StructOf(
bool =BoolType,
int =IntRange,
double =FloatRange,
blob =BLOBType,
string =StringType,
array =lambda _min, _max, subtype: ArrayOf(get_datatype(subtype), _min, _max),
tuple =lambda *subtypes: TupleOf(*map(get_datatype, subtypes)),
enum =lambda kwds: EnumType('', **kwds),
struct =lambda named_subtypes: StructOf(
**dict((n, get_datatype(t)) for n, t in list(named_subtypes.items()))),
command=lambda args, res: CommandType(map(get_datatype, args), get_datatype(res)),
command = lambda arg, res: CommandType(get_datatype(arg), get_datatype(res)),
)
@ -623,14 +602,10 @@ def get_datatype(json):
if len(json) < 1:
raise ValueError(u'can not validate %r' % json)
base = json[0]
if base in DATATYPES:
if base in (u'enum', u'struct'):
args = []
if len(json) > 1:
args = json[1:]
else:
args = []
else:
args = json[1:]
if base in DATATYPES:
try:
return DATATYPES[base](*args)
except (TypeError, AttributeError):

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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
@ -18,79 +19,117 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""error class for our little framework"""
# base class
class SECoPServerError(Exception):
errorclass = 'InternalError'
"""Define (internal) SECoP Errors"""
# those errors should never be seen remotely!
# just in case they are, these are flagged as InternalError
class ConfigError(SECoPServerError):
pass
class SECoPError(RuntimeError):
def __init__(self, *args, **kwds):
RuntimeError.__init__(self)
self.args = args
for k, v in list(kwds.items()):
setattr(self, k, v)
def __repr__(self):
args = ', '.join(map(repr, self.args))
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())])
res = []
if args:
res.append(args)
if kwds:
res.append(kwds)
return '%s(%s)' % (self.name, ', '.join(res))
@property
def name(self):
return self.__class__.__name__[:-len('Error')]
class ProgrammingError(SECoPServerError):
pass
class SECoPServerError(SECoPError):
name = 'InternalError'
class ParsingError(SECoPServerError):
pass
class InternalError(SECoPError):
name = 'InternalError'
# to be exported for remote operation
class SECoPError(SECoPServerError):
pass
class ProgrammingError(SECoPError):
name = 'InternalError'
class ConfigError(SECoPError):
name = 'InternalError'
class ProtocolError(SECoPError):
name = 'ProtocolError'
class NoSuchModuleError(SECoPError):
errorclass = 'NoSuchModule'
name = 'NoSuchModule'
class NoSuchParameterError(SECoPError):
errorclass = 'NoSuchParameter'
pass
class NoSuchCommandError(SECoPError):
errorclass = 'NoSuchCommand'
class CommandFailedError(SECoPError):
errorclass = 'CommandFailed'
class CommandRunningError(SECoPError):
errorclass = 'CommandRunning'
pass
class ReadOnlyError(SECoPError):
errorclass = 'ReadOnly'
pass
class BadValueError(SECoPError):
errorclass = 'BadValue'
pass
class CommunicationError(SECoPError):
errorclass = 'CommunicationFailed'
class CommandFailedError(SECoPError):
pass
class TimeoutError(SECoPError):
errorclass = 'CommunicationFailed' # XXX: add to SECop messages
class CommandRunningError(SECoPError):
pass
class HardwareError(SECoPError):
errorclass = 'CommunicationFailed' # XXX: Add to SECoP messages
class CommunicationFailedError(SECoPError):
pass
class IsBusyError(SECoPError):
errorclass = 'IsBusy'
pass
class IsErrorError(SECoPError):
errorclass = 'IsError'
pass
class DisabledError(SECoPError):
errorclass = 'Disabled'
pass
class HardwareError(SECoPError):
pass
EXCEPTIONS = dict(
NoSuchModule=NoSuchModuleError,
NoSuchParameter=NoSuchParameterError,
NoSuchCommand=NoSuchCommandError,
CommandFailed=CommandFailedError,
CommandRunning=CommandRunningError,
Readonly=ReadOnlyError,
BadValue=BadValueError,
CommunicationFailed=CommunicationFailedError,
HardwareError=HardwareError,
IsBusy=IsBusyError,
IsError=IsErrorError,
Disabled=DisabledError,
SyntaxError=ProtocolError,
InternalError=InternalError,
# internal short versions (candidates for spec)
Protocol=ProtocolError,
Internal=InternalError,
)

View File

@ -112,9 +112,9 @@ class HAS_Timeout(Feature):
class HAS_Pause(Feature):
# just a proposal, can't agree on it....
accessibles = {
'pause': Command('pauses movement', arguments=[], result=None),
'pause': Command('pauses movement', argument=None, result=None),
'go': Command('continues movement or start a new one if target was change since the last pause',
arguments=[], result=None),
argument=None, result=None),
}

430
secop/gui/miniplot.py Normal file
View File

@ -0,0 +1,430 @@
# -*- 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:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
from os import path
from secop.gui.qt import Qt, QColor, QWidget, QSize, \
QBrush, QPainter, QPolygonF, QPointF, QPen, QRectF
_magenta = QBrush(QColor('#A12F86'))
_yellow = QBrush(QColor('yellow'))
_white = QBrush(QColor('white'))
_lightgrey = QBrush(QColor('lightgrey'))
_grey = QBrush(QColor('grey'))
_darkgrey = QBrush(QColor('#404040'))
_black = QBrush(QColor('black'))
_blue = QBrush(QColor('blue'))
_green = QBrush(QColor('green'))
_red = QBrush(QColor('red'))
_olive = QBrush(QColor('olive'))
_orange = QBrush(QColor('#ffa500'))
my_uipath = path.dirname(__file__)
class MiniPlotCurve(object):
# placeholder for data
linecolor = _black
linewidth = 0 # set to 0 to disable lines
symbolcolors = (_black, _white) # line, fill
symbolsize = 3 # both symbol linewidth and symbolsize, set to 0 to disable
errorbarcolor = _darkgrey
errorbarwidth = 3 # set to 0 to disable errorbar
def __init__(self):
self.data = [] # tripels of x, y, err (err may be None)
@property
def xvalues(self):
return [p[0] for p in self.data] if self.data else [0]
@property
def yvalues(self):
return [p[1] for p in self.data] if self.data else [0]
@property
def errvalues(self):
return [p[2] or 0.0 for p in self.data] if self.data else [0]
@property
def xmin(self):
return min(self.xvalues)
@property
def xmax(self):
return max(self.xvalues)
@property
def ymin(self):
return min(self.yvalues)
@property
def ymax(self):
return max(self.yvalues)
@property
def yemin(self):
return min(y-(e or 0) for _, y, e in self.data) if self.data else 0
@property
def yemax(self):
return max(y+(e or 0) for _, y, e in self.data) if self.data else 0
def paint(self, scale, painter):
# note: scale returns a screen-XY tuple for data XY
# draw errorbars, lines and symbols in that order
if self.errorbarwidth > 0:
pen = QPen()
pen.setBrush(self.errorbarcolor)
pen.setWidth(self.errorbarwidth)
painter.setPen(pen)
for _x,_y,_e in self.data:
if _e is None:
continue
x, y = scale(_x,_y)
e = scale(_x,_y + _e)[1] - y
painter.drawLine(x, y-e, x, y+e)
painter.fillRect(x - self.errorbarwidth / 2., y - e,
self.errorbarwidth, 2 * e, self.errorbarcolor)
points = [QPointF(*scale(p[0], p[1])) for p in self.data]
if self.linewidth > 0:
pen = QPen()
pen.setBrush(self.linecolor)
pen.setWidth(self.linewidth)
painter.setPen(pen)
painter.drawPolyline(QPolygonF(points))
if self.symbolsize > 0:
pen = QPen()
pen.setBrush(self.symbolcolors[0]) # linecolor
pen.setWidth(self.symbolsize) # linewidth
painter.setPen(pen)
painter.setBrush(self.symbolcolors[1]) # fill color
if self.symbolsize > 0:
for p in points:
painter.drawEllipse(p, 2*self.symbolsize, 2*self.symbolsize)
def preparepainting(self, scale, xmin, xmax):
pass # nothing to do
class MiniPlotFitCurve(MiniPlotCurve):
# do not influence scaling of plotting window
@property
def xmin(self):
return float('inf')
@property
def xmax(self):
return float('-inf')
@property
def ymin(self):
return float('inf')
@property
def ymax(self):
return float('-inf')
@property
def yemin(self):
return float('inf')
@property
def yemax(self):
return float('-inf')
def __init__(self, formula, params):
super(MiniPlotFitCurve, self).__init__()
self.formula = formula
self.params = params
linecolor = _blue
linewidth = 5 # set to 0 to disable lines
symbolsize = 0 # both symbol linewidth and symbolsize, set to 0 to disable
errorbarwidth = 0 # set to 0 to disable errorbar
def preparepainting(self, scale, xmin, xmax):
# recalculate data
points = int(scale(xmax) - scale(xmin))
self.data = []
for idx in range(points+1):
x = xmin + idx * (xmax-xmin) / points
y = self.formula(x, *self.params)
self.data.append((x,y,None))
class MiniPlot(QWidget):
ticklinecolors = (_grey, _lightgrey) # ticks, subticks
ticklinewidth = 1
bordercolor = _black
borderwidth = 1
labelcolor = _black
xlabel = 'x'
ylabel = 'y'
xfmt = '%.1f'
yfmt = '%g'
autotickx = True
autoticky = True
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.xmin = self.xmax = None
self.ymin = self.ymax = None
self.curves = []
self.plotx = 0 # left of this are labels
self.ploty = self.height() # below this are labels
def scaleX(self, x):
if not self.curves:
return x # XXX: !!!!
x = self.plotx + (self.width() - self.plotx) * (x - self.xmin) / (self.xmax - self.xmin)
# x = max(min(x, self.width()), self.plotx)
return x
def scaleY(self, y):
if not self.curves:
return y # XXX: !!!!
y = self.ploty * (self.ymax - y) / (self.ymax - self.ymin)
# y = max(min(y, self.ploty), 0)
return y
def scale(self, x, y):
# scales a plotting xx/y to a screen x/y to be used for painting...
return self.scaleX(x), self.scaleY(y)
def removeCurve(self, curve):
if curve in self.curves:
self.curves.remove(curve)
self.updatePlot()
def addCurve(self, curve):
if curve is not None and curve not in self.curves:
# new curve, recalculate all
self.curves.append(curve)
self.updatePlot()
def updatePlot(self):
xmin,xmax = -1,1
ymin,ymax = -1,1
# find limits of known curves
if self.curves:
xmin = min(c.xmin for c in self.curves)
xmax = max(c.xmax for c in self.curves)
ymin = min(c.yemin for c in self.curves)
ymax = max(c.yemax for c in self.curves)
# fallback values for no curve
while xmin >= xmax:
xmin, xmax = xmin - 1, xmax + 1
while ymin >= ymax:
ymin, ymax = ymin - 1, ymax + 1
# adjust limits a little
self.xmin = xmin - 0.05 * (xmax - xmin)
self.xmax = xmax + 0.05 * (xmax - xmin)
self.ymin = ymin - 0.05 * (ymax - ymin)
self.ymax = ymax + 0.05 * (ymax - ymin)
# (re-)generate x/yticks
if self.autotickx:
self.calc_xticks(xmin, xmax)
if self. autoticky:
self.calc_yticks(ymin, ymax)
# redraw
self.update()
def calc_xticks(self, xmin, xmax):
self.xticks = self.calc_ticks(xmin, xmax, self.xfmt)
def calc_yticks(self, ymin, ymax):
self.yticks = self.calc_ticks(ymin, ymax, self.yfmt)
def calc_ticks(self, _min, _max, fmt):
min_intervals = 2
diff = _max - _min
if diff <= 0:
return [0]
# find a 'good' step size
step = abs(diff / min_intervals)
# split into mantissa and exp.
expo = 0
while step >= 10:
step /= 10.
expo += 1
while step < 1:
step *= 10.
expo -= 1
# make step 'latch' into smalle bigger magic number
subs = 1
for n, subs in reversed([(1,5.), (1.5,3.), (2,4.), (3,3.), (5,5.), (10,2.)]):
if step >= n:
step = n
break
# convert back to normal number
while expo > 0:
step *= 10.
expo -= 1
while expo < 0:
step /= 10.
expo += 1
substep = step / subs
# round lower
rounded_min = step * int(_min / step)
# generate ticks list
ticks = []
x = rounded_min
while x + substep < _min:
x += substep
for _ in range(100):
if x < _max + substep:
break
# check if x is a tick or a subtick
x = substep * int(x / substep)
if abs(x - step * int(x / step)) <= substep / 2:
# tick
ticks.append((x, fmt % x))
else:
# subtick
ticks.append((x, ''))
x += substep
return ticks
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# obtain a few properties we need for proper drawing
painter.setFont(self.font())
fm = painter.fontMetrics()
label_height = fm.height()
self.plotx = 3 + 2 * label_height
self.ploty = self.height() - 3 - 2 * label_height
# fill bg of plotting area
painter.fillRect(self.plotx ,0,self.width()-self.plotx, self.ploty,_white)
# paint ticklines
if self.curves and self.ticklinewidth > 0:
for e in self.xticks:
try:
_x = e[0] # pylint: disable=unsubscriptable-object
_l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_x = e
_l = self.xfmt % _x
x = self.scaleX(_x)
pen = QPen()
pen.setBrush(self.ticklinecolors[0 if _l else 1])
pen.setWidth(self.ticklinewidth)
painter.setPen(pen)
painter.drawLine(x, 0, x, self.ploty)
for e in self.yticks:
try:
_y = e[0] # pylint: disable=unsubscriptable-object
_l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_y = e
_l = self.xfmt % _x
y = self.scaleY(_y)
pen = QPen()
pen.setBrush(self.ticklinecolors[0 if _l else 1])
pen.setWidth(self.ticklinewidth)
painter.setPen(pen)
painter.drawLine(self.plotx, y, self.width(), y)
# paint curves
painter.setClipRect(QRectF(self.plotx, 0, self.width()-self.plotx, self.ploty))
for c in self.curves:
c.preparepainting(self.scaleX, self.xmin, self.xmax)
c.paint(self.scale, painter)
painter.setClipping(False)
# paint frame
pen = QPen()
pen.setBrush(self.bordercolor)
pen.setWidth(self.borderwidth)
painter.setPen(pen)
painter.drawPolyline(QPolygonF([
QPointF(self.plotx, 0),
QPointF(self.width()-1, 0),
QPointF(self.width()-1, self.ploty),
QPointF(self.plotx, self.ploty),
QPointF(self.plotx, 0),
]))
# draw labels
painter.setBrush(self.labelcolor)
h2 = (self.height()-self.ploty)/2.
# XXX: offset axis labels from axis a little
painter.drawText(self.plotx, self.ploty + h2,
self.width() - self.plotx, h2,
Qt.AlignCenter | Qt.AlignVCenter, self.xlabel)
# rotate ylabel?
painter.resetTransform()
painter.translate(0, self.ploty / 2.)
painter.rotate(-90)
w = fm.width(self.ylabel)
painter.drawText(-w, -fm.height() / 2., w * 2, self.plotx,
Qt.AlignCenter | Qt.AlignTop, self.ylabel)
painter.resetTransform()
if self.curves:
for e in self.xticks:
try:
_x = e[0] # pylint: disable=unsubscriptable-object
l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_x = e
l = self.xfmt % _x
x = self.scaleX(_x)
w = fm.width(l)
painter.drawText(x - w, self.ploty + 2, 2 * w, h2,
Qt.AlignCenter | Qt.AlignVCenter, l)
for e in self.yticks:
try:
_y = e[0] # pylint: disable=unsubscriptable-object
l = e[1] # pylint: disable=unsubscriptable-object
except TypeError:
_y = e
l = self.yfmt % _y
y = self.scaleY(_y)
w = fm.width(l)
painter.resetTransform()
painter.translate(self.plotx - fm.height(), y + w)
painter.rotate(-90)
painter.drawText(0, -1,
2 * w, fm.height(),
Qt.AlignCenter | Qt.AlignBottom, l)
painter.resetTransform()
def sizeHint(self):
return QSize(320, 240)

View File

@ -43,30 +43,31 @@ from secop.gui.valuewidgets import get_widget
class CommandDialog(QDialog):
def __init__(self, cmdname, arglist, parent=None):
def __init__(self, cmdname, argument, parent=None):
super(CommandDialog, self).__init__(parent)
loadUi(self, 'cmddialog.ui')
self.setWindowTitle('Arguments for %s' % cmdname)
row = 0
#row = 0
self._labels = []
self.widgets = []
for row, dtype in enumerate(arglist):
# improve! recursive?
dtype = argument
l = QLabel(repr(dtype))
l.setWordWrap(True)
w = get_widget(dtype, readonly=False)
self.gridLayout.addWidget(l, row, 0)
self.gridLayout.addWidget(w, row, 1)
self.gridLayout.addWidget(l, 0, 0)
self.gridLayout.addWidget(w, 0, 1)
self._labels.append(l)
self.widgets.append(w)
self.gridLayout.setRowStretch(len(arglist), 1)
self.gridLayout.setRowStretch(1, 1)
self.setModal(True)
self.resize(self.sizeHint())
def get_value(self):
return [w.get_value() for w in self.widgets]
return True, self.widgets[0].get_value()
def exec_(self):
if super(CommandDialog, self).exec_():
@ -127,7 +128,7 @@ class CommandButton(QPushButton):
super(CommandButton, self).__init__(parent)
self._cmdname = cmdname
self._argintypes = cmdinfo['datatype'].argtypes # list of datatypes
self._argintype = cmdinfo['datatype'].argtype # single datatype
self.resulttype = cmdinfo['datatype'].resulttype
self._cb = cb # callback function for exection
@ -138,11 +139,11 @@ class CommandButton(QPushButton):
def on_pushButton_pressed(self):
self.setEnabled(False)
if self._argintypes:
dlg = CommandDialog(self._cmdname, self._argintypes)
if self._argintype:
dlg = CommandDialog(self._cmdname, self._argintype)
args = dlg.exec_()
if args: # not 'Cancel' clicked
self._cb(self._cmdname, args)
self._cb(self._cmdname, args[1])
else:
# no need for arguments
self._cb(self._cmdname, None)
@ -172,8 +173,13 @@ class ModuleCtrl(QWidget):
def _execCommand(self, command, args=None):
if not args:
args = tuple()
try:
result, qualifiers = self._node.execCommand(
self._module, command, args)
except TypeError:
result = None
qualifiers = {}
# XXX: flag missing data report as error
showCommandResultDialog(command, args, result, qualifiers)
def _initModuleWidgets(self):
@ -182,7 +188,7 @@ class ModuleCtrl(QWidget):
# ignore groupings for commands (for now)
commands = self._node.getCommands(self._module)
# keep a reference or the widgets are detroyed to soon.
# keep a reference or the widgets are destroyed to soon.
self.cmdWidgets = cmdWidgets = {}
# create and insert widgets into our QGridLayout
for command in sorted(commands):
@ -191,6 +197,7 @@ class ModuleCtrl(QWidget):
cmdWidgets[command] = w
self.commandGroupBox.layout().addWidget(w, 0, row)
row += 1
row = 0
# collect grouping information
paramsByGroup = {} # groupname -> [paramnames]
@ -198,8 +205,8 @@ class ModuleCtrl(QWidget):
params = self._node.getParameters(self._module)
for param in params:
props = self._node.getProperties(self._module, param)
group = props.get('group', None)
if group is not None:
group = props.get('group', '')
if group:
allGroups.add(group)
paramsByGroup.setdefault(group, []).append(param)
# enforce reading initial value if not already in cache
@ -210,6 +217,7 @@ class ModuleCtrl(QWidget):
self._groupWidgets = groupWidgets = {}
# create and insert widgets into our QGridLayout
# iterate over a union of all groups and all params
for param in sorted(allGroups.union(set(params))):
labelstr = param + ':'
if param in paramsByGroup:
@ -226,7 +234,7 @@ class ModuleCtrl(QWidget):
'datatype', None)
# yes: create a widget for this as well
labelstr, buttons = self._makeEntry(
param, initValues[param].value, datatype=datatype, nolabel=True, checkbox=checkbox, invert=True)
group, initValues[param].value, datatype=datatype, nolabel=True, checkbox=checkbox, invert=True)
checkbox.setText(labelstr)
# add to Layout (yes: ignore the label!)
@ -249,7 +257,7 @@ class ModuleCtrl(QWidget):
self._module, param_).get(
'datatype', None)
label, buttons = self._makeEntry(
param_, initval, checkbox=checkbox, invert=False)
param_, initval, datatype=datatype, checkbox=checkbox, invert=False)
# add to Layout
self.paramGroupBox.layout().addWidget(label, row, 0)
@ -260,7 +268,7 @@ class ModuleCtrl(QWidget):
# param is a 'normal' param: create a widget if it has no group
# or is named after a group (otherwise its created above)
props = self._node.getProperties(self._module, param)
if props.get('group', param) == param:
if (props.get('group', '') or param) == param:
datatype = self._node.getProperties(
self._module, param).get(
'datatype', None)

View File

@ -37,7 +37,7 @@ from secop.gui.qt import QWidget, QTextCursor, QFont, QFontMetrics, QLabel, \
QMessageBox, pyqtSlot, toHtmlEscaped
from secop.gui.util import loadUi
from secop.protocol.errors import SECOPError
from secop.errors import SECoPError
from secop.datatypes import StringType, EnumType
@ -80,7 +80,7 @@ class NodeCtrl(QWidget):
self._addLogEntry(reply, newline=True, pretty=False)
else:
self._addLogEntry(reply, newline=True, pretty=True)
except SECOPError as e:
except SECoPError as e:
self._addLogEntry(
'error %s %s' % (e.name, json.dumps(e.args)),
newline=True,
@ -145,8 +145,8 @@ class NodeCtrl(QWidget):
if 'interface_class' in modprops:
interfaces = modprops['interface_class']
else:
interfaces = modprops['interfaces']
description = modprops['description']
interfaces = modprops.get('interfaces', '')
description = modprops.get('description', '!!! missing description !!!')
# fallback: allow (now) invalid 'Driveable'
unit = ''

View File

@ -134,7 +134,7 @@ class GenericCmdWidget(ParameterWidget):
loadUi(self, 'cmdbuttons.ui')
self.cmdLineEdit.setText('')
self.cmdLineEdit.setEnabled(self.datatype.argtypes is not None)
self.cmdLineEdit.setEnabled(self.datatype.argtype is not None)
self.cmdLineEdit.returnPressed.connect(
self.on_cmdPushButton_clicked)
@ -164,7 +164,6 @@ def ParameterView(module,
parent=None):
# depending on datatype returns an initialized widget fit for display and
# interaction
if datatype is not None:
if datatype.IS_COMMAND:
return GenericCmdWidget(

View File

@ -24,25 +24,34 @@
# pylint: disable=unused-import
from __future__ import print_function
import sys
try:
# Do not abort on exceptions in signal handlers.
# pylint: disable=unnecessary-lambda
sys.excepthook = lambda *args: sys.__excepthook__(*args)
from PyQt5 import uic
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics
from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, QPushButton, \
QSizePolicy, QMainWindow, QMessageBox, QInputDialog, QTreeWidgetItem, QApplication, \
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
QGridLayout, QScrollArea, QFrame
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QSize, QPointF, \
QRectF
from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics, QColor, QBrush, \
QPainter, QPolygonF, QPen
from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, \
QPushButton, QSizePolicy, QMainWindow, QMessageBox, QInputDialog, \
QTreeWidgetItem, QApplication, QGroupBox, QSpinBox, QDoubleSpinBox, \
QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, QGridLayout, \
QScrollArea, QFrame
from xml.sax.saxutils import escape as toHtmlEscaped
except ImportError:
from PyQt4 import uic
from PyQt4.QtCore import Qt, QObject, pyqtSignal, pyqtSlot
from PyQt4.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QSize, QPointF, QRectF
from PyQt4.QtGui import QFont, QTextCursor, QFontMetrics, \
QLabel, QWidget, QDialog, QLineEdit, QCheckBox, QPushButton, \
QSizePolicy, QMainWindow, QMessageBox, QInputDialog, QTreeWidgetItem, QApplication, \
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
QGridLayout, QScrollArea, QFrame
QGridLayout, QScrollArea, QFrame, QColor, QBrush, QPainter, QPolygonF, QPen
def toHtmlEscaped(s):
return Qt.escape(s)

View File

@ -6,10 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>730</width>
<width>464</width>
<height>33</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>33</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
@ -37,7 +43,7 @@
</property>
<property name="minimumSize">
<size>
<width>256</width>
<width>128</width>
<height>0</height>
</size>
</property>
@ -57,7 +63,7 @@
<widget class="QLineEdit" name="setLineEdit">
<property name="minimumSize">
<size>
<width>256</width>
<width>128</width>
<height>0</height>
</size>
</property>

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>238</width>
<height>121</height>
<width>100</width>
<height>100</height>
</rect>
</property>
<property name="windowTitle">

View File

@ -224,7 +224,8 @@ class Readable(Module):
WARN = 200,
UNSTABLE = 250,
ERROR = 400,
UNKNOWN = 900,
DISABLED = 500,
UNKNOWN = 0,
)
parameters = {
'value': Parameter('current value of the Module', readonly=True,
@ -312,7 +313,7 @@ class Drivable(Writable):
commands = {
'stop': Command(
'cease driving, go to IDLE state',
arguments=[],
argument=None,
result=None
),
}
@ -360,7 +361,7 @@ class Communicator(Module):
commands = {
"communicate": Command("provides the simplest mean to communication",
arguments=[StringType()],
argument=StringType(),
result=StringType()
),
}

View File

@ -155,15 +155,14 @@ class Override(CountedObj):
class Command(CountedObj):
"""storage for Commands settings (description + call signature...)
"""
def __init__(self, description, arguments=None, result=None, export=True, optional=False, datatype=None, ctr=None):
def __init__(self, description, argument=None, result=None, export=True, optional=False, datatype=None, ctr=None):
super(Command, self).__init__()
# descriptive text for humans
self.description = description
# list of datatypes for arguments
self.arguments = arguments or []
self.datatype = CommandType(arguments, result)
self.arguments = arguments
# datatypes for argument/result
self.argument = argument
self.result = result
self.datatype = CommandType(argument, result)
# whether implementation is optional
self.optional = optional
self.export = export

View File

@ -45,9 +45,9 @@ from secop.protocol.messages import EVENTREPLY, IDENTREQUEST, IDENTREPLY, \
ENABLEEVENTSREPLY, DESCRIPTIONREPLY, WRITEREPLY, COMMANDREPLY, \
DISABLEEVENTSREPLY, HEARTBEATREPLY
from secop.protocol.errors import InternalError, NoSuchModuleError, \
NoSuchCommandError, NoSuchParameterError, BadValueError, ReadonlyError, \
ProtocolError
from secop.errors import NoSuchModuleError, NoSuchCommandError, \
NoSuchParameterError, BadValueError, ReadOnlyError, \
ProtocolError, SECoPServerError as InternalError
from secop.params import Parameter
@ -63,9 +63,9 @@ class Dispatcher(object):
def __init__(self, name, logger, options, srv):
# to avoid errors, we want to eat all options here
self.equipment_id = name
self.nodeopts = {}
self.nodeprops = {}
for k in list(options):
self.nodeopts[k] = options.pop(k)
self.nodeprops[k] = options.pop(k)
self.log = logger
# map ALL modulename -> moduleobj
@ -169,7 +169,7 @@ class Dispatcher(object):
res = []
for aname, aobj in self.get_module(modulename).accessibles.items():
if aobj.export:
res.extend([aname, aobj.for_export()])
res.append([aname, aobj.for_export()])
self.log.debug(u'list accessibles for module %s -> %r' %
(modulename, res))
return res
@ -183,21 +183,22 @@ class Dispatcher(object):
result = {u'modules': []}
for modulename in self._export:
module = self.get_module(modulename)
if not module.properties.get('export', False):
continue
# some of these need rework !
mod_desc = {u'accessibles': self.export_accessibles(modulename)}
for propname, prop in list(module.properties.items()):
if propname == 'export':
continue
mod_desc[propname] = prop
result[u'modules'].extend([modulename, mod_desc])
result[u'modules'].append([modulename, mod_desc])
result[u'equipment_id'] = self.equipment_id
result[u'firmware'] = u'FRAPPY - The Python Framework for SECoP'
result[u'version'] = u'2018.09'
result.update(self.nodeopts)
result.update(self.nodeprops)
return result
def _execute_command(self, modulename, command, arguments=None):
if arguments is None:
arguments = []
def _execute_command(self, modulename, command, argument=None):
moduleobj = self.get_module(modulename)
if moduleobj is None:
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
@ -205,14 +206,16 @@ class Dispatcher(object):
cmdspec = moduleobj.accessibles.get(command, None)
if cmdspec is None:
raise NoSuchCommandError('Module has no such command!')
num_args_required = len(cmdspec.datatype.argtypes)
if num_args_required != len(arguments):
raise BadValueError(u'Wrong number of arguments (need %d, got %d)!' % (num_args_required, len(arguments)))
if argument is None and cmdspec.datatype.argtype is not None:
raise BadValueError(u'Command needs an argument!')
if argument is not None and cmdspec.datatype.argtype is None:
raise BadValueError(u'Command takes no argument!')
# now call func and wrap result as value
# note: exceptions are handled in handle_request, not here!
func = getattr(moduleobj, u'do_' + command)
res = func(*arguments)
res = func(argument) if argument else func()
# XXX: pipe through cmdspec.datatype.result ?
return res, dict(t=currenttime())
@ -225,7 +228,7 @@ class Dispatcher(object):
if pobj is None or not isinstance(pobj, Parameter):
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
if pobj.readonly:
raise ReadonlyError('This parameter can not be changed remotely.')
raise ReadOnlyError('This parameter can not be changed remotely.')
writefunc = getattr(moduleobj, u'write_%s' % pname, None)
# note: exceptions are handled in handle_request, not here!
@ -272,7 +275,7 @@ class Dispatcher(object):
action, specifier, data = msg
# special case for *IDN?
if action == IDENTREQUEST:
action, specifier, data = 'ident', None, None
action, specifier, data = '_ident', None, None
self.log.debug(u'Looking for handle_%s' % action)
handler = getattr(self, u'handle_%s' % action, None)
@ -286,7 +289,7 @@ class Dispatcher(object):
def handle_help(self, conn, specifier, data):
self.log.error('should have been handled in the interface!')
def handle_ident(self, conn, specifier, data):
def handle__ident(self, conn, specifier, data):
return (IDENTREPLY, None, None)
def handle_describe(self, conn, specifier, data):

View File

@ -1,118 +0,0 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""Define (internal) SECoP Errors"""
class SECOPError(RuntimeError):
def __init__(self, *args, **kwds):
RuntimeError.__init__(self)
self.args = args
for k, v in list(kwds.items()):
setattr(self, k, v)
def __repr__(self):
args = ', '.join(map(repr, self.args))
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())])
res = []
if args:
res.append(args)
if kwds:
res.append(kwds)
return '%s(%s)' % (self.name, ', '.join(res))
@property
def name(self):
return self.__class__.__name__[:-len('Error')]
class InternalError(SECOPError):
name = 'InternalError'
class ProtocolError(SECOPError):
name = 'SyntaxError'
class NoSuchModuleError(SECOPError):
pass
class NoSuchParameterError(SECOPError):
pass
class NoSuchCommandError(SECOPError):
pass
class ReadonlyError(SECOPError):
pass
class BadValueError(SECOPError):
pass
class CommandFailedError(SECOPError):
pass
class CommandRunningError(SECOPError):
pass
class CommunicationFailedError(SECOPError):
pass
class IsBusyError(SECOPError):
pass
class IsErrorError(SECOPError):
pass
class DisabledError(SECOPError):
pass
EXCEPTIONS = dict(
NoSuchModule=NoSuchModuleError,
NoSuchParameter=NoSuchParameterError,
NoSuchCommand=NoSuchCommandError,
CommandFailed=CommandFailedError,
CommandRunning=CommandRunningError,
Readonly=ReadonlyError,
BadValue=BadValueError,
CommunicationFailed=CommunicationFailedError,
IsBusy=IsBusyError,
IsError=IsErrorError,
Disabled=DisabledError,
SyntaxError=ProtocolError,
InternalError=InternalError,
# internal short versions (candidates for spec)
Protocol=ProtocolError,
Internal=InternalError,
)

View File

@ -21,6 +21,7 @@
"""provides tcp interface to the SECoP Server"""
from __future__ import print_function
import sys
import socket
import collections
@ -29,7 +30,7 @@ try:
except ImportError:
import SocketServer as socketserver # py2
from secop.lib import formatExtendedStack, formatException
from secop.lib import formatExtendedStack, formatException, formatExtendedTraceback
from secop.protocol.messages import HELPREQUEST, HELPREPLY, HelpMessage
from secop.errors import SECoPError
from secop.protocol.interface import encode_msg_frame, get_msg, decode_msg
@ -136,7 +137,7 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
print('--------------------')
print(formatException())
print('--------------------')
print(formatExtendedStack())
print(formatExtendedTraceback(sys.exc_info()))
print('====================')
if not result:

View File

@ -26,7 +26,7 @@ from __future__ import print_function
IDENTREQUEST = u'*IDN?' # literal
# literal! first part is fixed!
IDENTREPLY = u'SINE2020&ISSE,SECoP,V2018-06-16,rc1'
IDENTREPLY = u'SINE2020&ISSE,SECoP,V2018-11-07,v1.0\\beta'
DESCRIPTIONREQUEST = u'describe' # literal
DESCRIPTIONREPLY = u'describing' # +<id> +json
@ -41,11 +41,16 @@ COMMANDREQUEST = u'do' # +module:command +json args (if needed)
# +module:command +json args (if needed) # send after the command finished !
COMMANDREPLY = u'done'
# +module[:parameter] +json_value -> NO direct reply, calls POLL internally
# +module[:parameter] +json_value
WRITEREQUEST = u'change'
# +module[:parameter] +json_value # send with the read back value
WRITEREPLY = u'changed'
# +module[:parameter] +json_value
BUFFERREQUEST = u'buffer'
# +module[:parameter] +json_value # send with the read back value
BUFFERREPLY = u'buffered'
# +module[:parameter] -> NO direct reply, calls POLL internally!
POLLREQUEST = u'read'
EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
@ -66,6 +71,7 @@ REQUEST2REPLY = {
DISABLEEVENTSREQUEST: DISABLEEVENTSREPLY,
COMMANDREQUEST: COMMANDREPLY,
WRITEREQUEST: WRITEREPLY,
BUFFERREQUEST: BUFFERREPLY,
POLLREQUEST: EVENTREPLY,
HEARTBEATREQUEST: HEARTBEATREPLY,
HELPREQUEST: HELPREPLY,

View File

@ -125,7 +125,7 @@ class Cryostat(CryoBase):
commands = dict(
stop=Command(
"Stop ramping the setpoint\n\nby setting the current setpoint as new target",
[],
None,
None),
)

View File

@ -314,6 +314,6 @@ class DatatypesTest(Readable):
class ArrayTest(Readable):
parameters = {
"x": Parameter('value', datatype=ArrayOf(FloatRange(), 100000, 100000),
"x": Parameter('value', datatype=ArrayOf(FloatRange(), 0, 100000),
default = 100000 * [0]),
}

View File

@ -39,7 +39,7 @@ from secop.lib import lazy_property
#from secop.parse import Parser
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
ArrayOf, EnumType
from secop.errors import ConfigError, ProgrammingError, CommunicationError, \
from secop.errors import ConfigError, ProgrammingError, CommunicationFailedError, \
HardwareError
from secop.modules import Parameter, Command, Override, Module, Readable, Drivable
@ -57,7 +57,7 @@ __all__ = [
]
EXC_MAPPING = {
PyTango.CommunicationFailed: CommunicationError,
PyTango.CommunicationFailedError: CommunicationFailedError,
PyTango.WrongNameSyntax: ConfigError,
PyTango.DevFailed: HardwareError,
}
@ -65,7 +65,7 @@ EXC_MAPPING = {
REASON_MAPPING = {
'Entangle_ConfigurationError': ConfigError,
'Entangle_WrongAPICall': ProgrammingError,
'Entangle_CommunicationFailure': CommunicationError,
'Entangle_CommunicationFailure': CommunicationFailedError,
'Entangle_InvalidValue': ValueError,
'Entangle_ProgrammingError': ProgrammingError,
'Entangle_HardwareFailure': HardwareError,
@ -174,7 +174,7 @@ class PyTangoDevice(Module):
}
commands = {
'reset': Command('Tango reset command', arguments=[], result=None),
'reset': Command('Tango reset command', argument=None, result=None),
}
tango_status_mapping = {
@ -255,7 +255,7 @@ class PyTangoDevice(Module):
try:
device.State
except AttributeError:
raise CommunicationError(
raise CommunicationFailedError(
self, 'connection to Tango server failed, '
'is the server running?')
return self._applyGuardsToPyTangoDevice(device)
@ -351,11 +351,11 @@ class PyTangoDevice(Module):
"""Process the exception raised either by communication or _com_return.
Should raise a NICOS exception. Default is to raise
CommunicationError.
CommunicationFailedError.
"""
reason = self._tango_exc_reason(err)
exclass = REASON_MAPPING.get(
reason, EXC_MAPPING.get(type(err), CommunicationError))
reason, EXC_MAPPING.get(type(err), CommunicationFailedError))
fulldesc = self._tango_exc_desc(err)
self.log.debug('PyTango error: %s', fulldesc)
raise exclass(self, fulldesc)
@ -405,7 +405,7 @@ class Sensor(AnalogInput):
commands = {
'setposition': Command('Set the position to the given value.',
arguments=[FloatRange()], result=None,
argument=FloatRange(), result=None,
),
}
@ -450,7 +450,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
),
}
commands = {
'stop': Command('Stops current movement.', arguments=[], result=None),
'stop': Command('Stops current movement.', argument=None, result=None),
}
_history = ()
_timeout = None
@ -615,7 +615,7 @@ class Actuator(AnalogOutput):
commands = {
'setposition': Command('Set the position to the given value.',
arguments=[FloatRange()], result=None,
argument=FloatRange(), result=None,
),
}
@ -656,7 +656,7 @@ class Motor(Actuator):
}
commands = {
'reference': Command('Do a reference run', arguments=[], result=None),
'reference': Command('Do a reference run', argument=None, result=None),
}
def read_refpos(self, maxage=0):
@ -952,25 +952,25 @@ class StringIO(PyTangoDevice, Module):
commands = {
'communicate': Command('Send a string and return the reply',
arguments=[StringType()],
argument=StringType(),
result=StringType()),
'flush': Command('Flush output buffer',
arguments=[], result=None),
argument=None, result=None),
'read': Command('read some characters from input buffer',
arguments=[IntRange()], result=StringType()),
argument=IntRange(0), result=StringType()),
'write': Command('write some chars to output',
arguments=[StringType()], result=None),
argument=StringType(), result=None),
'readLine': Command('Read sol - a whole line - eol',
arguments=[], result=StringType()),
argument=None, result=StringType()),
'writeLine': Command('write sol + a whole line + eol',
arguments=[StringType()], result=None),
argument=StringType(), result=None),
'availableChars': Command('return number of chars in input buffer',
arguments=[], result=IntRange(0)),
argument=None, result=IntRange(0)),
'availableLines': Command('return number of lines in input buffer',
arguments=[], result=IntRange(0)),
argument=None, result=IntRange(0)),
'multiCommunicate': Command('perform a sequence of communications',
arguments=[ArrayOf(
TupleOf(StringType(), IntRange()), 100)],
argument=ArrayOf(
TupleOf(StringType(), IntRange()), 100),
result=ArrayOf(StringType(), 100)),
}

View File

@ -57,30 +57,28 @@ def clientobj(request):
# pylint: disable=redefined-outer-name
def test_describing_data_decode(clientobj):
assert OrderedDict(
[('a', 1)]) == clientobj._decode_list_to_ordereddict(['a', 1])
assert {'modules': {}, 'properties': {}
assert {'modules': OrderedDict(), 'properties': {}
} == clientobj._decode_substruct(['modules'], {})
describing_data = {'equipment_id': 'eid',
'modules': ['LN2', {'commands': [],
'modules': [['LN2', {'commands': [],
'interfaces': ['Readable', 'Module'],
'parameters': ['value', {'datatype': ['double'],
'parameters': [['value', {'datatype': ['double'],
'description': 'current value',
'readonly': True,
}
]
]]
}
]
]]
}
decoded_data = {'modules': {'LN2': {'commands': {},
'parameters': {'value': {'datatype': ['double'],
decoded_data = {'modules': OrderedDict([('LN2', {'commands': OrderedDict(),
'parameters': OrderedDict([('value', {'datatype': ['double'],
'description': 'current value',
'readonly': True,
}
},
)]),
'properties': {'interfaces': ['Readable', 'Module']}
}
},
)]),
'properties': {'equipment_id': 'eid',
}
}

View File

@ -65,7 +65,7 @@ def test_FloatRange():
FloatRange('x', 'Y')
dt = FloatRange()
assert dt.as_json == ['double']
assert dt.as_json == ['double', None, None]
def test_IntRange():
@ -86,7 +86,8 @@ def test_IntRange():
IntRange('xc', 'Yx')
dt = IntRange()
assert dt.as_json == ['int']
assert dt.as_json[0] == 'int'
assert dt.as_json[1] < 0 < dt.as_json[2]
def test_EnumType():
@ -128,13 +129,13 @@ def test_EnumType():
def test_BLOBType():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
dt = BLOBType()
assert dt.as_json == ['blob', 255, 255]
dt = BLOBType(10)
assert dt.as_json == ['blob', 10]
assert dt.as_json == ['blob', 10, 10]
dt = BLOBType(3, 10)
assert dt.as_json == ['blob', 10, 3]
assert dt.as_json == ['blob', 3, 10]
with pytest.raises(ValueError):
dt.validate(9)
@ -156,10 +157,10 @@ def test_StringType():
# test constructor catching illegal arguments
dt = StringType()
dt = StringType(12)
assert dt.as_json == ['string', 12]
assert dt.as_json == ['string', 0, 12]
dt = StringType(4, 11)
assert dt.as_json == ['string', 11, 4]
assert dt.as_json == ['string', 4, 11]
with pytest.raises(ValueError):
dt.validate(9)
@ -208,12 +209,12 @@ def test_ArrayOf():
with pytest.raises(ValueError):
ArrayOf(int)
with pytest.raises(ValueError):
ArrayOf(IntRange(-10,10))
ArrayOf(-3, IntRange(-10,10))
dt = ArrayOf(IntRange(-10, 10), 5)
assert dt.as_json == ['array', ['int', -10, 10], 5]
assert dt.as_json == ['array', 5, 5, ['int', -10, 10]]
dt = ArrayOf(IntRange(-10, 10), 1, 3)
assert dt.as_json == ['array', ['int', -10, 10], 3, 1]
assert dt.as_json == ['array', 1, 3, ['int', -10, 10]]
with pytest.raises(ValueError):
dt.validate(9)
with pytest.raises(ValueError):
@ -231,7 +232,7 @@ def test_TupleOf():
TupleOf(2)
dt = TupleOf(IntRange(-10, 10), BoolType())
assert dt.as_json == ['tuple', [['int', -10, 10], ['bool']]]
assert dt.as_json == ['tuple', ['int', -10, 10], ['bool']]
with pytest.raises(ValueError):
dt.validate(9)
@ -252,8 +253,8 @@ def test_StructOf():
StructOf(IntRange=1)
dt = StructOf(a_string=StringType(55), an_int=IntRange(0, 999))
assert dt.as_json == ['struct', {'a_string': ['string', 55],
'an_int': ['int', 0, 999],
assert dt.as_json == [u'struct', {u'a_string': [u'string', 0, 55],
u'an_int': [u'int', 0, 999],
}]
with pytest.raises(ValueError):
@ -285,7 +286,6 @@ def test_get_datatype():
assert isinstance(get_datatype(['int']), IntRange)
assert isinstance(get_datatype(['int', -10]), IntRange)
assert isinstance(get_datatype(['int', None, 10]), IntRange)
assert isinstance(get_datatype(['int', -10, 10]), IntRange)
with pytest.raises(ValueError):
@ -320,7 +320,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['blob', 10, -10, 1])
with pytest.raises(ValueError):
get_datatype(['string'])
assert isinstance(get_datatype(['string', 1]), StringType)
assert isinstance(get_datatype(['string', 10, 1]), StringType)
@ -336,15 +335,15 @@ def test_get_datatype():
get_datatype(['array', 1])
with pytest.raises(ValueError):
get_datatype(['array', [1], 2, 3])
assert isinstance(get_datatype(['array', ['blob', 1], 1]), ArrayOf)
assert isinstance(get_datatype(['array', ['blob', 1], 1]).subtype, BLOBType)
assert isinstance(get_datatype(['array', 1, 1, ['blob', 1]]), ArrayOf)
assert isinstance(get_datatype(['array', 1, 1, ['blob', 1]]).subtype, BLOBType)
with pytest.raises(ValueError):
get_datatype(['array', ['blob', 1], -10])
with pytest.raises(ValueError):
get_datatype(['array', ['blob', 1], 10, -10])
assert isinstance(get_datatype(['array', ['blob', 1], 10, 1]), ArrayOf)
assert isinstance(get_datatype(['array', 1, 10, ['blob', 1]]), ArrayOf)
with pytest.raises(ValueError):
get_datatype(['tuple'])
@ -352,16 +351,15 @@ def test_get_datatype():
get_datatype(['tuple', 1])
with pytest.raises(ValueError):
get_datatype(['tuple', [1], 2, 3])
assert isinstance(get_datatype(['tuple', [['blob', 1]]]), TupleOf)
assert isinstance(get_datatype(
['tuple', [['blob', 1]]]).subtypes[0], BLOBType)
assert isinstance(get_datatype(['tuple', ['blob', 1]]), TupleOf)
assert isinstance(get_datatype(['tuple', ['blob', 1]]).subtypes[0], BLOBType)
with pytest.raises(ValueError):
get_datatype(['tuple', [['blob', 1]], -10])
get_datatype(['tuple', ['blob', 1], -10])
with pytest.raises(ValueError):
get_datatype(['tuple', [['blob', 1]], 10, -10])
get_datatype(['tuple', ['blob', 1], 10, -10])
assert isinstance(get_datatype(['tuple', [['blob', 1], ['int']]]), TupleOf)
assert isinstance(get_datatype(['tuple', ['blob', 1], ['int']]), TupleOf)
with pytest.raises(ValueError):
get_datatype(['struct'])
@ -370,13 +368,12 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['struct', [1], 2, 3])
assert isinstance(get_datatype(['struct', {'blob': ['blob', 1]}]), StructOf)
assert isinstance(get_datatype(
['struct', {'blob': ['blob', 1]}]).named_subtypes['blob'], BLOBType)
assert isinstance(get_datatype(['struct', {'blob': ['blob', 1]}]).named_subtypes['blob'], BLOBType)
with pytest.raises(ValueError):
get_datatype(['struct', [['blob', 1]], -10])
get_datatype(['struct', ['blob', 1], -10])
with pytest.raises(ValueError):
get_datatype(['struct', [['blob', 1]], 10, -10])
get_datatype(['struct', ['blob', 1], 10, -10])
assert isinstance(get_datatype(
['struct', {'blob': ['blob', 1], 'int':['int']}]), StructOf)

View File

@ -69,13 +69,13 @@ def test_ModuleMeta():
'param2': Parameter('param2', datatype=BoolType(), default=True),
},
"commands": {
"cmd": Command('stuff',[BoolType()], BoolType())
"cmd": Command('stuff',BoolType(), BoolType())
},
"accessibles": {
'a1': Parameter('a1', datatype=BoolType(), default=False),
'a2': Parameter('a2', datatype=BoolType(), default=True),
'value':Override(datatype=BoolType(), default = True),
'cmd2': Command('another stuff', [BoolType()], BoolType()),
'cmd2': Command('another stuff', BoolType(), BoolType()),
},
"do_cmd": lambda self, arg: not arg,
"do_cmd2": lambda self, arg: not arg,

View File

@ -35,10 +35,10 @@ from secop.params import Command, Parameter, Override
def test_Command():
cmd = Command('do_something', [], None)
cmd = Command('do_something')
assert cmd.description
assert cmd.ctr
assert cmd.arguments == []
assert cmd.argument is None
assert cmd.result is None