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.