rework EnumType to use better Enum's
unfortunately IntEnum can't be bent like we would need it (extensible). So we had to write our own.... The members of the Enum still behave like ints, but also have .name and .value attributes, should they be needed. needed adoptions to correctly use (and test) the EnumType are included. Change-Id: Ie019d2f449a244c4fab00554b6c6daaac8948b59 Reviewed-on: https://forge.frm2.tum.de/review/17843 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
parent
927ca854a2
commit
574a66c65b
@ -41,8 +41,8 @@ except ImportError:
|
|||||||
|
|
||||||
import mlzlog
|
import mlzlog
|
||||||
|
|
||||||
from secop.datatypes import get_datatype
|
from secop.datatypes import get_datatype, EnumType
|
||||||
from secop.lib import mkthread, formatException
|
from secop.lib import mkthread, formatException, formatExtendedStack
|
||||||
from secop.lib.parsing import parse_time, format_time
|
from secop.lib.parsing import parse_time, format_time
|
||||||
#from secop.protocol.encoding import ENCODERS
|
#from secop.protocol.encoding import ENCODERS
|
||||||
#from secop.protocol.framing import FRAMERS
|
#from secop.protocol.framing import FRAMERS
|
||||||
@ -226,6 +226,7 @@ class Client(object):
|
|||||||
try:
|
try:
|
||||||
self._inner_run()
|
self._inner_run()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
print(formatExtendedStack())
|
||||||
self.log.exception(err)
|
self.log.exception(err)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -383,6 +384,11 @@ class Client(object):
|
|||||||
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'])
|
||||||
|
# *sigh* special handling for 'some' parameters....
|
||||||
|
if isinstance(datatype, EnumType):
|
||||||
|
datatype._enum.name = parameter
|
||||||
|
if parameter == 'status':
|
||||||
|
datatype.subtypes[0]._enum.name = 'status'
|
||||||
self.describing_data['modules'][module]['parameters'] \
|
self.describing_data['modules'][module]['parameters'] \
|
||||||
[parameter]['datatype'] = datatype
|
[parameter]['datatype'] = datatype
|
||||||
for _cmdname, cmdData in moduleData['commands'].items():
|
for _cmdname, cmdData in moduleData['commands'].items():
|
||||||
|
@ -21,17 +21,21 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define validated data types."""
|
"""Define validated data types."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# py2
|
# py2
|
||||||
unicode(u'')
|
unicode
|
||||||
except NameError:
|
except NameError:
|
||||||
# py3
|
# py3
|
||||||
unicode = str # pylint: disable=redefined-builtin
|
unicode = str # pylint: disable=redefined-builtin
|
||||||
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
|
|
||||||
from .errors import ProgrammingError, ParsingError
|
from secop.lib.enum import Enum
|
||||||
from .parse import Parser
|
from secop.errors import ProgrammingError, ParsingError
|
||||||
|
from secop.parse import Parser
|
||||||
|
|
||||||
|
|
||||||
Parser = Parser()
|
Parser = Parser()
|
||||||
|
|
||||||
@ -181,64 +185,33 @@ class IntRange(DataType):
|
|||||||
|
|
||||||
|
|
||||||
class EnumType(DataType):
|
class EnumType(DataType):
|
||||||
as_json = [u'enum']
|
def __init__(self, enum_or_name='', **kwds):
|
||||||
|
self._enum = Enum(enum_or_name, **kwds)
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
@property
|
||||||
# enum keys are ints! remember mapping from intvalue to 'name'
|
def as_json(self):
|
||||||
self.entries = {} # maps ints to strings
|
return [u'enum'] + [dict((m.name, m.value) for m in self._enum.members)]
|
||||||
num = 0
|
|
||||||
for arg in args:
|
|
||||||
if not isinstance(arg, (str, unicode)):
|
|
||||||
raise ValueError(u'EnumType entries MUST be strings!')
|
|
||||||
self.entries[num] = arg
|
|
||||||
num += 1
|
|
||||||
for k, v in list(kwds.items()):
|
|
||||||
v = int(v)
|
|
||||||
if v in self.entries:
|
|
||||||
raise ValueError(
|
|
||||||
u'keyword argument %r=%d is already assigned %r' %
|
|
||||||
(k, v, self.entries[v]))
|
|
||||||
self.entries[v] = unicode(k)
|
|
||||||
# if len(self.entries) == 0:
|
|
||||||
# raise ValueError('Empty enums ae not allowed!')
|
|
||||||
# also keep a mapping from name strings to numbers
|
|
||||||
self.reversed = {} # maps Strings to ints
|
|
||||||
for k, v in self.entries.items():
|
|
||||||
if v in self.reversed:
|
|
||||||
raise ValueError(u'Mapping for %r=%r is not Unique!' % (v, k))
|
|
||||||
self.reversed[v] = k
|
|
||||||
self.as_json = [u'enum', self.reversed.copy()]
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return u'EnumType(%s)' % u', '.join(
|
return "EnumType(%r, %s" % (self._enum.name, ', '.join('%s=%d' %(m.name, m.value) for m in self._enum.members))
|
||||||
[u'%s=%d' % (v, k) for k, v in list(self.entries.items())])
|
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
if value in self.reversed:
|
return int(self.validate(value))
|
||||||
return self.reversed[value]
|
|
||||||
if int(value) in self.entries:
|
|
||||||
return int(value)
|
|
||||||
raise ValueError(u'%r is not one of %s' %
|
|
||||||
(unicode(value), u', '.join(list(self.reversed.keys()))))
|
|
||||||
|
|
||||||
def import_value(self, value):
|
def import_value(self, value):
|
||||||
"""returns a python object from serialisation"""
|
"""returns a python object from serialisation"""
|
||||||
# internally we store the key (which is a string)
|
return self.validate(value)
|
||||||
return self.entries[int(value)]
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
if value in self.reversed:
|
try:
|
||||||
return self.reversed[value]
|
return self._enum[value]
|
||||||
if int(value) in self.entries:
|
except KeyError:
|
||||||
return int(value)
|
raise ValueError('%r is not a member of enum %r' % (value, self._enum))
|
||||||
raise ValueError(u'%r is not one of %s' %
|
|
||||||
(unicode(value), u', '.join(map(unicode, self.entries))))
|
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
value = text
|
return self.validate(text)
|
||||||
return self.validate(value)
|
|
||||||
|
|
||||||
|
|
||||||
class BLOBType(DataType):
|
class BLOBType(DataType):
|
||||||
@ -606,7 +579,7 @@ DATATYPES = dict(
|
|||||||
string=lambda _max=None, _min=0: StringType(_max, _min),
|
string=lambda _max=None, _min=0: StringType(_max, _min),
|
||||||
array=lambda subtype, _max=None, _min=0: ArrayOf(get_datatype(subtype), _max, _min),
|
array=lambda subtype, _max=None, _min=0: ArrayOf(get_datatype(subtype), _max, _min),
|
||||||
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
|
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
|
||||||
enum=lambda kwds: EnumType(**kwds),
|
enum=lambda kwds: EnumType('', **kwds),
|
||||||
struct=lambda named_subtypes: StructOf(
|
struct=lambda named_subtypes: StructOf(
|
||||||
**dict((n, get_datatype(t)) for n, t in list(named_subtypes.items()))),
|
**dict((n, get_datatype(t)) for n, t in list(named_subtypes.items()))),
|
||||||
command=Command,
|
command=Command,
|
||||||
|
@ -211,12 +211,11 @@ class ReadableWidget(QWidget):
|
|||||||
if self._is_enum:
|
if self._is_enum:
|
||||||
self._map = {} # maps QT-idx to name/value
|
self._map = {} # maps QT-idx to name/value
|
||||||
self._revmap = {} # maps value/name to QT-idx
|
self._revmap = {} # maps value/name to QT-idx
|
||||||
for idx, (val, name) in enumerate(
|
for idx, member in enumerate(datatype._enum.members):
|
||||||
sorted(datatype.entries.items())):
|
self._map[idx] = member
|
||||||
self._map[idx] = (name, val)
|
self._revmap[member.name] = idx
|
||||||
self._revmap[name] = idx
|
self._revmap[member.value] = idx
|
||||||
self._revmap[val] = idx
|
self.targetComboBox.addItem(member.name, member.value)
|
||||||
self.targetComboBox.addItem(name, val)
|
|
||||||
|
|
||||||
self._init_status_widgets()
|
self._init_status_widgets()
|
||||||
self._init_current_widgets()
|
self._init_current_widgets()
|
||||||
@ -298,7 +297,8 @@ class DrivableWidget(ReadableWidget):
|
|||||||
|
|
||||||
def update_current(self, value, qualifiers=None):
|
def update_current(self, value, qualifiers=None):
|
||||||
if self._is_enum:
|
if self._is_enum:
|
||||||
self.currentLineEdit.setText(self._map[self._revmap[value]][0])
|
member = self._map[self._revmap[value]]
|
||||||
|
self.currentLineEdit.setText('%s.%s (%d)' % (member.enum.name, member.name, member.value))
|
||||||
else:
|
else:
|
||||||
self.currentLineEdit.setText(str(value))
|
self.currentLineEdit.setText(str(value))
|
||||||
|
|
||||||
@ -333,5 +333,5 @@ class DrivableWidget(ReadableWidget):
|
|||||||
self.target_go(self.targetLineEdit.text())
|
self.target_go(self.targetLineEdit.text())
|
||||||
|
|
||||||
@pyqtSlot(unicode)
|
@pyqtSlot(unicode)
|
||||||
def on_targetComboBox_activated(self, stuff):
|
def on_targetComboBox_activated(self, selection):
|
||||||
self.target_go(stuff)
|
self.target_go(selection)
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# py2
|
# py2
|
||||||
unicode(u'')
|
unicode(u'')
|
||||||
@ -32,6 +34,7 @@ from secop.gui.qt import QWidget, QLabel, QPushButton as QButton, QLineEdit, \
|
|||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
from secop.datatypes import EnumType
|
from secop.datatypes import EnumType
|
||||||
|
from secop.lib import formatExtendedStack
|
||||||
|
|
||||||
|
|
||||||
class ParameterWidget(QWidget):
|
class ParameterWidget(QWidget):
|
||||||
@ -94,15 +97,12 @@ class EnumParameterWidget(GenericParameterWidget):
|
|||||||
loadUi(self, 'parambuttons_select.ui')
|
loadUi(self, 'parambuttons_select.ui')
|
||||||
|
|
||||||
# transfer allowed settings from datatype to comboBoxes
|
# transfer allowed settings from datatype to comboBoxes
|
||||||
self._map = {} # maps index to enumstring
|
self._map = {} # maps index to EnumMember
|
||||||
self._revmap = {} # maps enumstring to index
|
self._revmap = {} # maps Enum.name + Enum.value to index
|
||||||
index = 0
|
for index, member in enumerate(self._datatype._enum.members):
|
||||||
for enumval, enumname in sorted(self._datatype.entries.items()):
|
self.setComboBox.addItem(member.name, member.value)
|
||||||
self.setComboBox.addItem(enumname, enumval)
|
self._map[index] = member
|
||||||
self._map[index] = (enumval, enumname)
|
self._revmap[member.name] = self._revmap[member.value] = index
|
||||||
self._revmap[enumname] = index
|
|
||||||
self._revmap[enumval] = index
|
|
||||||
index += 1
|
|
||||||
if self._readonly:
|
if self._readonly:
|
||||||
self.setLabel.setEnabled(False)
|
self.setLabel.setEnabled(False)
|
||||||
self.setComboBox.setEnabled(False)
|
self.setComboBox.setEnabled(False)
|
||||||
@ -115,19 +115,16 @@ class EnumParameterWidget(GenericParameterWidget):
|
|||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_setPushButton_clicked(self):
|
def on_setPushButton_clicked(self):
|
||||||
_enumval, enumname = self._map[self.setComboBox.currentIndex()]
|
member = self._map[self.setComboBox.currentIndex()]
|
||||||
self.setRequested.emit(self._module, self._paramcmd, enumname)
|
self.setRequested.emit(self._module, self._paramcmd, member)
|
||||||
|
|
||||||
def updateValue(self, value):
|
def updateValue(self, value):
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
member = self._map[self._revmap[int(value)]]
|
||||||
except ValueError:
|
self.currentLineEdit.setText('%s.%s (%d)' % (member.enum.name, member.name, member.value))
|
||||||
pass
|
except Exception:
|
||||||
if value in self._revmap:
|
|
||||||
index = self._revmap[value]
|
|
||||||
self.currentLineEdit.setText('(%d): %s' % self._map[index])
|
|
||||||
else:
|
|
||||||
self.currentLineEdit.setText('undefined Value: %r' % value)
|
self.currentLineEdit.setText('undefined Value: %r' % value)
|
||||||
|
print(formatExtendedStack())
|
||||||
|
|
||||||
|
|
||||||
class GenericCmdWidget(ParameterWidget):
|
class GenericCmdWidget(ParameterWidget):
|
||||||
|
@ -62,16 +62,14 @@ class EnumWidget(QComboBox):
|
|||||||
|
|
||||||
self._map = {}
|
self._map = {}
|
||||||
self._revmap = {}
|
self._revmap = {}
|
||||||
for idx, (val, name) in enumerate(datatype.entries.items()):
|
for idx, member in enumerate(datatype._enum.members):
|
||||||
self._map[idx] = (name, val)
|
self._map[idx] = member
|
||||||
self._revmap[name] = idx
|
self._revmap[member.name] = idx
|
||||||
self._revmap[val] = idx
|
self._revmap[member.value] = idx
|
||||||
self.addItem(name, val)
|
self.addItem(member.name, member.value)
|
||||||
# XXX: fill Combobox from datatype
|
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
# XXX: return integer corresponding to the selected item
|
return self._map[self.currentIndex()].value
|
||||||
return self._map[self.currentIndex()][1]
|
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
self.setCurrentIndex(self._revmap[value])
|
self.setCurrentIndex(self._revmap[value])
|
||||||
|
@ -49,6 +49,8 @@ CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unset_value = object()
|
||||||
|
|
||||||
class lazy_property(object):
|
class lazy_property(object):
|
||||||
"""A property that calculates its value only once."""
|
"""A property that calculates its value only once."""
|
||||||
|
|
||||||
|
306
secop/lib/enum.py
Executable file
306
secop/lib/enum.py
Executable file
@ -0,0 +1,306 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
||||||
|
#
|
||||||
|
# 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>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""Enum class"""
|
||||||
|
|
||||||
|
__ALL__ = ['Enum']
|
||||||
|
|
||||||
|
try:
|
||||||
|
text_type = unicode # Py2
|
||||||
|
except NameError:
|
||||||
|
text_type = str # Py3
|
||||||
|
|
||||||
|
|
||||||
|
class EnumMember(object):
|
||||||
|
"""represents one member of an Enum
|
||||||
|
|
||||||
|
has an int-type value and attributes 'name' and 'value'
|
||||||
|
"""
|
||||||
|
__slots__ = ['name', 'value', 'enum']
|
||||||
|
def __init__(self, enum, name, value):
|
||||||
|
if not isinstance(enum, Enum):
|
||||||
|
raise TypeError('1st Argument must be an instance of class Enum()')
|
||||||
|
self.value = int(value)
|
||||||
|
self.enum = enum
|
||||||
|
self.name = name or 'unnamed'
|
||||||
|
|
||||||
|
# to behave like an int for comparisons
|
||||||
|
def __cmp__(self, other):
|
||||||
|
if isinstance(other, EnumMember):
|
||||||
|
other = other.value
|
||||||
|
if isinstance(other, (str, unicode)):
|
||||||
|
if other in self.enum:
|
||||||
|
other = self.enum[other].value
|
||||||
|
try:
|
||||||
|
other = int(other)
|
||||||
|
except Exception:
|
||||||
|
#raise TypeError('%r can not be compared to %r!' %(other, self))
|
||||||
|
return -1 # XXX:!
|
||||||
|
if self.value < other:
|
||||||
|
return -1
|
||||||
|
elif self.value > other:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__cmp__(other.value if isinstance(other, EnumMember) else other) == -1
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.__cmp__(other.value if isinstance(other, EnumMember) else other) < 1
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, (EnumMember)):
|
||||||
|
return other.value == self.value
|
||||||
|
if isinstance(other, (int, long)):
|
||||||
|
return other == self.value
|
||||||
|
# compare by name (for (in)equality only)
|
||||||
|
if isinstance(other, (str, unicode)):
|
||||||
|
if other in self.enum:
|
||||||
|
return self.name == other
|
||||||
|
return False
|
||||||
|
return self.__cmp__(other.value if isinstance(other, EnumMember) else other) == 0
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.__cmp__(other.value if isinstance(other, EnumMember) else other) > -1
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__cmp__(other.value if isinstance(other, EnumMember) else other) == 1
|
||||||
|
|
||||||
|
# to be useful in indexing
|
||||||
|
def __hash__(self):
|
||||||
|
return self.value.__hash__()
|
||||||
|
|
||||||
|
# be read-only (except during initialization)
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
if key in self.__slots__ and not getattr(self, 'name', None):
|
||||||
|
return object.__setattr__(self, key, value)
|
||||||
|
raise TypeError('Modifying EnumMember\'s is not allowed!')
|
||||||
|
|
||||||
|
# allow access to other EnumMembers (via the Enum)
|
||||||
|
def __getattr__(self, key):
|
||||||
|
enum = object.__getattribute__(self, 'enum')
|
||||||
|
if key in enum:
|
||||||
|
return enum[key]
|
||||||
|
return object.__getattribute__(self, key)
|
||||||
|
|
||||||
|
# be human readable (for debugging)
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s.%s (%d)>' % (self.enum.name, self.name, self.value)
|
||||||
|
|
||||||
|
|
||||||
|
# numeric operations: delegate to int. Do we really need any of those?
|
||||||
|
def __add__(self, other):
|
||||||
|
return self.value.__add__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __sub__(self, other):
|
||||||
|
return self.value.__sub__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __mul__(self, other):
|
||||||
|
return self.value.__mul__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __div__(self, other):
|
||||||
|
return self.value.__div__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __truediv__(self, other):
|
||||||
|
return self.value.__truediv__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __floordiv__(self, other):
|
||||||
|
return self.value.__floordiv__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __mod__(self, other):
|
||||||
|
return self.value.__mod__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __divmod__(self, other):
|
||||||
|
return self.value.__divmod__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __pow__(self, other, *args):
|
||||||
|
return self.value.__pow__(other, *args)
|
||||||
|
def __lshift__(self, other):
|
||||||
|
return self.value.__lshift__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rshift__(self, other):
|
||||||
|
return self.value.__rshift__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return self.value.__radd__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rsub__(self, other):
|
||||||
|
return self.value.__rsub__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return self.value.__rmul__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rdiv__(self, other):
|
||||||
|
return self.value.__rdiv__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rtruediv__(self, other):
|
||||||
|
return self.value.__rtruediv__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rfloordiv__(self, other):
|
||||||
|
return self.value.__rfloordiv__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rmod__(self, other):
|
||||||
|
return self.value.__rmod__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rdivmod__(self, other):
|
||||||
|
return self.value.__rdivmod__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rpow__(self, other, *args):
|
||||||
|
return self.value.__rpow__(other, *args)
|
||||||
|
def __rlshift__(self, other):
|
||||||
|
return self.value.__rlshift__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rrshift__(self, other):
|
||||||
|
return self.value.__rrshift__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
|
||||||
|
# logical operations
|
||||||
|
def __and__(self, other):
|
||||||
|
return self.value.__and__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __xor__(self, other):
|
||||||
|
return self.value.__xor__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __or__(self, other):
|
||||||
|
return self.value.__or__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rand__(self, other):
|
||||||
|
return self.value.__rand__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __rxor__(self, other):
|
||||||
|
return self.value.__rxor__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
def __ror__(self, other):
|
||||||
|
return self.value.__ror__(other.value if isinstance(other, EnumMember) else other)
|
||||||
|
# other stuff
|
||||||
|
def __neg__(self):
|
||||||
|
return self.value.__neg__()
|
||||||
|
def __pos__(self):
|
||||||
|
return self.value.__pos__()
|
||||||
|
def __abs__(self):
|
||||||
|
return self.value.__abs__()
|
||||||
|
def __invert__(self):
|
||||||
|
return self.value.__invert__()
|
||||||
|
def __int__(self):
|
||||||
|
return self.value.__int__()
|
||||||
|
def __long__(self):
|
||||||
|
return self.value.__long__()
|
||||||
|
def __float__(self):
|
||||||
|
return self.value.__float__()
|
||||||
|
#return NotImplemented # makes no sense
|
||||||
|
def __oct__(self):
|
||||||
|
return self.value.__oct__()
|
||||||
|
def __hex__(self):
|
||||||
|
return self.value.__hex__()
|
||||||
|
def __index__(self):
|
||||||
|
return self.value.__index__()
|
||||||
|
|
||||||
|
# note: we do not implement the __i*__ methods as they modify our value
|
||||||
|
# inplace and we want to have a const
|
||||||
|
def __forbidden__(self, *args):
|
||||||
|
raise TypeError('Operation is forbidden!')
|
||||||
|
__iadd__ = __isub__ = __imul__ = __idiv__ = __itruediv__ = __ifloordiv__ = \
|
||||||
|
__imod__ = __ipow__ = __ilshift__ = __irshift__ = __iand__ = \
|
||||||
|
__ixor__ = __ior__ = __forbidden__
|
||||||
|
|
||||||
|
|
||||||
|
class Enum(dict):
|
||||||
|
"""The Enum class
|
||||||
|
|
||||||
|
use instance of this like this:
|
||||||
|
>>> status = Enum('status', idle=1, busy=2, error=3)
|
||||||
|
|
||||||
|
you may create an extended Enum:
|
||||||
|
>>> moveable_status = Enum(status, alarm=5)
|
||||||
|
>>> yet_another_enum = Enum('X', dict(a=1, b=2), c=3)
|
||||||
|
last example 'extends' the definition given by the dict with c=3.
|
||||||
|
|
||||||
|
accessing the members:
|
||||||
|
>>> status['idle'] == status.idle == status('idle')
|
||||||
|
>>> status[1] == status.idle == status(1)
|
||||||
|
|
||||||
|
Each member can be used like an int, so:
|
||||||
|
>>> status.idle == 1 is True
|
||||||
|
>>> status.error +5
|
||||||
|
|
||||||
|
You can neither modify members nor Enums.
|
||||||
|
You only can create an extended Enum.
|
||||||
|
"""
|
||||||
|
name = ''
|
||||||
|
def __init__(self, name='', parent=None, **kwds):
|
||||||
|
super(Enum, self).__init__()
|
||||||
|
if isinstance(name, (dict, Enum)) and parent is None:
|
||||||
|
# swap if only parent is given as positional argument
|
||||||
|
name, parent = '', name
|
||||||
|
# parent may be dict, or Enum....
|
||||||
|
if not name:
|
||||||
|
if isinstance(parent, Enum):
|
||||||
|
# if name was not given, use that of the parent
|
||||||
|
# this means, an extended Enum behaves like the parent
|
||||||
|
# THIS MAY BE CONFUSING SOMETIMES!
|
||||||
|
name=parent.name
|
||||||
|
# else:
|
||||||
|
# raise TypeError('Enum instances need a name or an Enum parent!')
|
||||||
|
if not isinstance(name, (str, text_type)):
|
||||||
|
raise TypeError('1st argument to Enum must be a name or an Enum!')
|
||||||
|
|
||||||
|
names = set()
|
||||||
|
values = set()
|
||||||
|
# pylint: disable=dangerous-default-value
|
||||||
|
def add(self, k, v, names = names, value = values):
|
||||||
|
"""helper for creating the enum members"""
|
||||||
|
if v is None:
|
||||||
|
# sugar: take the next free number if value was None
|
||||||
|
v = max(values or [0]) + 1
|
||||||
|
# sugar: if value is a name of another member,
|
||||||
|
# auto-assign the smallest free number which is bigger
|
||||||
|
# then that assigned to that name
|
||||||
|
if v in names:
|
||||||
|
v = self[v].value
|
||||||
|
while v in values:
|
||||||
|
v +=1
|
||||||
|
|
||||||
|
# check that the value is an int
|
||||||
|
_v = int(v)
|
||||||
|
if _v != v:
|
||||||
|
raise TypeError('Values must be integers!')
|
||||||
|
v = _v
|
||||||
|
|
||||||
|
# check for duplicates
|
||||||
|
if k in names:
|
||||||
|
raise TypeError('duplicate name %r' % k)
|
||||||
|
if v in values:
|
||||||
|
raise TypeError('duplicate value %d (key=%r)' % (v, k))
|
||||||
|
|
||||||
|
# remember it
|
||||||
|
self[v] = self[k] = EnumMember(self, k, v)
|
||||||
|
names.add(k)
|
||||||
|
values.add(v)
|
||||||
|
|
||||||
|
if isinstance(parent, Enum):
|
||||||
|
for m in parent.members:
|
||||||
|
add(self, m.name, m.value)
|
||||||
|
elif isinstance(parent, dict):
|
||||||
|
for k, v in parent.items():
|
||||||
|
add(self, k, v)
|
||||||
|
elif parent != None:
|
||||||
|
raise TypeError('parent (if given) MUST be a dict or an Enum!')
|
||||||
|
for k, v in kwds.items():
|
||||||
|
add(self, k, v)
|
||||||
|
self.members = tuple(sorted(self[n] for n in names))
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
if self.name:
|
||||||
|
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||||
|
super(Enum, self).__setattr__(key, value)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if self.name:
|
||||||
|
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||||
|
super(Enum, self).__setitem__(key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Enum %r (%d values)>' % (self.name, len(self)/2)
|
||||||
|
|
||||||
|
def __call__(self, key):
|
||||||
|
return self[key]
|
171
secop/modules.py
171
secop/modules.py
@ -52,10 +52,10 @@ except ImportError:
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
from secop.lib import formatExtendedStack, mkthread
|
from secop.lib import formatExtendedStack, mkthread, unset_value
|
||||||
|
from secop.lib.enum import Enum
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.errors import ConfigError, ProgrammingError
|
from secop.errors import ConfigError, ProgrammingError
|
||||||
from secop.protocol import status
|
|
||||||
from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange, get_datatype
|
from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange, get_datatype
|
||||||
|
|
||||||
|
|
||||||
@ -81,12 +81,14 @@ class Param(object):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
description,
|
description,
|
||||||
datatype=None,
|
datatype=None,
|
||||||
default=Ellipsis,
|
default=unset_value,
|
||||||
unit=None,
|
unit='',
|
||||||
readonly=True,
|
readonly=True,
|
||||||
export=True,
|
export=True,
|
||||||
group='',
|
group='',
|
||||||
poll=False):
|
poll=False,
|
||||||
|
value=unset_value,
|
||||||
|
timestamp=0):
|
||||||
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 ()???)
|
||||||
@ -115,15 +117,7 @@ class Param(object):
|
|||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
# return a copy of ourselfs
|
# return a copy of ourselfs
|
||||||
return Param(description=self.description,
|
return Param(**self.__dict__)
|
||||||
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
|
||||||
@ -147,7 +141,10 @@ class Param(object):
|
|||||||
|
|
||||||
|
|
||||||
class Override(object):
|
class Override(object):
|
||||||
|
"""Stores the overrides to ba applied to a Param
|
||||||
|
|
||||||
|
note: overrides are applied by the metaclass during class creating
|
||||||
|
"""
|
||||||
def __init__(self, **kwds):
|
def __init__(self, **kwds):
|
||||||
self.kwds = kwds
|
self.kwds = kwds
|
||||||
|
|
||||||
@ -167,9 +164,9 @@ class Override(object):
|
|||||||
paramobj)
|
paramobj)
|
||||||
|
|
||||||
|
|
||||||
# storage for Commands settings (description + call signature...)
|
|
||||||
class Command(object):
|
class Command(object):
|
||||||
|
"""storage for Commands settings (description + call signature...)
|
||||||
|
"""
|
||||||
def __init__(self, description, arguments=None, result=None):
|
def __init__(self, description, arguments=None, result=None):
|
||||||
# descriptive text for humans
|
# descriptive text for humans
|
||||||
self.description = description
|
self.description = description
|
||||||
@ -195,7 +192,14 @@ class Command(object):
|
|||||||
# warning: MAGIC!
|
# warning: MAGIC!
|
||||||
|
|
||||||
class ModuleMeta(type):
|
class ModuleMeta(type):
|
||||||
|
"""Metaclass
|
||||||
|
|
||||||
|
joining the class's properties, parameters and commands dicts with
|
||||||
|
those of base classes.
|
||||||
|
also creates getters/setter for parameter access
|
||||||
|
and wraps read_*/write_* methods
|
||||||
|
(so the dispatcher will get notfied of changed values)
|
||||||
|
"""
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(mcs, name, bases, attrs):
|
||||||
newtype = type.__new__(mcs, name, bases, attrs)
|
newtype = type.__new__(mcs, name, bases, attrs)
|
||||||
if '__constructed__' in attrs:
|
if '__constructed__' in attrs:
|
||||||
@ -219,6 +223,11 @@ class ModuleMeta(type):
|
|||||||
for n, o in attrs.get('overrides', {}).items():
|
for n, o in attrs.get('overrides', {}).items():
|
||||||
newparams[n] = o.apply(newparams[n].copy())
|
newparams[n] = o.apply(newparams[n].copy())
|
||||||
|
|
||||||
|
# Check naming of EnumType
|
||||||
|
for k, v in newparams.items():
|
||||||
|
if isinstance(v.datatype, EnumType) and not v.datatype._enum.name:
|
||||||
|
v.datatype._enum.name = k
|
||||||
|
|
||||||
# check validity of Param entries
|
# check validity of Param entries
|
||||||
for pname, pobj in newtype.parameters.items():
|
for pname, pobj in newtype.parameters.items():
|
||||||
# XXX: allow dicts for overriding certain aspects only.
|
# XXX: allow dicts for overriding certain aspects only.
|
||||||
@ -284,7 +293,7 @@ class ModuleMeta(type):
|
|||||||
pobj = self.parameters[pname]
|
pobj = self.parameters[pname]
|
||||||
value = pobj.datatype.validate(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
|
||||||
# also send notification
|
# also send notification
|
||||||
if self.parameters[pname].export:
|
if self.parameters[pname].export:
|
||||||
@ -311,52 +320,61 @@ class ModuleMeta(type):
|
|||||||
return newtype
|
return newtype
|
||||||
|
|
||||||
|
|
||||||
# Basic module class
|
|
||||||
#
|
|
||||||
# within Modules, parameters should only be addressed as self.<pname>
|
|
||||||
# i.e. self.value, self.target etc...
|
|
||||||
# these are accesses to the cached version.
|
|
||||||
# they can also be written to
|
|
||||||
# (which auto-calls self.write_<pname> and generate an async update)
|
|
||||||
# if you want to 'update from the hardware', call self.read_<pname>
|
|
||||||
# the return value of this method will be used as the new cached value and
|
|
||||||
# be returned.
|
|
||||||
@add_metaclass(ModuleMeta)
|
@add_metaclass(ModuleMeta)
|
||||||
class Module(object):
|
class Module(object):
|
||||||
"""Basic Module, doesn't do much"""
|
"""Basic Module
|
||||||
|
|
||||||
|
ALL secop Modules derive from this
|
||||||
|
|
||||||
|
note: within Modules, parameters should only be addressed as self.<pname>
|
||||||
|
i.e. self.value, self.target etc...
|
||||||
|
these are accessing the cached version.
|
||||||
|
they can also be written to (which auto-calls self.write_<pname> and
|
||||||
|
generate an async update)
|
||||||
|
|
||||||
|
if you want to 'update from the hardware', call self.read_<pname>() instead
|
||||||
|
the return value of this method will be used as the new cached value and
|
||||||
|
be an async update sent automatically.
|
||||||
|
"""
|
||||||
# static properties, definitions in derived classes should overwrite earlier ones.
|
# static properties, definitions in derived classes should overwrite earlier ones.
|
||||||
# how to configure some stuff which makes sense to take from configfile???
|
# note: properties don't change after startup and are usually filled
|
||||||
|
# with data from a cfg file...
|
||||||
|
# note: so far all properties are STRINGS
|
||||||
|
# note: only the properties defined here are allowed to be set in the cfg file
|
||||||
properties = {
|
properties = {
|
||||||
'group': None, # some Modules may be grouped together
|
'group': None, # some Modules may be grouped together
|
||||||
|
'description': "Short description of this Module class and its functionality.",
|
||||||
|
|
||||||
'meaning': None, # XXX: ???
|
'meaning': None, # XXX: ???
|
||||||
'priority': None, # XXX: ???
|
'priority': None, # XXX: ???
|
||||||
'visibility': None, # XXX: ????
|
'visibility': None, # XXX: ????
|
||||||
'description': "The manufacturer forgot to set a meaningful description. please nag him!",
|
|
||||||
# what else?
|
# what else?
|
||||||
}
|
}
|
||||||
# parameter and commands are auto-merged upon subclassing
|
# properties, parameter and commands are auto-merged upon subclassing
|
||||||
# parameters = {
|
parameters = {}
|
||||||
# 'description': Param('short description of this module and its function', datatype=StringType(), default='no specified'),
|
|
||||||
# }
|
|
||||||
commands = {}
|
commands = {}
|
||||||
|
|
||||||
|
# reference to the dispatcher (used for sending async updates)
|
||||||
DISPATCHER = None
|
DISPATCHER = None
|
||||||
|
|
||||||
def __init__(self, logger, cfgdict, devname, dispatcher):
|
def __init__(self, logger, cfgdict, modname, dispatcher):
|
||||||
# remember the dispatcher object (for the async callbacks)
|
# remember the dispatcher object (for the async callbacks)
|
||||||
self.DISPATCHER = dispatcher
|
self.DISPATCHER = dispatcher
|
||||||
self.log = logger
|
self.log = logger
|
||||||
self.name = devname
|
self.name = modname
|
||||||
# make local copies of parameter
|
# make local copies of parameter objects
|
||||||
|
# they need to be individual per instance since we use them also
|
||||||
|
# to cache the current value + qualifiers...
|
||||||
params = {}
|
params = {}
|
||||||
for k, v in list(self.parameters.items()):
|
for k, v in list(self.parameters.items()):
|
||||||
params[k] = v.copy()
|
params[k] = v.copy()
|
||||||
|
# do not re-use self.parameters as this is the same for all instances
|
||||||
self.parameters = params
|
self.parameters = params
|
||||||
|
|
||||||
# make local copies of properties
|
# make local copies of properties
|
||||||
props = {}
|
props = {}
|
||||||
for k, v in list(self.properties.items()):
|
for k, v in list(self.properties.items()):
|
||||||
props[k] = v
|
props[k] = v
|
||||||
|
|
||||||
self.properties = props
|
self.properties = props
|
||||||
|
|
||||||
# check and apply properties specified in cfgdict
|
# check and apply properties specified in cfgdict
|
||||||
@ -365,21 +383,21 @@ class Module(object):
|
|||||||
for k, v in list(cfgdict.items()): # keep list() as dict may change during iter
|
for k, v in list(cfgdict.items()): # keep list() as dict may change during iter
|
||||||
if k[0] == '.':
|
if k[0] == '.':
|
||||||
if k[1:] in self.properties:
|
if k[1:] in self.properties:
|
||||||
self.properties[k[1:]] = v
|
self.properties[k[1:]] = cfgdict.pop(k)
|
||||||
del cfgdict[k]
|
else:
|
||||||
|
raise ConfigError('Module %r has no property %r' %
|
||||||
|
(self.name, k[1:]))
|
||||||
|
# remove unset (default) module properties
|
||||||
|
for k, v in list(self.properties.items()): # keep list() as dict may change during iter
|
||||||
|
if v is None:
|
||||||
|
del self.properties[k]
|
||||||
|
|
||||||
# derive automatic properties
|
# MAGIC: derive automatic properties
|
||||||
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['interface_class'] = [
|
self.properties['interface_class'] = [
|
||||||
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')]
|
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')]
|
||||||
#self.properties['interface'] = self.properties['interfaces'][0]
|
|
||||||
|
|
||||||
# remove unset (default) module properties
|
|
||||||
for k, v in list(self.properties.items()): # keep list() as dict may change during iter
|
|
||||||
if v is None:
|
|
||||||
del self.properties[k]
|
|
||||||
|
|
||||||
# check and apply parameter_properties
|
# check and apply parameter_properties
|
||||||
# specified as '<paramname>.<propertyname> = <propertyvalue>'
|
# specified as '<paramname>.<propertyname> = <propertyvalue>'
|
||||||
@ -391,11 +409,10 @@ class Module(object):
|
|||||||
if propname == 'datatype':
|
if propname == 'datatype':
|
||||||
paramobj.datatype = get_datatype(cfgdict.pop(k))
|
paramobj.datatype = get_datatype(cfgdict.pop(k))
|
||||||
elif hasattr(paramobj, propname):
|
elif hasattr(paramobj, propname):
|
||||||
setattr(paramobj, propname, v)
|
setattr(paramobj, propname, cfgdict.pop(k))
|
||||||
del cfgdict[k]
|
|
||||||
|
|
||||||
# check config for problems
|
# check config for problems
|
||||||
# only accept config items specified in parameters
|
# only accept remaining config items specified in parameters
|
||||||
for k, v in cfgdict.items():
|
for k, v in cfgdict.items():
|
||||||
if k not in self.parameters:
|
if k not in self.parameters:
|
||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
@ -407,8 +424,8 @@ class Module(object):
|
|||||||
# is not specified in cfgdict
|
# is not specified in cfgdict
|
||||||
for k, v in self.parameters.items():
|
for k, v in self.parameters.items():
|
||||||
if k not in cfgdict:
|
if k not in cfgdict:
|
||||||
if v.default is Ellipsis and k != 'value':
|
if v.default is unset_value and k != 'value':
|
||||||
# Ellipsis is the one single value you can not specify....
|
# unset_value is the one single value you can not specify....
|
||||||
raise ConfigError('Module %s: Parameter %r has no default '
|
raise ConfigError('Module %s: Parameter %r has no default '
|
||||||
'value and was not given in config!' %
|
'value and was not given in config!' %
|
||||||
(self.name, k))
|
(self.name, k))
|
||||||
@ -416,7 +433,7 @@ class Module(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.parameters[k] = self.parameters[k].copy()
|
# self.parameters[k] = self.parameters[k].copy() # already done above...
|
||||||
|
|
||||||
# now 'apply' config:
|
# now 'apply' config:
|
||||||
# pass values through the datatypes and store as attributes
|
# pass values through the datatypes and store as attributes
|
||||||
@ -425,15 +442,15 @@ class Module(object):
|
|||||||
continue
|
continue
|
||||||
# apply datatype, complain if type does not fit
|
# apply datatype, complain if type does not fit
|
||||||
datatype = self.parameters[k].datatype
|
datatype = self.parameters[k].datatype
|
||||||
if datatype is not None:
|
try:
|
||||||
# only check if datatype given
|
v = datatype.validate(v)
|
||||||
try:
|
except (ValueError, TypeError):
|
||||||
v = datatype.validate(v)
|
self.log.exception(formatExtendedStack())
|
||||||
except (ValueError, TypeError):
|
raise
|
||||||
self.log.exception(formatExtendedStack())
|
|
||||||
raise
|
|
||||||
# raise ConfigError('Module %s: config parameter %r:\n%r' %
|
# raise ConfigError('Module %s: config parameter %r:\n%r' %
|
||||||
# (self.name, k, e))
|
# (self.name, k, e))
|
||||||
|
# note: this will call write_* methods which will
|
||||||
|
# write to the hardware, if possible!
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -450,23 +467,26 @@ class Readable(Module):
|
|||||||
"""Basic readable Module
|
"""Basic readable Module
|
||||||
|
|
||||||
providing the readonly parameter 'value' and 'status'
|
providing the readonly parameter 'value' and 'status'
|
||||||
|
|
||||||
|
Also allow configurable polling per 'pollinterval' parameter.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
Status = Enum('Status',
|
||||||
|
IDLE = 100,
|
||||||
|
WARN = 200,
|
||||||
|
UNSTABLE = 250,
|
||||||
|
ERROR = 400,
|
||||||
|
UNKNOWN = 900,
|
||||||
|
)
|
||||||
parameters = {
|
parameters = {
|
||||||
'value': Param('current value of the Module', readonly=True, default=0.,
|
'value': Param('current value of the Module', readonly=True, default=0.,
|
||||||
datatype=FloatRange(), unit='', poll=True),
|
datatype=FloatRange(), unit='', 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 Module', default=(status.OK, ''),
|
'status': Param('current status of the Module',
|
||||||
datatype=TupleOf(
|
default=(Status.IDLE, ''),
|
||||||
EnumType(**{
|
datatype=TupleOf(EnumType(Status), StringType()),
|
||||||
'IDLE': status.OK,
|
readonly=True, poll=True),
|
||||||
'BUSY': status.BUSY,
|
|
||||||
'WARN': status.WARN,
|
|
||||||
'UNSTABLE': status.UNSTABLE,
|
|
||||||
'ERROR': status.ERROR,
|
|
||||||
'UNKNOWN': status.UNKNOWN
|
|
||||||
}), StringType()),
|
|
||||||
readonly=True, poll=True),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -530,11 +550,16 @@ class Drivable(Writable):
|
|||||||
Also status gets extended with a BUSY state indicating a running action.
|
Also status gets extended with a BUSY state indicating a running action.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Status = Enum(Readable.Status, BUSY=300)
|
||||||
|
overrides = {
|
||||||
|
'status' : Override(datatype=TupleOf(EnumType(Status), StringType())),
|
||||||
|
}
|
||||||
|
|
||||||
# improved polling: may poll faster if module is BUSY
|
# improved polling: may poll faster if module is BUSY
|
||||||
def poll(self, nr=0):
|
def poll(self, nr=0):
|
||||||
# poll status first
|
# poll status first
|
||||||
stat = self.read_status(0)
|
stat = self.read_status(0)
|
||||||
fastpoll = stat[0] == status.BUSY
|
fastpoll = stat[0] == self.Status.BUSY
|
||||||
for pname, pobj in self.parameters.items():
|
for pname, pobj in self.parameters.items():
|
||||||
if not pobj.poll:
|
if not pobj.poll:
|
||||||
continue
|
continue
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
# -*- 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>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
"""Define Status constants"""
|
|
||||||
|
|
||||||
# could also be some objects
|
|
||||||
OK = 100
|
|
||||||
WARN = 200
|
|
||||||
UNSTABLE = 250
|
|
||||||
BUSY = 300
|
|
||||||
ERROR = 400
|
|
||||||
UNKNOWN = -1
|
|
||||||
|
|
||||||
#OK = 'idle'
|
|
||||||
#BUSY = 'busy'
|
|
||||||
#WARN = 'alarm'
|
|
||||||
#UNSTABLE = 'unstable'
|
|
||||||
#ERROR = 'ERROR'
|
|
||||||
#UNKNOWN = 'unknown'
|
|
@ -25,7 +25,6 @@ import time
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from secop.modules import Drivable, Command, Param
|
from secop.modules import Drivable, Command, Param
|
||||||
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
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ class Cryostat(CryoBase):
|
|||||||
group='pid',
|
group='pid',
|
||||||
),
|
),
|
||||||
mode=Param("mode of regulation",
|
mode=Param("mode of regulation",
|
||||||
datatype=EnumType('ramp', 'pid', 'openloop'),
|
datatype=EnumType('mode', ramp=None, pid=None, openloop=None),
|
||||||
default='ramp',
|
default='ramp',
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
@ -153,7 +152,7 @@ class Cryostat(CryoBase):
|
|||||||
return value
|
return value
|
||||||
self.target = value
|
self.target = value
|
||||||
# next read_status will see this status, until the loop updates it
|
# next read_status will see this status, until the loop updates it
|
||||||
self.status = status.BUSY, 'new target set'
|
self.status = self.Status.BUSY, 'new target set'
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def read_maxpower(self, maxage=0):
|
def read_maxpower(self, maxage=0):
|
||||||
@ -209,13 +208,13 @@ class Cryostat(CryoBase):
|
|||||||
def thread(self):
|
def thread(self):
|
||||||
self.sampletemp = self.T_start
|
self.sampletemp = self.T_start
|
||||||
self.regulationtemp = self.T_start
|
self.regulationtemp = self.T_start
|
||||||
self.status = status.OK, ''
|
self.status = self.Status.IDLE, ''
|
||||||
while not self._stopflag:
|
while not self._stopflag:
|
||||||
try:
|
try:
|
||||||
self.__sim()
|
self.__sim()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.exception(e)
|
self.log.exception(e)
|
||||||
self.status = status.ERROR, str(e)
|
self.status = self.Status.ERROR, str(e)
|
||||||
|
|
||||||
def __sim(self):
|
def __sim(self):
|
||||||
# complex thread handling:
|
# complex thread handling:
|
||||||
@ -264,7 +263,7 @@ class Cryostat(CryoBase):
|
|||||||
# b) see
|
# b) see
|
||||||
# http://brettbeauregard.com/blog/2011/04/
|
# http://brettbeauregard.com/blog/2011/04/
|
||||||
# improving-the-beginners-pid-introduction/
|
# improving-the-beginners-pid-introduction/
|
||||||
if self.mode != 'openloop':
|
if self.mode != self.mode.openloop:
|
||||||
# fix artefacts due to too big timesteps
|
# fix artefacts due to too big timesteps
|
||||||
# actually i would prefer reducing looptime, but i have no
|
# actually i would prefer reducing looptime, but i have no
|
||||||
# good idea on when to increase it back again
|
# good idea on when to increase it back again
|
||||||
@ -328,7 +327,7 @@ class Cryostat(CryoBase):
|
|||||||
lastmode = self.mode
|
lastmode = self.mode
|
||||||
# c)
|
# c)
|
||||||
if self.setpoint != self.target:
|
if self.setpoint != self.target:
|
||||||
if self.ramp == 0:
|
if self.ramp == 0 or self.mode == self.mode.enum.pid:
|
||||||
maxdelta = 10000
|
maxdelta = 10000
|
||||||
else:
|
else:
|
||||||
maxdelta = self.ramp / 60. * h
|
maxdelta = self.ramp / 60. * h
|
||||||
@ -354,12 +353,12 @@ class Cryostat(CryoBase):
|
|||||||
if abs(_T - self.target) > deviation:
|
if abs(_T - self.target) > deviation:
|
||||||
deviation = abs(_T - self.target)
|
deviation = abs(_T - self.target)
|
||||||
if (len(window) < 3) or deviation > self.tolerance:
|
if (len(window) < 3) or deviation > self.tolerance:
|
||||||
self.status = status.BUSY, 'unstable'
|
self.status = self.Status.BUSY, 'unstable'
|
||||||
elif self.setpoint == self.target:
|
elif self.setpoint == self.target:
|
||||||
self.status = status.OK, 'at target'
|
self.status = self.Status.IDLE, 'at target'
|
||||||
damper -= (damper - 1) * 0.1 # max value for damper is 11
|
damper -= (damper - 1) * 0.1 # max value for damper is 11
|
||||||
else:
|
else:
|
||||||
self.status = status.BUSY, 'ramping setpoint'
|
self.status = self.Status.BUSY, 'ramping setpoint'
|
||||||
damper -= (damper - 1) * 0.05
|
damper -= (damper - 1) * 0.05
|
||||||
self.regulationtemp = round(regulation, 3)
|
self.regulationtemp = round(regulation, 3)
|
||||||
self.sampletemp = round(sample, 3)
|
self.sampletemp = round(sample, 3)
|
||||||
|
@ -24,9 +24,9 @@ import time
|
|||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from secop.lib.enum import Enum
|
||||||
from secop.modules import Readable, Drivable, 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
|
|
||||||
|
|
||||||
|
|
||||||
class Switch(Drivable):
|
class Switch(Drivable):
|
||||||
@ -50,9 +50,6 @@ class Switch(Drivable):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
|
||||||
self._started = 0
|
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
# could ask HW
|
# could ask HW
|
||||||
# we just return the value of the target here.
|
# we just return the value of the target here.
|
||||||
@ -65,7 +62,7 @@ class Switch(Drivable):
|
|||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
# could tell HW
|
# could tell HW
|
||||||
pass
|
setattr(self, 'status', (self.Status.BUSY, 'switching %s' % value.name.upper()))
|
||||||
# note: setting self.target to the new value is done after this....
|
# note: setting self.target to the new value is done after this....
|
||||||
# note: we may also return the read-back value from the hw here
|
# note: we may also return the read-back value from the hw here
|
||||||
|
|
||||||
@ -73,8 +70,8 @@ class Switch(Drivable):
|
|||||||
self.log.info("read status")
|
self.log.info("read status")
|
||||||
info = self._update()
|
info = self._update()
|
||||||
if self.target == self.value:
|
if self.target == self.value:
|
||||||
return status.OK, ''
|
return self.Status.IDLE, ''
|
||||||
return status.BUSY, info
|
return self.Status.BUSY, info
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
started = self.parameters['target'].timestamp
|
started = self.parameters['target'].timestamp
|
||||||
@ -90,7 +87,7 @@ class Switch(Drivable):
|
|||||||
info = 'is switched OFF'
|
info = 'is switched OFF'
|
||||||
self.value = self.target
|
self.value = self.target
|
||||||
if info:
|
if info:
|
||||||
self.log.debug(info)
|
self.log.info(info)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
@ -119,7 +116,7 @@ class MagneticField(Drivable):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
self._state = 'idle'
|
self._state = Enum('state', idle=1, switch_on=2, switch_off=3, ramp=4).idle
|
||||||
self._heatswitch = self.DISPATCHER.get_module(self.heatswitch)
|
self._heatswitch = self.DISPATCHER.get_module(self.heatswitch)
|
||||||
_thread = threading.Thread(target=self._thread)
|
_thread = threading.Thread(target=self._thread)
|
||||||
_thread.daemon = True
|
_thread.daemon = True
|
||||||
@ -135,44 +132,45 @@ class MagneticField(Drivable):
|
|||||||
# note: we may also return the read-back value from the hw here
|
# note: we may also return the read-back value from the hw here
|
||||||
|
|
||||||
def read_status(self, maxage=0):
|
def read_status(self, maxage=0):
|
||||||
return (status.OK, '') if self._state == 'idle' else (status.BUSY,
|
if self._state == self._state.enum.idle:
|
||||||
self._state)
|
return (self.Status.IDLE, '')
|
||||||
|
return (self.Status.BUSY, self._state.name)
|
||||||
|
|
||||||
def _thread(self):
|
def _thread(self):
|
||||||
loopdelay = 1
|
loopdelay = 1
|
||||||
while True:
|
while True:
|
||||||
ts = time.time()
|
ts = time.time()
|
||||||
if self._state == 'idle':
|
if self._state == self._state.enum.idle:
|
||||||
if self.target != self.value:
|
if self.target != self.value:
|
||||||
self.log.debug('got new target -> switching heater on')
|
self.log.debug('got new target -> switching heater on')
|
||||||
self._state = 'switch_on'
|
self._state = self._state.enum.switch_on
|
||||||
self._heatswitch.write_target('on')
|
self._heatswitch.write_target('on')
|
||||||
if self._state == 'switch_on':
|
if self._state == self._state.enum.switch_on:
|
||||||
# wait until switch is on
|
# wait until switch is on
|
||||||
if self._heatswitch.read_value() == 'on':
|
if self._heatswitch.read_value() == 'on':
|
||||||
self.log.debug('heatswitch is on -> ramp to %.3f' %
|
self.log.debug('heatswitch is on -> ramp to %.3f' %
|
||||||
self.target)
|
self.target)
|
||||||
self._state = 'ramp'
|
self._state = self._state.enum.ramp
|
||||||
if self._state == 'ramp':
|
if self._state == self._state.enum.ramp:
|
||||||
if self.target == self.value:
|
if self.target == self.value:
|
||||||
self.log.debug('at field! mode is %r' % self.mode)
|
self.log.debug('at field! mode is %r' % self.mode)
|
||||||
if self.mode:
|
if self.mode:
|
||||||
self.log.debug('at field -> switching heater off')
|
self.log.debug('at field -> switching heater off')
|
||||||
self._state = 'switch_off'
|
self._state = self._state.enum.switch_off
|
||||||
self._heatswitch.write_target('off')
|
self._heatswitch.write_target('off')
|
||||||
else:
|
else:
|
||||||
self.log.debug('at field -> hold')
|
self.log.debug('at field -> hold')
|
||||||
self._state = 'idle'
|
self._state = self._state.enum.idle
|
||||||
self.status = self.read_status() # push async
|
self.read_status() # push async
|
||||||
else:
|
else:
|
||||||
step = self.ramp * loopdelay / 60.
|
step = self.ramp * loopdelay / 60.
|
||||||
step = max(min(self.target - self.value, step), -step)
|
step = max(min(self.target - self.value, step), -step)
|
||||||
self.value += step
|
self.value += step
|
||||||
if self._state == 'switch_off':
|
if self._state == self._state.enum.switch_off:
|
||||||
# wait until switch is off
|
# wait until switch is off
|
||||||
if self._heatswitch.read_value() == 'off':
|
if self._heatswitch.read_value() == 'off':
|
||||||
self.log.debug('heatswitch is off at %.3f' % self.value)
|
self.log.debug('heatswitch is off at %.3f' % self.value)
|
||||||
self._state = 'idle'
|
self._state = self._state.enum.idle
|
||||||
self.read_status() # update async
|
self.read_status() # update async
|
||||||
time.sleep(max(0.01, ts + loopdelay - time.time()))
|
time.sleep(max(0.01, ts + loopdelay - time.time()))
|
||||||
self.log.error(self, 'main thread exited unexpectedly!')
|
self.log.error(self, 'main thread exited unexpectedly!')
|
||||||
@ -229,10 +227,10 @@ class SampleTemp(Drivable):
|
|||||||
while True:
|
while True:
|
||||||
ts = time.time()
|
ts = time.time()
|
||||||
if self.value == self.target:
|
if self.value == self.target:
|
||||||
if self.status != status.OK:
|
if self.status[0] != self.Status.IDLE:
|
||||||
self.status = status.OK, ''
|
self.status = self.Status.IDLE, ''
|
||||||
else:
|
else:
|
||||||
self.status = status.BUSY, 'ramping'
|
self.status = self.Status.BUSY, 'ramping'
|
||||||
step = self.ramp * loopdelay / 60.
|
step = self.ramp * loopdelay / 60.
|
||||||
step = max(min(self.target - self.value, step), -step)
|
step = max(min(self.target - self.value, step), -step)
|
||||||
self.value += step
|
self.value += step
|
||||||
@ -278,7 +276,7 @@ class Label(Readable):
|
|||||||
mf_mode = dev_mf.mode
|
mf_mode = dev_mf.mode
|
||||||
mf_val = dev_mf.value
|
mf_val = dev_mf.value
|
||||||
mf_unit = dev_mf.parameters['value'].unit
|
mf_unit = dev_mf.parameters['value'].unit
|
||||||
if mf_stat[0] == status.OK:
|
if mf_stat[0] == self.Status.IDLE:
|
||||||
state = 'Persistent' if mf_mode else 'Non-persistent'
|
state = 'Persistent' if mf_mode else 'Non-persistent'
|
||||||
else:
|
else:
|
||||||
state = mf_stat[1] or 'ramping'
|
state = mf_stat[1] or 'ramping'
|
||||||
@ -293,21 +291,24 @@ class DatatypesTest(Readable):
|
|||||||
"""for demoing all datatypes
|
"""for demoing all datatypes
|
||||||
"""
|
"""
|
||||||
parameters = {
|
parameters = {
|
||||||
'enum': Param(
|
'enum': Param('enum', datatype=EnumType(boo=None, faar=None, z=9),
|
||||||
'enum', datatype=EnumType(
|
readonly=False, default=1),
|
||||||
'boo', 'faar', z=9), readonly=False, default=1), 'tupleof': Param(
|
'tupleof': Param('tuple of int, float and str',
|
||||||
'tuple of int, float and str', datatype=TupleOf(
|
datatype=TupleOf(IntRange(), FloatRange(),
|
||||||
IntRange(), FloatRange(), StringType()), readonly=False, default=(
|
StringType()),
|
||||||
1, 2.3, 'a')), 'arrayof': Param(
|
readonly=False, default=(1, 2.3, 'a')),
|
||||||
'array: 2..3 times bool', datatype=ArrayOf(
|
'arrayof': Param('array: 2..3 times bool',
|
||||||
BoolType(), 2, 3), readonly=False, default=[
|
datatype=ArrayOf(BoolType(), 2, 3),
|
||||||
1, 0, 1]), 'intrange': Param(
|
readonly=False, default=[1, 0, 1]),
|
||||||
'intrange', datatype=IntRange(
|
'intrange': Param('intrange', datatype=IntRange(2, 9),
|
||||||
2, 9), readonly=False, default=4), 'floatrange': Param(
|
readonly=False, default=4),
|
||||||
'floatrange', datatype=FloatRange(
|
'floatrange': Param('floatrange', datatype=FloatRange(-1, 1),
|
||||||
-1, 1), readonly=False, default=0, ), 'struct': Param(
|
readonly=False, default=0, ),
|
||||||
'struct(a=str, b=int, c=bool)', datatype=StructOf(
|
'struct': Param('struct(a=str, b=int, c=bool)',
|
||||||
a=StringType(), b=IntRange(), c=BoolType()), ), }
|
datatype=StructOf(a=StringType(), b=IntRange(),
|
||||||
|
c=BoolType()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ArrayTest(Readable):
|
class ArrayTest(Readable):
|
||||||
|
@ -24,7 +24,6 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
from secop.datatypes import EnumType, FloatRange, StringType
|
from secop.datatypes import EnumType, FloatRange, StringType
|
||||||
from secop.modules import Readable, Drivable, Param
|
from secop.modules import Readable, Drivable, Param
|
||||||
from secop.protocol import status
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pvaccess import Channel # import EPIVSv4 functionallity, PV access
|
from pvaccess import Channel # import EPIVSv4 functionallity, PV access
|
||||||
@ -112,9 +111,9 @@ class EpicsReadable(Readable):
|
|||||||
# XXX: Hardware may have it's own idea about the status: how to obtain?
|
# XXX: Hardware may have it's own idea about the status: how to obtain?
|
||||||
if self.status_pv != 'unset':
|
if self.status_pv != 'unset':
|
||||||
# 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 Drivable.Status.UNKNOWN, self._read_pv(self.status_pv)
|
||||||
# status_pv is unset
|
# status_pv is unset
|
||||||
return (status.OK, 'no pv set')
|
return (Drivable.Status.IDLE, 'no pv set')
|
||||||
|
|
||||||
|
|
||||||
class EpicsDrivable(Drivable):
|
class EpicsDrivable(Drivable):
|
||||||
@ -179,13 +178,11 @@ class EpicsDrivable(Drivable):
|
|||||||
# XXX: Hardware may have it's own idea about the status: how to obtain?
|
# XXX: Hardware may have it's own idea about the status: how to obtain?
|
||||||
if self.status_pv != 'unset':
|
if self.status_pv != 'unset':
|
||||||
# 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 Drivable.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 (
|
if self.read_value() == self.read_target():
|
||||||
status.OK,
|
return (Drivable.Status.OK, '')
|
||||||
'') if self.read_value() == self.read_target() else (
|
return (Drivable.Status.BUSY, 'Moving')
|
||||||
status.BUSY,
|
|
||||||
'Moving')
|
|
||||||
|
|
||||||
|
|
||||||
# """Temperature control loop"""
|
# """Temperature control loop"""
|
||||||
@ -223,11 +220,9 @@ class EpicsTempCtrl(EpicsDrivable):
|
|||||||
# 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 (
|
if at_target:
|
||||||
status.OK,
|
return (Drivable.Status.OK, 'at Target')
|
||||||
'at Target') if at_target else (
|
return (Drivable.Status.BUSY, 'Moving')
|
||||||
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):
|
||||||
|
@ -36,7 +36,6 @@ import threading
|
|||||||
import PyTango
|
import PyTango
|
||||||
|
|
||||||
from secop.lib import lazy_property
|
from secop.lib import lazy_property
|
||||||
from secop.protocol import status
|
|
||||||
#from secop.parse import Parser
|
#from secop.parse import Parser
|
||||||
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
|
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
|
||||||
ArrayOf, EnumType
|
ArrayOf, EnumType
|
||||||
@ -175,11 +174,11 @@ class PyTangoDevice(Module):
|
|||||||
}
|
}
|
||||||
|
|
||||||
tango_status_mapping = {
|
tango_status_mapping = {
|
||||||
PyTango.DevState.ON: status.OK,
|
PyTango.DevState.ON: Drivable.Status.IDLE,
|
||||||
PyTango.DevState.ALARM: status.WARN,
|
PyTango.DevState.ALARM: Drivable.Status.WARN,
|
||||||
PyTango.DevState.OFF: status.ERROR,
|
PyTango.DevState.OFF: Drivable.Status.ERROR,
|
||||||
PyTango.DevState.FAULT: status.ERROR,
|
PyTango.DevState.FAULT: Drivable.Status.ERROR,
|
||||||
PyTango.DevState.MOVING: status.BUSY,
|
PyTango.DevState.MOVING: Drivable.Status.BUSY,
|
||||||
}
|
}
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
@ -225,7 +224,7 @@ class PyTangoDevice(Module):
|
|||||||
|
|
||||||
def _hw_wait(self):
|
def _hw_wait(self):
|
||||||
"""Wait until hardware status is not BUSY."""
|
"""Wait until hardware status is not BUSY."""
|
||||||
while self.read_status(0)[0] == 'BUSY':
|
while self.read_status(0)[0] == Drivable.Status.BUSY:
|
||||||
sleep(0.3)
|
sleep(0.3)
|
||||||
|
|
||||||
def _getProperty(self, name, dev=None):
|
def _getProperty(self, name, dev=None):
|
||||||
@ -363,7 +362,7 @@ class PyTangoDevice(Module):
|
|||||||
tangoStatus = self._dev.Status()
|
tangoStatus = self._dev.Status()
|
||||||
|
|
||||||
# Map status
|
# Map status
|
||||||
myState = self.tango_status_mapping.get(tangoState, status.UNKNOWN)
|
myState = self.tango_status_mapping.get(tangoState, Drivable.Status.UNKNOWN)
|
||||||
|
|
||||||
return (myState, tangoStatus)
|
return (myState, tangoStatus)
|
||||||
|
|
||||||
@ -465,7 +464,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
if attrInfo.unit != 'No unit':
|
if attrInfo.unit != 'No unit':
|
||||||
self.parameters['value'].unit = attrInfo.unit
|
self.parameters['value'].unit = attrInfo.unit
|
||||||
|
|
||||||
def poll(self, nr):
|
def poll(self, nr=0):
|
||||||
super(AnalogOutput, self).poll(nr)
|
super(AnalogOutput, self).poll(nr)
|
||||||
while len(self._history) > 2:
|
while len(self._history) > 2:
|
||||||
# if history would be too short, break
|
# if history would be too short, break
|
||||||
@ -509,8 +508,10 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
return super(AnalogOutput, self).read_status()
|
return super(AnalogOutput, self).read_status()
|
||||||
if self._timeout:
|
if self._timeout:
|
||||||
if self._timeout < currenttime():
|
if self._timeout < currenttime():
|
||||||
return status.UNSTABLE, 'timeout after waiting for stable value'
|
return self.Status.UNSTABLE, 'timeout after waiting for stable value'
|
||||||
return (status.BUSY, 'Moving') if self._moving else (status.OK, 'stable')
|
if self._moving:
|
||||||
|
return (self.Status.BUSY, 'moving')
|
||||||
|
return (self.Status.OK, 'stable')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def absmin(self):
|
def absmin(self):
|
||||||
@ -555,7 +556,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
return self._checkLimits(value)
|
return self._checkLimits(value)
|
||||||
|
|
||||||
def write_target(self, value=FloatRange()):
|
def write_target(self, value=FloatRange()):
|
||||||
if self.status[0] == status.BUSY:
|
if self.status[0] == self.Status.BUSY:
|
||||||
# changing target value during movement is not allowed by the
|
# changing target value during movement is not allowed by the
|
||||||
# Tango base class state machine. If we are moving, stop first.
|
# Tango base class state machine. If we are moving, stop first.
|
||||||
self.do_stop()
|
self.do_stop()
|
||||||
@ -576,7 +577,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
self.read_status(0) # poll our status to keep it updated
|
self.read_status(0) # poll our status to keep it updated
|
||||||
|
|
||||||
def _hw_wait(self):
|
def _hw_wait(self):
|
||||||
while super(AnalogOutput, self).read_status()[0] == status.BUSY:
|
while super(AnalogOutput, self).read_status()[0] == self.Status.BUSY:
|
||||||
sleep(0.3)
|
sleep(0.3)
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
@ -791,7 +792,7 @@ class NamedDigitalInput(DigitalInput):
|
|||||||
super(NamedDigitalInput, self).init()
|
super(NamedDigitalInput, self).init()
|
||||||
try:
|
try:
|
||||||
# pylint: disable=eval-used
|
# pylint: disable=eval-used
|
||||||
self.parameters['value'].datatype = EnumType(**eval(self.mapping))
|
self.parameters['value'].datatype = EnumType('value', **eval(self.mapping))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError('Illegal Value for mapping: %r' % e)
|
raise ValueError('Illegal Value for mapping: %r' % e)
|
||||||
|
|
||||||
@ -858,16 +859,15 @@ class NamedDigitalOutput(DigitalOutput):
|
|||||||
super(NamedDigitalOutput, self).init()
|
super(NamedDigitalOutput, self).init()
|
||||||
try:
|
try:
|
||||||
# pylint: disable=eval-used
|
# pylint: disable=eval-used
|
||||||
self.parameters['value'].datatype = EnumType(**eval(self.mapping))
|
self.parameters['value'].datatype = EnumType('value', **eval(self.mapping))
|
||||||
# pylint: disable=eval-used
|
# pylint: disable=eval-used
|
||||||
self.parameters['target'].datatype = EnumType(**eval(self.mapping))
|
self.parameters['target'].datatype = EnumType('target', **eval(self.mapping))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError('Illegal Value for mapping: %r' % e)
|
raise ValueError('Illegal Value for mapping: %r' % e)
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
# map from enum-str to integer value
|
# map from enum-str to integer value
|
||||||
self._dev.value = self.parameters[
|
self._dev.value = int(value)
|
||||||
'target'].datatype.reversed.get(value, value)
|
|
||||||
self.read_value()
|
self.read_value()
|
||||||
|
|
||||||
|
|
||||||
@ -941,20 +941,20 @@ class StringIO(PyTangoDevice, Module):
|
|||||||
'communicate': Command('Send a string and return the reply',
|
'communicate': Command('Send a string and return the reply',
|
||||||
arguments=[StringType()],
|
arguments=[StringType()],
|
||||||
result=StringType()),
|
result=StringType()),
|
||||||
'flush': Command('Flush output buffer',
|
'flush': Command('Flush output buffer',
|
||||||
arguments=[], result=None),
|
arguments=[], result=None),
|
||||||
'read': Command('read some characters from input buffer',
|
'read': Command('read some characters from input buffer',
|
||||||
arguments=[IntRange()], result=StringType()),
|
arguments=[IntRange()], result=StringType()),
|
||||||
'write': Command('write some chars to output',
|
'write': Command('write some chars to output',
|
||||||
arguments=[StringType()], result=None),
|
arguments=[StringType()], result=None),
|
||||||
'readLine': Command('Read sol - a whole line - eol',
|
'readLine': Command('Read sol - a whole line - eol',
|
||||||
arguments=[], result=StringType()),
|
arguments=[], result=StringType()),
|
||||||
'writeLine': Command('write sol + a whole line + eol',
|
'writeLine': Command('write sol + a whole line + eol',
|
||||||
arguments=[StringType()], result=None),
|
arguments=[StringType()], result=None),
|
||||||
'availablechars': Command('return number of chars in input buffer',
|
'availablechars': Command('return number of chars in input buffer',
|
||||||
arguments=[], result=IntRange(0)),
|
arguments=[], result=IntRange(0)),
|
||||||
'availablelines': Command('return number of lines in input buffer',
|
'availablelines': Command('return number of lines in input buffer',
|
||||||
arguments=[], result=IntRange(0)),
|
arguments=[], result=IntRange(0)),
|
||||||
'multicommunicate': Command('perform a sequence of communications',
|
'multicommunicate': Command('perform a sequence of communications',
|
||||||
arguments=[ArrayOf(
|
arguments=[ArrayOf(
|
||||||
TupleOf(StringType(), IntRange()), 100)],
|
TupleOf(StringType(), IntRange()), 100)],
|
||||||
|
@ -90,12 +90,12 @@ def test_IntRange():
|
|||||||
|
|
||||||
def test_EnumType():
|
def test_EnumType():
|
||||||
# test constructor catching illegal arguments
|
# test constructor catching illegal arguments
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(TypeError):
|
||||||
EnumType(1)
|
EnumType(1)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(TypeError):
|
||||||
EnumType('a', b=0)
|
EnumType(['b', 0])
|
||||||
|
|
||||||
dt = EnumType(a=3, c=7, stuff=1)
|
dt = EnumType('dt', a=3, c=7, stuff=1)
|
||||||
assert dt.as_json == ['enum', dict(a=3, c=7, stuff=1)]
|
assert dt.as_json == ['enum', dict(a=3, c=7, stuff=1)]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -116,9 +116,9 @@ def test_EnumType():
|
|||||||
assert dt.export_value('c') == 7
|
assert dt.export_value('c') == 7
|
||||||
assert dt.export_value('stuff') == 1
|
assert dt.export_value('stuff') == 1
|
||||||
assert dt.export_value(1) == 1
|
assert dt.export_value(1) == 1
|
||||||
assert dt.import_value(7) == 'c'
|
assert dt.import_value('c') == 7
|
||||||
assert dt.import_value(3) == 'a'
|
assert dt.import_value('a') == 3
|
||||||
assert dt.import_value(1) == 'stuff'
|
assert dt.import_value('stuff') == 1
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dt.export_value(2)
|
dt.export_value(2)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -304,7 +304,7 @@ def test_get_datatype():
|
|||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['enum'])
|
get_datatype(['enum'])
|
||||||
assert isinstance(get_datatype(['enum', dict(a=-2.718)]), EnumType)
|
assert isinstance(get_datatype(['enum', dict(a=-2)]), EnumType)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
get_datatype(['enum', 10, -10])
|
get_datatype(['enum', 10, -10])
|
||||||
|
80
test/test_lib_enum.py
Normal file
80
test/test_lib_enum.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# -*- 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 Enum type."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, sys.path[0] + '/..')
|
||||||
|
|
||||||
|
# no fixtures needed
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
from secop.lib.enum import EnumMember, Enum
|
||||||
|
|
||||||
|
|
||||||
|
def test_EnumMember():
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
a = EnumMember(None, 'name', 'value')
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
a = EnumMember(None, 'name', 1)
|
||||||
|
|
||||||
|
e1=Enum('X')
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
a = EnumMember(e1, 'a', 'value')
|
||||||
|
a = EnumMember(e1, 'a', 1)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
a.value = 2
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
a.value += 2
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
a += 2
|
||||||
|
|
||||||
|
# this shall work
|
||||||
|
assert 2 == (a + 1) # pylint: disable=C0122
|
||||||
|
assert (a - 1) == 0
|
||||||
|
assert a
|
||||||
|
assert a + a
|
||||||
|
assert (2 - a) == 1
|
||||||
|
assert -a == -1 # numeric negation
|
||||||
|
assert ~a == -2 # bitmask like NOT
|
||||||
|
assert (a & 3) == 1
|
||||||
|
assert (a | 6) == 7
|
||||||
|
assert (a ^ 7) == 6
|
||||||
|
assert a < 2
|
||||||
|
assert a > 0
|
||||||
|
assert a != 3
|
||||||
|
assert a == 1
|
||||||
|
|
||||||
|
def test_Enum():
|
||||||
|
e1 = Enum('e1')
|
||||||
|
e2 = Enum('e2', e1, a=1, b=3)
|
||||||
|
e3 = Enum('e3', e2, c='a')
|
||||||
|
assert e3.c == 2
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
e2.c = 2
|
||||||
|
assert e3.a < e2.b
|
||||||
|
assert e2.b > e3.a
|
||||||
|
assert e3.c >= e2.a
|
||||||
|
assert e3.b <= e2.b
|
Loading…
x
Reference in New Issue
Block a user