fix transport and display of node-properties

Change-Id: I35a3021768e386a5ce922c8e24128d0bc3a039be
This commit is contained in:
Enrico Faulhaber
2017-09-12 16:10:22 +02:00
parent 7d5b211a0e
commit 96ac437fd3
18 changed files with 292 additions and 225 deletions

View File

@ -179,7 +179,6 @@ texinfo_documents = [
]
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
@ -201,6 +200,5 @@ epub_copyright = copyright
epub_exclude_files = ['search.html']
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}

View File

@ -1,6 +1,5 @@
[equipment]
id=MLZ_amagnet(Garfield)
.visibility=expert
[equipment MLZ_amagnet(Garfield)]
visibility=expert
foo=bar
[interface tcp]

View File

@ -1,5 +1,5 @@
[equipment]
id=ccr12
[node ccr12]
description = CCR12 box of MLZ Sample environment group
[interface tcp]
interface=tcp

View File

@ -1,14 +1,9 @@
[equipment]
[equipment cryo_7]
# set SEC-node properties
id=cryo_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
.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
[interface tcp]
interface=tcp

View File

@ -1,5 +1,5 @@
[equipment]
id=Equipment_ID_for_demonstration
[equipment Equipment_ID_for_demonstration]
description = virtual modules to play around with
[interface tcp]
interface=tcp

View File

@ -1,5 +1,4 @@
[equipment]
id=see_demo_equipment
[equipment see_demo_equipment]
[client]
connectto=0.0.0.0

View File

@ -1,5 +1,12 @@
[equipment]
id=test config
[node test config]
description=description of the testing sec-node
.
Here should be the long description.
It can be very long.
.
a single . on a line gets stripped so you can make paragraphs.
.
These texts are supposed to be possibly very long.
[interface tcp]
interface=tcp

View File

@ -317,7 +317,8 @@ class Client(object):
if data:
self._cache.setdefault(modname, {})[pname] = Value(*data)
else:
self.log.warning('got malformed answer! (spec data)' % (spec, data))
self.log.warning(
'got malformed answer! (spec data)' % (spec, data))
# self.log.info('cache: %s:%s=%r (was: %s)', modname, pname, data, previous)
if spec in self.callbacks:
for func in self.callbacks[spec]:

View File

