Change-Id: I72abe9b4c2deb08e58ce69786f853ccc4b385a5d
This commit is contained in:
Enrico Faulhaber
2017-09-11 15:22:33 +02:00
parent 9a402857f3
commit 357056d478
12 changed files with 71 additions and 34 deletions

View File

@ -242,11 +242,11 @@ merge datatype and validator:
----------------------------- -----------------------------
* ["enum", {<number_value>:<json_string>}] * ["enum", {<number_value>:<json_string>}]
* ["int"] or ["int", <lowest_allowed_value>, <highest_allowed_value>] * ["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>] * ["double"] or ["double", <lowest_allowed_value>, <highest_allowed_value>]
* ["string"] or ["string", <maximum_allowed_length>] or ["string", <min_size>, <max_size>]
* ["bool"] * ["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 ]] * ["tuple", [ <list_of_dtypes ]]
* ["struct", { <name_of_component_as_json_string>:<dtype>}] * ["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 * 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.... * 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')

View File

@ -64,7 +64,7 @@ vorerst folgende Festlegung:
], ],
"group": "very important/stuff", "group": "very important/stuff",
"implementation": "secop.devices.cryo.Cryostat", "implementation": "secop.devices.cryo.Cryostat",
"interfaces": ["Driveable", "Readable", "Device"], "interfaces": ["Drivable", "Readable", "Device"],
"parameters": ["status", {"readonly": true, "parameters": ["status", {"readonly": true,
"datatype": ["tuple", ["enum", {"unknown":-1,"idle":100, "warn":200, "unstable":250, "busy":300,"error":400}], "string"], "datatype": ["tuple", ["enum", {"unknown":-1,"idle":100, "warn":200, "unstable":250, "busy":300,"error":400}], "string"],
"description": "current status of the device" "description": "current status of the device"

View File

@ -38,7 +38,7 @@
## Testsuite ## ## Testsuite ##
* embedded tests inside the actual files grow difficult to maintain * embedded tests inside the actual files grow difficult to maintain
=> need a testsuite (nose+pylint?) => need a testsuite (pytest)
## docu ## ## docu ##
@ -48,3 +48,8 @@
## transfer of blobs via json ##
* use base64

View File

@ -312,7 +312,10 @@ class Client(object):
if modname in self._cache: if modname in self._cache:
if pname in self._cache: if pname in self._cache:
previous = self._cache[modname][pname] previous = self._cache[modname][pname]
if data:
self._cache.setdefault(modname, {})[pname] = Value(*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) # self.log.info('cache: %s:%s=%r (was: %s)', modname, pname, data, previous)
if spec in self.callbacks: if spec in self.callbacks:
for func in self.callbacks[spec]: for func in self.callbacks[spec]:
@ -537,7 +540,7 @@ class Client(object):
return self.describing_data['modules'][module]['properties'] return self.describing_data['modules'][module]['properties']
def getModuleBaseClass(self, module): def getModuleBaseClass(self, module):
return self.getModuleProperties(module)['interface'] return self.getModuleProperties(module)['interface_class']
def getCommands(self, module): def getCommands(self, module):
return self.describing_data['modules'][module]['commands'] return self.describing_data['modules'][module]['commands']

View File

@ -131,8 +131,8 @@ class NodeCtrl(QWidget):
row = 0 row = 0
for modname in sorted(self._node.modules): for modname in sorted(self._node.modules):
modprops = self._node.getModuleProperties(modname) modprops = self._node.getModuleProperties(modname)
baseclass = modprops['interface'] interfaces = modprops['interface_class']
description = modprops['interface'] description = modprops['description']
unit = self._node.getProperties(modname, 'value').get('unit', '') unit = self._node.getProperties(modname, 'value').get('unit', '')
if unit: if unit:
@ -142,12 +142,12 @@ class NodeCtrl(QWidget):
label = QLabel(labelstr) label = QLabel(labelstr)
label.setFont(labelfont) label.setFont(labelfont)
if baseclass == 'Driveable': if 'Drivable' in interfaces:
widget = DriveableWidget(self._node, modname, self) widget = DrivableWidget(self._node, modname, self)
elif baseclass == 'Readable': elif 'Readable' in interfaces:
widget = ReadableWidget(self._node, modname, self) widget = ReadableWidget(self._node, modname, self)
else: else:
widget = QLabel('Unsupported Interfaceclass %r' % baseclass) widget = QLabel('Unsupported Interfaceclasses %r' % interfaces)
if description: if description:
widget.setToolTip(description) widget.setToolTip(description)
@ -159,6 +159,7 @@ class NodeCtrl(QWidget):
self._moduleWidgets.extend((label, widget)) self._moduleWidgets.extend((label, widget))
layout.setRowStretch(row, 1) layout.setRowStretch(row, 1)
class ReadableWidget(QWidget): class ReadableWidget(QWidget):
def __init__(self, node, module, parent=None): def __init__(self, node, module, parent=None):
@ -166,6 +167,8 @@ class ReadableWidget(QWidget):
self._node = node self._node = node
self._module = module self._module = module
self._status_type = self._node.getProperties(self._module, 'status').get('datatype')
params = self._node.getProperties(self._module, 'value') params = self._node.getProperties(self._module, 'value')
datatype = params.get('datatype', StringType()) datatype = params.get('datatype', StringType())
self._is_enum = isinstance(datatype, EnumType) self._is_enum = isinstance(datatype, EnumType)
@ -206,7 +209,10 @@ class ReadableWidget(QWidget):
# XXX: also connect update_status signal to LineEdit ?? # XXX: also connect update_status signal to LineEdit ??
def update_status(self, status, qualifiers={}): 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 # may change meaning of cmdPushButton
def _init_current_widgets(self): def _init_current_widgets(self):
@ -241,7 +247,7 @@ class ReadableWidget(QWidget):
self.update_target(*value) self.update_target(*value)
class DriveableWidget(ReadableWidget): class DrivableWidget(ReadableWidget):
def _init_target_widgets(self): def _init_target_widgets(self):
params = self._node.getProperties(self._module, 'target') params = self._node.getProperties(self._module, 'target')

View File

@ -40,6 +40,9 @@
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="styleSheet">
<string notr="true">background-color: lightgrey;</string>
</property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -67,6 +70,12 @@
</item> </item>
<item row="0" column="0" colspan="3"> <item row="0" column="0" colspan="3">
<widget class="QLineEdit" name="statusLineEdit"> <widget class="QLineEdit" name="statusLineEdit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: lightgrey;</string>
</property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@ -25,13 +25,13 @@ import time
import random import random
import threading import threading
from secop.modules import Driveable, CMD, PARAM from secop.modules import Drivable, 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
class CryoBase(Driveable): class CryoBase(Drivable):
pass pass

View File

@ -24,12 +24,12 @@ import time
import random import random
import threading 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.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
from secop.protocol import status from secop.protocol import status
class Switch(Driveable): class Switch(Drivable):
"""switch it on or off.... """switch it on or off....
""" """
PARAMS = { PARAMS = {
@ -94,7 +94,7 @@ class Switch(Driveable):
return info return info
class MagneticField(Driveable): class MagneticField(Drivable):
"""a liquid magnet """a liquid magnet
""" """
PARAMS = { PARAMS = {
@ -194,7 +194,7 @@ class CoilTemp(Readable):
return round(2.3 + random.random(), 3) return round(2.3 + random.random(), 3)
class SampleTemp(Driveable): class SampleTemp(Drivable):
"""a sample temperature """a sample temperature
""" """
PARAMS = { PARAMS = {

View File

@ -22,7 +22,7 @@
import random import random
from secop.modules import Readable, Driveable, PARAM from secop.modules import Readable, Drivable, PARAM
from secop.datatypes import FloatRange, StringType from secop.datatypes import FloatRange, StringType
@ -37,7 +37,7 @@ class LN2(Readable):
return round(100 * random.random(), 1) return round(100 * random.random(), 1)
class Heater(Driveable): class Heater(Drivable):
"""Just a driveable. """Just a driveable.
class name indicates it to be some heating element, class name indicates it to be some heating element,
@ -56,7 +56,7 @@ class Heater(Driveable):
pass pass
class Temp(Driveable): class Temp(Drivable):
"""Just a driveable. """Just a driveable.
class name indicates it to be some temperature controller, class name indicates it to be some temperature controller,

View File

@ -23,7 +23,7 @@
import random import random
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType 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 from secop.protocol import status
try: try:
@ -58,7 +58,7 @@ except ImportError:
class EpicsReadable(Readable): 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 # Commmon PARAMS for all EPICS devices
PARAMS = { PARAMS = {
'value': PARAM('EPICS generic value', 'value': PARAM('EPICS generic value',
@ -117,8 +117,8 @@ class EpicsReadable(Readable):
return (status.OK, 'no pv set') return (status.OK, 'no pv set')
class EpicsDriveable(Driveable): class EpicsDrivable(Drivable):
"""EpicsDriveable handles a Driveable interfacing to EPICS v4""" """EpicsDrivable handles a Drivable 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(),
@ -193,7 +193,7 @@ class EpicsDriveable(Driveable):
# features are agreed upon # features are agreed upon
class EpicsTempCtrl(EpicsDriveable): class EpicsTempCtrl(EpicsDrivable):
PARAMS = { PARAMS = {
# TODO: restrict possible values with oneof datatype # TODO: restrict possible values with oneof datatype

View File

@ -33,10 +33,10 @@ from secop.lib.sequence import SequencerMixin, Step
from secop.protocol import status from secop.protocol import status
from secop.datatypes import * from secop.datatypes import *
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError, DisabledError 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 """Garfield Magnet
uses a polarity switch ('+' or '-') to flip polarity and an onoff switch uses a polarity switch ('+' or '-') to flip polarity and an onoff switch

View File

@ -41,7 +41,7 @@ from secop.lib import lazy_property, mkthread
from secop.protocol import status from secop.protocol import status
from secop.datatypes import * from secop.datatypes import *
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError 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 *' # Only export these classes for 'from secop_mlz import *'
@ -394,7 +394,7 @@ class Sensor(AnalogInput):
self._dev.Adjust(value) self._dev.Adjust(value)
class AnalogOutput(PyTangoDevice, Driveable): class AnalogOutput(PyTangoDevice, Drivable):
""" """
The AnalogOutput handles all devices which set an analogue value. 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() 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 A devices that can set and read a digital value corresponding to a
bitfield. bitfield.