several improvements and bugfixes
+ rework GUI - include a combobox for selection of visibility - include a checkbox wether validation should be done in the client - remove unused lineEdit + improve datatypes + improve tests for new descriptive data + metaclasse: fix overlooked read_* or write_* func's + improve polling + Introduce new ErrorClasses + dispatcher: use new features of datatypes + PARAMS + improve lib + autopep8 + first working version of MLZ_entangle integration + split specific stuff into it's own package (MLZ,demo,ess) Change-Id: I8ac3ce871b28f44afecbba6332ca741095426712
This commit is contained in:
parent
8a63a6c63f
commit
29ee07c5b3
45
etc/ccr12.cfg
Normal file
45
etc/ccr12.cfg
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[equipment]
|
||||||
|
id=ccr12
|
||||||
|
|
||||||
|
[interface tcp]
|
||||||
|
interface=tcp
|
||||||
|
bindto=0.0.0.0
|
||||||
|
bindport=10767
|
||||||
|
# protocol to use for this interface
|
||||||
|
framing=eol
|
||||||
|
encoding=demo
|
||||||
|
|
||||||
|
[device automatik]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_automatik
|
||||||
|
mapping=dict(Off=0,p1=1,p2=2)
|
||||||
|
|
||||||
|
[device compressor]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_cooler_onoff
|
||||||
|
mapping=dict(Off=0,On=1)
|
||||||
|
|
||||||
|
[device gas]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_gas_onoff
|
||||||
|
mapping=dict(Off=0,On=1)
|
||||||
|
|
||||||
|
[device vacuum]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_vacuum_onoff
|
||||||
|
mapping=dict(Off=0,On=1)
|
||||||
|
|
||||||
|
[device p1]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_p1
|
||||||
|
|
||||||
|
[device p2]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_p2
|
||||||
|
|
||||||
|
[device curve_p2]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalInput
|
||||||
|
tangodevice=tango://ccr12:10000/box/plc/_curve
|
||||||
|
value.default='undefined'
|
||||||
|
mapping=dict(curve1=1,curve2=2,curve3=3)
|
||||||
|
|
@ -5,6 +5,7 @@ description = short description
|
|||||||
|
|
||||||
This is a very long description providing all the glory details in all the glory details about the stuff we are describing
|
This is a very long description providing all the glory details in all the glory details about the stuff we are describing
|
||||||
|
|
||||||
|
|
||||||
[interface tcp]
|
[interface tcp]
|
||||||
interface=tcp
|
interface=tcp
|
||||||
bindto=0.0.0.0
|
bindto=0.0.0.0
|
||||||
@ -18,7 +19,7 @@ encoding=demo
|
|||||||
# some (non-defaut) module properties
|
# some (non-defaut) module properties
|
||||||
.group=very important/stuff
|
.group=very important/stuff
|
||||||
# class of module:
|
# class of module:
|
||||||
class=devices.cryo.Cryostat
|
class=secop_demo.cryo.Cryostat
|
||||||
|
|
||||||
# some parameters
|
# some parameters
|
||||||
jitter=0.1
|
jitter=0.1
|
||||||
|
14
etc/demo.cfg
14
etc/demo.cfg
@ -10,34 +10,34 @@ framing=eol
|
|||||||
encoding=demo
|
encoding=demo
|
||||||
|
|
||||||
[device heatswitch]
|
[device heatswitch]
|
||||||
class=devices.demo.Switch
|
class=secop_demo.demo.Switch
|
||||||
switch_on_time=5
|
switch_on_time=5
|
||||||
switch_off_time=10
|
switch_off_time=10
|
||||||
|
|
||||||
[device mf]
|
[device mf]
|
||||||
class=devices.demo.MagneticField
|
class=secop_demo.demo.MagneticField
|
||||||
heatswitch = heatswitch
|
heatswitch = heatswitch
|
||||||
|
|
||||||
[device ts]
|
[device ts]
|
||||||
class=devices.demo.SampleTemp
|
class=secop_demo.demo.SampleTemp
|
||||||
sensor = 'Q1329V7R3'
|
sensor = 'Q1329V7R3'
|
||||||
ramp = 4
|
ramp = 4
|
||||||
target = 10
|
target = 10
|
||||||
default = 10
|
default = 10
|
||||||
|
|
||||||
[device tc1]
|
[device tc1]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X34598T7"
|
sensor="X34598T7"
|
||||||
|
|
||||||
[device tc2]
|
[device tc2]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X39284Q8'
|
sensor="X39284Q8'
|
||||||
|
|
||||||
[device label]
|
[device label]
|
||||||
class=devices.demo.Label
|
class=secop_demo.demo.Label
|
||||||
system=Cryomagnet MX15
|
system=Cryomagnet MX15
|
||||||
subdev_mf=mf
|
subdev_mf=mf
|
||||||
subdev_ts=ts
|
subdev_ts=ts
|
||||||
|
|
||||||
#[device vt]
|
#[device vt]
|
||||||
#class=devices.demo.ValidatorTest
|
#class=secop_demo.demo.ValidatorTest
|
||||||
|
@ -17,23 +17,23 @@ framing=eol
|
|||||||
encoding=demo
|
encoding=demo
|
||||||
|
|
||||||
[device tc1]
|
[device tc1]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X34598T7"
|
sensor="X34598T7"
|
||||||
|
|
||||||
[device tc2]
|
[device tc2]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X39284Q8'
|
sensor="X39284Q8'
|
||||||
|
|
||||||
|
|
||||||
[device sensor1]
|
[device sensor1]
|
||||||
class=devices.epics.EpicsReadable
|
class=secop_ess.epics.EpicsReadable
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
value_pv="DEV:KRDG1"
|
value_pv="DEV:KRDG1"
|
||||||
|
|
||||||
|
|
||||||
[device loop1]
|
[device loop1]
|
||||||
class=devices.epics.EpicsTempCtrl
|
class=secop_ess.epics.EpicsTempCtrl
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
|
|
||||||
@ -43,14 +43,14 @@ heaterrange_pv="DEV:RANGE_S1"
|
|||||||
|
|
||||||
|
|
||||||
[device sensor2]
|
[device sensor2]
|
||||||
class=devices.epics.EpicsReadable
|
class=secop_ess.epics.EpicsReadable
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
value_pv="DEV:KRDG2"
|
value_pv="DEV:KRDG2"
|
||||||
|
|
||||||
|
|
||||||
[device loop2]
|
[device loop2]
|
||||||
class=devices.epics.EpicsTempCtrl
|
class=secop_ess.epics.EpicsTempCtrl
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
|
|
||||||
|
10
etc/test.cfg
10
etc/test.cfg
@ -11,21 +11,21 @@ encoding=demo
|
|||||||
|
|
||||||
|
|
||||||
[device LN2]
|
[device LN2]
|
||||||
class=devices.test.LN2
|
class=secop_demo.test.LN2
|
||||||
|
|
||||||
[device heater]
|
[device heater]
|
||||||
class=devices.test.Heater
|
class=secop_demo.test.Heater
|
||||||
maxheaterpower=10
|
maxheaterpower=10
|
||||||
|
|
||||||
[device T1]
|
[device T1]
|
||||||
class=devices.test.Temp
|
class=secop_demo.test.Temp
|
||||||
sensor="X34598T7"
|
sensor="X34598T7"
|
||||||
|
|
||||||
[device T2]
|
[device T2]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X34598T8"
|
sensor="X34598T8"
|
||||||
|
|
||||||
[device T3]
|
[device T3]
|
||||||
class=devices.demo.CoilTemp
|
class=secop_demo.demo.CoilTemp
|
||||||
sensor="X34598T9"
|
sensor="X34598T9"
|
||||||
|
|
||||||
|
@ -156,6 +156,7 @@ class Client(object):
|
|||||||
self.log = mlzlog.log.getChild('client', True)
|
self.log = mlzlog.log.getChild('client', True)
|
||||||
else:
|
else:
|
||||||
class logStub(object):
|
class logStub(object):
|
||||||
|
|
||||||
def info(self, *args):
|
def info(self, *args):
|
||||||
pass
|
pass
|
||||||
debug = info
|
debug = info
|
||||||
@ -343,14 +344,17 @@ class Client(object):
|
|||||||
def _issueDescribe(self):
|
def _issueDescribe(self):
|
||||||
_, self.equipment_id, describing_data = self._communicate('describe')
|
_, self.equipment_id, describing_data = self._communicate('describe')
|
||||||
try:
|
try:
|
||||||
describing_data = self._decode_substruct(['modules'], describing_data)
|
describing_data = self._decode_substruct(
|
||||||
|
['modules'], describing_data)
|
||||||
for modname, module in describing_data['modules'].items():
|
for modname, module in describing_data['modules'].items():
|
||||||
describing_data['modules'][modname] = self._decode_substruct(['parameters', 'commands'], module)
|
describing_data['modules'][modname] = self._decode_substruct(
|
||||||
|
['parameters', 'commands'], module)
|
||||||
|
|
||||||
self.describing_data = describing_data
|
self.describing_data = describing_data
|
||||||
|
|
||||||
for module, moduleData in self.describing_data['modules'].items():
|
for module, moduleData in self.describing_data['modules'].items():
|
||||||
for parameter, parameterData in moduleData['parameters'].items():
|
for parameter, parameterData in moduleData[
|
||||||
|
'parameters'].items():
|
||||||
datatype = get_datatype(parameterData['datatype'])
|
datatype = get_datatype(parameterData['datatype'])
|
||||||
self.describing_data['modules'][module]['parameters'] \
|
self.describing_data['modules'][module]['parameters'] \
|
||||||
[parameter]['datatype'] = datatype
|
[parameter]['datatype'] = datatype
|
||||||
|
@ -22,12 +22,21 @@
|
|||||||
"""Define validated data types."""
|
"""Define validated data types."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from .errors import ProgrammingError
|
from .errors import ProgrammingError
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
# Only export these classes for 'from secop.datatypes import *'
|
||||||
|
__all__ = [
|
||||||
|
"DataType",
|
||||||
|
"FloatRange", "IntRange",
|
||||||
|
"BoolType", "EnumType",
|
||||||
|
"BLOBType", "StringType",
|
||||||
|
"TupleOf", "ArrayOf", "StructOf",
|
||||||
|
]
|
||||||
|
|
||||||
# base class for all DataTypes
|
# base class for all DataTypes
|
||||||
|
|
||||||
|
|
||||||
class DataType(object):
|
class DataType(object):
|
||||||
as_json = ['undefined']
|
as_json = ['undefined']
|
||||||
|
|
||||||
@ -39,6 +48,11 @@ class DataType(object):
|
|||||||
"""returns a python object fit for external serialisation or logging"""
|
"""returns a python object fit for external serialisation or logging"""
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
"""interprets a given string and returns a validated (internal) value"""
|
||||||
|
# to evaluate values from configfiles, etc...
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
# goodie: if called, validate
|
# goodie: if called, validate
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
return self.validate(value)
|
return self.validate(value)
|
||||||
@ -52,7 +66,7 @@ class FloatRange(DataType):
|
|||||||
self.max = None if max is None else float(max)
|
self.max = None if max is None else float(max)
|
||||||
# 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('-inf')) <= (self.max or float('+inf')):
|
if (self.min or float('-inf')) <= (self.max or float('+inf')):
|
||||||
if min == None and max == None:
|
if min is None and max is None:
|
||||||
self.as_json = ['double']
|
self.as_json = ['double']
|
||||||
else:
|
else:
|
||||||
self.as_json = ['double', min, max]
|
self.as_json = ['double', min, max]
|
||||||
@ -65,9 +79,11 @@ class FloatRange(DataType):
|
|||||||
except:
|
except:
|
||||||
raise ValueError('Can not validate %r to float' % value)
|
raise ValueError('Can not validate %r to float' % value)
|
||||||
if self.min is not None and value < self.min:
|
if self.min is not None and value < self.min:
|
||||||
raise ValueError('%r should not be less then %s' % (value, self.min))
|
raise ValueError('%r should not be less then %s' %
|
||||||
|
(value, self.min))
|
||||||
if self.max is not None and value > self.max:
|
if self.max is not None and value > self.max:
|
||||||
raise ValueError('%r should not be greater than %s' % (value, self.max))
|
raise ValueError('%r should not be greater than %s' %
|
||||||
|
(value, self.max))
|
||||||
if None in (self.min, self.max):
|
if None in (self.min, self.max):
|
||||||
return value
|
return value
|
||||||
if self.min <= value <= self.max:
|
if self.min <= value <= self.max:
|
||||||
@ -76,9 +92,10 @@ class FloatRange(DataType):
|
|||||||
(value, self.min, self.max))
|
(value, self.min, self.max))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.max != None:
|
if self.max is not None:
|
||||||
return "FloatRange(%r, %r)" % (float('-inf') if self.min is None else self.min, self.max)
|
return "FloatRange(%r, %r)" % (
|
||||||
if self.min != None:
|
float('-inf') if self.min is None else self.min, self.max)
|
||||||
|
if self.min is not None:
|
||||||
return "FloatRange(%r)" % self.min
|
return "FloatRange(%r)" % self.min
|
||||||
return "FloatRange()"
|
return "FloatRange()"
|
||||||
|
|
||||||
@ -86,15 +103,20 @@ class FloatRange(DataType):
|
|||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = float(text)
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class IntRange(DataType):
|
class IntRange(DataType):
|
||||||
"""Restricted int type"""
|
"""Restricted int type"""
|
||||||
|
|
||||||
def __init__(self, min=None, max=None):
|
def __init__(self, min=None, max=None):
|
||||||
self.min = int(min) if min is not None else min
|
self.min = int(min) if min is not None else min
|
||||||
self.max = int(max) if max is not None else max
|
self.max = int(max) if max is not None else max
|
||||||
if self.min is not None and self.max is not None and self.min > self.max:
|
if self.min is not None and self.max is not None and self.min > self.max:
|
||||||
raise ValueError('Max must be larger then min!')
|
raise ValueError('Max must be larger then min!')
|
||||||
if self.min == None and self.max == None:
|
if self.min is None and self.max is None:
|
||||||
self.as_json = ['int']
|
self.as_json = ['int']
|
||||||
else:
|
else:
|
||||||
self.as_json = ['int', self.min, self.max]
|
self.as_json = ['int', self.min, self.max]
|
||||||
@ -104,10 +126,10 @@ class IntRange(DataType):
|
|||||||
value = int(value)
|
value = int(value)
|
||||||
if self.min is not None and value < self.min:
|
if self.min is not None and value < self.min:
|
||||||
raise ValueError('%r should be an int between %d and %d' %
|
raise ValueError('%r should be an int between %d and %d' %
|
||||||
(value, self.min, self.max or 0))
|
(value, self.min, self.max or 0))
|
||||||
if self.max is not None and value > self.max:
|
if self.max is not None and value > self.max:
|
||||||
raise ValueError('%r should be an int between %d and %d' %
|
raise ValueError('%r should be an int between %d and %d' %
|
||||||
(value, self.min or 0, self.max))
|
(value, self.min or 0, self.max))
|
||||||
return value
|
return value
|
||||||
except:
|
except:
|
||||||
raise ValueError('Can not validate %r to int' % value)
|
raise ValueError('Can not validate %r to int' % value)
|
||||||
@ -123,15 +145,20 @@ class IntRange(DataType):
|
|||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = int(text)
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class EnumType(DataType):
|
class EnumType(DataType):
|
||||||
as_json = ['enum']
|
as_json = ['enum']
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
# enum keys are ints! check
|
# enum keys are ints! check
|
||||||
self.entries = {}
|
self.entries = {}
|
||||||
num = 0
|
num = 0
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if type(arg) != str:
|
if not isinstance(arg, str):
|
||||||
print arg, type(arg)
|
print arg, type(arg)
|
||||||
raise ValueError('EnumType entries MUST be strings!')
|
raise ValueError('EnumType entries MUST be strings!')
|
||||||
self.entries[num] = arg
|
self.entries[num] = arg
|
||||||
@ -139,19 +166,24 @@ class EnumType(DataType):
|
|||||||
for k, v in kwds.items():
|
for k, v in kwds.items():
|
||||||
v = int(v)
|
v = int(v)
|
||||||
if v in self.entries:
|
if v in self.entries:
|
||||||
raise ValueError('keyword argument %r=%d is already assigned %r', k, v, self.entries[v])
|
raise ValueError(
|
||||||
|
'keyword argument %r=%d is already assigned %r',
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
self.entries[v])
|
||||||
self.entries[v] = k
|
self.entries[v] = k
|
||||||
if len(self.entries) == 0:
|
if len(self.entries) == 0:
|
||||||
raise ValueError('Empty enums ae not allowed!')
|
raise ValueError('Empty enums ae not allowed!')
|
||||||
self.reversed = {}
|
self.reversed = {}
|
||||||
for k,v in self.entries.items():
|
for k, v in self.entries.items():
|
||||||
if v in self.reversed:
|
if v in self.reversed:
|
||||||
raise ValueError('Mapping for %r=%r is not Unique!', v, k)
|
raise ValueError('Mapping for %r=%r is not Unique!', v, k)
|
||||||
self.reversed[v] = k
|
self.reversed[v] = k
|
||||||
self.as_json = ['enum', self.reversed.copy()]
|
self.as_json = ['enum', self.reversed.copy()]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "EnumType(%s)" % ', '.join(['%s=%d' % (v,k) for k,v in self.entries.items()])
|
return "EnumType(%s)" % ', '.join(
|
||||||
|
['%s=%d' % (v, k) for k, v in self.entries.items()])
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -159,7 +191,8 @@ class EnumType(DataType):
|
|||||||
return self.reversed[value]
|
return self.reversed[value]
|
||||||
if int(value) in self.entries:
|
if int(value) in self.entries:
|
||||||
return int(value)
|
return int(value)
|
||||||
raise ValueError('%r is not one of %s', str(value), ', '.join(self.reversed.keys()))
|
raise ValueError('%r is not one of %s', str(
|
||||||
|
value), ', '.join(self.reversed.keys()))
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
@ -167,10 +200,16 @@ class EnumType(DataType):
|
|||||||
return value
|
return value
|
||||||
if int(value) in self.entries:
|
if int(value) in self.entries:
|
||||||
return self.entries[int(value)]
|
return self.entries[int(value)]
|
||||||
raise ValueError('%r is not one of %s', str(value), ', '.join(map(str,self.entries.keys())))
|
raise ValueError('%r is not one of %s', str(value),
|
||||||
|
', '.join(map(str, self.entries.keys())))
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = text
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class BLOBType(DataType):
|
class BLOBType(DataType):
|
||||||
|
|
||||||
def __init__(self, minsize=0, maxsize=None):
|
def __init__(self, minsize=0, maxsize=None):
|
||||||
self.minsize = minsize
|
self.minsize = minsize
|
||||||
self.maxsize = maxsize
|
self.maxsize = maxsize
|
||||||
@ -194,19 +233,26 @@ class BLOBType(DataType):
|
|||||||
raise ValueError('%r has the wrong type!', value)
|
raise ValueError('%r has the wrong type!', value)
|
||||||
size = len(value)
|
size = len(value)
|
||||||
if size < self.minsize:
|
if size < self.minsize:
|
||||||
raise ValueError('%r must be at least %d bytes long!', value, self.minsize)
|
raise ValueError(
|
||||||
|
'%r must be at least %d bytes long!', value, self.minsize)
|
||||||
if self.maxsize is not None:
|
if self.maxsize is not None:
|
||||||
if size > self.maxsize:
|
if size > self.maxsize:
|
||||||
raise ValueError('%r must be at most %d bytes long!', value, self.maxsize)
|
raise ValueError(
|
||||||
|
'%r must be at most %d bytes long!', value, self.maxsize)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return b'%s' % value
|
return b'%s' % value
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = text
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class StringType(DataType):
|
class StringType(DataType):
|
||||||
as_json = ['string']
|
as_json = ['string']
|
||||||
|
|
||||||
def __init__(self, minsize=0, maxsize=None):
|
def __init__(self, minsize=0, maxsize=None):
|
||||||
self.minsize = minsize
|
self.minsize = minsize
|
||||||
self.maxsize = maxsize
|
self.maxsize = maxsize
|
||||||
@ -219,7 +265,8 @@ class StringType(DataType):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.maxsize:
|
if self.maxsize:
|
||||||
return 'StringType(%s, %s)' % (str(self.minsize), str(self.maxsize))
|
return 'StringType(%s, %s)' % (
|
||||||
|
str(self.minsize), str(self.maxsize))
|
||||||
if self.minsize:
|
if self.minsize:
|
||||||
return 'StringType(%d)' % str(self.minsize)
|
return 'StringType(%d)' % str(self.minsize)
|
||||||
return 'StringType()'
|
return 'StringType()'
|
||||||
@ -230,22 +277,31 @@ class StringType(DataType):
|
|||||||
raise ValueError('%r has the wrong type!', value)
|
raise ValueError('%r has the wrong type!', value)
|
||||||
size = len(value)
|
size = len(value)
|
||||||
if size < self.minsize:
|
if size < self.minsize:
|
||||||
raise ValueError('%r must be at least %d bytes long!', value, self.minsize)
|
raise ValueError(
|
||||||
|
'%r must be at least %d bytes long!', value, self.minsize)
|
||||||
if self.maxsize is not None:
|
if self.maxsize is not None:
|
||||||
if size > self.maxsize:
|
if size > self.maxsize:
|
||||||
raise ValueError('%r must be at most %d bytes long!', value, self.maxsize)
|
raise ValueError(
|
||||||
|
'%r must be at most %d bytes long!', value, self.maxsize)
|
||||||
if '\0' in value:
|
if '\0' in value:
|
||||||
raise ValueError('Strings are not allowed to embed a \\0! Use a Blob instead!')
|
raise ValueError(
|
||||||
|
'Strings are not allowed to embed a \\0! Use a Blob instead!')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return '%s' % value
|
return '%s' % value
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = text
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
# Bool is a special enum
|
# Bool is a special enum
|
||||||
|
|
||||||
|
|
||||||
class BoolType(DataType):
|
class BoolType(DataType):
|
||||||
as_json = ['bool']
|
as_json = ['bool']
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'BoolType()'
|
return 'BoolType()'
|
||||||
|
|
||||||
@ -261,23 +317,31 @@ class BoolType(DataType):
|
|||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return True if self.validate(value) else False
|
return True if self.validate(value) else False
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = text
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
#
|
#
|
||||||
# nested types
|
# nested types
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class ArrayOf(DataType):
|
class ArrayOf(DataType):
|
||||||
|
|
||||||
def __init__(self, subtype, minsize_or_size=None, maxsize=None):
|
def __init__(self, subtype, minsize_or_size=None, maxsize=None):
|
||||||
if maxsize is None:
|
if maxsize is None:
|
||||||
maxsize = minsize_or_size
|
maxsize = minsize_or_size
|
||||||
self.minsize = minsize_or_size
|
self.minsize = minsize_or_size
|
||||||
self.maxsize = maxsize
|
self.maxsize = maxsize
|
||||||
if self.minsize is not None and self.maxsize is not None and \
|
if self.minsize is not None and self.maxsize is not None and \
|
||||||
self.minsize > self.maxsize:
|
self.minsize > self.maxsize:
|
||||||
raise ValueError('minsize must be less than or equal to maxsize!')
|
raise ValueError('minsize must be less than or equal to maxsize!')
|
||||||
if not isinstance(subtype, DataType):
|
if not isinstance(subtype, DataType):
|
||||||
raise ValueError('ArrayOf only works with DataType objs as first argument!')
|
raise ValueError(
|
||||||
|
'ArrayOf only works with DataType objs as first argument!')
|
||||||
self.subtype = subtype
|
self.subtype = subtype
|
||||||
self.as_json = ['array', self.subtype.as_json, self.minsize, self.maxsize]
|
self.as_json = ['array', self.subtype.as_json,
|
||||||
|
self.minsize, self.maxsize]
|
||||||
if self.minsize is not None and self.minsize < 0:
|
if self.minsize is not None and self.minsize < 0:
|
||||||
raise ValueError('Minimum size must be >= 0!')
|
raise ValueError('Minimum size must be >= 0!')
|
||||||
if self.maxsize is not None and self.maxsize < 1:
|
if self.maxsize is not None and self.maxsize < 1:
|
||||||
@ -286,32 +350,43 @@ class ArrayOf(DataType):
|
|||||||
raise ValueError('Maximum size must be >= Minimum size')
|
raise ValueError('Maximum size must be >= Minimum size')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'ArrayOf(%s, %s, %s)' % (repr(self.subtype), self.minsize, self.maxsize)
|
return 'ArrayOf(%s, %s, %s)' % (
|
||||||
|
repr(self.subtype), self.minsize, self.maxsize)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""validate a external representation to an internal one"""
|
"""validate a external representation to an internal one"""
|
||||||
if isinstance(value, (tuple, list)):
|
if isinstance(value, (tuple, list)):
|
||||||
# check number of elements
|
# check number of elements
|
||||||
if self.minsize is not None and len(value) < self.minsize:
|
if self.minsize is not None and len(value) < self.minsize:
|
||||||
raise ValueError('Array too small, needs at least %d elements!', self.minsize)
|
raise ValueError(
|
||||||
|
'Array too small, needs at least %d elements!',
|
||||||
|
self.minsize)
|
||||||
if self.maxsize is not None and len(value) > self.maxsize:
|
if self.maxsize is not None and len(value) > self.maxsize:
|
||||||
raise ValueError('Array too big, holds at most %d elements!', self.minsize)
|
raise ValueError(
|
||||||
|
'Array too big, holds at most %d elements!', self.minsize)
|
||||||
# apply subtype valiation to all elements and return as list
|
# apply subtype valiation to all elements and return as list
|
||||||
return [self.subtype.validate(elem) for elem in value]
|
return [self.subtype.validate(elem) for elem in value]
|
||||||
raise ValueError('Can not convert %s to ArrayOf DataType!', repr(value))
|
raise ValueError(
|
||||||
|
'Can not convert %s to ArrayOf DataType!', repr(value))
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return [self.subtype.export(elem) for elem in value]
|
return [self.subtype.export(elem) for elem in value]
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = eval(text) # XXX: !!!
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
class TupleOf(DataType):
|
class TupleOf(DataType):
|
||||||
|
|
||||||
def __init__(self, *subtypes):
|
def __init__(self, *subtypes):
|
||||||
if not subtypes:
|
if not subtypes:
|
||||||
raise ValueError('Empty tuples are not allowed!')
|
raise ValueError('Empty tuples are not allowed!')
|
||||||
for subtype in subtypes:
|
for subtype in subtypes:
|
||||||
if not isinstance(subtype, DataType):
|
if not isinstance(subtype, DataType):
|
||||||
raise ValueError('TupleOf only works with DataType objs as arguments!')
|
raise ValueError(
|
||||||
|
'TupleOf only works with DataType objs as arguments!')
|
||||||
self.subtypes = subtypes
|
self.subtypes = subtypes
|
||||||
self.as_json = ['tuple', [subtype.as_json for subtype in subtypes]]
|
self.as_json = ['tuple', [subtype.as_json for subtype in subtypes]]
|
||||||
|
|
||||||
@ -323,65 +398,84 @@ class TupleOf(DataType):
|
|||||||
# keep the ordering!
|
# keep the ordering!
|
||||||
try:
|
try:
|
||||||
if len(value) != len(self.subtypes):
|
if len(value) != len(self.subtypes):
|
||||||
raise ValueError('Illegal number of Arguments! Need %d arguments.', len(self.subtypes))
|
raise ValueError(
|
||||||
|
'Illegal number of Arguments! Need %d arguments.', len(
|
||||||
|
self.subtypes))
|
||||||
# validate elements and return as list
|
# validate elements and return as list
|
||||||
return [sub.validate(elem) for sub,elem in zip(self.subtypes, value)]
|
return [sub.validate(elem)
|
||||||
|
for sub, elem in zip(self.subtypes, value)]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError('Can not validate:', str(exc))
|
raise ValueError('Can not validate:', str(exc))
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
return [sub.export(elem) for sub,elem in zip(self.subtypes, value)]
|
return [sub.export(elem) for sub, elem in zip(self.subtypes, value)]
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = eval(text) # XXX: !!!
|
||||||
|
return self.validate(tuple(value))
|
||||||
|
|
||||||
|
|
||||||
class StructOf(DataType):
|
class StructOf(DataType):
|
||||||
|
|
||||||
def __init__(self, **named_subtypes):
|
def __init__(self, **named_subtypes):
|
||||||
if not named_subtypes:
|
if not named_subtypes:
|
||||||
raise ValueError('Empty structs are not allowed!')
|
raise ValueError('Empty structs are not allowed!')
|
||||||
for name, subtype in named_subtypes.items():
|
for name, subtype in named_subtypes.items():
|
||||||
if not isinstance(subtype, DataType):
|
if not isinstance(subtype, DataType):
|
||||||
raise ProgrammingError('StructOf only works with named DataType objs as keyworded arguments!')
|
raise ProgrammingError(
|
||||||
|
'StructOf only works with named DataType objs as keyworded arguments!')
|
||||||
if not isinstance(name, (str, unicode)):
|
if not isinstance(name, (str, unicode)):
|
||||||
raise ProgrammingError('StructOf only works with named DataType objs as keyworded arguments!')
|
raise ProgrammingError(
|
||||||
|
'StructOf only works with named DataType objs as keyworded arguments!')
|
||||||
self.named_subtypes = named_subtypes
|
self.named_subtypes = named_subtypes
|
||||||
self.as_json = ['struct', dict((n,s.as_json) for n,s in named_subtypes.items())]
|
self.as_json = ['struct', dict((n, s.as_json)
|
||||||
|
for n, s in named_subtypes.items())]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'StructOf(%s)' % ', '.join(['%s=%s'%(n,repr(st)) for n,st in self.named_subtypes.iteritems()])
|
return 'StructOf(%s)' % ', '.join(
|
||||||
|
['%s=%s' % (n, repr(st)) for n, st in self.named_subtypes.iteritems()])
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated value or raise"""
|
"""return the validated value or raise"""
|
||||||
try:
|
try:
|
||||||
if len(value.keys()) != len(self.named_subtypes.keys()):
|
if len(value.keys()) != len(self.named_subtypes.keys()):
|
||||||
raise ValueError('Illegal number of Arguments! Need %d arguments.', len(self.namd_subtypes.keys()))
|
raise ValueError(
|
||||||
|
'Illegal number of Arguments! Need %d arguments.', len(
|
||||||
|
self.namd_subtypes.keys()))
|
||||||
# validate elements and return as dict
|
# validate elements and return as dict
|
||||||
return dict((str(k), self.named_subtypes[k].validate(v))
|
return dict((str(k), self.named_subtypes[k].validate(v))
|
||||||
for k,v in value.items())
|
for k, v in value.items())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError('Can not validate %s: %s', repr(value),str(exc))
|
raise ValueError('Can not validate %s: %s', repr(value), str(exc))
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
if len(value.keys()) != len(self.named_subtypes.keys()):
|
if len(value.keys()) != len(self.named_subtypes.keys()):
|
||||||
raise ValueError('Illegal number of Arguments! Need %d arguments.', len(self.namd_subtypes.keys()))
|
raise ValueError(
|
||||||
return dict((str(k),self.named_subtypes[k].export(v))
|
'Illegal number of Arguments! Need %d arguments.', len(
|
||||||
for k,v in value.items())
|
self.namd_subtypes.keys()))
|
||||||
|
return dict((str(k), self.named_subtypes[k].export(v))
|
||||||
|
for k, v in value.items())
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
value = eval(text) # XXX: !!!
|
||||||
|
return self.validate(dict(value))
|
||||||
|
|
||||||
|
|
||||||
# XXX: derive from above classes automagically!
|
# XXX: derive from above classes automagically!
|
||||||
DATATYPES = dict(
|
DATATYPES = dict(
|
||||||
bool = lambda : BoolType(),
|
bool=lambda: BoolType(),
|
||||||
int = lambda _min=None, _max=None: IntRange(_min, _max),
|
int=lambda _min=None, _max=None: IntRange(_min, _max),
|
||||||
double = lambda _min=None, _max=None: FloatRange(_min, _max),
|
double=lambda _min=None, _max=None: FloatRange(_min, _max),
|
||||||
blob = lambda _min=None, _max=None: BLOBType(_min, _max),
|
blob=lambda _min=None, _max=None: BLOBType(_min, _max),
|
||||||
string = lambda _min=None, _max=None: StringType(_min, _max),
|
string=lambda _min=None, _max=None: StringType(_min, _max),
|
||||||
array = lambda subtype, _min=None, _max=None: ArrayOf(get_datatype(subtype), _min, _max),
|
array=lambda subtype, _min=None, _max=None: ArrayOf(
|
||||||
tuple = lambda subtypes: TupleOf(*map(get_datatype,subtypes)),
|
get_datatype(subtype), _min, _max),
|
||||||
enum = lambda kwds: EnumType(**kwds),
|
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
|
||||||
struct = lambda named_subtypes: StructOf(**dict((n,get_datatype(t)) for n,t in named_subtypes.items())),
|
enum=lambda kwds: EnumType(**kwds),
|
||||||
|
struct=lambda named_subtypes: StructOf(
|
||||||
|
**dict((n, get_datatype(t)) for n, t in named_subtypes.items())),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -392,12 +486,14 @@ def export_datatype(datatype):
|
|||||||
return datatype.as_json
|
return datatype.as_json
|
||||||
|
|
||||||
# important for getting the right datatype from formerly jsonified descr.
|
# important for getting the right datatype from formerly jsonified descr.
|
||||||
|
|
||||||
|
|
||||||
def get_datatype(json):
|
def get_datatype(json):
|
||||||
if json is None:
|
if json is None:
|
||||||
return json
|
return json
|
||||||
if not isinstance(json, list):
|
if not isinstance(json, list):
|
||||||
raise ValueError('Argument must be a properly formatted list!')
|
raise ValueError('Argument must be a properly formatted list!')
|
||||||
if len(json)<1:
|
if len(json) < 1:
|
||||||
raise ValueError('can not validate %r', json)
|
raise ValueError('can not validate %r', json)
|
||||||
base = json[0]
|
base = json[0]
|
||||||
if base in DATATYPES:
|
if base in DATATYPES:
|
||||||
|
@ -33,3 +33,11 @@ class ConfigError(SECoPServerError):
|
|||||||
|
|
||||||
class ProgrammingError(SECoPServerError):
|
class ProgrammingError(SECoPServerError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CommunicationError(SECoPServerError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HardwareError(SECoPServerError):
|
||||||
|
pass
|
||||||
|
@ -71,7 +71,6 @@ class MainWindow(QMainWindow):
|
|||||||
loadUi(self, 'mainwindow.ui')
|
loadUi(self, 'mainwindow.ui')
|
||||||
|
|
||||||
self.toolBar.hide()
|
self.toolBar.hide()
|
||||||
self.lineEdit.hide()
|
|
||||||
|
|
||||||
self.splitter.setStretchFactor(0, 1)
|
self.splitter.setStretchFactor(0, 1)
|
||||||
self.splitter.setStretchFactor(1, 70)
|
self.splitter.setStretchFactor(1, 70)
|
||||||
@ -108,6 +107,13 @@ class MainWindow(QMainWindow):
|
|||||||
QMessageBox.critical(self.parent(),
|
QMessageBox.critical(self.parent(),
|
||||||
'Connecting to %s failed!' % host, str(e))
|
'Connecting to %s failed!' % host, str(e))
|
||||||
|
|
||||||
|
def on_validateCheckBox_toggled(self, state):
|
||||||
|
print "validateCheckBox_toggled", state
|
||||||
|
|
||||||
|
def on_visibilityComboBox_activated(self, level):
|
||||||
|
if level in ['user', 'admin', 'expert']:
|
||||||
|
print "visibility Level now:", level
|
||||||
|
|
||||||
def on_treeWidget_currentItemChanged(self, current, previous):
|
def on_treeWidget_currentItemChanged(self, current, previous):
|
||||||
if current.type() == ITEM_TYPE_NODE:
|
if current.type() == ITEM_TYPE_NODE:
|
||||||
self._displayNode(current.text(0))
|
self._displayNode(current.text(0))
|
||||||
|
@ -56,6 +56,7 @@ class ParameterButtons(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class ParameterGroup(QWidget):
|
class ParameterGroup(QWidget):
|
||||||
|
|
||||||
def __init__(self, groupname, parent=None):
|
def __init__(self, groupname, parent=None):
|
||||||
super(ParameterGroup, self).__init__(parent)
|
super(ParameterGroup, self).__init__(parent)
|
||||||
loadUi(self, 'paramgroup.ui')
|
loadUi(self, 'paramgroup.ui')
|
||||||
@ -107,19 +108,17 @@ class ModuleCtrl(QWidget):
|
|||||||
|
|
||||||
self._node.newData.connect(self._updateValue)
|
self._node.newData.connect(self._updateValue)
|
||||||
|
|
||||||
|
|
||||||
def _initModuleWidgets(self):
|
def _initModuleWidgets(self):
|
||||||
initValues = self._node.queryCache(self._module)
|
initValues = self._node.queryCache(self._module)
|
||||||
row = 0
|
row = 0
|
||||||
|
|
||||||
|
|
||||||
# collect grouping information
|
# collect grouping information
|
||||||
paramsByGroup = {} # groupname -> [paramnames]
|
paramsByGroup = {} # groupname -> [paramnames]
|
||||||
allGroups = set()
|
allGroups = set()
|
||||||
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', None)
|
||||||
if group is not None:
|
if group is not None:
|
||||||
allGroups.add(group)
|
allGroups.add(group)
|
||||||
paramsByGroup.setdefault(group, []).append(param)
|
paramsByGroup.setdefault(group, []).append(param)
|
||||||
@ -139,7 +138,8 @@ class ModuleCtrl(QWidget):
|
|||||||
# check if there is a param of the same name too
|
# check if there is a param of the same name too
|
||||||
if group in params:
|
if group in params:
|
||||||
# yes: create a widget for this as well
|
# yes: create a widget for this as well
|
||||||
labelstr, buttons = self._makeEntry(param, initValues[param].value, nolabel=True, checkbox=checkbox, invert=True)
|
labelstr, buttons = self._makeEntry(
|
||||||
|
param, initValues[param].value, 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!)
|
||||||
@ -153,7 +153,8 @@ class ModuleCtrl(QWidget):
|
|||||||
for param in paramsByGroup[param]:
|
for param in paramsByGroup[param]:
|
||||||
if param == group:
|
if param == group:
|
||||||
continue
|
continue
|
||||||
label, buttons = self._makeEntry(param, initValues[param].value, checkbox=checkbox, invert=False)
|
label, buttons = self._makeEntry(
|
||||||
|
param, initValues[param].value, checkbox=checkbox, invert=False)
|
||||||
|
|
||||||
# add to Layout
|
# add to Layout
|
||||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
self.paramGroupBox.layout().addWidget(label, row, 0)
|
||||||
@ -161,22 +162,29 @@ class ModuleCtrl(QWidget):
|
|||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# param is a 'normal' param: create a widget if it has no group or is named after a group (otherwise its created above)
|
# param is a 'normal' param: create a widget if it has no group
|
||||||
|
# or is named after a group (otherwise its created above)
|
||||||
props = self._node.getProperties(self._module, param)
|
props = self._node.getProperties(self._module, param)
|
||||||
if props.get('group', param) == param:
|
if props.get('group', param) == param:
|
||||||
label, buttons = self._makeEntry(param, initValues[param].value)
|
label, buttons = self._makeEntry(
|
||||||
|
param, initValues[param].value)
|
||||||
|
|
||||||
# add to Layout
|
# add to Layout
|
||||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
self.paramGroupBox.layout().addWidget(label, row, 0)
|
||||||
self.paramGroupBox.layout().addWidget(buttons, row, 1)
|
self.paramGroupBox.layout().addWidget(buttons, row, 1)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
|
def _makeEntry(
|
||||||
def _makeEntry(self, param, initvalue, nolabel=False, checkbox=None, invert=False):
|
self,
|
||||||
|
param,
|
||||||
|
initvalue,
|
||||||
|
nolabel=False,
|
||||||
|
checkbox=None,
|
||||||
|
invert=False):
|
||||||
props = self._node.getProperties(self._module, param)
|
props = self._node.getProperties(self._module, param)
|
||||||
|
|
||||||
description = props.get('description', '')
|
description = props.get('description', '')
|
||||||
unit = props.get('unit','')
|
unit = props.get('unit', '')
|
||||||
|
|
||||||
if unit:
|
if unit:
|
||||||
labelstr = '%s (%s):' % (param, unit)
|
labelstr = '%s (%s):' % (param, unit)
|
||||||
@ -186,7 +194,8 @@ class ModuleCtrl(QWidget):
|
|||||||
if checkbox and not invert:
|
if checkbox and not invert:
|
||||||
labelstr = ' ' + labelstr
|
labelstr = ' ' + labelstr
|
||||||
|
|
||||||
buttons = ParameterButtons(self._module, param, initvalue, props['readonly'])
|
buttons = ParameterButtons(
|
||||||
|
self._module, param, initvalue, props['readonly'])
|
||||||
buttons.setRequested.connect(self._set_Button_pressed)
|
buttons.setRequested.connect(self._set_Button_pressed)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
@ -199,7 +208,11 @@ class ModuleCtrl(QWidget):
|
|||||||
label.setFont(self._labelfont)
|
label.setFont(self._labelfont)
|
||||||
|
|
||||||
if checkbox:
|
if checkbox:
|
||||||
def stateChanged(newstate, buttons=buttons, label=None if nolabel else label, invert=invert):
|
def stateChanged(
|
||||||
|
newstate,
|
||||||
|
buttons=buttons,
|
||||||
|
label=None if nolabel else label,
|
||||||
|
invert=invert):
|
||||||
if (newstate and not invert) or (invert and not newstate):
|
if (newstate and not invert) or (invert and not newstate):
|
||||||
buttons.show()
|
buttons.show()
|
||||||
if label:
|
if label:
|
||||||
@ -217,9 +230,6 @@ class ModuleCtrl(QWidget):
|
|||||||
|
|
||||||
return label, buttons
|
return label, buttons
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _set_Button_pressed(self, module, parameter, target):
|
def _set_Button_pressed(self, module, parameter, target):
|
||||||
sig = (module, parameter, target)
|
sig = (module, parameter, target)
|
||||||
if self._lastclick == sig:
|
if self._lastclick == sig:
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
<string>secop-gui</string>
|
<string>secop-gui</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item row="0" column="0">
|
<item>
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -23,7 +23,53 @@
|
|||||||
<widget class="QWidget" name="layoutWidget">
|
<widget class="QWidget" name="layoutWidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="lineEdit"/>
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="visibilityComboBox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>user</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>admin</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>expert</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="validateCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Validate locally</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeWidget" name="treeWidget">
|
<widget class="QTreeWidget" name="treeWidget">
|
||||||
@ -58,7 +104,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1228</width>
|
<width>1228</width>
|
||||||
<height>25</height>
|
<height>23</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>230</width>
|
<width>230</width>
|
||||||
<height>121</height>
|
<height>195</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -92,6 +92,40 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QGroupBox" name="propertyGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Properties:</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
76
secop/gui/ui/parambuttons_select.ui
Normal file
76
secop/gui/ui/parambuttons_select.ui
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>730</width>
|
||||||
|
<height>33</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="horizontalSpacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Current: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="targetValueComboBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<widget class="QComboBox" name="comboBox_2"/>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="5">
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -21,7 +21,34 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define helpers"""
|
"""Define helpers"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import errno
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import fnmatch
|
||||||
|
import linecache
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
|
import subprocess
|
||||||
|
import unicodedata
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
|
||||||
|
class lazy_property(object):
|
||||||
|
"""A property that calculates its value only once."""
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
self._func = func
|
||||||
|
self.__name__ = func.__name__
|
||||||
|
self.__doc__ = func.__doc__
|
||||||
|
|
||||||
|
def __get__(self, obj, obj_class):
|
||||||
|
if obj is None:
|
||||||
|
return obj
|
||||||
|
obj.__dict__[self.__name__] = self._func(obj)
|
||||||
|
return obj.__dict__[self.__name__]
|
||||||
|
|
||||||
|
|
||||||
class attrdict(dict):
|
class attrdict(dict):
|
||||||
@ -48,8 +75,11 @@ def get_class(spec):
|
|||||||
"""loads a class given by string in dotted notaion (as python would do)"""
|
"""loads a class given by string in dotted notaion (as python would do)"""
|
||||||
modname, classname = spec.rsplit('.', 1)
|
modname, classname = spec.rsplit('.', 1)
|
||||||
import importlib
|
import importlib
|
||||||
module = importlib.import_module('secop.' + modname)
|
if modname.startswith('secop'):
|
||||||
# module = __import__(spec)
|
module = importlib.import_module(modname)
|
||||||
|
else:
|
||||||
|
# rarely needed by now....
|
||||||
|
module = importlib.import_module('secop.' + modname)
|
||||||
return getattr(module, classname)
|
return getattr(module, classname)
|
||||||
|
|
||||||
|
|
||||||
@ -64,10 +94,6 @@ def mkthread(func, *args, **kwds):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import linecache
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
def formatExtendedFrame(frame):
|
def formatExtendedFrame(frame):
|
||||||
ret = []
|
ret = []
|
||||||
for key, value in frame.f_locals.iteritems():
|
for key, value in frame.f_locals.iteritems():
|
||||||
@ -79,6 +105,7 @@ def formatExtendedFrame(frame):
|
|||||||
ret.append('\n')
|
ret.append('\n')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def formatExtendedTraceback(exc_info=None):
|
def formatExtendedTraceback(exc_info=None):
|
||||||
if exc_info is None:
|
if exc_info is None:
|
||||||
etype, value, tb = sys.exc_info()
|
etype, value, tb = sys.exc_info()
|
||||||
@ -101,6 +128,7 @@ def formatExtendedTraceback(exc_info=None):
|
|||||||
ret += traceback.format_exception_only(etype, value)
|
ret += traceback.format_exception_only(etype, value)
|
||||||
return ''.join(ret).rstrip('\n')
|
return ''.join(ret).rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
def formatExtendedStack(level=1):
|
def formatExtendedStack(level=1):
|
||||||
f = sys._getframe(level)
|
f = sys._getframe(level)
|
||||||
ret = ['Stack trace (most recent call last):\n\n']
|
ret = ['Stack trace (most recent call last):\n\n']
|
||||||
@ -120,6 +148,7 @@ def formatExtendedStack(level=1):
|
|||||||
f = f.f_back
|
f = f.f_back
|
||||||
return ''.join(ret).rstrip('\n')
|
return ''.join(ret).rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
def formatException(cut=0, exc_info=None, verbose=False):
|
def formatException(cut=0, exc_info=None, verbose=False):
|
||||||
"""Format an exception with traceback, but leave out the first `cut`
|
"""Format an exception with traceback, but leave out the first `cut`
|
||||||
number of frames.
|
number of frames.
|
||||||
@ -137,6 +166,63 @@ def formatException(cut=0, exc_info=None, verbose=False):
|
|||||||
return ''.join(res)
|
return ''.join(res)
|
||||||
|
|
||||||
|
|
||||||
|
def parseHostPort(host, defaultport):
|
||||||
|
"""Parse host[:port] string and tuples
|
||||||
|
|
||||||
|
Specify 'host[:port]' or a (host, port) tuple for the mandatory argument.
|
||||||
|
If the port specification is missing, the value of the defaultport is used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(host, (tuple, list)):
|
||||||
|
host, port = host
|
||||||
|
elif ':' in host:
|
||||||
|
host, port = host.rsplit(':', 1)
|
||||||
|
port = int(port)
|
||||||
|
else:
|
||||||
|
port = defaultport
|
||||||
|
assert 0 < port < 65536
|
||||||
|
assert ':' not in host
|
||||||
|
return host, port
|
||||||
|
|
||||||
|
|
||||||
|
def tcpSocket(host, defaultport, timeout=None):
|
||||||
|
"""Helper for opening a TCP client socket to a remote server.
|
||||||
|
|
||||||
|
Specify 'host[:port]' or a (host, port) tuple for the mandatory argument.
|
||||||
|
If the port specification is missing, the value of the defaultport is used.
|
||||||
|
If timeout is set to a number, the timout of the connection is set to this
|
||||||
|
number, else the socket stays in blocking mode.
|
||||||
|
"""
|
||||||
|
host, port = parseHostPort(host, defaultport)
|
||||||
|
|
||||||
|
# open socket and set options
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
if timeout:
|
||||||
|
s.settimeout(timeout)
|
||||||
|
# connect
|
||||||
|
s.connect((host, int(port)))
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def closeSocket(sock, socket=socket):
|
||||||
|
"""Do our best to close a socket."""
|
||||||
|
if sock is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
sock.shutdown(socket.SHUT_RDWR)
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
sock.close()
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def getfqdn(name=''):
|
||||||
|
"""Get fully qualified hostname."""
|
||||||
|
return socket.getfqdn(name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print "minimal testing: lib"
|
print "minimal testing: lib"
|
||||||
d = attrdict(a=1, b=2)
|
d = attrdict(a=1, b=2)
|
||||||
|
@ -56,17 +56,15 @@ class PARAM(object):
|
|||||||
unit=None,
|
unit=None,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
export=True,
|
export=True,
|
||||||
group=''):
|
group='',
|
||||||
if isinstance(description, PARAM):
|
poll=False):
|
||||||
# make a copy of a PARAM object
|
|
||||||
self.__dict__.update(description.__dict__)
|
|
||||||
return
|
|
||||||
if not isinstance(datatype, DataType):
|
if not isinstance(datatype, DataType):
|
||||||
if issubclass(datatype, DataType):
|
if issubclass(datatype, DataType):
|
||||||
# goodie: make an instance from a class (forgotten ()???)
|
# goodie: make an instance from a class (forgotten ()???)
|
||||||
datatype = datatype()
|
datatype = datatype()
|
||||||
else:
|
else:
|
||||||
raise ValueError('Datatype MUST be from datatypes!')
|
raise ValueError(
|
||||||
|
'datatype MUST be derived from class DataType!')
|
||||||
self.description = description
|
self.description = description
|
||||||
self.datatype = datatype
|
self.datatype = datatype
|
||||||
self.default = default
|
self.default = default
|
||||||
@ -74,6 +72,9 @@ class PARAM(object):
|
|||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
self.export = export
|
self.export = export
|
||||||
self.group = group
|
self.group = group
|
||||||
|
# note: auto-converts True/False to 1/0 which yield the expected
|
||||||
|
# behaviour...
|
||||||
|
self.poll = int(poll)
|
||||||
# internal caching: value and timestamp of last change...
|
# internal caching: value and timestamp of last change...
|
||||||
self.value = default
|
self.value = default
|
||||||
self.timestamp = 0
|
self.timestamp = 0
|
||||||
@ -82,6 +83,18 @@ class PARAM(object):
|
|||||||
return '%s(%s)' % (self.__class__.__name__, ', '.join(
|
return '%s(%s)' % (self.__class__.__name__, ', '.join(
|
||||||
['%s=%r' % (k, v) for k, v in sorted(self.__dict__.items())]))
|
['%s=%r' % (k, v) for k, v in sorted(self.__dict__.items())]))
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
# return a copy of ourselfs
|
||||||
|
return PARAM(description=self.description,
|
||||||
|
datatype=self.datatype,
|
||||||
|
default=self.default,
|
||||||
|
unit=self.unit,
|
||||||
|
readonly=self.readonly,
|
||||||
|
export=self.export,
|
||||||
|
group=self.group,
|
||||||
|
poll=self.poll,
|
||||||
|
)
|
||||||
|
|
||||||
def as_dict(self, static_only=False):
|
def as_dict(self, static_only=False):
|
||||||
# used for serialisation only
|
# used for serialisation only
|
||||||
res = dict(
|
res = dict(
|
||||||
@ -99,11 +112,36 @@ class PARAM(object):
|
|||||||
res['timestamp'] = format_time(self.timestamp)
|
res['timestamp'] = format_time(self.timestamp)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@property
|
||||||
|
def export_value(self):
|
||||||
|
return self.datatype.export(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class OVERRIDE(object):
|
||||||
|
|
||||||
|
def __init__(self, **kwds):
|
||||||
|
self.kwds = kwds
|
||||||
|
|
||||||
|
def apply(self, paramobj):
|
||||||
|
if isinstance(paramobj, PARAM):
|
||||||
|
for k, v in self.kwds.iteritems():
|
||||||
|
if hasattr(paramobj, k):
|
||||||
|
setattr(paramobj, k, v)
|
||||||
|
return paramobj
|
||||||
|
else:
|
||||||
|
raise ProgrammingError(
|
||||||
|
"Can not apply Override(%s=%r) to %r: non-existing property!" %
|
||||||
|
(k, v, paramobj))
|
||||||
|
else:
|
||||||
|
raise ProgrammingError(
|
||||||
|
"Overrides can only be applied to PARAM's, %r is none!" %
|
||||||
|
paramobj)
|
||||||
|
|
||||||
|
|
||||||
# storage for CMDs settings (description + call signature...)
|
# storage for CMDs settings (description + call signature...)
|
||||||
class CMD(object):
|
class CMD(object):
|
||||||
|
|
||||||
def __init__(self, description, arguments, result):
|
def __init__(self, description, arguments=[], result=None):
|
||||||
# descriptive text for humans
|
# descriptive text for humans
|
||||||
self.description = description
|
self.description = description
|
||||||
# list of datatypes for arguments
|
# list of datatypes for arguments
|
||||||
@ -122,10 +160,10 @@ class CMD(object):
|
|||||||
arguments=map(export_datatype, self.arguments),
|
arguments=map(export_datatype, self.arguments),
|
||||||
resulttype=export_datatype(self.resulttype), )
|
resulttype=export_datatype(self.resulttype), )
|
||||||
|
|
||||||
|
|
||||||
# Meta class
|
# Meta class
|
||||||
# warning: MAGIC!
|
# warning: MAGIC!
|
||||||
|
|
||||||
|
|
||||||
class DeviceMeta(type):
|
class DeviceMeta(type):
|
||||||
|
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(mcs, name, bases, attrs):
|
||||||
@ -142,37 +180,61 @@ class DeviceMeta(type):
|
|||||||
newentry.update(attrs.get(entry, {}))
|
newentry.update(attrs.get(entry, {}))
|
||||||
setattr(newtype, entry, newentry)
|
setattr(newtype, entry, newentry)
|
||||||
|
|
||||||
|
# apply Overrides from all sub-classes
|
||||||
|
newparams = getattr(newtype, 'PARAMS')
|
||||||
|
for base in reversed(bases):
|
||||||
|
overrides = getattr(base, 'OVERRIDES', {})
|
||||||
|
for n, o in overrides.iteritems():
|
||||||
|
newparams[n] = o.apply(newparams[n].copy())
|
||||||
|
for n, o in attrs.get('OVERRIDES', {}).iteritems():
|
||||||
|
newparams[n] = o.apply(newparams[n].copy())
|
||||||
|
|
||||||
# check validity of PARAM entries
|
# check validity of PARAM entries
|
||||||
for pname, pobj in newtype.PARAMS.items():
|
for pname, pobj in newtype.PARAMS.items():
|
||||||
# XXX: allow dicts for overriding certain aspects only.
|
# XXX: allow dicts for overriding certain aspects only.
|
||||||
if not isinstance(pobj, PARAM):
|
if not isinstance(pobj, PARAM):
|
||||||
raise ProgrammingError('%r: device PARAM %r should be a '
|
raise ProgrammingError('%r: PARAMs entry %r should be a '
|
||||||
'PARAM object!' % (name, pname))
|
'PARAM object!' % (name, pname))
|
||||||
|
|
||||||
# XXX: create getters for the units of params ??
|
# XXX: create getters for the units of params ??
|
||||||
|
|
||||||
# wrap of reading/writing funcs
|
# wrap of reading/writing funcs
|
||||||
rfunc = attrs.get('read_' + pname, None)
|
rfunc = attrs.get('read_' + pname, None)
|
||||||
|
for base in bases:
|
||||||
|
if rfunc is not None:
|
||||||
|
break
|
||||||
|
rfunc = getattr(base, 'read_' + pname, None)
|
||||||
|
|
||||||
def wrapped_rfunc(self, maxage=0, pname=pname, rfunc=rfunc):
|
def wrapped_rfunc(self, maxage=0, pname=pname, rfunc=rfunc):
|
||||||
if rfunc:
|
if rfunc:
|
||||||
|
self.log.debug("rfunc(%s): call %r" % (pname, rfunc))
|
||||||
value = rfunc(self, maxage)
|
value = rfunc(self, maxage)
|
||||||
setattr(self, pname, value)
|
setattr(self, pname, value)
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
# return cached value
|
# return cached value
|
||||||
|
self.log.debug("rfunc(%s): return cached value" % pname)
|
||||||
return self.PARAMS[pname].value
|
return self.PARAMS[pname].value
|
||||||
|
|
||||||
if rfunc:
|
if rfunc:
|
||||||
wrapped_rfunc.__doc__ = rfunc.__doc__
|
wrapped_rfunc.__doc__ = rfunc.__doc__
|
||||||
setattr(newtype, 'read_' + pname, wrapped_rfunc)
|
if getattr(rfunc, '__wrapped__', False) == False:
|
||||||
|
setattr(newtype, 'read_' + pname, wrapped_rfunc)
|
||||||
|
wrapped_rfunc.__wrapped__ = True
|
||||||
|
|
||||||
if not pobj.readonly:
|
if not pobj.readonly:
|
||||||
wfunc = attrs.get('write_' + pname, None)
|
wfunc = attrs.get('write_' + pname, None)
|
||||||
|
for base in bases:
|
||||||
|
if wfunc is not None:
|
||||||
|
break
|
||||||
|
wfunc = getattr(base, 'write_' + pname, None)
|
||||||
|
|
||||||
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
|
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
|
||||||
self.log.debug("wfunc: set %s to %r" % (pname, value))
|
self.log.debug("wfunc(%s): set %r" % (pname, value))
|
||||||
pobj = self.PARAMS[pname]
|
pobj = self.PARAMS[pname]
|
||||||
value = pobj.datatype.validate(value) if pobj.datatype else value
|
value = pobj.datatype.validate(value)
|
||||||
if wfunc:
|
if wfunc:
|
||||||
|
self.log.debug('calling %r(%r)' % (wfunc, value))
|
||||||
value = wfunc(self, value) or value
|
value = wfunc(self, value) or value
|
||||||
# XXX: use setattr or direct manipulation
|
# XXX: use setattr or direct manipulation
|
||||||
# of self.PARAMS[pname]?
|
# of self.PARAMS[pname]?
|
||||||
@ -181,14 +243,16 @@ class DeviceMeta(type):
|
|||||||
|
|
||||||
if wfunc:
|
if wfunc:
|
||||||
wrapped_wfunc.__doc__ = wfunc.__doc__
|
wrapped_wfunc.__doc__ = wfunc.__doc__
|
||||||
setattr(newtype, 'write_' + pname, wrapped_wfunc)
|
if getattr(wfunc, '__wrapped__', False) == False:
|
||||||
|
setattr(newtype, 'write_' + pname, wrapped_wfunc)
|
||||||
|
wrapped_wfunc.__wrapped__ = True
|
||||||
|
|
||||||
def getter(self, pname=pname):
|
def getter(self, pname=pname):
|
||||||
return self.PARAMS[pname].value
|
return self.PARAMS[pname].value
|
||||||
|
|
||||||
def setter(self, value, pname=pname):
|
def setter(self, value, pname=pname):
|
||||||
pobj = self.PARAMS[pname]
|
pobj = self.PARAMS[pname]
|
||||||
value = pobj.datatype.validate(value) if pobj.datatype else value
|
value = pobj.datatype.validate(value)
|
||||||
pobj.timestamp = time.time()
|
pobj.timestamp = time.time()
|
||||||
if not EVENT_ONLY_ON_CHANGED_VALUES or (value != pobj.value):
|
if not EVENT_ONLY_ON_CHANGED_VALUES or (value != pobj.value):
|
||||||
pobj.value = value
|
pobj.value = value
|
||||||
@ -251,13 +315,7 @@ class Device(object):
|
|||||||
# make local copies of PARAMS
|
# make local copies of PARAMS
|
||||||
params = {}
|
params = {}
|
||||||
for k, v in self.PARAMS.items()[:]:
|
for k, v in self.PARAMS.items()[:]:
|
||||||
#params[k] = PARAM(v)
|
params[k] = v.copy()
|
||||||
# PARAM: type(v) -> PARAM
|
|
||||||
# type(v)(v) -> PARAM(v)
|
|
||||||
# EPICS_PARAM: type(v) -> EPICS_PARAM
|
|
||||||
# type(v)(v) -> EPICS_PARAM(v)
|
|
||||||
param_type = type(v)
|
|
||||||
params[k] = param_type(v)
|
|
||||||
|
|
||||||
self.PARAMS = params
|
self.PARAMS = params
|
||||||
|
|
||||||
@ -273,13 +331,13 @@ class Device(object):
|
|||||||
mycls = self.__class__
|
mycls = self.__class__
|
||||||
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
||||||
self.PROPERTIES['implementation'] = myclassname
|
self.PROPERTIES['implementation'] = myclassname
|
||||||
self.PROPERTIES['interfaces'] = [b.__name__ for b in mycls.__mro__
|
self.PROPERTIES['interfaces'] = [
|
||||||
if b.__module__.startswith('secop.devices.core')]
|
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')]
|
||||||
self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
|
self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
|
||||||
|
|
||||||
# remove unset (default) module properties
|
# remove unset (default) module properties
|
||||||
for k, v in self.PROPERTIES.items():
|
for k, v in self.PROPERTIES.items():
|
||||||
if v == None:
|
if v is None:
|
||||||
del self.PROPERTIES[k]
|
del self.PROPERTIES[k]
|
||||||
|
|
||||||
# check and apply parameter_properties
|
# check and apply parameter_properties
|
||||||
@ -312,13 +370,13 @@ class Device(object):
|
|||||||
cfgdict[k] = v.default
|
cfgdict[k] = v.default
|
||||||
|
|
||||||
# replace CLASS level PARAM objects with INSTANCE level ones
|
# replace CLASS level PARAM objects with INSTANCE level ones
|
||||||
#self.PARAMS[k] = PARAM(self.PARAMS[k])
|
self.PARAMS[k] = self.PARAMS[k].copy()
|
||||||
param_type = type(self.PARAMS[k])
|
|
||||||
self.PARAMS[k] = param_type(self.PARAMS[k])
|
|
||||||
|
|
||||||
# now 'apply' config:
|
# now 'apply' config:
|
||||||
# pass values through the datatypes and store as attributes
|
# pass values through the datatypes and store as attributes
|
||||||
for k, v in cfgdict.items():
|
for k, v in cfgdict.items():
|
||||||
|
if k == 'value':
|
||||||
|
continue
|
||||||
# apply datatype, complain if type does not fit
|
# apply datatype, complain if type does not fit
|
||||||
datatype = self.PARAMS[k].datatype
|
datatype = self.PARAMS[k].datatype
|
||||||
if datatype is not None:
|
if datatype is not None:
|
||||||
@ -334,11 +392,7 @@ class Device(object):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
# may be overriden in derived classes to init stuff
|
# may be overriden in derived classes to init stuff
|
||||||
self.log.debug('init()')
|
self.log.debug('empty init()')
|
||||||
|
|
||||||
def _pollThread(self):
|
|
||||||
# may be overriden in derived classes to init stuff
|
|
||||||
self.log.debug('init()')
|
|
||||||
|
|
||||||
|
|
||||||
class Readable(Device):
|
class Readable(Device):
|
||||||
@ -348,7 +402,7 @@ class Readable(Device):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current value of the device', readonly=True, default=0.,
|
'value': PARAM('current value of the device', readonly=True, default=0.,
|
||||||
datatype=FloatRange()),
|
datatype=FloatRange(), poll=True),
|
||||||
'pollinterval': PARAM('sleeptime between polls', default=5,
|
'pollinterval': PARAM('sleeptime between polls', default=5,
|
||||||
readonly=False, datatype=FloatRange(0.1, 120), ),
|
readonly=False, datatype=FloatRange(0.1, 120), ),
|
||||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||||
@ -360,25 +414,38 @@ class Readable(Device):
|
|||||||
'UNSTABLE': status.UNSTABLE,
|
'UNSTABLE': status.UNSTABLE,
|
||||||
'ERROR': status.ERROR,
|
'ERROR': status.ERROR,
|
||||||
'UNKNOWN': status.UNKNOWN
|
'UNKNOWN': status.UNKNOWN
|
||||||
}), StringType() ),
|
}), StringType()),
|
||||||
readonly=True),
|
readonly=True, poll=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
Device.init(self)
|
Device.init(self)
|
||||||
self._pollthread = threading.Thread(target=self._pollThread)
|
self._pollthread = threading.Thread(target=self.__pollThread)
|
||||||
self._pollthread.daemon = True
|
self._pollthread.daemon = True
|
||||||
self._pollthread.start()
|
self._pollthread.start()
|
||||||
|
|
||||||
def _pollThread(self):
|
def __pollThread(self):
|
||||||
"""super simple and super stupid per-module polling thread"""
|
"""super simple and super stupid per-module polling thread"""
|
||||||
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
time.sleep(self.pollinterval)
|
i = 1
|
||||||
for pname in self.PARAMS:
|
try:
|
||||||
if pname != 'pollinterval':
|
time.sleep(self.pollinterval)
|
||||||
rfunc = getattr(self, 'read_%s' % pname, None)
|
except TypeError:
|
||||||
if rfunc:
|
time.sleep(max(self.pollinterval))
|
||||||
rfunc()
|
try:
|
||||||
|
self.poll(i)
|
||||||
|
except Exception: # really ALL
|
||||||
|
pass
|
||||||
|
|
||||||
|
def poll(self, nr):
|
||||||
|
for pname, pobj in self.PARAMS.iteritems():
|
||||||
|
if not pobj.poll:
|
||||||
|
continue
|
||||||
|
if 0 == nr % int(pobj.poll):
|
||||||
|
rfunc = getattr(self, 'read_' + pname, None)
|
||||||
|
if rfunc:
|
||||||
|
rfunc()
|
||||||
|
|
||||||
|
|
||||||
class Driveable(Readable):
|
class Driveable(Readable):
|
||||||
@ -387,9 +454,12 @@ class Driveable(Readable):
|
|||||||
providing a settable 'target' parameter to those of a Readable
|
providing a settable 'target' parameter to those of a Readable
|
||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'target': PARAM('target value of the device', default=0., readonly=False,
|
'target': PARAM(
|
||||||
datatype=FloatRange(),
|
'target value of the device',
|
||||||
),
|
default=0.,
|
||||||
|
readonly=False,
|
||||||
|
datatype=FloatRange(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
# XXX: CMDS ???? auto deriving working well enough?
|
# XXX: CMDS ???? auto deriving working well enough?
|
||||||
|
|
@ -96,8 +96,8 @@ class Dispatcher(object):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.log.exception(err)
|
self.log.exception(err)
|
||||||
reply = msg.get_error(
|
reply = msg.get_error(
|
||||||
errorclass='InternalError',
|
errorclass='InternalError', errorinfo=[
|
||||||
errorinfo=[formatException(), str(msg), formatExtendedStack()])
|
formatException(), str(msg), formatExtendedStack()])
|
||||||
else:
|
else:
|
||||||
self.log.debug('Can not handle msg %r' % msg)
|
self.log.debug('Can not handle msg %r' % msg)
|
||||||
reply = self.unhandled(conn, msg)
|
reply = self.unhandled(conn, msg)
|
||||||
@ -125,7 +125,7 @@ class Dispatcher(object):
|
|||||||
msg = Value(
|
msg = Value(
|
||||||
moduleobj.name,
|
moduleobj.name,
|
||||||
parameter=pname,
|
parameter=pname,
|
||||||
value=pobj.value,
|
value=pobj.export_value,
|
||||||
t=pobj.timestamp)
|
t=pobj.timestamp)
|
||||||
self.broadcast_event(msg)
|
self.broadcast_event(msg)
|
||||||
|
|
||||||
@ -215,8 +215,9 @@ class Dispatcher(object):
|
|||||||
for modulename in self._export:
|
for modulename in self._export:
|
||||||
module = self.get_module(modulename)
|
module = self.get_module(modulename)
|
||||||
# some of these need rework !
|
# some of these need rework !
|
||||||
mod_desc = {'parameters':[], 'commands':[]}
|
mod_desc = {'parameters': [], 'commands': []}
|
||||||
for pname, param in self.list_module_params(modulename, only_static=True).items():
|
for pname, param in self.list_module_params(
|
||||||
|
modulename, only_static=True).items():
|
||||||
mod_desc['parameters'].extend([pname, param])
|
mod_desc['parameters'].extend([pname, param])
|
||||||
for cname, cmd in self.list_module_cmds(modulename).items():
|
for cname, cmd in self.list_module_cmds(modulename).items():
|
||||||
mod_desc['commands'].extend([cname, cmd])
|
mod_desc['commands'].extend([cname, cmd])
|
||||||
@ -236,7 +237,9 @@ class Dispatcher(object):
|
|||||||
module = self.get_module(modulename)
|
module = self.get_module(modulename)
|
||||||
# some of these need rework !
|
# some of these need rework !
|
||||||
dd = {
|
dd = {
|
||||||
'parameters': self.list_module_params(modulename, only_static=True),
|
'parameters': self.list_module_params(
|
||||||
|
modulename,
|
||||||
|
only_static=True),
|
||||||
'commands': self.list_module_cmds(modulename),
|
'commands': self.list_module_cmds(modulename),
|
||||||
'properties': module.PROPERTIES,
|
'properties': module.PROPERTIES,
|
||||||
}
|
}
|
||||||
@ -319,9 +322,9 @@ class Dispatcher(object):
|
|||||||
return Value(
|
return Value(
|
||||||
modulename,
|
modulename,
|
||||||
parameter=pname,
|
parameter=pname,
|
||||||
value=pobj.value,
|
value=pobj.export_value,
|
||||||
t=pobj.timestamp)
|
t=pobj.timestamp)
|
||||||
return Value(modulename, parameter=pname, value=pobj.value)
|
return Value(modulename, parameter=pname, value=pobj.export_value)
|
||||||
|
|
||||||
# now the (defined) handlers for the different requests
|
# now the (defined) handlers for the different requests
|
||||||
def handle_Help(self, conn, msg):
|
def handle_Help(self, conn, msg):
|
||||||
@ -396,7 +399,7 @@ class Dispatcher(object):
|
|||||||
res = Value(
|
res = Value(
|
||||||
module=modulename,
|
module=modulename,
|
||||||
parameter=pname,
|
parameter=pname,
|
||||||
value=pobj.value,
|
value=pobj.export_value,
|
||||||
t=pobj.timestamp,
|
t=pobj.timestamp,
|
||||||
unit=pobj.unit)
|
unit=pobj.unit)
|
||||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||||
|
@ -25,7 +25,7 @@ import time
|
|||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from secop.devices.core import Driveable, CMD, PARAM
|
from secop.modules import Driveable, CMD, PARAM
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
from secop.datatypes import FloatRange, EnumType, TupleOf
|
from secop.datatypes import FloatRange, EnumType, TupleOf
|
||||||
from secop.lib import clamp, mkthread
|
from secop.lib import clamp, mkthread
|
||||||
@ -66,15 +66,15 @@ class Cryostat(CryoBase):
|
|||||||
datatype=FloatRange(0), default=1, unit="W",
|
datatype=FloatRange(0), default=1, unit="W",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
heater=PARAM("current heater setting",
|
heater=PARAM("current heater setting",
|
||||||
datatype=FloatRange(0, 100), default=0, unit="%",
|
datatype=FloatRange(0, 100), default=0, unit="%",
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
heaterpower=PARAM("current heater power",
|
heaterpower=PARAM("current heater power",
|
||||||
datatype=FloatRange(0), default=0, unit="W",
|
datatype=FloatRange(0), default=0, unit="W",
|
||||||
group='heater_settings',
|
group='heater_settings',
|
||||||
),
|
),
|
||||||
target=PARAM("target temperature",
|
target=PARAM("target temperature",
|
||||||
datatype=FloatRange(0), default=0, unit="K",
|
datatype=FloatRange(0), default=0, unit="K",
|
||||||
readonly=False,
|
readonly=False,
|
||||||
@ -112,21 +112,23 @@ class Cryostat(CryoBase):
|
|||||||
datatype=FloatRange(0, 100), default=0.1, unit='K',
|
datatype=FloatRange(0, 100), default=0.1, unit='K',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
window=PARAM("time window for stability checking",
|
window=PARAM("time window for stability checking",
|
||||||
datatype=FloatRange(1, 900), default=30, unit='s',
|
datatype=FloatRange(1, 900), default=30, unit='s',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
timeout=PARAM("max waiting time for stabilisation check",
|
timeout=PARAM("max waiting time for stabilisation check",
|
||||||
datatype=FloatRange(1, 36000), default=900, unit='s',
|
datatype=FloatRange(1, 36000), default=900, unit='s',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
group='stability',
|
group='stability',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
CMDS = dict(
|
CMDS = dict(
|
||||||
Stop=CMD("Stop ramping the setpoint\n\nby setting the current setpoint as new target",
|
Stop=CMD(
|
||||||
[], None),
|
"Stop ramping the setpoint\n\nby setting the current setpoint as new target",
|
||||||
|
[],
|
||||||
|
None),
|
||||||
)
|
)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
@ -24,7 +24,7 @@ import time
|
|||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from secop.devices.core import Readable, Driveable, PARAM
|
from secop.modules import Readable, Driveable, PARAM
|
||||||
from secop.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
|
from secop.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
|
|
||||||
@ -287,18 +287,18 @@ class DatatypesTest(Readable):
|
|||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'enum': PARAM('enum',
|
'enum': PARAM(
|
||||||
datatype=EnumType('boo', 'faar', z=9), readonly=False, default=1),
|
'enum', datatype=EnumType(
|
||||||
'tupleof': PARAM('tuple of int, float and str',
|
'boo', 'faar', z=9), readonly=False, default=1), 'tupleof': PARAM(
|
||||||
datatype=TupleOf(IntRange(), FloatRange(), StringType()), readonly=False, default=(1, 2.3, 'a')),
|
'tuple of int, float and str', datatype=TupleOf(
|
||||||
'arrayof': PARAM('array: 2..3 times bool',
|
IntRange(), FloatRange(), StringType()), readonly=False, default=(
|
||||||
datatype=ArrayOf(BoolType(), 2, 3), readonly=False, default=[1, 0, 1]),
|
1, 2.3, 'a')), 'arrayof': PARAM(
|
||||||
'intrange': PARAM('intrange',
|
'array: 2..3 times bool', datatype=ArrayOf(
|
||||||
datatype=IntRange(2, 9), readonly=False, default=4),
|
BoolType(), 2, 3), readonly=False, default=[
|
||||||
'floatrange': PARAM('floatrange',
|
1, 0, 1]), 'intrange': PARAM(
|
||||||
datatype=FloatRange(-1, 1), readonly=False, default=0,
|
'intrange', datatype=IntRange(
|
||||||
),
|
2, 9), readonly=False, default=4), 'floatrange': PARAM(
|
||||||
'struct': PARAM('struct(a=str, b=int, c=bool)',
|
'floatrange', datatype=FloatRange(
|
||||||
datatype=StructOf(a=StringType(), b=IntRange(), c=BoolType()),
|
-1, 1), readonly=False, default=0, ), 'struct': PARAM(
|
||||||
),
|
'struct(a=str, b=int, c=bool)', datatype=StructOf(
|
||||||
}
|
a=StringType(), b=IntRange(), c=BoolType()), ), }
|
@ -22,9 +22,10 @@
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from secop.devices.core import Readable, Driveable, PARAM
|
from secop.modules import Readable, Driveable, PARAM
|
||||||
from secop.datatypes import FloatRange, StringType
|
from secop.datatypes import FloatRange, StringType
|
||||||
|
|
||||||
|
|
||||||
class LN2(Readable):
|
class LN2(Readable):
|
||||||
"""Just a readable.
|
"""Just a readable.
|
||||||
|
|
||||||
@ -62,12 +63,20 @@ class Temp(Driveable):
|
|||||||
but the implementation may do anything
|
but the implementation may do anything
|
||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM(
|
||||||
datatype=StringType(8,16), readonly=True,
|
"Sensor number or calibration id",
|
||||||
),
|
datatype=StringType(
|
||||||
'target': PARAM("Target temperature",
|
8,
|
||||||
default=300.0, datatype=FloatRange(0), readonly=False, unit='K',
|
16),
|
||||||
),
|
readonly=True,
|
||||||
|
),
|
||||||
|
'target': PARAM(
|
||||||
|
"Target temperature",
|
||||||
|
default=300.0,
|
||||||
|
datatype=FloatRange(0),
|
||||||
|
readonly=False,
|
||||||
|
unit='K',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
0
secop_ess/__init__.py
Normal file
0
secop_ess/__init__.py
Normal file
@ -24,7 +24,7 @@ import random
|
|||||||
|
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType
|
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType
|
||||||
from secop.devices.core import Readable, Device, Driveable, PARAM
|
from secop.modules import Readable, Device, Driveable, PARAM
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -62,18 +62,18 @@ class EpicsReadable(Readable):
|
|||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('EPICS generic value',
|
'value': PARAM('EPICS generic value',
|
||||||
datatype=FloatRange(),
|
datatype=FloatRange(),
|
||||||
default=300.0,),
|
default=300.0,),
|
||||||
'epics_version': PARAM("EPICS version used, v3 or v4",
|
'epics_version': PARAM("EPICS version used, v3 or v4",
|
||||||
datatype=EnumType(v3=3, v4=4),),
|
datatype=EnumType(v3=3, v4=4),),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'value_pv': PARAM('EPICS pv_name of value',
|
'value_pv': PARAM('EPICS pv_name of value',
|
||||||
datatype=StringType(),
|
datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status',
|
'status_pv': PARAM('EPICS pv_name of status',
|
||||||
datatype=StringType(),
|
datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generic read and write functions
|
# Generic read and write functions
|
||||||
@ -122,19 +122,19 @@ class EpicsDriveable(Driveable):
|
|||||||
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
"""EpicsDriveable handles a Driveable interfacing to EPICS v4"""
|
||||||
# Commmon PARAMS for all EPICS devices
|
# Commmon PARAMS for all EPICS devices
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'target': PARAM('EPICS generic target', datatype=FloatRange(),
|
'target': PARAM('EPICS generic target', datatype=FloatRange(),
|
||||||
default=300.0, readonly=False),
|
default=300.0, readonly=False),
|
||||||
'value': PARAM('EPICS generic value', datatype=FloatRange(),
|
'value': PARAM('EPICS generic value', datatype=FloatRange(),
|
||||||
default=300.0,),
|
default=300.0,),
|
||||||
'epics_version': PARAM("EPICS version used, v3 or v4",
|
'epics_version': PARAM("EPICS version used, v3 or v4",
|
||||||
datatype=StringType(),),
|
datatype=StringType(),),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'target_pv': PARAM('EPICS pv_name of target', datatype=StringType(),
|
'target_pv': PARAM('EPICS pv_name of target', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'value_pv': PARAM('EPICS pv_name of value', datatype=StringType(),
|
'value_pv': PARAM('EPICS pv_name of value', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
'status_pv': PARAM('EPICS pv_name of status', datatype=StringType(),
|
'status_pv': PARAM('EPICS pv_name of status', datatype=StringType(),
|
||||||
default="unset", export=False),
|
default="unset", export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generic read and write functions
|
# Generic read and write functions
|
||||||
@ -182,8 +182,11 @@ class EpicsDriveable(Driveable):
|
|||||||
# XXX: how to map an unknown type+value to an valid status ???
|
# XXX: how to map an unknown type+value to an valid status ???
|
||||||
return status.UNKNOWN, self._read_pv(self.status_pv)
|
return status.UNKNOWN, self._read_pv(self.status_pv)
|
||||||
# status_pv is unset, derive status from equality of value + target
|
# status_pv is unset, derive status from equality of value + target
|
||||||
return (status.OK, '') if self.read_value() == self.read_target() else \
|
return (
|
||||||
(status.BUSY, 'Moving')
|
status.OK,
|
||||||
|
'') if self.read_value() == self.read_target() else (
|
||||||
|
status.BUSY,
|
||||||
|
'Moving')
|
||||||
|
|
||||||
|
|
||||||
"""Temperature control loop"""
|
"""Temperature control loop"""
|
||||||
@ -195,11 +198,11 @@ class EpicsTempCtrl(EpicsDriveable):
|
|||||||
|
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
# TODO: restrict possible values with oneof datatype
|
# TODO: restrict possible values with oneof datatype
|
||||||
'heaterrange': PARAM('Heater range', datatype=StringType(),
|
'heaterrange': PARAM('Heater range', datatype=StringType(),
|
||||||
default='Off', readonly=False,),
|
default='Off', readonly=False,),
|
||||||
'tolerance': PARAM('allowed deviation between value and target',
|
'tolerance': PARAM('allowed deviation between value and target',
|
||||||
datatype=FloatRange(1e-6, 1e6), default=0.1,
|
datatype=FloatRange(1e-6, 1e6), default=0.1,
|
||||||
readonly=False,),
|
readonly=False,),
|
||||||
# 'private' parameters: not remotely accessible
|
# 'private' parameters: not remotely accessible
|
||||||
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
'heaterrange_pv': PARAM('EPICS pv_name of heater range',
|
||||||
datatype=StringType(), default="unset", export=False,),
|
datatype=StringType(), default="unset", export=False,),
|
||||||
@ -221,7 +224,11 @@ class EpicsTempCtrl(EpicsDriveable):
|
|||||||
# XXX: comparison may need to collect a history to detect oscillations
|
# XXX: comparison may need to collect a history to detect oscillations
|
||||||
at_target = abs(self.read_value(maxage) - self.read_target(maxage)) \
|
at_target = abs(self.read_value(maxage) - self.read_target(maxage)) \
|
||||||
<= self.tolerance
|
<= self.tolerance
|
||||||
return (status.OK, 'at Target') if at_target else (status.BUSY, 'Moving')
|
return (
|
||||||
|
status.OK,
|
||||||
|
'at Target') if at_target else (
|
||||||
|
status.BUSY,
|
||||||
|
'Moving')
|
||||||
|
|
||||||
# TODO: add support for strings over epics pv
|
# TODO: add support for strings over epics pv
|
||||||
# def read_heaterrange(self, maxage=0):
|
# def read_heaterrange(self, maxage=0):
|
0
secop_mlz/__init__.py
Normal file
0
secop_mlz/__init__.py
Normal file
1024
secop_mlz/entangle.py
Normal file
1024
secop_mlz/entangle.py
Normal file
File diff suppressed because it is too large
Load Diff
83
test/test_client_baseclient.py
Normal file
83
test/test_client_baseclient.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# -*- 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>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""test base client."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, sys.path[0]+'/..')
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from secop.client.baseclient import Client
|
||||||
|
|
||||||
|
# define Test-only connection object
|
||||||
|
class TestConnect(object):
|
||||||
|
callbacks = []
|
||||||
|
def writeline(self, line):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def clientobj(request):
|
||||||
|
print (" SETUP ClientObj")
|
||||||
|
testconnect = TestConnect()
|
||||||
|
yield Client(dict(testing=testconnect), autoconnect=False)
|
||||||
|
for cb, arg in testconnect.callbacks:
|
||||||
|
cb(arg)
|
||||||
|
print (" TEARDOWN ClientObj")
|
||||||
|
|
||||||
|
|
||||||
|
def test_describing_data_decode(clientobj):
|
||||||
|
assert OrderedDict([('a',1)]) == clientobj._decode_list_to_ordereddict(['a',1])
|
||||||
|
assert {'modules':{}, 'properties':{}} == clientobj._decode_substruct(['modules'],{})
|
||||||
|
describing_data = {'equipment_id': 'eid',
|
||||||
|
'modules': ['LN2', {'commands': [],
|
||||||
|
'interfaces': ['Readable', 'Device'],
|
||||||
|
'parameters': ['value', {'datatype': ['double'],
|
||||||
|
'description': 'current value',
|
||||||
|
'readonly': True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
decoded_data = {'modules': {'LN2': {'commands': {},
|
||||||
|
'parameters': {'value': {'datatype': ['double'],
|
||||||
|
'description': 'current value',
|
||||||
|
'readonly': True,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'properties': {'interfaces': ['Readable', 'Device']}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'properties': {'equipment_id': 'eid',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a = clientobj._decode_substruct(['modules'], describing_data)
|
||||||
|
for modname, module in a['modules'].items():
|
||||||
|
a['modules'][modname] = clientobj._decode_substruct(['parameters', 'commands'], module)
|
||||||
|
assert a == decoded_data
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user