@ -340,25 +340,30 @@ class BoolType(DataType):
class ArrayOf(DataType):
def __init__(self, subtype, minsize_or_size=None, maxsize=None):
if maxsize is None:
maxsize = minsize_or_size
self.minsize = minsize_or_size
self.maxsize = maxsize
if self.minsize is not None and self.maxsize is not None and \
self.minsize > self.maxsize:
raise ValueError('minsize must be less than or equal to maxsize!')
def __init__(self, subtype, minsize=0, maxsize=None):
if not isinstance(subtype, DataType):
raise ValueError(
'ArrayOf only works with DataType objs as first argument!')
# if only one arg is given, it is maxsize!
if minsize and not maxsize:
maxsize = minsize
minsize = 0
self.as_json = ['array', subtype.as_json, maxsize]
elif maxsize:
self.as_json = ['array', subtype.as_json, maxsize, minsize]
else:
self.as_json = ['array', subtype.as_json]
self.minsize = minsize or 0
self.maxsize = maxsize
self.subtype = subtype
self.as_json = ['array', self.subtype.as_json,
self.maxsize, self.minsize]
if self.minsize is not None and self.minsize < 0:
if self.maxsize is not None and self.minsize > maxsize:
raise ValueError('minsize must be less than or equal to maxsize!')
if self.minsize < 0:
raise ValueError('Minimum size must be >= 0!')
if self.maxsize is not None and self.maxsize < 1:
raise ValueError('Maximum size must be >= 1!')
if self.minsize is not None and self.maxsize is not None and self.minsize > self.maxsize:
if self.maxsize is not None and self.minsize > self.maxsize:
raise ValueError('Maximum size must be >= Minimum size')
def __repr__(self):
@ -534,10 +539,12 @@ DATATYPES = dict(
bool=lambda: BoolType(),
int=lambda _min=None, _max=None: IntRange(_min, _max),
double=lambda _min=None, _max=None: FloatRange(_min, _max),
blob=lambda _min=None, _max=None: BLOBType(_min, _max),
string=lambda _min=None, _max=None: StringType(_min, _max),
array=lambda subtype, _min=None, _max=None: ArrayOf(
get_datatype(subtype), _min, _max),
blob=lambda _max=None, _min=None: BLOBType(
_min, _max) if _min else BLOBType(_max),
string=lambda _max=None, _min=None: StringType(
_min, _max) if _min else StringType(_max),
array=lambda subtype, _max=None, _min=None: ArrayOf(
get_datatype(subtype), _min, _max) if _min else ArrayOf(getdatatype(subtype), _min),
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
enum=lambda kwds: EnumType(**kwds),
struct=lambda named_subtypes: StructOf(

View File

@ -43,7 +43,8 @@ class NodeCtrl(QWidget):
self.contactPointLabel.setText(self._node.contactPoint)
self.equipmentIdLabel.setText(self._node.equipmentId)
self.protocolVersionLabel.setText(self._node.protocolVersion)
self.nodeDescriptionLabel.setText(self._node.describingData.get('description','no description available'))
self.nodeDescriptionLabel.setText(self._node.describingData['properties'].get(
'description', 'no description available'))
self._clearLog()
# now populate modules tab
@ -168,7 +169,8 @@ class ReadableWidget(QWidget):
self._node = node
self._module = module
self._status_type = self._node.getProperties(self._module, 'status').get('datatype')
self._status_type = self._node.getProperties(
self._module, 'status').get('datatype')
params = self._node.getProperties(self._module, 'value')
datatype = params.get('datatype', StringType())
@ -198,9 +200,11 @@ class ReadableWidget(QWidget):
if pname in params:
return params[pname].value
try:
# if queried, we get the qualifiers as well, but don't want them here
# if queried, we get the qualifiers as well, but don't want them
# here
import mlzlog
mlzlog.getLogger('cached values').warn('no cached value for %s:%s' % (self._module, pname))
mlzlog.getLogger('cached values').warn(
'no cached value for %s:%s' % (self._module, pname))
val = self._node.getParameter(self._module, pname)[0]
return val
except Exception:

View File

@ -82,7 +82,20 @@ p, li { white-space: pre-wrap; }
<string>NodeInfo</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea_2">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>610</width>
<height>413</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
@ -159,9 +172,15 @@ p, li { white-space: pre-wrap; }
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="nodeDescriptionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Description
long line</string>
<string>aaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaa</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@ -172,19 +191,10 @@ long line</string>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
<zorder>label_2</zorder>
<zorder>gridLayoutWidget</zorder>
</widget>
</widget>
</item>
</layout>
</widget>

View File

@ -49,7 +49,6 @@ CONFIG = {
}
class lazy_property(object):
"""A property that calculates its value only once."""

View File

@ -49,7 +49,12 @@ from secop.lib import formatExtendedStack, formatException
class Dispatcher(object):
def __init__(self, logger, options):
self.equipment_id = options.pop('equipment_id')
# to avoid errors, we want to eat all options here
self.equipment_id = options['equipment_id']
self.nodeopts = {}
for k in list(options):
self.nodeopts[k] = options.pop(k)
self.log = logger
# map ALL modulename -> moduleobj
self._modules = {}
@ -227,6 +232,7 @@ class Dispatcher(object):
result['equipment_id'] = self.equipment_id
result['firmware'] = 'The SECoP playground'
result['version'] = "2017.07"
result.update(self.nodeopts)
# XXX: what else?
return result

View File

@ -107,7 +107,8 @@ class Server(object):
deviceopts = []
interfaceopts = []
equipment_id = 'unknown'
equipment_id = None
nodeopts = []
for section in parser.sections():
if section.lower().startswith('device '):
# device section
@ -135,11 +136,30 @@ class Server(object):
(self._cfgfile, ifname))
# all went well so far
interfaceopts.append([ifname, ifopts])
if parser.has_option('equipment', 'id'):
equipment_id = parser.get('equipment', 'id').replace(' ', '_')
if section.lower().startswith('equipment ') or section.lower().startswith('node '):
if equipment_id is not None:
raise ConfigError('cfgfile %r: only one [node <id>] section allowed, found another [%s]!' % (
self._cfgfile, section))
# equipment/node settings
equipment_id = section.split(' ', 1)[1].replace(' ', '_')
nodeopts = dict(item for item in parser.items(section))
nodeopts['equipment_id'] = equipment_id
nodeopts['id'] = equipment_id
# MAGIC: transform \n.\n into \n\n which are normally stripped
# by the ini parser
for k in nodeopts:
v = nodeopts[k]
while '\n.\n' in v:
v = v.replace('\n.\n', '\n\n')
nodeopts[k] = v
if equipment_id is None:
self.log.error('Need a [node <id>] section, none found!')
raise ConfigError(
'cfgfile %r: need an [node <id>] option!' % (self._cfgfile))
self._dispatcher = self._buildObject(
'Dispatcher', Dispatcher, dict(equipment_id=equipment_id))
'Dispatcher', Dispatcher, nodeopts)
self._processInterfaceOptions(interfaceopts)
self._processModuleOptions(deviceopts)

