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:
@ -40,9 +40,9 @@ timeout=900
|
|||||||
pollinterval.export=False
|
pollinterval.export=False
|
||||||
|
|
||||||
# some parameter grouping
|
# some parameter grouping
|
||||||
p.group='pid'
|
p.group=pid
|
||||||
i.group='pid'
|
i.group=pid
|
||||||
d.group='pid'
|
d.group=pid
|
||||||
|
|
||||||
value.unit='K'
|
value.unit=K
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ from secop.lib.parsing import parse_time, format_time
|
|||||||
#from secop.protocol.encoding import ENCODERS
|
#from secop.protocol.encoding import ENCODERS
|
||||||
#from secop.protocol.framing import FRAMERS
|
#from secop.protocol.framing import FRAMERS
|
||||||
#from secop.protocol.messages import *
|
#from secop.protocol.messages import *
|
||||||
from secop.protocol.errors import EXCEPTIONS
|
from secop.errors import EXCEPTIONS
|
||||||
|
|
||||||
|
|
||||||
class TCPConnection(object):
|
class TCPConnection(object):
|
||||||
@ -344,25 +344,18 @@ class Client(object):
|
|||||||
def _getDescribingParameterData(self, module, parameter):
|
def _getDescribingParameterData(self, module, parameter):
|
||||||
return self._getDescribingModuleData(module)['accessibles'][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
|
def _decode_substruct(self, specialkeys=[], data={}): # pylint: disable=W0102
|
||||||
# take a dict and move all keys which are not in specialkeys
|
# take a dict and move all keys which are not in specialkeys
|
||||||
# into a 'properties' subdict
|
# into a 'properties' subdict
|
||||||
# specialkeys entries are converted from list to ordereddict
|
# specialkeys entries are converted from list to ordereddict
|
||||||
result = {}
|
try:
|
||||||
for k in specialkeys:
|
result = {}
|
||||||
result[k] = self._decode_list_to_ordereddict(data.pop(k, []))
|
for k in specialkeys:
|
||||||
result['properties'] = data
|
result[k] = OrderedDict(data.pop(k, []))
|
||||||
return result
|
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):
|
def _issueDescribe(self):
|
||||||
_, _, describing_data = self._communicate('describe')
|
_, _, describing_data = self._communicate('describe')
|
||||||
|
@ -33,7 +33,7 @@ except NameError:
|
|||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
from secop.lib.enum import Enum
|
from secop.lib.enum import Enum
|
||||||
from secop.errors import ProgrammingError, ParsingError
|
from secop.errors import ProgrammingError, ProtocolError
|
||||||
from secop.parse import Parser
|
from secop.parse import Parser
|
||||||
|
|
||||||
|
|
||||||
@ -92,10 +92,7 @@ class FloatRange(DataType):
|
|||||||
self.max = None if maxval is None else float(maxval)
|
self.max = None if maxval is None else float(maxval)
|
||||||
# note: as we may compare to Inf all comparisons would be false
|
# 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 (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', minval, maxval]
|
||||||
self.as_json = [u'double']
|
|
||||||
else:
|
|
||||||
self.as_json = [u'double', minval, maxval]
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(u'Max must be larger then min!')
|
raise ValueError(u'Max must be larger then min!')
|
||||||
|
|
||||||
@ -141,15 +138,14 @@ class FloatRange(DataType):
|
|||||||
class IntRange(DataType):
|
class IntRange(DataType):
|
||||||
"""Restricted int type"""
|
"""Restricted int type"""
|
||||||
|
|
||||||
def __init__(self, minval=None, maxval=None):
|
def __init__(self, minval=-16777216, maxval=16777216):
|
||||||
self.min = int(minval) if minval is not None else minval
|
self.min = int(minval)
|
||||||
self.max = int(maxval) if maxval is not None else maxval
|
self.max = int(maxval)
|
||||||
if self.min is not None and self.max is not None and self.min > self.max:
|
if self.min > self.max:
|
||||||
raise ValueError(u'Max must be larger then min!')
|
raise ValueError(u'Max must be larger then min!')
|
||||||
if self.min is None and self.max is None:
|
if None in (self.min, self.max):
|
||||||
self.as_json = [u'int']
|
raise ValueError(u'Limits can not be None!')
|
||||||
else:
|
self.as_json = [u'int', self.min, self.max]
|
||||||
self.as_json = [u'int', self.min, self.max]
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
try:
|
try:
|
||||||
@ -193,7 +189,7 @@ class EnumType(DataType):
|
|||||||
return [u'enum'] + [dict((m.name, m.value) for m in self._enum.members)]
|
return [u'enum'] + [dict((m.name, m.value) for m in self._enum.members)]
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -208,7 +204,7 @@ class EnumType(DataType):
|
|||||||
try:
|
try:
|
||||||
return self._enum[value]
|
return self._enum[value]
|
||||||
except KeyError:
|
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):
|
def from_string(self, text):
|
||||||
return self.validate(text)
|
return self.validate(text)
|
||||||
@ -217,26 +213,21 @@ class EnumType(DataType):
|
|||||||
class BLOBType(DataType):
|
class BLOBType(DataType):
|
||||||
minsize = None
|
minsize = None
|
||||||
maxsize = 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:
|
if maxsize is None:
|
||||||
raise ValueError(u'BLOBType needs a maximum number of Bytes count!')
|
maxsize = minsize or 255
|
||||||
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
|
minsize = maxsize
|
||||||
self.minsize = minsize
|
minsize = int(minsize)
|
||||||
self.maxsize = maxsize
|
maxsize = int(maxsize)
|
||||||
if minsize < 0:
|
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!')
|
raise ValueError(u'sizes must be bigger than or equal to 0!')
|
||||||
if minsize:
|
self.as_json = [u'blob', minsize, maxsize]
|
||||||
self.as_json = [u'blob', maxsize, minsize]
|
|
||||||
else:
|
|
||||||
self.as_json = [u'blob', maxsize]
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.minsize:
|
return u'BLOB(%s, %s)' % (unicode(self.minsize), unicode(self.maxsize))
|
||||||
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')
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
@ -271,25 +262,19 @@ class StringType(DataType):
|
|||||||
minsize = None
|
minsize = None
|
||||||
maxsize = None
|
maxsize = None
|
||||||
|
|
||||||
def __init__(self, maxsize=255, minsize=0):
|
def __init__(self, minsize=0, maxsize=None):
|
||||||
if maxsize is None:
|
if maxsize is None:
|
||||||
raise ValueError(u'StringType needs a maximum bytes count!')
|
maxsize = minsize or 255
|
||||||
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
|
minsize = 0
|
||||||
|
minsize = int(minsize)
|
||||||
if minsize < 0:
|
maxsize = int(maxsize)
|
||||||
raise ValueError(u'sizes must be >= 0')
|
self.minsize, self.maxsize = min(minsize, maxsize), max(minsize, maxsize)
|
||||||
if minsize:
|
if self.minsize < 0:
|
||||||
self.as_json = [u'string', maxsize, minsize]
|
raise ValueError(u'sizes must be bigger than or equal to 0!')
|
||||||
else:
|
self.as_json = [u'string', minsize, maxsize]
|
||||||
self.as_json = [u'string', maxsize]
|
|
||||||
self.minsize = minsize
|
|
||||||
self.maxsize = maxsize
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.minsize:
|
return u'StringType(%s, %s)' % (unicode(self.minsize), unicode(self.maxsize))
|
||||||
return u'StringType(%s, %s)' % (
|
|
||||||
unicode(self.minsize) or u'unspecified', unicode(self.maxsize) or u'unspecified')
|
|
||||||
return u'StringType(%s)' % unicode(self.maxsize)
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
@ -358,24 +343,27 @@ class BoolType(DataType):
|
|||||||
class ArrayOf(DataType):
|
class ArrayOf(DataType):
|
||||||
minsize = None
|
minsize = None
|
||||||
maxsize = None
|
maxsize = None
|
||||||
def __init__(self, subtype, maxsize=None, minsize=0):
|
subtype = None
|
||||||
self.subtype = subtype
|
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):
|
if not isinstance(subtype, DataType):
|
||||||
raise ValueError(
|
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:
|
minsize = int(minsize)
|
||||||
raise ValueError(u'ArrayOf needs a maximum size')
|
maxsize = int(maxsize)
|
||||||
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
|
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
|
||||||
if minsize < 0:
|
if minsize < 0:
|
||||||
raise ValueError(u'sizes must be > 0')
|
raise ValueError(u'sizes must be > 0')
|
||||||
if maxsize < 1:
|
if maxsize < 1:
|
||||||
raise ValueError(u'Maximum size must be >= 1!')
|
raise ValueError(u'Maximum size must be >= 1!')
|
||||||
# if only one arg is given, it is maxsize!
|
# if only one arg is given, it is maxsize!
|
||||||
if minsize:
|
self.as_json = [u'array', minsize, maxsize, subtype.as_json]
|
||||||
self.as_json = [u'array', subtype.as_json, maxsize, minsize]
|
|
||||||
else:
|
|
||||||
self.as_json = [u'array', subtype.as_json, maxsize]
|
|
||||||
self.minsize = minsize
|
self.minsize = minsize
|
||||||
self.maxsize = maxsize
|
self.maxsize = maxsize
|
||||||
|
|
||||||
@ -410,7 +398,7 @@ class ArrayOf(DataType):
|
|||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value, rem = Parser.parse(text)
|
value, rem = Parser.parse(text)
|
||||||
if rem:
|
if rem:
|
||||||
raise ParsingError(u'trailing garbage: %r' % rem)
|
raise ProtocolError(u'trailing garbage: %r' % rem)
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
@ -424,7 +412,7 @@ class TupleOf(DataType):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
u'TupleOf only works with DataType objs as arguments!')
|
u'TupleOf only works with DataType objs as arguments!')
|
||||||
self.subtypes = subtypes
|
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):
|
def __repr__(self):
|
||||||
return u'TupleOf(%s)' % u', '.join([repr(st) for st in self.subtypes])
|
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):
|
def from_string(self, text):
|
||||||
value, rem = Parser.parse(text)
|
value, rem = Parser.parse(text)
|
||||||
if rem:
|
if rem:
|
||||||
raise ParsingError(u'trailing garbage: %r' % rem)
|
raise ProtocolError(u'trailing garbage: %r' % rem)
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
@ -512,49 +500,40 @@ class StructOf(DataType):
|
|||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value, rem = Parser.parse(text)
|
value, rem = Parser.parse(text)
|
||||||
if rem:
|
if rem:
|
||||||
raise ParsingError(u'trailing garbage: %r' % rem)
|
raise ProtocolError(u'trailing garbage: %r' % rem)
|
||||||
return self.validate(dict(value))
|
return self.validate(dict(value))
|
||||||
|
|
||||||
|
|
||||||
class CommandType(DataType):
|
class CommandType(DataType):
|
||||||
IS_COMMAND = True
|
IS_COMMAND = True
|
||||||
|
argtype = None
|
||||||
|
resulttype = None
|
||||||
|
|
||||||
def __init__(self, argtypes=tuple(), resulttype=None):
|
def __init__(self, argtype=None, resulttype=None):
|
||||||
for arg in argtypes:
|
if argtype is not None:
|
||||||
if not isinstance(arg, DataType):
|
if not isinstance(argtype, DataType):
|
||||||
raise ValueError(u'CommandType: Argument types must be DataTypes!')
|
raise ValueError(u'CommandType: Argument type must be a DataType!')
|
||||||
if resulttype is not None:
|
if resulttype is not None:
|
||||||
if not isinstance(resulttype, DataType):
|
if not isinstance(resulttype, DataType):
|
||||||
raise ValueError(u'CommandType: result type must be DataTypes!')
|
raise ValueError(u'CommandType: Result type must be a DataType!')
|
||||||
self.argtypes = argtypes
|
self.argtype = argtype
|
||||||
self.resulttype = resulttype
|
self.resulttype = resulttype
|
||||||
|
|
||||||
if resulttype is not None:
|
if argtype:
|
||||||
self.as_json = [u'command',
|
argtype = argtype.as_json
|
||||||
[t.as_json for t in argtypes],
|
if resulttype:
|
||||||
resulttype.as_json]
|
resulttype = resulttype.as_json
|
||||||
else:
|
self.as_json = [u'command', argtype, resulttype]
|
||||||
self.as_json = [u'command',
|
|
||||||
[t.as_json for t in argtypes],
|
|
||||||
None] # XXX: or NoneType ???
|
|
||||||
|
|
||||||
def __repr__(self):
|
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:
|
if self.resulttype is None:
|
||||||
return u'CommandType(%s)' % argstr
|
return u'CommandType(%s)' % argstr
|
||||||
return u'CommandType(%s)->%s' % (argstr, repr(self.resulttype))
|
return u'CommandType(%s)->%s' % (argstr, repr(self.resulttype))
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated arguments value or raise"""
|
"""return the validated argument value or raise"""
|
||||||
try:
|
return self.argtype.validate(value)
|
||||||
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)))
|
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
raise ProgrammingError(u'values of type command can not be transported!')
|
raise ProgrammingError(u'values of type command can not be transported!')
|
||||||
@ -565,7 +544,7 @@ class CommandType(DataType):
|
|||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value, rem = Parser.parse(text)
|
value, rem = Parser.parse(text)
|
||||||
if rem:
|
if rem:
|
||||||
raise ParsingError(u'trailing garbage: %r' % rem)
|
raise ProtocolError(u'trailing garbage: %r' % rem)
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
@ -584,7 +563,7 @@ class LimitsType(StructOf):
|
|||||||
class Status(TupleOf):
|
class Status(TupleOf):
|
||||||
# shorten initialisation and allow acces to status enumMembers from status values
|
# shorten initialisation and allow acces to status enumMembers from status values
|
||||||
def __init__(self, enum):
|
def __init__(self, enum):
|
||||||
TupleOf.__init__(self, EnumType(enum), StringType(255))
|
TupleOf.__init__(self, EnumType(enum), StringType())
|
||||||
self.enum = enum
|
self.enum = enum
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
enum = TupleOf.__getattr__(self, 'enum')
|
enum = TupleOf.__getattr__(self, 'enum')
|
||||||
@ -595,17 +574,17 @@ class Status(TupleOf):
|
|||||||
|
|
||||||
# XXX: derive from above classes automagically!
|
# XXX: derive from above classes automagically!
|
||||||
DATATYPES = dict(
|
DATATYPES = dict(
|
||||||
bool=BoolType,
|
bool =BoolType,
|
||||||
int=lambda _min=None, _max=None: IntRange(_min, _max),
|
int =IntRange,
|
||||||
double=lambda _min=None, _max=None: FloatRange(_min, _max),
|
double =FloatRange,
|
||||||
blob=lambda _max=None, _min=0: BLOBType(_max, _min),
|
blob =BLOBType,
|
||||||
string=lambda _max=None, _min=0: StringType(_max, _min),
|
string =StringType,
|
||||||
array=lambda subtype, _max=None, _min=0: ArrayOf(get_datatype(subtype), _max, _min),
|
array =lambda _min, _max, subtype: ArrayOf(get_datatype(subtype), _min, _max),
|
||||||
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
|
tuple =lambda *subtypes: TupleOf(*map(get_datatype, subtypes)),
|
||||||
enum=lambda kwds: EnumType('', **kwds),
|
enum =lambda kwds: EnumType('', **kwds),
|
||||||
struct=lambda named_subtypes: StructOf(
|
struct =lambda named_subtypes: StructOf(
|
||||||
**dict((n, get_datatype(t)) for n, t in list(named_subtypes.items()))),
|
**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:
|
if len(json) < 1:
|
||||||
raise ValueError(u'can not validate %r' % json)
|
raise ValueError(u'can not validate %r' % json)
|
||||||
base = json[0]
|
base = json[0]
|
||||||
|
args = []
|
||||||
|
if len(json) > 1:
|
||||||
|
args = json[1:]
|
||||||
if base in DATATYPES:
|
if base in DATATYPES:
|
||||||
if base in (u'enum', u'struct'):
|
|
||||||
if len(json) > 1:
|
|
||||||
args = json[1:]
|
|
||||||
else:
|
|
||||||
args = []
|
|
||||||
else:
|
|
||||||
args = json[1:]
|
|
||||||
try:
|
try:
|
||||||
return DATATYPES[base](*args)
|
return DATATYPES[base](*args)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
|
115
secop/errors.py
115
secop/errors.py
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
# 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
|
# 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
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
@ -18,79 +19,117 @@
|
|||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""error class for our little framework"""
|
"""Define (internal) SECoP Errors"""
|
||||||
|
|
||||||
# base class
|
|
||||||
class SECoPServerError(Exception):
|
|
||||||
errorclass = 'InternalError'
|
|
||||||
|
|
||||||
|
|
||||||
# those errors should never be seen remotely!
|
class SECoPError(RuntimeError):
|
||||||
# just in case they are, these are flagged as InternalError
|
|
||||||
class ConfigError(SECoPServerError):
|
def __init__(self, *args, **kwds):
|
||||||
pass
|
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):
|
class SECoPServerError(SECoPError):
|
||||||
pass
|
name = 'InternalError'
|
||||||
|
|
||||||
|
|
||||||
class ParsingError(SECoPServerError):
|
class InternalError(SECoPError):
|
||||||
pass
|
name = 'InternalError'
|
||||||
|
|
||||||
|
|
||||||
# to be exported for remote operation
|
class ProgrammingError(SECoPError):
|
||||||
class SECoPError(SECoPServerError):
|
name = 'InternalError'
|
||||||
pass
|
|
||||||
|
|
||||||
|
class ConfigError(SECoPError):
|
||||||
|
name = 'InternalError'
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolError(SECoPError):
|
||||||
|
name = 'ProtocolError'
|
||||||
|
|
||||||
|
|
||||||
class NoSuchModuleError(SECoPError):
|
class NoSuchModuleError(SECoPError):
|
||||||
errorclass = 'NoSuchModule'
|
name = 'NoSuchModule'
|
||||||
|
|
||||||
|
|
||||||
class NoSuchParameterError(SECoPError):
|
class NoSuchParameterError(SECoPError):
|
||||||
errorclass = 'NoSuchParameter'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoSuchCommandError(SECoPError):
|
class NoSuchCommandError(SECoPError):
|
||||||
errorclass = 'NoSuchCommand'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CommandFailedError(SECoPError):
|
|
||||||
errorclass = 'CommandFailed'
|
|
||||||
|
|
||||||
|
|
||||||
class CommandRunningError(SECoPError):
|
|
||||||
errorclass = 'CommandRunning'
|
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyError(SECoPError):
|
class ReadOnlyError(SECoPError):
|
||||||
errorclass = 'ReadOnly'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BadValueError(SECoPError):
|
class BadValueError(SECoPError):
|
||||||
errorclass = 'BadValue'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CommunicationError(SECoPError):
|
class CommandFailedError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TimeoutError(SECoPError):
|
class CommandRunningError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed' # XXX: add to SECop messages
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HardwareError(SECoPError):
|
class CommunicationFailedError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed' # XXX: Add to SECoP messages
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IsBusyError(SECoPError):
|
class IsBusyError(SECoPError):
|
||||||
errorclass = 'IsBusy'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IsErrorError(SECoPError):
|
class IsErrorError(SECoPError):
|
||||||
errorclass = 'IsError'
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DisabledError(SECoPError):
|
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,
|
||||||
|
)
|
||||||
|
@ -112,9 +112,9 @@ class HAS_Timeout(Feature):
|
|||||||
class HAS_Pause(Feature):
|
class HAS_Pause(Feature):
|
||||||
# just a proposal, can't agree on it....
|
# just a proposal, can't agree on it....
|
||||||
accessibles = {
|
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',
|
'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
430
secop/gui/miniplot.py
Normal 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)
|
@ -43,30 +43,31 @@ from secop.gui.valuewidgets import get_widget
|
|||||||
|
|
||||||
|
|
||||||
class CommandDialog(QDialog):
|
class CommandDialog(QDialog):
|
||||||
def __init__(self, cmdname, arglist, parent=None):
|
def __init__(self, cmdname, argument, parent=None):
|
||||||
super(CommandDialog, self).__init__(parent)
|
super(CommandDialog, self).__init__(parent)
|
||||||
loadUi(self, 'cmddialog.ui')
|
loadUi(self, 'cmddialog.ui')
|
||||||
|
|
||||||
self.setWindowTitle('Arguments for %s' % cmdname)
|
self.setWindowTitle('Arguments for %s' % cmdname)
|
||||||
row = 0
|
#row = 0
|
||||||
|
|
||||||
self._labels = []
|
self._labels = []
|
||||||
self.widgets = []
|
self.widgets = []
|
||||||
for row, dtype in enumerate(arglist):
|
# improve! recursive?
|
||||||
l = QLabel(repr(dtype))
|
dtype = argument
|
||||||
l.setWordWrap(True)
|
l = QLabel(repr(dtype))
|
||||||
w = get_widget(dtype, readonly=False)
|
l.setWordWrap(True)
|
||||||
self.gridLayout.addWidget(l, row, 0)
|
w = get_widget(dtype, readonly=False)
|
||||||
self.gridLayout.addWidget(w, row, 1)
|
self.gridLayout.addWidget(l, 0, 0)
|
||||||
self._labels.append(l)
|
self.gridLayout.addWidget(w, 0, 1)
|
||||||
self.widgets.append(w)
|
self._labels.append(l)
|
||||||
|
self.widgets.append(w)
|
||||||
|
|
||||||
self.gridLayout.setRowStretch(len(arglist), 1)
|
self.gridLayout.setRowStretch(1, 1)
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return [w.get_value() for w in self.widgets]
|
return True, self.widgets[0].get_value()
|
||||||
|
|
||||||
def exec_(self):
|
def exec_(self):
|
||||||
if super(CommandDialog, self).exec_():
|
if super(CommandDialog, self).exec_():
|
||||||
@ -127,7 +128,7 @@ class CommandButton(QPushButton):
|
|||||||
super(CommandButton, self).__init__(parent)
|
super(CommandButton, self).__init__(parent)
|
||||||
|
|
||||||
self._cmdname = cmdname
|
self._cmdname = cmdname
|
||||||
self._argintypes = cmdinfo['datatype'].argtypes # list of datatypes
|
self._argintype = cmdinfo['datatype'].argtype # single datatype
|
||||||
self.resulttype = cmdinfo['datatype'].resulttype
|
self.resulttype = cmdinfo['datatype'].resulttype
|
||||||
self._cb = cb # callback function for exection
|
self._cb = cb # callback function for exection
|
||||||
|
|
||||||
@ -138,11 +139,11 @@ class CommandButton(QPushButton):
|
|||||||
|
|
||||||
def on_pushButton_pressed(self):
|
def on_pushButton_pressed(self):
|
||||||
self.setEnabled(False)
|
self.setEnabled(False)
|
||||||
if self._argintypes:
|
if self._argintype:
|
||||||
dlg = CommandDialog(self._cmdname, self._argintypes)
|
dlg = CommandDialog(self._cmdname, self._argintype)
|
||||||
args = dlg.exec_()
|
args = dlg.exec_()
|
||||||
if args: # not 'Cancel' clicked
|
if args: # not 'Cancel' clicked
|
||||||
self._cb(self._cmdname, args)
|
self._cb(self._cmdname, args[1])
|
||||||
else:
|
else:
|
||||||
# no need for arguments
|
# no need for arguments
|
||||||
self._cb(self._cmdname, None)
|
self._cb(self._cmdname, None)
|
||||||
@ -172,8 +173,13 @@ class ModuleCtrl(QWidget):
|
|||||||
def _execCommand(self, command, args=None):
|
def _execCommand(self, command, args=None):
|
||||||
if not args:
|
if not args:
|
||||||
args = tuple()
|
args = tuple()
|
||||||
result, qualifiers = self._node.execCommand(
|
try:
|
||||||
self._module, command, args)
|
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)
|
showCommandResultDialog(command, args, result, qualifiers)
|
||||||
|
|
||||||
def _initModuleWidgets(self):
|
def _initModuleWidgets(self):
|
||||||
@ -182,7 +188,7 @@ class ModuleCtrl(QWidget):
|
|||||||
|
|
||||||
# ignore groupings for commands (for now)
|
# ignore groupings for commands (for now)
|
||||||
commands = self._node.getCommands(self._module)
|
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 = {}
|
self.cmdWidgets = cmdWidgets = {}
|
||||||
# create and insert widgets into our QGridLayout
|
# create and insert widgets into our QGridLayout
|
||||||
for command in sorted(commands):
|
for command in sorted(commands):
|
||||||
@ -191,6 +197,7 @@ class ModuleCtrl(QWidget):
|
|||||||
cmdWidgets[command] = w
|
cmdWidgets[command] = w
|
||||||
self.commandGroupBox.layout().addWidget(w, 0, row)
|
self.commandGroupBox.layout().addWidget(w, 0, row)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
row = 0
|
row = 0
|
||||||
# collect grouping information
|
# collect grouping information
|
||||||
paramsByGroup = {} # groupname -> [paramnames]
|
paramsByGroup = {} # groupname -> [paramnames]
|
||||||
@ -198,8 +205,8 @@ class ModuleCtrl(QWidget):
|
|||||||
params = self._node.getParameters(self._module)
|
params = self._node.getParameters(self._module)
|
||||||
for param in params:
|
for param in params:
|
||||||
props = self._node.getProperties(self._module, param)
|
props = self._node.getProperties(self._module, param)
|
||||||
group = props.get('group', None)
|
group = props.get('group', '')
|
||||||
if group is not None:
|
if group:
|
||||||
allGroups.add(group)
|
allGroups.add(group)
|
||||||
paramsByGroup.setdefault(group, []).append(param)
|
paramsByGroup.setdefault(group, []).append(param)
|
||||||
# enforce reading initial value if not already in cache
|
# enforce reading initial value if not already in cache
|
||||||
@ -210,6 +217,7 @@ class ModuleCtrl(QWidget):
|
|||||||
self._groupWidgets = groupWidgets = {}
|
self._groupWidgets = groupWidgets = {}
|
||||||
|
|
||||||
# create and insert widgets into our QGridLayout
|
# 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))):
|
for param in sorted(allGroups.union(set(params))):
|
||||||
labelstr = param + ':'
|
labelstr = param + ':'
|
||||||
if param in paramsByGroup:
|
if param in paramsByGroup:
|
||||||
@ -226,7 +234,7 @@ class ModuleCtrl(QWidget):
|
|||||||
'datatype', None)
|
'datatype', None)
|
||||||
# yes: create a widget for this as well
|
# yes: create a widget for this as well
|
||||||
labelstr, buttons = self._makeEntry(
|
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)
|
checkbox.setText(labelstr)
|
||||||
|
|
||||||
# add to Layout (yes: ignore the label!)
|
# add to Layout (yes: ignore the label!)
|
||||||
@ -249,7 +257,7 @@ class ModuleCtrl(QWidget):
|
|||||||
self._module, param_).get(
|
self._module, param_).get(
|
||||||
'datatype', None)
|
'datatype', None)
|
||||||
label, buttons = self._makeEntry(
|
label, buttons = self._makeEntry(
|
||||||
param_, initval, checkbox=checkbox, invert=False)
|
param_, initval, datatype=datatype, checkbox=checkbox, invert=False)
|
||||||
|
|
||||||
# add to Layout
|
# add to Layout
|
||||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
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
|
# param is a 'normal' param: create a widget if it has no group
|
||||||
# or is named after a group (otherwise its created above)
|
# or is named after a group (otherwise its created above)
|
||||||
props = self._node.getProperties(self._module, param)
|
props = self._node.getProperties(self._module, param)
|
||||||
if props.get('group', param) == param:
|
if (props.get('group', '') or param) == param:
|
||||||
datatype = self._node.getProperties(
|
datatype = self._node.getProperties(
|
||||||
self._module, param).get(
|
self._module, param).get(
|
||||||
'datatype', None)
|
'datatype', None)
|
||||||
|
@ -37,7 +37,7 @@ from secop.gui.qt import QWidget, QTextCursor, QFont, QFontMetrics, QLabel, \
|
|||||||
QMessageBox, pyqtSlot, toHtmlEscaped
|
QMessageBox, pyqtSlot, toHtmlEscaped
|
||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
from secop.protocol.errors import SECOPError
|
from secop.errors import SECoPError
|
||||||
from secop.datatypes import StringType, EnumType
|
from secop.datatypes import StringType, EnumType
|
||||||
|
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ class NodeCtrl(QWidget):
|
|||||||
self._addLogEntry(reply, newline=True, pretty=False)
|
self._addLogEntry(reply, newline=True, pretty=False)
|
||||||
else:
|
else:
|
||||||
self._addLogEntry(reply, newline=True, pretty=True)
|
self._addLogEntry(reply, newline=True, pretty=True)
|
||||||
except SECOPError as e:
|
except SECoPError as e:
|
||||||
self._addLogEntry(
|
self._addLogEntry(
|
||||||
'error %s %s' % (e.name, json.dumps(e.args)),
|
'error %s %s' % (e.name, json.dumps(e.args)),
|
||||||
newline=True,
|
newline=True,
|
||||||
@ -145,8 +145,8 @@ class NodeCtrl(QWidget):
|
|||||||
if 'interface_class' in modprops:
|
if 'interface_class' in modprops:
|
||||||
interfaces = modprops['interface_class']
|
interfaces = modprops['interface_class']
|
||||||
else:
|
else:
|
||||||
interfaces = modprops['interfaces']
|
interfaces = modprops.get('interfaces', '')
|
||||||
description = modprops['description']
|
description = modprops.get('description', '!!! missing description !!!')
|
||||||
|
|
||||||
# fallback: allow (now) invalid 'Driveable'
|
# fallback: allow (now) invalid 'Driveable'
|
||||||
unit = ''
|
unit = ''
|
||||||
|
@ -134,7 +134,7 @@ class GenericCmdWidget(ParameterWidget):
|
|||||||
loadUi(self, 'cmdbuttons.ui')
|
loadUi(self, 'cmdbuttons.ui')
|
||||||
|
|
||||||
self.cmdLineEdit.setText('')
|
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.cmdLineEdit.returnPressed.connect(
|
||||||
self.on_cmdPushButton_clicked)
|
self.on_cmdPushButton_clicked)
|
||||||
|
|
||||||
@ -164,7 +164,6 @@ def ParameterView(module,
|
|||||||
parent=None):
|
parent=None):
|
||||||
# depending on datatype returns an initialized widget fit for display and
|
# depending on datatype returns an initialized widget fit for display and
|
||||||
# interaction
|
# interaction
|
||||||
|
|
||||||
if datatype is not None:
|
if datatype is not None:
|
||||||
if datatype.IS_COMMAND:
|
if datatype.IS_COMMAND:
|
||||||
return GenericCmdWidget(
|
return GenericCmdWidget(
|
||||||
|
@ -24,25 +24,34 @@
|
|||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
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 import uic
|
||||||
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import Qt, QObject, pyqtSignal, pyqtSlot, QSize, QPointF, \
|
||||||
from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics
|
QRectF
|
||||||
from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, QPushButton, \
|
from PyQt5.QtGui import QFont, QTextCursor, QFontMetrics, QColor, QBrush, \
|
||||||
QSizePolicy, QMainWindow, QMessageBox, QInputDialog, QTreeWidgetItem, QApplication, \
|
QPainter, QPolygonF, QPen
|
||||||
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
|
from PyQt5.QtWidgets import QLabel, QWidget, QDialog, QLineEdit, QCheckBox, \
|
||||||
QGridLayout, QScrollArea, QFrame
|
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
|
from xml.sax.saxutils import escape as toHtmlEscaped
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4 import uic
|
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, \
|
from PyQt4.QtGui import QFont, QTextCursor, QFontMetrics, \
|
||||||
QLabel, QWidget, QDialog, QLineEdit, QCheckBox, QPushButton, \
|
QLabel, QWidget, QDialog, QLineEdit, QCheckBox, QPushButton, \
|
||||||
QSizePolicy, QMainWindow, QMessageBox, QInputDialog, QTreeWidgetItem, QApplication, \
|
QSizePolicy, QMainWindow, QMessageBox, QInputDialog, QTreeWidgetItem, QApplication, \
|
||||||
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
|
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QRadioButton, QVBoxLayout, QHBoxLayout, \
|
||||||
QGridLayout, QScrollArea, QFrame
|
QGridLayout, QScrollArea, QFrame, QColor, QBrush, QPainter, QPolygonF, QPen
|
||||||
|
|
||||||
def toHtmlEscaped(s):
|
def toHtmlEscaped(s):
|
||||||
return Qt.escape(s)
|
return Qt.escape(s)
|
||||||
|
@ -6,10 +6,16 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>730</width>
|
<width>464</width>
|
||||||
<height>33</height>
|
<height>33</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>33</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
@ -37,7 +43,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>256</width>
|
<width>128</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -57,7 +63,7 @@
|
|||||||
<widget class="QLineEdit" name="setLineEdit">
|
<widget class="QLineEdit" name="setLineEdit">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>256</width>
|
<width>128</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>238</width>
|
<width>100</width>
|
||||||
<height>121</height>
|
<height>100</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -224,7 +224,8 @@ class Readable(Module):
|
|||||||
WARN = 200,
|
WARN = 200,
|
||||||
UNSTABLE = 250,
|
UNSTABLE = 250,
|
||||||
ERROR = 400,
|
ERROR = 400,
|
||||||
UNKNOWN = 900,
|
DISABLED = 500,
|
||||||
|
UNKNOWN = 0,
|
||||||
)
|
)
|
||||||
parameters = {
|
parameters = {
|
||||||
'value': Parameter('current value of the Module', readonly=True,
|
'value': Parameter('current value of the Module', readonly=True,
|
||||||
@ -312,7 +313,7 @@ class Drivable(Writable):
|
|||||||
commands = {
|
commands = {
|
||||||
'stop': Command(
|
'stop': Command(
|
||||||
'cease driving, go to IDLE state',
|
'cease driving, go to IDLE state',
|
||||||
arguments=[],
|
argument=None,
|
||||||
result=None
|
result=None
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -360,7 +361,7 @@ class Communicator(Module):
|
|||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
"communicate": Command("provides the simplest mean to communication",
|
"communicate": Command("provides the simplest mean to communication",
|
||||||
arguments=[StringType()],
|
argument=StringType(),
|
||||||
result=StringType()
|
result=StringType()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -155,15 +155,14 @@ class Override(CountedObj):
|
|||||||
class Command(CountedObj):
|
class Command(CountedObj):
|
||||||
"""storage for Commands settings (description + call signature...)
|
"""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__()
|
super(Command, self).__init__()
|
||||||
# descriptive text for humans
|
# descriptive text for humans
|
||||||
self.description = description
|
self.description = description
|
||||||
# list of datatypes for arguments
|
# datatypes for argument/result
|
||||||
self.arguments = arguments or []
|
self.argument = argument
|
||||||
self.datatype = CommandType(arguments, result)
|
|
||||||
self.arguments = arguments
|
|
||||||
self.result = result
|
self.result = result
|
||||||
|
self.datatype = CommandType(argument, result)
|
||||||
# whether implementation is optional
|
# whether implementation is optional
|
||||||
self.optional = optional
|
self.optional = optional
|
||||||
self.export = export
|
self.export = export
|
||||||
|
@ -45,9 +45,9 @@ from secop.protocol.messages import EVENTREPLY, IDENTREQUEST, IDENTREPLY, \
|
|||||||
ENABLEEVENTSREPLY, DESCRIPTIONREPLY, WRITEREPLY, COMMANDREPLY, \
|
ENABLEEVENTSREPLY, DESCRIPTIONREPLY, WRITEREPLY, COMMANDREPLY, \
|
||||||
DISABLEEVENTSREPLY, HEARTBEATREPLY
|
DISABLEEVENTSREPLY, HEARTBEATREPLY
|
||||||
|
|
||||||
from secop.protocol.errors import InternalError, NoSuchModuleError, \
|
from secop.errors import NoSuchModuleError, NoSuchCommandError, \
|
||||||
NoSuchCommandError, NoSuchParameterError, BadValueError, ReadonlyError, \
|
NoSuchParameterError, BadValueError, ReadOnlyError, \
|
||||||
ProtocolError
|
ProtocolError, SECoPServerError as InternalError
|
||||||
|
|
||||||
from secop.params import Parameter
|
from secop.params import Parameter
|
||||||
|
|
||||||
@ -63,9 +63,9 @@ class Dispatcher(object):
|
|||||||
def __init__(self, name, logger, options, srv):
|
def __init__(self, name, logger, options, srv):
|
||||||
# to avoid errors, we want to eat all options here
|
# to avoid errors, we want to eat all options here
|
||||||
self.equipment_id = name
|
self.equipment_id = name
|
||||||
self.nodeopts = {}
|
self.nodeprops = {}
|
||||||
for k in list(options):
|
for k in list(options):
|
||||||
self.nodeopts[k] = options.pop(k)
|
self.nodeprops[k] = options.pop(k)
|
||||||
|
|
||||||
self.log = logger
|
self.log = logger
|
||||||
# map ALL modulename -> moduleobj
|
# map ALL modulename -> moduleobj
|
||||||
@ -169,7 +169,7 @@ class Dispatcher(object):
|
|||||||
res = []
|
res = []
|
||||||
for aname, aobj in self.get_module(modulename).accessibles.items():
|
for aname, aobj in self.get_module(modulename).accessibles.items():
|
||||||
if aobj.export:
|
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' %
|
self.log.debug(u'list accessibles for module %s -> %r' %
|
||||||
(modulename, res))
|
(modulename, res))
|
||||||
return res
|
return res
|
||||||
@ -183,21 +183,22 @@ class Dispatcher(object):
|
|||||||
result = {u'modules': []}
|
result = {u'modules': []}
|
||||||
for modulename in self._export:
|
for modulename in self._export:
|
||||||
module = self.get_module(modulename)
|
module = self.get_module(modulename)
|
||||||
|
if not module.properties.get('export', False):
|
||||||
|
continue
|
||||||
# some of these need rework !
|
# some of these need rework !
|
||||||
mod_desc = {u'accessibles': self.export_accessibles(modulename)}
|
mod_desc = {u'accessibles': self.export_accessibles(modulename)}
|
||||||
for propname, prop in list(module.properties.items()):
|
for propname, prop in list(module.properties.items()):
|
||||||
|
if propname == 'export':
|
||||||
|
continue
|
||||||
mod_desc[propname] = prop
|
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'equipment_id'] = self.equipment_id
|
||||||
result[u'firmware'] = u'FRAPPY - The Python Framework for SECoP'
|
result[u'firmware'] = u'FRAPPY - The Python Framework for SECoP'
|
||||||
result[u'version'] = u'2018.09'
|
result[u'version'] = u'2018.09'
|
||||||
result.update(self.nodeopts)
|
result.update(self.nodeprops)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _execute_command(self, modulename, command, arguments=None):
|
def _execute_command(self, modulename, command, argument=None):
|
||||||
if arguments is None:
|
|
||||||
arguments = []
|
|
||||||
|
|
||||||
moduleobj = self.get_module(modulename)
|
moduleobj = self.get_module(modulename)
|
||||||
if moduleobj is None:
|
if moduleobj is None:
|
||||||
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
|
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
|
||||||
@ -205,14 +206,16 @@ class Dispatcher(object):
|
|||||||
cmdspec = moduleobj.accessibles.get(command, None)
|
cmdspec = moduleobj.accessibles.get(command, None)
|
||||||
if cmdspec is None:
|
if cmdspec is None:
|
||||||
raise NoSuchCommandError('Module has no such command!')
|
raise NoSuchCommandError('Module has no such command!')
|
||||||
num_args_required = len(cmdspec.datatype.argtypes)
|
if argument is None and cmdspec.datatype.argtype is not None:
|
||||||
if num_args_required != len(arguments):
|
raise BadValueError(u'Command needs an argument!')
|
||||||
raise BadValueError(u'Wrong number of arguments (need %d, got %d)!' % (num_args_required, len(arguments)))
|
|
||||||
|
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
|
# now call func and wrap result as value
|
||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
func = getattr(moduleobj, u'do_' + command)
|
func = getattr(moduleobj, u'do_' + command)
|
||||||
res = func(*arguments)
|
res = func(argument) if argument else func()
|
||||||
# XXX: pipe through cmdspec.datatype.result ?
|
# XXX: pipe through cmdspec.datatype.result ?
|
||||||
return res, dict(t=currenttime())
|
return res, dict(t=currenttime())
|
||||||
|
|
||||||
@ -225,7 +228,7 @@ class Dispatcher(object):
|
|||||||
if pobj is None or not isinstance(pobj, Parameter):
|
if pobj is None or not isinstance(pobj, Parameter):
|
||||||
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
|
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
|
||||||
if pobj.readonly:
|
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)
|
writefunc = getattr(moduleobj, u'write_%s' % pname, None)
|
||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
@ -272,7 +275,7 @@ class Dispatcher(object):
|
|||||||
action, specifier, data = msg
|
action, specifier, data = msg
|
||||||
# special case for *IDN?
|
# special case for *IDN?
|
||||||
if action == IDENTREQUEST:
|
if action == IDENTREQUEST:
|
||||||
action, specifier, data = 'ident', None, None
|
action, specifier, data = '_ident', None, None
|
||||||
|
|
||||||
self.log.debug(u'Looking for handle_%s' % action)
|
self.log.debug(u'Looking for handle_%s' % action)
|
||||||
handler = getattr(self, u'handle_%s' % action, None)
|
handler = getattr(self, u'handle_%s' % action, None)
|
||||||
@ -286,7 +289,7 @@ class Dispatcher(object):
|
|||||||
def handle_help(self, conn, specifier, data):
|
def handle_help(self, conn, specifier, data):
|
||||||
self.log.error('should have been handled in the interface!')
|
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)
|
return (IDENTREPLY, None, None)
|
||||||
|
|
||||||
def handle_describe(self, conn, specifier, data):
|
def handle_describe(self, conn, specifier, data):
|
||||||
|
@ -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,
|
|
||||||
)
|
|
@ -21,6 +21,7 @@
|
|||||||
"""provides tcp interface to the SECoP Server"""
|
"""provides tcp interface to the SECoP Server"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
import socket
|
import socket
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import SocketServer as socketserver # py2
|
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.protocol.messages import HELPREQUEST, HELPREPLY, HelpMessage
|
||||||
from secop.errors import SECoPError
|
from secop.errors import SECoPError
|
||||||
from secop.protocol.interface import encode_msg_frame, get_msg, decode_msg
|
from secop.protocol.interface import encode_msg_frame, get_msg, decode_msg
|
||||||
@ -136,7 +137,7 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
print('--------------------')
|
print('--------------------')
|
||||||
print(formatException())
|
print(formatException())
|
||||||
print('--------------------')
|
print('--------------------')
|
||||||
print(formatExtendedStack())
|
print(formatExtendedTraceback(sys.exc_info()))
|
||||||
print('====================')
|
print('====================')
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
|
@ -26,7 +26,7 @@ from __future__ import print_function
|
|||||||
|
|
||||||
IDENTREQUEST = u'*IDN?' # literal
|
IDENTREQUEST = u'*IDN?' # literal
|
||||||
# literal! first part is fixed!
|
# 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
|
DESCRIPTIONREQUEST = u'describe' # literal
|
||||||
DESCRIPTIONREPLY = u'describing' # +<id> +json
|
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 !
|
# +module:command +json args (if needed) # send after the command finished !
|
||||||
COMMANDREPLY = u'done'
|
COMMANDREPLY = u'done'
|
||||||
|
|
||||||
# +module[:parameter] +json_value -> NO direct reply, calls POLL internally
|
# +module[:parameter] +json_value
|
||||||
WRITEREQUEST = u'change'
|
WRITEREQUEST = u'change'
|
||||||
# +module[:parameter] +json_value # send with the read back value
|
# +module[:parameter] +json_value # send with the read back value
|
||||||
WRITEREPLY = u'changed'
|
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!
|
# +module[:parameter] -> NO direct reply, calls POLL internally!
|
||||||
POLLREQUEST = u'read'
|
POLLREQUEST = u'read'
|
||||||
EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
|
EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
|
||||||
@ -66,6 +71,7 @@ REQUEST2REPLY = {
|
|||||||
DISABLEEVENTSREQUEST: DISABLEEVENTSREPLY,
|
DISABLEEVENTSREQUEST: DISABLEEVENTSREPLY,
|
||||||
COMMANDREQUEST: COMMANDREPLY,
|
COMMANDREQUEST: COMMANDREPLY,
|
||||||
WRITEREQUEST: WRITEREPLY,
|
WRITEREQUEST: WRITEREPLY,
|
||||||
|
BUFFERREQUEST: BUFFERREPLY,
|
||||||
POLLREQUEST: EVENTREPLY,
|
POLLREQUEST: EVENTREPLY,
|
||||||
HEARTBEATREQUEST: HEARTBEATREPLY,
|
HEARTBEATREQUEST: HEARTBEATREPLY,
|
||||||
HELPREQUEST: HELPREPLY,
|
HELPREQUEST: HELPREPLY,
|
||||||
|
@ -125,7 +125,7 @@ class Cryostat(CryoBase):
|
|||||||
commands = dict(
|
commands = dict(
|
||||||
stop=Command(
|
stop=Command(
|
||||||
"Stop ramping the setpoint\n\nby setting the current setpoint as new target",
|
"Stop ramping the setpoint\n\nby setting the current setpoint as new target",
|
||||||
[],
|
None,
|
||||||
None),
|
None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -314,6 +314,6 @@ class DatatypesTest(Readable):
|
|||||||
|
|
||||||
class ArrayTest(Readable):
|
class ArrayTest(Readable):
|
||||||
parameters = {
|
parameters = {
|
||||||
"x": Parameter('value', datatype=ArrayOf(FloatRange(), 100000, 100000),
|
"x": Parameter('value', datatype=ArrayOf(FloatRange(), 0, 100000),
|
||||||
default = 100000 * [0]),
|
default = 100000 * [0]),
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ from secop.lib import lazy_property
|
|||||||
#from secop.parse import Parser
|
#from secop.parse import Parser
|
||||||
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
|
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
|
||||||
ArrayOf, EnumType
|
ArrayOf, EnumType
|
||||||
from secop.errors import ConfigError, ProgrammingError, CommunicationError, \
|
from secop.errors import ConfigError, ProgrammingError, CommunicationFailedError, \
|
||||||
HardwareError
|
HardwareError
|
||||||
from secop.modules import Parameter, Command, Override, Module, Readable, Drivable
|
from secop.modules import Parameter, Command, Override, Module, Readable, Drivable
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
EXC_MAPPING = {
|
EXC_MAPPING = {
|
||||||
PyTango.CommunicationFailed: CommunicationError,
|
PyTango.CommunicationFailedError: CommunicationFailedError,
|
||||||
PyTango.WrongNameSyntax: ConfigError,
|
PyTango.WrongNameSyntax: ConfigError,
|
||||||
PyTango.DevFailed: HardwareError,
|
PyTango.DevFailed: HardwareError,
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ EXC_MAPPING = {
|
|||||||
REASON_MAPPING = {
|
REASON_MAPPING = {
|
||||||
'Entangle_ConfigurationError': ConfigError,
|
'Entangle_ConfigurationError': ConfigError,
|
||||||
'Entangle_WrongAPICall': ProgrammingError,
|
'Entangle_WrongAPICall': ProgrammingError,
|
||||||
'Entangle_CommunicationFailure': CommunicationError,
|
'Entangle_CommunicationFailure': CommunicationFailedError,
|
||||||
'Entangle_InvalidValue': ValueError,
|
'Entangle_InvalidValue': ValueError,
|
||||||
'Entangle_ProgrammingError': ProgrammingError,
|
'Entangle_ProgrammingError': ProgrammingError,
|
||||||
'Entangle_HardwareFailure': HardwareError,
|
'Entangle_HardwareFailure': HardwareError,
|
||||||
@ -174,7 +174,7 @@ class PyTangoDevice(Module):
|
|||||||
}
|
}
|
||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
'reset': Command('Tango reset command', arguments=[], result=None),
|
'reset': Command('Tango reset command', argument=None, result=None),
|
||||||
}
|
}
|
||||||
|
|
||||||
tango_status_mapping = {
|
tango_status_mapping = {
|
||||||
@ -255,7 +255,7 @@ class PyTangoDevice(Module):
|
|||||||
try:
|
try:
|
||||||
device.State
|
device.State
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise CommunicationError(
|
raise CommunicationFailedError(
|
||||||
self, 'connection to Tango server failed, '
|
self, 'connection to Tango server failed, '
|
||||||
'is the server running?')
|
'is the server running?')
|
||||||
return self._applyGuardsToPyTangoDevice(device)
|
return self._applyGuardsToPyTangoDevice(device)
|
||||||
@ -351,11 +351,11 @@ class PyTangoDevice(Module):
|
|||||||
"""Process the exception raised either by communication or _com_return.
|
"""Process the exception raised either by communication or _com_return.
|
||||||
|
|
||||||
Should raise a NICOS exception. Default is to raise
|
Should raise a NICOS exception. Default is to raise
|
||||||
CommunicationError.
|
CommunicationFailedError.
|
||||||
"""
|
"""
|
||||||
reason = self._tango_exc_reason(err)
|
reason = self._tango_exc_reason(err)
|
||||||
exclass = REASON_MAPPING.get(
|
exclass = REASON_MAPPING.get(
|
||||||
reason, EXC_MAPPING.get(type(err), CommunicationError))
|
reason, EXC_MAPPING.get(type(err), CommunicationFailedError))
|
||||||
fulldesc = self._tango_exc_desc(err)
|
fulldesc = self._tango_exc_desc(err)
|
||||||
self.log.debug('PyTango error: %s', fulldesc)
|
self.log.debug('PyTango error: %s', fulldesc)
|
||||||
raise exclass(self, fulldesc)
|
raise exclass(self, fulldesc)
|
||||||
@ -405,7 +405,7 @@ class Sensor(AnalogInput):
|
|||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
'setposition': Command('Set the position to the given value.',
|
'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 = {
|
commands = {
|
||||||
'stop': Command('Stops current movement.', arguments=[], result=None),
|
'stop': Command('Stops current movement.', argument=None, result=None),
|
||||||
}
|
}
|
||||||
_history = ()
|
_history = ()
|
||||||
_timeout = None
|
_timeout = None
|
||||||
@ -615,7 +615,7 @@ class Actuator(AnalogOutput):
|
|||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
'setposition': Command('Set the position to the given value.',
|
'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 = {
|
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):
|
def read_refpos(self, maxage=0):
|
||||||
@ -952,25 +952,25 @@ class StringIO(PyTangoDevice, Module):
|
|||||||
|
|
||||||
commands = {
|
commands = {
|
||||||
'communicate': Command('Send a string and return the reply',
|
'communicate': Command('Send a string and return the reply',
|
||||||
arguments=[StringType()],
|
argument=StringType(),
|
||||||
result=StringType()),
|
result=StringType()),
|
||||||
'flush': Command('Flush output buffer',
|
'flush': Command('Flush output buffer',
|
||||||
arguments=[], result=None),
|
argument=None, result=None),
|
||||||
'read': Command('read some characters from input buffer',
|
'read': Command('read some characters from input buffer',
|
||||||
arguments=[IntRange()], result=StringType()),
|
argument=IntRange(0), result=StringType()),
|
||||||
'write': Command('write some chars to output',
|
'write': Command('write some chars to output',
|
||||||
arguments=[StringType()], result=None),
|
argument=StringType(), result=None),
|
||||||
'readLine': Command('Read sol - a whole line - eol',
|
'readLine': Command('Read sol - a whole line - eol',
|
||||||
arguments=[], result=StringType()),
|
argument=None, result=StringType()),
|
||||||
'writeLine': Command('write sol + a whole line + eol',
|
'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',
|
'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',
|
'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',
|
'multiCommunicate': Command('perform a sequence of communications',
|
||||||
arguments=[ArrayOf(
|
argument=ArrayOf(
|
||||||
TupleOf(StringType(), IntRange()), 100)],
|
TupleOf(StringType(), IntRange()), 100),
|
||||||
result=ArrayOf(StringType(), 100)),
|
result=ArrayOf(StringType(), 100)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,30 +57,28 @@ def clientobj(request):
|
|||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
def test_describing_data_decode(clientobj):
|
def test_describing_data_decode(clientobj):
|
||||||
assert OrderedDict(
|
assert {'modules': OrderedDict(), 'properties': {}
|
||||||
[('a', 1)]) == clientobj._decode_list_to_ordereddict(['a', 1])
|
|
||||||
assert {'modules': {}, 'properties': {}
|
|
||||||
} == clientobj._decode_substruct(['modules'], {})
|
} == clientobj._decode_substruct(['modules'], {})
|
||||||
describing_data = {'equipment_id': 'eid',
|
describing_data = {'equipment_id': 'eid',
|
||||||
'modules': ['LN2', {'commands': [],
|
'modules': [['LN2', {'commands': [],
|
||||||
'interfaces': ['Readable', 'Module'],
|
'interfaces': ['Readable', 'Module'],
|
||||||
'parameters': ['value', {'datatype': ['double'],
|
'parameters': [['value', {'datatype': ['double'],
|
||||||
'description': 'current value',
|
'description': 'current value',
|
||||||
'readonly': True,
|
'readonly': True,
|
||||||
}
|
}
|
||||||
]
|
]]
|
||||||
}
|
}
|
||||||
]
|
]]
|
||||||
}
|
}
|
||||||
decoded_data = {'modules': {'LN2': {'commands': {},
|
decoded_data = {'modules': OrderedDict([('LN2', {'commands': OrderedDict(),
|
||||||
'parameters': {'value': {'datatype': ['double'],
|
'parameters': OrderedDict([('value', {'datatype': ['double'],
|
||||||
'description': 'current value',
|
'description': 'current value',
|
||||||
'readonly': True,
|
'readonly': True,
|
||||||
}
|
}
|
||||||
},
|
)]),
|
||||||
'properties': {'interfaces': ['Readable', 'Module']}
|
'properties': {'interfaces': ['Readable', 'Module']}
|
||||||
}
|
}
|
||||||
},
|
)]),
|
||||||
'properties': {'equipment_id': 'eid',
|
'properties': {'equipment_id': 'eid',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ def test_FloatRange():
|
|||||||
FloatRange('x', 'Y')
|
FloatRange('x', 'Y')
|
||||||
|
|
||||||
dt = FloatRange()
|
dt = FloatRange()
|
||||||
assert dt.as_json == ['double']
|
assert dt.as_json == ['double', None, None]
|
||||||
|
|
||||||
|
|
||||||
def test_IntRange():
|
def test_IntRange():
|
||||||
@ -86,7 +86,8 @@ def test_IntRange():
|
|||||||
IntRange('xc', 'Yx')
|
IntRange('xc', 'Yx')
|
||||||
|
|
||||||
dt = IntRange()
|
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():
|
def test_EnumType():
|
||||||
@ -128,13 +129,13 @@ def test_EnumType():
|
|||||||
|
|
||||||
def test_BLOBType():
|
def test_BLOBType():
|
||||||
# test constructor catching illegal arguments
|
# test constructor catching illegal arguments
|
||||||
with pytest.raises(ValueError):
|
dt = BLOBType()
|
||||||
dt = BLOBType()
|
assert dt.as_json == ['blob', 255, 255]
|
||||||
dt = BLOBType(10)
|
dt = BLOBType(10)
|
||||||
assert dt.as_json == ['blob', 10]
|
assert dt.as_json == ['blob', 10, 10]
|
||||||
|
|
||||||
dt = BLOBType(3, 10)
|
dt = BLOBType(3, 10)
|
||||||
assert dt.as_json == ['blob', 10, 3]
|
assert dt.as_json == ['blob', 3, 10]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dt.validate(9)
|
dt.validate(9)
|
||||||
@ -156,10 +157,10 @@ def test_StringType():
|
|||||||
# test constructor catching illegal arguments
|
# test constructor catching illegal arguments
|
||||||
dt = StringType()
|
dt = StringType()
|
||||||
dt = StringType(12)
|
dt = StringType(12)
|
||||||
assert dt.as_json == ['string', 12]
|
assert dt.as_json == ['string', 0, 12]
|
||||||
|
|
||||||
dt = StringType(4, 11)
|
dt = StringType(4, 11)
|
||||||
assert dt.as_json == ['string', 11, 4]
|
assert dt.as_json == ['string', 4, 11]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dt.validate(9)
|
dt.validate(9)
|
||||||
@ -208,12 +209,12 @@ def test_ArrayOf():
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ArrayOf(int)
|
ArrayOf(int)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ArrayOf(IntRange(-10,10))
|
ArrayOf(-3, IntRange(-10,10))
|
||||||
dt = ArrayOf(IntRange(-10, 10), 5)
|
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)
|
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):
|
with pytest.raises(ValueError):
|
||||||
dt.validate(9)
|
dt.validate(9)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -231,7 +232,7 @@ def test_TupleOf():
|
|||||||
TupleOf(2)
|
TupleOf(2)
|
||||||
|
|
||||||
dt = TupleOf(IntRange(-10, 10), BoolType())
|
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):
|
with pytest.raises(ValueError):
|
||||||
dt.validate(9)
|
dt.validate(9)
|
||||||
@ -252,8 +253,8 @@ def test_StructOf():
|
|||||||
StructOf(IntRange=1)
|
StructOf(IntRange=1)
|
||||||
|
|
||||||
dt = StructOf(a_string=StringType(55), an_int=IntRange(0, 999))
|
dt = StructOf(a_string=StringType(55), an_int=IntRange(0, 999))
|
||||||
assert dt.as_json == ['struct', {'a_string': ['string', 55],
|
assert dt.as_json == [u'struct', {u'a_string': [u'string', 0, 55],
|
||||||
'an_int': ['int', 0, 999],
|
u'an_int': [u'int', 0, 999],
|
||||||
}]
|
}]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -285,7 +286,6 @@ def test_get_datatype():
|
|||||||
|
|
||||||
assert isinstance(get_datatype(['int']), IntRange)
|
assert isinstance(get_datatype(['int']), IntRange)
|
||||||
assert isinstance(get_datatype(['int', -10]), IntRange)
|
assert isinstance(get_datatype(['int', -10]), IntRange)
|
||||||
assert isinstance(get_datatype(['int', None, 10]), IntRange)
|
|
||||||
assert isinstance(get_datatype(['int', -10, 10]), IntRange)
|
assert isinstance(get_datatype(['int', -10, 10]), IntRange)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -320,8 +320,7 @@ def test_get_datatype():
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['blob', 10, -10, 1])
|
get_datatype(['blob', 10, -10, 1])
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
get_datatype(['string'])
|
||||||
get_datatype(['string'])
|
|
||||||
assert isinstance(get_datatype(['string', 1]), StringType)
|
assert isinstance(get_datatype(['string', 1]), StringType)
|
||||||
assert isinstance(get_datatype(['string', 10, 1]), StringType)
|
assert isinstance(get_datatype(['string', 10, 1]), StringType)
|
||||||
|
|
||||||
@ -336,15 +335,15 @@ def test_get_datatype():
|
|||||||
get_datatype(['array', 1])
|
get_datatype(['array', 1])
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['array', [1], 2, 3])
|
get_datatype(['array', [1], 2, 3])
|
||||||
assert isinstance(get_datatype(['array', ['blob', 1], 1]), ArrayOf)
|
assert isinstance(get_datatype(['array', 1, 1, ['blob', 1]]), ArrayOf)
|
||||||
assert isinstance(get_datatype(['array', ['blob', 1], 1]).subtype, BLOBType)
|
assert isinstance(get_datatype(['array', 1, 1, ['blob', 1]]).subtype, BLOBType)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['array', ['blob', 1], -10])
|
get_datatype(['array', ['blob', 1], -10])
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['array', ['blob', 1], 10, -10])
|
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):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['tuple'])
|
get_datatype(['tuple'])
|
||||||
@ -352,16 +351,15 @@ def test_get_datatype():
|
|||||||
get_datatype(['tuple', 1])
|
get_datatype(['tuple', 1])
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['tuple', [1], 2, 3])
|
get_datatype(['tuple', [1], 2, 3])
|
||||||
assert isinstance(get_datatype(['tuple', [['blob', 1]]]), TupleOf)
|
assert isinstance(get_datatype(['tuple', ['blob', 1]]), TupleOf)
|
||||||
assert isinstance(get_datatype(
|
assert isinstance(get_datatype(['tuple', ['blob', 1]]).subtypes[0], BLOBType)
|
||||||
['tuple', [['blob', 1]]]).subtypes[0], BLOBType)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['tuple', [['blob', 1]], -10])
|
get_datatype(['tuple', ['blob', 1], -10])
|
||||||
with pytest.raises(ValueError):
|
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):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['struct'])
|
get_datatype(['struct'])
|
||||||
@ -370,13 +368,12 @@ def test_get_datatype():
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['struct', [1], 2, 3])
|
get_datatype(['struct', [1], 2, 3])
|
||||||
assert isinstance(get_datatype(['struct', {'blob': ['blob', 1]}]), StructOf)
|
assert isinstance(get_datatype(['struct', {'blob': ['blob', 1]}]), StructOf)
|
||||||
assert isinstance(get_datatype(
|
assert isinstance(get_datatype(['struct', {'blob': ['blob', 1]}]).named_subtypes['blob'], BLOBType)
|
||||||
['struct', {'blob': ['blob', 1]}]).named_subtypes['blob'], BLOBType)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['struct', [['blob', 1]], -10])
|
get_datatype(['struct', ['blob', 1], -10])
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['struct', [['blob', 1]], 10, -10])
|
get_datatype(['struct', ['blob', 1], 10, -10])
|
||||||
|
|
||||||
assert isinstance(get_datatype(
|
assert isinstance(get_datatype(
|
||||||
['struct', {'blob': ['blob', 1], 'int':['int']}]), StructOf)
|
['struct', {'blob': ['blob', 1], 'int':['int']}]), StructOf)
|
||||||
|
@ -69,13 +69,13 @@ def test_ModuleMeta():
|
|||||||
'param2': Parameter('param2', datatype=BoolType(), default=True),
|
'param2': Parameter('param2', datatype=BoolType(), default=True),
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"cmd": Command('stuff',[BoolType()], BoolType())
|
"cmd": Command('stuff',BoolType(), BoolType())
|
||||||
},
|
},
|
||||||
"accessibles": {
|
"accessibles": {
|
||||||
'a1': Parameter('a1', datatype=BoolType(), default=False),
|
'a1': Parameter('a1', datatype=BoolType(), default=False),
|
||||||
'a2': Parameter('a2', datatype=BoolType(), default=True),
|
'a2': Parameter('a2', datatype=BoolType(), default=True),
|
||||||
'value':Override(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_cmd": lambda self, arg: not arg,
|
||||||
"do_cmd2": lambda self, arg: not arg,
|
"do_cmd2": lambda self, arg: not arg,
|
||||||
|
@ -35,10 +35,10 @@ from secop.params import Command, Parameter, Override
|
|||||||
|
|
||||||
|
|
||||||
def test_Command():
|
def test_Command():
|
||||||
cmd = Command('do_something', [], None)
|
cmd = Command('do_something')
|
||||||
assert cmd.description
|
assert cmd.description
|
||||||
assert cmd.ctr
|
assert cmd.ctr
|
||||||
assert cmd.arguments == []
|
assert cmd.argument is None
|
||||||
assert cmd.result is None
|
assert cmd.result is None
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user