diff --git a/doc/SECoP_Messages.md b/doc/SECoP_Messages.md
index 69a8383..88e208a 100644
--- a/doc/SECoP_Messages.md
+++ b/doc/SECoP_Messages.md
@@ -242,11 +242,11 @@ merge datatype and validator:
-----------------------------
* ["enum", {<number_value>:<json_string>}]
* ["int"] or ["int", <lowest_allowed_value>, <highest_allowed_value>]
- * ["blob"] or ["blob", <minimum_size_in_bytes or 0>, <maximum_size_in_bytes>]
* ["double"] or ["double", <lowest_allowed_value>, <highest_allowed_value>]
- * ["string"] or ["string", <maximum_allowed_length>] or ["string", <min_size>, <max_size>]
* ["bool"]
- * ["array", <basic_data_type>] or ["array", <dtype>, <min_elements>, <max_elements>]
+ * ["blob", <maximum_size_in_bytes>] or ["blob", <minimum_size_in_bytes>, <maximum_size_in_bytes>]
+ * ["string", <maximum_allowed_length>] or ["string", <min_size>, <max_size>]
+ * ["array", <basic_data_type>, <max_elements>] or ["array", <dtype>, <min_elements>, <max_elements>]
* ["tuple", [ <list_of_dtypes ]]
* ["struct", { <name_of_component_as_json_string>:<dtype>}]
@@ -346,3 +346,17 @@ heartbeat
* if the client receives no pong within 3s it may close the connection
* later discussions showed, that the ping/pong should stay untouched and the keepalive time should be (de-)activated by a special message instead. Also the 'connection specific settings' from earlier drafts may be resurrected for this....
+
+11.9.2017
+=========
+
+merge datatype and validator:
+-----------------------------
+ * enum, int, double, bool, tuple, struct as before
+ * ["blob", <maximum_size_in_bytes>] or ["blob", <maximum_size_in_bytes>, <minimum_size_in_bytes>]
+ * ["string", <maximum_allowed_length>] or ["string", <max_size_in_bytes>, <minimum_size_in_bytes>]
+ * ["array", <basic_data_type>, <max_elements>] or ["array", <dtype>, <max_elements>, <min_elements>]
+
+interface_class
+---------------
+ * Drivable, Writable, Readable, Module (first character uppercase, no middle 'e')
diff --git a/doc/json_structure.md b/doc/json_structure.md
index ce3b555..cd25c94 100644
--- a/doc/json_structure.md
+++ b/doc/json_structure.md
@@ -64,7 +64,7 @@ vorerst folgende Festlegung:
],
"group": "very important/stuff",
"implementation": "secop.devices.cryo.Cryostat",
- "interfaces": ["Driveable", "Readable", "Device"],
+ "interfaces": ["Drivable", "Readable", "Device"],
"parameters": ["status", {"readonly": true,
"datatype": ["tuple", ["enum", {"unknown":-1,"idle":100, "warn":200, "unstable":250, "busy":300,"error":400}], "string"],
"description": "current status of the device"
diff --git a/doc/todo.md b/doc/todo.md
index 9822715..fa8a0da 100644
--- a/doc/todo.md
+++ b/doc/todo.md
@@ -38,7 +38,7 @@
## Testsuite ##
* embedded tests inside the actual files grow difficult to maintain
-=> need a testsuite (nose+pylint?)
+=> need a testsuite (pytest)
## docu ##
@@ -48,3 +48,8 @@
+
+## transfer of blobs via json ##
+
+ * use base64
+
diff --git a/secop/client/baseclient.py b/secop/client/baseclient.py
index 416b57a..d656686 100644
--- a/secop/client/baseclient.py
+++ b/secop/client/baseclient.py
@@ -312,7 +312,10 @@ class Client(object):
if modname in self._cache:
if pname in self._cache:
previous = self._cache[modname][pname]
- self._cache.setdefault(modname, {})[pname] = Value(*data)
+ if data:
+ self._cache.setdefault(modname, {})[pname] = Value(*data)
+ else:
+ 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]:
@@ -537,7 +540,7 @@ class Client(object):
return self.describing_data['modules'][module]['properties']
def getModuleBaseClass(self, module):
- return self.getModuleProperties(module)['interface']
+ return self.getModuleProperties(module)['interface_class']
def getCommands(self, module):
return self.describing_data['modules'][module]['commands']
diff --git a/secop/gui/nodectrl.py b/secop/gui/nodectrl.py
index c979e9b..8311d84 100644
--- a/secop/gui/nodectrl.py
+++ b/secop/gui/nodectrl.py
@@ -131,8 +131,8 @@ class NodeCtrl(QWidget):
row = 0
for modname in sorted(self._node.modules):
modprops = self._node.getModuleProperties(modname)
- baseclass = modprops['interface']
- description = modprops['interface']
+ interfaces = modprops['interface_class']
+ description = modprops['description']
unit = self._node.getProperties(modname, 'value').get('unit', '')
if unit:
@@ -142,12 +142,12 @@ class NodeCtrl(QWidget):
label = QLabel(labelstr)
label.setFont(labelfont)
- if baseclass == 'Driveable':
- widget = DriveableWidget(self._node, modname, self)
- elif baseclass == 'Readable':
+ if 'Drivable' in interfaces:
+ widget = DrivableWidget(self._node, modname, self)
+ elif 'Readable' in interfaces:
widget = ReadableWidget(self._node, modname, self)
else:
- widget = QLabel('Unsupported Interfaceclass %r' % baseclass)
+ widget = QLabel('Unsupported Interfaceclasses %r' % interfaces)
if description:
widget.setToolTip(description)
@@ -159,6 +159,7 @@ class NodeCtrl(QWidget):
self._moduleWidgets.extend((label, widget))
layout.setRowStretch(row, 1)
+
class ReadableWidget(QWidget):
def __init__(self, node, module, parent=None):
@@ -166,6 +167,8 @@ class ReadableWidget(QWidget):
self._node = node
self._module = module
+ self._status_type = self._node.getProperties(self._module, 'status').get('datatype')
+
params = self._node.getProperties(self._module, 'value')
datatype = params.get('datatype', StringType())
self._is_enum = isinstance(datatype, EnumType)
@@ -206,7 +209,10 @@ class ReadableWidget(QWidget):
# XXX: also connect update_status signal to LineEdit ??
def update_status(self, status, qualifiers={}):
- self.statusLineEdit.setText(str(status))
+ display_string = self._status_type.subtypes[0].entries.get(status[0])
+ if status[1]:
+ display_string += ':' + status[1]
+ self.statusLineEdit.setText(display_string)
# may change meaning of cmdPushButton
def _init_current_widgets(self):
@@ -241,7 +247,7 @@ class ReadableWidget(QWidget):
self.update_target(*value)
-class DriveableWidget(ReadableWidget):
+class DrivableWidget(ReadableWidget):
def _init_target_widgets(self):
params = self._node.getProperties(self._module, 'target')
diff --git a/secop/gui/ui/modulebuttons.ui b/secop/gui/ui/modulebuttons.ui
index cce4687..14d723c 100644
--- a/secop/gui/ui/modulebuttons.ui
+++ b/secop/gui/ui/modulebuttons.ui
@@ -40,6 +40,9 @@
0
+
+ background-color: lightgrey;
+
true
@@ -67,6 +70,12 @@
-
+
+ true
+
+
+ background-color: lightgrey;
+
true
diff --git a/secop_demo/cryo.py b/secop_demo/cryo.py
index 47b36b6..0c60695 100644
--- a/secop_demo/cryo.py
+++ b/secop_demo/cryo.py
@@ -25,13 +25,13 @@ import time
import random
import threading
-from secop.modules import Driveable, CMD, PARAM
+from secop.modules import Drivable, CMD, PARAM
from secop.protocol import status
from secop.datatypes import FloatRange, EnumType, TupleOf
from secop.lib import clamp, mkthread
-class CryoBase(Driveable):
+class CryoBase(Drivable):
pass
diff --git a/secop_demo/modules.py b/secop_demo/modules.py
index b565525..9ac2124 100644
--- a/secop_demo/modules.py
+++ b/secop_demo/modules.py
@@ -24,12 +24,12 @@ import time
import random
import threading
-from secop.modules import Readable, Driveable, PARAM
+from secop.modules import Readable, Drivable, PARAM
from secop.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
from secop.protocol import status
-class Switch(Driveable):
+class Switch(Drivable):
"""switch it on or off....
"""
PARAMS = {
@@ -94,7 +94,7 @@ class Switch(Driveable):
return info
-class MagneticField(Driveable):
+class MagneticField(Drivable):
"""a liquid magnet
"""
PARAMS = {
@@ -194,7 +194,7 @@ class CoilTemp(Readable):
return round(2.3 + random.random(), 3)
-class SampleTemp(Driveable):
+class SampleTemp(Drivable):
"""a sample temperature
"""
PARAMS = {
diff --git a/secop_demo/test.py b/secop_demo/test.py
index 0ff6ccc..40e385c 100644
--- a/secop_demo/test.py
+++ b/secop_demo/test.py
@@ -22,7 +22,7 @@
import random
-from secop.modules import Readable, Driveable, PARAM
+from secop.modules import Readable, Drivable, PARAM
from secop.datatypes import FloatRange, StringType
@@ -37,7 +37,7 @@ class LN2(Readable):
return round(100 * random.random(), 1)
-class Heater(Driveable):
+class Heater(Drivable):
"""Just a driveable.
class name indicates it to be some heating element,
@@ -56,7 +56,7 @@ class Heater(Driveable):
pass
-class Temp(Driveable):
+class Temp(Drivable):
"""Just a driveable.
class name indicates it to be some temperature controller,
diff --git a/secop_ess/epics.py b/secop_ess/epics.py
index ca41b3f..3c36946 100644
--- a/secop_ess/epics.py
+++ b/secop_ess/epics.py
@@ -23,7 +23,7 @@
import random
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType
-from secop.modules import Readable, Device, Driveable, PARAM
+from secop.modules import Readable, Device, Drivable, PARAM
from secop.protocol import status
try:
@@ -58,7 +58,7 @@ except ImportError:
class EpicsReadable(Readable):
- """EpicsDriveable handles a Driveable interfacing to EPICS v4"""
+ """EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices
PARAMS = {
'value': PARAM('EPICS generic value',
@@ -117,8 +117,8 @@ class EpicsReadable(Readable):
return (status.OK, 'no pv set')
-class EpicsDriveable(Driveable):
- """EpicsDriveable handles a Driveable interfacing to EPICS v4"""
+class EpicsDrivable(Drivable):
+ """EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices
PARAMS = {
'target': PARAM('EPICS generic target', datatype=FloatRange(),
@@ -193,7 +193,7 @@ class EpicsDriveable(Driveable):
# features are agreed upon
-class EpicsTempCtrl(EpicsDriveable):
+class EpicsTempCtrl(EpicsDrivable):
PARAMS = {
# TODO: restrict possible values with oneof datatype
diff --git a/secop_mlz/amagnet.py b/secop_mlz/amagnet.py
index 509a2b8..0c65a68 100644
--- a/secop_mlz/amagnet.py
+++ b/secop_mlz/amagnet.py
@@ -33,10 +33,10 @@ from secop.lib.sequence import SequencerMixin, Step
from secop.protocol import status
from secop.datatypes import *
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError, DisabledError
-from secop.modules import PARAM, CMD, OVERRIDE, Device, Readable, Driveable
+from secop.modules import PARAM, CMD, OVERRIDE, Device, Readable, Drivable
-class GarfieldMagnet(SequencerMixin, Driveable):
+class GarfieldMagnet(SequencerMixin, Drivable):
"""Garfield Magnet
uses a polarity switch ('+' or '-') to flip polarity and an onoff switch
diff --git a/secop_mlz/entangle.py b/secop_mlz/entangle.py
index 565061c..695be6e 100644
--- a/secop_mlz/entangle.py
+++ b/secop_mlz/entangle.py
@@ -41,7 +41,7 @@ from secop.lib import lazy_property, mkthread
from secop.protocol import status
from secop.datatypes import *
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError
-from secop.modules import PARAM, CMD, OVERRIDE, Device, Readable, Driveable
+from secop.modules import PARAM, CMD, OVERRIDE, Device, Readable, Drivable
# Only export these classes for 'from secop_mlz import *'
@@ -394,7 +394,7 @@ class Sensor(AnalogInput):
self._dev.Adjust(value)
-class AnalogOutput(PyTangoDevice, Driveable):
+class AnalogOutput(PyTangoDevice, Drivable):
"""
The AnalogOutput handles all devices which set an analogue value.
@@ -800,7 +800,7 @@ class PartialDigitalInput(NamedDigitalInput):
return value # mapping is done by datatype upon export()
-class DigitalOutput(PyTangoDevice, Driveable):
+class DigitalOutput(PyTangoDevice, Drivable):
"""
A devices that can set and read a digital value corresponding to a
bitfield.