View File

@ -75,7 +75,8 @@ class GarfieldMagnet(SequencerMixin, Drivable):
default=(1.0, 0.0, 0.0, 0.0, 0.0)),
'calibrationtable': PARAM('Map of Coefficients for calibration per symmetry setting',
datatype=StructOf(symmetric=ArrayOf(FloatRange(), 5, 5),
short=ArrayOf(FloatRange(), 5, 5),
short=ArrayOf(
FloatRange(), 5, 5),
asymmetric=ArrayOf(FloatRange(), 5, 5)), export=False),
}

View File

@ -30,8 +30,11 @@ from collections import OrderedDict
from secop.client.baseclient import Client
# define Test-only connection object
class TestConnect(object):
callbacks = []
def writeline(self, line):
pass
@ -50,8 +53,10 @@ def clientobj(request):
def test_describing_data_decode(clientobj):
assert OrderedDict([('a',1)]) == clientobj._decode_list_to_ordereddict(['a',1])
assert {'modules':{}, 'properties':{}} == clientobj._decode_substruct(['modules'],{})
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', 'Module'],
@ -78,6 +83,6 @@ def test_describing_data_decode(clientobj):
a = clientobj._decode_substruct(['modules'], describing_data)
for modname, module in a['modules'].items():
a['modules'][modname] = clientobj._decode_substruct(['parameters', 'commands'], module)
a['modules'][modname] = clientobj._decode_substruct(
['parameters', 'commands'], module)
assert a == decoded_data

View File

