support write_ method on readonly param and more

- write method may be used internally on a readonly parameter
+ add IDLE, WARN, BUSY and ERROR to secop.core
+ secop.datatype.EnumType: allow 'self' as member name
+ secop.lib.statemachine: log Restart and Stop exceptions only on debug level
+ secop_psi.ccu4.CCU4: explicit conversion to float
+ secop.proxy: remove superfluos and erroneous make_secop_error

Change-Id: I2f13d31ceacd2bde65eab64f8eae4225556c18f5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27963
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2022-03-16 15:50:46 +01:00
parent 3c0c60615a
commit 60c62a340d
10 changed files with 26 additions and 20 deletions

View File

@ -162,7 +162,7 @@ max-line-length=132
no-space-check=trailing-comma,dict-separator no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module # Maximum number of lines in a module
max-module-lines=1200 max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab). # tab).

View File

@ -7,7 +7,7 @@ Module Base Classes
.. autodata:: secop.modules.Done .. autodata:: secop.modules.Done
.. autoclass:: secop.modules.Module .. autoclass:: secop.modules.Module
:members: earlyInit, initModule, startModule, pollerClass :members: earlyInit, initModule, startModule
.. autoclass:: secop.modules.Readable .. autoclass:: secop.modules.Readable
:members: Status :members: Status

View File

@ -31,7 +31,7 @@ from secop.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
from secop.iohandler import IOHandler, IOHandlerBase from secop.iohandler import IOHandler, IOHandlerBase
from secop.lib.enum import Enum from secop.lib.enum import Enum
from secop.modules import Attached, Communicator, \ from secop.modules import Attached, Communicator, \
Done, Drivable, Module, Readable, Writable Done, Drivable, Module, Readable, Writable, HasAccessibles
from secop.params import Command, Parameter from secop.params import Command, Parameter
from secop.properties import Property from secop.properties import Property
from secop.proxy import Proxy, SecNode, proxy_class from secop.proxy import Proxy, SecNode, proxy_class
@ -39,3 +39,8 @@ from secop.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev
from secop.persistent import PersistentMixin, PersistentParam from secop.persistent import PersistentMixin, PersistentParam
from secop.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \ from secop.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \
CommonWriteHandler, nopoll CommonWriteHandler, nopoll
ERROR = Drivable.Status.ERROR
WARN = Drivable.Status.WARN
BUSY = Drivable.Status.BUSY
IDLE = Drivable.Status.IDLE

View File

@ -22,7 +22,7 @@
# ***************************************************************************** # *****************************************************************************
"""Define validated data types.""" """Define validated data types."""
# pylint: disable=abstract-method # pylint: disable=abstract-method, too-many-lines
import sys import sys
@ -454,7 +454,10 @@ class EnumType(DataType):
super().__init__() super().__init__()
if members is not None: if members is not None:
kwds.update(members) kwds.update(members)
self._enum = Enum(enum_or_name, **kwds) if isinstance(enum_or_name, str):
self._enum = Enum(enum_or_name, kwds) # allow 'self' as name
else:
self._enum = Enum(enum_or_name, **kwds)
self.default = self._enum[self._enum.members[0]] self.default = self._enum[self._enum.members[0]]
def copy(self): def copy(self):

View File

@ -178,7 +178,9 @@ class StateMachine:
if self.stop_exc: if self.stop_exc:
raise self.stop_exc raise self.stop_exc
except Exception as e: except Exception as e:
self.log.info('%r raised in state %r', e, self.status_string) # Stop and Restart are not unusual -> no warning
log = self.log.debug if isinstance(e, Stop) else self.log.warning
log('%r raised in state %r', e, self.status_string)
self.last_error = e self.last_error = e
ret = self.cleanup(self) ret = self.cleanup(self)
self.log.debug('cleanup %r %r %r', self.cleanup, self.last_error, ret) self.log.debug('cleanup %r %r %r', self.cleanup, self.last_error, ret)

View File

