implement SECoP proxy modules
A proxy module is a module with a known structure, but accessed over a SECoP connection. For the configuration, a Frappy module class has to be given. The proxy class is created from this, but does not inherit from it. However, the class of the returned object will be subclass of the SECoP base classes (Readable, Drivable etc.). A possible extension might be, that instead of the Frappy class, the JSON module description can be given, as a separate file or directly in the config file. Or we might offer a tool to convert the JSON description to a python class. Change-Id: I9212d9f3fe82ec56dfc08611d0e1efc0b0112271 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22386 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
@ -26,6 +26,7 @@
|
||||
|
||||
|
||||
import sys
|
||||
import math
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from secop.errors import ProgrammingError, ProtocolError, BadValueError, ConfigError
|
||||
@ -36,8 +37,8 @@ from secop.properties import HasProperties, Property
|
||||
|
||||
# Only export these classes for 'from secop.datatypes import *'
|
||||
__all__ = [
|
||||
'DataType',
|
||||
'FloatRange', 'IntRange',
|
||||
'DataType', 'get_datatype',
|
||||
'FloatRange', 'IntRange', 'ScaledInteger',
|
||||
'BoolType', 'EnumType',
|
||||
'BLOBType', 'StringType',
|
||||
'TupleOf', 'ArrayOf', 'StructOf',
|
||||
@ -51,6 +52,7 @@ UNLIMITED = 1 << 64 # internal limit for integers, is probably high enough for
|
||||
|
||||
Parser = Parser()
|
||||
|
||||
|
||||
# base class for all DataTypes
|
||||
class DataType(HasProperties):
|
||||
IS_COMMAND = False
|
||||
@ -116,6 +118,14 @@ class DataType(HasProperties):
|
||||
# looks like the simplest way to make a deep copy
|
||||
return get_datatype(self.export_datatype())
|
||||
|
||||
def compatible(self, other):
|
||||
"""check other for compatibility
|
||||
|
||||
raise an exception if <other> is not compatible, i.e. there
|
||||
exists a value which is valid for ourselfs, but not for <other>
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Stub(DataType):
|
||||
"""incomplete datatype, to be replaced with a proper one later during module load
|
||||
@ -182,6 +192,9 @@ class FloatRange(DataType):
|
||||
value = float(value)
|
||||
except Exception:
|
||||
raise BadValueError('Can not __call__ %r to float' % value)
|
||||
if math.isinf(value):
|
||||
raise BadValueError('FloatRange does not accept infinity')
|
||||
|
||||
prec = max(abs(value * self.relative_resolution), self.absolute_resolution)
|
||||
if self.min - prec <= value <= self.max + prec:
|
||||
return min(max(value, self.min), self.max)
|
||||
@ -215,6 +228,12 @@ class FloatRange(DataType):
|
||||
return ' '.join([self.fmtstr % value, unit])
|
||||
return self.fmtstr % value
|
||||
|
||||
def compatible(self, other):
|
||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
# avoid infinity
|
||||
other(max(sys.float_info.min, self.min))
|
||||
other(min(sys.float_info.max, self.max))
|
||||
|
||||
|
||||
class IntRange(DataType):
|
||||
@ -266,6 +285,15 @@ class IntRange(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return '%d' % value
|
||||
|
||||
def compatible(self, other):
|
||||
if isinstance(other, IntRange):
|
||||
other(self.min)
|
||||
other(self.max)
|
||||
return
|
||||
# this will accept some EnumType, BoolType
|
||||
for i in range(self.min, self.max + 1):
|
||||
other(i)
|
||||
|
||||
|
||||
class ScaledInteger(DataType):
|
||||
"""Scaled integer int type
|
||||
@ -365,6 +393,12 @@ class ScaledInteger(DataType):
|
||||
return ' '.join([self.fmtstr % value, unit])
|
||||
return self.fmtstr % value
|
||||
|
||||
def compatible(self, other):
|
||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
other(self.min)
|
||||
other(self.max)
|
||||
|
||||
|
||||
class EnumType(DataType):
|
||||
|
||||
@ -408,6 +442,10 @@ class EnumType(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return '%s<%s>' % (self._enum[value].name, self._enum[value].value)
|
||||
|
||||
def compatible(self, other):
|
||||
for m in self._enum.members:
|
||||
other(m)
|
||||
|
||||
|
||||
class BLOBType(DataType):
|
||||
properties = {
|
||||
@ -438,7 +476,7 @@ class BLOBType(DataType):
|
||||
def __call__(self, value):
|
||||
"""return the validated (internal) value or raise"""
|
||||
if not isinstance(value, bytes):
|
||||
raise BadValueError('%r has the wrong type!' % value)
|
||||
raise BadValueError('%s has the wrong type!' % repr(value))
|
||||
size = len(value)
|
||||
if size < self.minbytes:
|
||||
raise BadValueError(
|
||||
@ -464,6 +502,13 @@ class BLOBType(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return repr(value)
|
||||
|
||||
def compatible(self, other):
|
||||
try:
|
||||
if self.minbytes < other.minbytes or self.maxbytes > other.maxbytes:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
class StringType(DataType):
|
||||
properties = {
|
||||
@ -494,7 +539,7 @@ class StringType(DataType):
|
||||
def __call__(self, value):
|
||||
"""return the validated (internal) value or raise"""
|
||||
if not isinstance(value, str):
|
||||
raise BadValueError('%r has the wrong type!' % value)
|
||||
raise BadValueError('%s has the wrong type!' % repr(value))
|
||||
if not self.isUTF8:
|
||||
try:
|
||||
value.encode('ascii')
|
||||
@ -527,6 +572,14 @@ class StringType(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return repr(value)
|
||||
|
||||
def compatible(self, other):
|
||||
try:
|
||||
if self.minchars < other.minchars or self.maxchars > other.maxchars or \
|
||||
self.isUTF8 > other.isUTF8:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
# TextType is a special StringType intended for longer texts (i.e. embedding \n),
|
||||
# whereas StringType is supposed to not contain '\n'
|
||||
@ -578,6 +631,11 @@ class BoolType(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return repr(bool(value))
|
||||
|
||||
def compatible(self, other):
|
||||
other(False)
|
||||
other(True)
|
||||
|
||||
|
||||
Stub.fix_datatypes()
|
||||
|
||||
#
|
||||
@ -673,6 +731,14 @@ class ArrayOf(DataType):
|
||||
return ' '.join([res, unit])
|
||||
return res
|
||||
|
||||
def compatible(self, other):
|
||||
try:
|
||||
if self.minlen < other.minlen or self.maxlen > other.maxlen:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
self.members.compatible(other.members)
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
class TupleOf(DataType):
|
||||
|
||||
@ -729,6 +795,15 @@ class TupleOf(DataType):
|
||||
return '(%s)' % (', '.join([sub.format_value(elem)
|
||||
for sub, elem in zip(self.members, value)]))
|
||||
|
||||
def compatible(self, other):
|
||||
if not isinstance(other, TupleOf):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
if len(self.members) != len(other.members) :
|
||||
raise BadValueError('incompatible datatypes')
|
||||
for a, b in zip(self.members, other.members):
|
||||
a.compatible(b)
|
||||
|
||||
|
||||
|
||||
class StructOf(DataType):
|
||||
|
||||
@ -763,7 +838,7 @@ class StructOf(DataType):
|
||||
return res
|
||||
|
||||
def __repr__(self):
|
||||
opt = self.optional if self.optional else ''
|
||||
opt = ', optional=%r' % self.optional if self.optional else ''
|
||||
return 'StructOf(%s%s)' % (', '.join(
|
||||
['%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]), opt)
|
||||
|
||||
@ -808,6 +883,17 @@ class StructOf(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return '{%s}' % (', '.join(['%s=%s' % (k, self.members[k].format_value(v)) for k, v in sorted(value.items())]))
|
||||
|
||||
def compatible(self, other):
|
||||
try:
|
||||
mandatory = set(other.members) - set(other.optional)
|
||||
for k, m in self.members.items():
|
||||
m.compatible(other.members[k])
|
||||
mandatory.discard(k)
|
||||
if mandatory:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
except (AttributeError, TypeError, KeyError):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
class CommandType(DataType):
|
||||
IS_COMMAND = True
|
||||
@ -858,6 +944,16 @@ class CommandType(DataType):
|
||||
# actually I have no idea what to do here!
|
||||
raise NotImplementedError
|
||||
|
||||
def compatible(self, other):
|
||||
try:
|
||||
if self.argument != other.argument: # not both are None
|
||||
self.argument.compatible(other.argument)
|
||||
if self.result != other.result: # not both are None
|
||||
other.result.compatible(self.result)
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
|
||||
# internally used datatypes (i.e. only for programming the SEC-node)
|
||||
class DataTypeType(DataType):
|
||||
|
Reference in New Issue
Block a user