@ -41,6 +41,7 @@ def test_DataType():
dt.validate('')
dt.export()
def test_FloatRange():
dt = FloatRange(-3.14, 3.14)
assert dt.as_json == ['double', -3.14, 3.14]
@ -62,6 +63,7 @@ def test_FloatRange():
dt = FloatRange()
assert dt.as_json == ['double']
def test_IntRange():
dt = IntRange(-3, 3)
assert dt.as_json == ['int', -3, 3]
@ -82,6 +84,7 @@ def test_IntRange():
dt = IntRange()
assert dt.as_json == ['int']
def test_EnumType():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
@ -113,8 +116,14 @@ def test_EnumType():
with pytest.raises(ValueError):
dt.export(2)
def test_BLOBType():
# test constructor catching illegal arguments
dt = BLOBType()
assert dt.as_json == ['blob']
dt = BLOBType(10)
assert dt.as_json == ['blob', 10]
dt = BLOBType(3, 10)
assert dt.as_json == ['blob', 10, 3]
@ -135,6 +144,11 @@ def test_BLOBType():
def test_StringType():
# test constructor catching illegal arguments
dt = StringType()
assert dt.as_json == ['string']
dt = StringType(12)
assert dt.as_json == ['string', 12]
dt = StringType(4, 11)
assert dt.as_json == ['string', 11, 4]
@ -178,6 +192,11 @@ def test_ArrayOf():
# test constructor catching illegal arguments
with pytest.raises(ValueError):
ArrayOf(int)
dt = ArrayOf(IntRange(-10, 10))
assert dt.as_json == ['array', ['int', -10, 10]]
dt = ArrayOf(IntRange(-10, 10), 5)
assert dt.as_json == ['array', ['int', -10, 10], 5]
dt = ArrayOf(IntRange(-10, 10), 1, 3)
assert dt.as_json == ['array', ['int', -10, 10], 3, 1]
with pytest.raises(ValueError):
@ -232,6 +251,7 @@ def test_StructOf():
assert dt.export({'an_int': 13, 'a_string': 'WFEC'}) == {'a_string': 'WFEC',
'an_int': 13}
def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(1)
@ -256,7 +276,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['int', 1, 2, 3])
assert isinstance(get_datatype(['double']), FloatRange)
assert isinstance(get_datatype(['double', -2.718]), FloatRange)
assert isinstance(get_datatype(['double', None, 3.14]), FloatRange)
@ -267,7 +286,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['double', 1, 2, 3])
with pytest.raises(ValueError):
get_datatype(['enum'])
assert isinstance(get_datatype(['enum', dict(a=-2.718)]), EnumType)
@ -277,7 +295,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['enum', [1, 2, 3]])
assert isinstance(get_datatype(['blob']), BLOBType)
assert isinstance(get_datatype(['blob', 1]), BLOBType)
assert isinstance(get_datatype(['blob', 1, 10]), BLOBType)
@ -287,7 +304,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['blob', 10, -10, 1])
assert isinstance(get_datatype(['string']), StringType)
assert isinstance(get_datatype(['string', 1]), StringType)
assert isinstance(get_datatype(['string', 1, 10]), StringType)
@ -297,7 +313,6 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['string', 10, -10, 1])
with pytest.raises(ValueError):
get_datatype(['array'])
with pytest.raises(ValueError):
@ -314,7 +329,6 @@ def test_get_datatype():
assert isinstance(get_datatype(['array', ['blob'], 1, 10]), ArrayOf)
with pytest.raises(ValueError):
get_datatype(['tuple'])
with pytest.raises(ValueError):
@ -322,7 +336,8 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['tuple', [1], 2, 3])
assert isinstance(get_datatype(['tuple', [['blob']]]), TupleOf)
assert isinstance(get_datatype(['tuple', [['blob']]]).subtypes[0], BLOBType)
assert isinstance(get_datatype(
['tuple', [['blob']]]).subtypes[0], BLOBType)
with pytest.raises(ValueError):
get_datatype(['tuple', [['blob']], -10])
@ -331,7 +346,6 @@ def test_get_datatype():
assert isinstance(get_datatype(['tuple', [['blob'], ['int']]]), TupleOf)
with pytest.raises(ValueError):
get_datatype(['struct'])
with pytest.raises(ValueError):
@ -339,11 +353,13 @@ def test_get_datatype():
with pytest.raises(ValueError):
get_datatype(['struct', [1], 2, 3])
assert isinstance(get_datatype(['struct', {'blob': ['blob']}]), StructOf)
assert isinstance(get_datatype(['struct', {'blob':['blob']}]).named_subtypes['blob'], BLOBType)
assert isinstance(get_datatype(
['struct', {'blob': ['blob']}]).named_subtypes['blob'], BLOBType)
with pytest.raises(ValueError):
get_datatype(['struct', [['blob']], -10])
with pytest.raises(ValueError):
get_datatype(['struct', [['blob']], -10, 10])
assert isinstance(get_datatype(['struct', {'blob':['blob'], 'int':['int']}]), StructOf)
assert isinstance(get_datatype(
['struct', {'blob': ['blob'], 'int':['int']}]), StructOf)