@ -161,8 +161,8 @@ class HasAccessibles(HasProperties):
new_rfunc.wrapped = True # indicate to subclasses that no more wrapping is needed new_rfunc.wrapped = True # indicate to subclasses that no more wrapping is needed
setattr(cls, 'read_' + pname, new_rfunc) setattr(cls, 'read_' + pname, new_rfunc)
if not pobj.readonly: wfunc = getattr(cls, 'write_' + pname, None)
wfunc = getattr(cls, 'write_' + pname, None) if not pobj.readonly or wfunc: # allow write_ method even when pobj is not readonly
wrapped = getattr(wfunc, 'wrapped', False) # meaning: wrapped or auto generated wrapped = getattr(wfunc, 'wrapped', False) # meaning: wrapped or auto generated
if (wfunc is None or wrapped) and pobj.handler: if (wfunc is None or wrapped) and pobj.handler:
# ignore the handler, if a write function is present # ignore the handler, if a write function is present
@ -392,9 +392,8 @@ class Module(HasAccessibles):
continue continue
if pname in cfgdict: if pname in cfgdict:
if not pobj.readonly and pobj.initwrite is not False: if pobj.initwrite is not False and hasattr(self, 'write_' + pname):
# parameters given in cfgdict have to call write_<pname> # parameters given in cfgdict have to call write_<pname>
# TODO: not sure about readonly (why not a parameter which can only be written from config?)
try: try:
pobj.value = pobj.datatype(cfgdict[pname]) pobj.value = pobj.datatype(cfgdict[pname])
self.writeDict[pname] = pobj.value self.writeDict[pname] = pobj.value
@ -417,10 +416,8 @@ class Module(HasAccessibles):
except BadValueError as e: except BadValueError as e:
# this should not happen, as the default is already checked in Parameter # this should not happen, as the default is already checked in Parameter
raise ProgrammingError('bad default for %s:%s: %s' % (name, pname, e)) from None raise ProgrammingError('bad default for %s:%s: %s' % (name, pname, e)) from None
if pobj.initwrite and not pobj.readonly: if pobj.initwrite and hasattr(self, 'write_' + pname):
# we will need to call write_<pname> # we will need to call write_<pname>
# if this is not desired, the default must not be given
# TODO: not sure about readonly (why not a parameter which can only be written from config?)
pobj.value = value pobj.value = value
self.writeDict[pname] = value self.writeDict[pname] = value
else: else:

View File

@ -75,7 +75,7 @@ class PersistentMixin(HasAccessibles):
self.initData = {} self.initData = {}
for pname in self.parameters: for pname in self.parameters:
pobj = self.parameters[pname] pobj = self.parameters[pname]
if not pobj.readonly and getattr(pobj, 'persistent', 0): if hasattr(self, 'write_' + pname) and getattr(pobj, 'persistent', 0):
self.initData[pname] = pobj.value self.initData[pname] = pobj.value
if pobj.persistent == 'auto': if pobj.persistent == 'auto':
def cb(value, m=self): def cb(value, m=self):

View File

@ -23,8 +23,7 @@
from secop.client import SecopClient, decode_msg, encode_msg_frame from secop.client import SecopClient, decode_msg, encode_msg_frame
from secop.datatypes import StringType from secop.datatypes import StringType
from secop.errors import BadValueError, \ from secop.errors import BadValueError, CommunicationFailedError, ConfigError
CommunicationFailedError, ConfigError, make_secop_error
from secop.lib import get_class from secop.lib import get_class
from secop.modules import Drivable, Module, Readable, Writable from secop.modules import Drivable, Module, Readable, Writable
from secop.params import Command, Parameter from secop.params import Command, Parameter
@ -47,8 +46,6 @@ class ProxyModule(HasIO, Module):
if parameter not in self.parameters: if parameter not in self.parameters:
return # ignore unknown parameters return # ignore unknown parameters
# should be done here: deal with clock differences # should be done here: deal with clock differences
if readerror:
readerror = make_secop_error(*readerror)
self.announceUpdate(parameter, value, readerror, timestamp) self.announceUpdate(parameter, value, readerror, timestamp)
def initModule(self): def initModule(self):
@ -201,7 +198,7 @@ def proxy_class(remote_class, name=None):
def wfunc(self, value, pname=aname): def wfunc(self, value, pname=aname):
value, _, readerror = self._secnode.setParameter(self.name, pname, value) value, _, readerror = self._secnode.setParameter(self.name, pname, value)
if readerror: if readerror:
raise make_secop_error(*readerror) raise readerror
return value return value
attrs['write_' + aname] = wfunc attrs['write_' + aname] = wfunc

View File

@ -72,7 +72,7 @@ class HeLevel(HasIO, Readable):
""" """
name, txtvalue = self.communicate(cmd).split('=') name, txtvalue = self.communicate(cmd).split('=')
assert name == cmd.split('=')[0] # check that we got a reply to our command assert name == cmd.split('=')[0] # check that we got a reply to our command
return txtvalue # Frappy will automatically convert the string to the needed data type return float(txtvalue)
def read_value(self): def read_value(self):
return self.query('h') return self.query('h')

View File

@ -64,6 +64,7 @@ def test_EnumMember():
assert a != 3 assert a != 3
assert a == 1 assert a == 1
def test_Enum(): def test_Enum():
e1 = Enum('e1') e1 = Enum('e1')
e2 = Enum('e2', e1, a=1, b=3) e2 = Enum('e2', e1, a=1, b=3)
@ -75,3 +76,4 @@ def test_Enum():
assert e2.b > e3.a assert e2.b > e3.a
assert e3.c >= e2.a assert e3.c >= e2.a
assert e3.b <= e2.b assert e3.b <= e2.b
assert Enum({'self': 0, 'other': 1})('self') == 0