improved commandhandler

- analyze_<group> returns now a dict
- change_<group> has no more values arguments.
  values may be read with change.readValues(), which leaves more
  freedom to the programmer, especially if the values do not need
  to be read before a change

Change-Id: I67b70302eaf9bbdac107df61123062fa133d501d
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22047
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2019-12-16 17:23:20 +01:00
parent fcad78a682
commit a876eba5f6
5 changed files with 213 additions and 219 deletions

View File

@ -28,23 +28,29 @@ for all parameters with the same handler. Before analyze_<group> is called, the
reply is parsed and converted to values, which are then given as arguments.
def analyze_<group>(self, value1, value2, ...):
# <here> we have to calculate parameters from the values (value1, value2 ...)
# and assign them to self.<parameter>
# no return value is expected
# here we have to calculate parameters from the values (value1, value2 ...)
# and return a dict with parameter names as keys and new values.
It is an error to have a read_<parameter> method implemented on a parameter with a
handler.
For write, instead of the methods write_<parameter>" we write one method change_<group>
for all parameters with the same handler.
def change_<group>(self, new, value1, value2, ...):
# <new> is a wrapper object around the module, containing already the new values.
# if READ_BEFORE_WRITE is True (the default), the additional arguments (value1, ...)
# must be in the argument list. They contain the values read from the hardware.
# If they are not needed, set READ_BEFORE_WRITE to False, or declare them as '*args'.
# The expression ('<parameter>' in new) returns a boolean indicating, whether
# this parameter is subject to change.
def change_<group>(self, change):
# Change contains the to be changed parameters as attributes, and also the unchanged
# parameters taking part to the handler group. If the method needs the current values
# from the hardware, it can read them with change.getValues(). This call does also
# update the values of the attributes of change, which are not subject to change.
# In addtion, the method may call change.toBeChanged(<parameter name>) to determine,
# whether a specific parameter is subject to change.
# The return value must be either a sequence of values to be written to the hardware,
# which will be formatted by the handler, or None. The latter is used only in some
# special cases, when nothing has to be written.
A write_<parameter> method may be implemented in addition. In that case, it is executed
before change_<group>. write_<parameters> may return None or Done, in these cases
change_<group> is not called.
"""
import re
@ -56,8 +62,8 @@ from secop.errors import ProgrammingError
class CmdParser:
"""helper for parsing replies
using a subset of old style python formatting
the same format can be used or formatting command arguments
using a subset of old style python formatting.
The same format can be used or formatting command arguments
"""
# make a map of cast functions
@ -139,34 +145,43 @@ class CmdParser:
class Change:
"""contains new values for the call to change_<group>
A Change instance is used as the 'new' argument for the change_<group> method.
new.<parameter> is either the new, changed value or the old value from the module.
In addition '<parameter>' in new indicates, whether <parameter> is to be changed.
new.<parameter> can not be changed
A Change instance is used as an argument for the change_<group> method.
Getting the value of change.<parameter> returns either the new, changed value or the
current one from the module, if there is no new value.
"""
def __init__(self, module, parameters, valuedict):
self.__dict__.update(valuedict, _module=module, _parameters=parameters)
def __init__(self, handler, module, valuedict):
self._handler = handler
self._module = module
self._valuedict = valuedict
self._to_be_changed = set(self._valuedict)
self._do_read = True
def __getattr__(self, pname):
"""get current values from _module for unchanged parameters"""
if not pname in self._parameters:
raise AttributeError("parameter '%s' is not within the handlers group"
% pname)
return getattr(self._module, pname)
def __getattr__(self, key):
"""return attribute from module key is not in self._valuedict"""
if key in self._valuedict:
return self._valuedict[key]
return getattr(self._module, key)
def __setattr__(self, pname, value):
raise AttributeError("can't set attribute ")
def doesInclude(self, *args):
"""check whether one of the specified parameters is to be changed"""
return bool(set(args) & self._to_be_changed)
def __contains__(self, pname):
"""check whether a specific parameter is to be changed"""
return pname in self.__dict__
def readValues(self):
"""read values from the hardware
and update our parameter attributes accordingly (i.e. do not touch the new values)
"""
if self._do_read:
self._do_read = False
self._reply = self._handler.send_command(self._module)
result = self._handler.analyze(self._module, *self._reply)
result.update(self._valuedict)
self._valuedict.update(result)
return self._reply
class CmdHandlerBase:
"""generic command handler"""
READ_BEFORE_WRITE = True
# if READ_BEFORE_WRITE is True, a read is performed before a write, and the parsed
# additional parameters are added to the argument list of change_<group>.
def __init__(self, group):
# group is used for calling the proper analyze_<group> and change_<group> methods
@ -216,19 +231,19 @@ class CmdHandlerBase:
raise ProgrammingError("the handler '%s' for '%s.%s' is already used in module '%s'"
% (self.group, modclass.__name__, pname, self._module_class.__name__))
self.parameters.add(pname)
self.analyze = getattr(modclass, 'analyze_' + self.group)
return self.read
def read(self, module):
"""the read function passed to the metaclass
overwrite with None if not used
"""
"""write values from module"""
assert module.__class__ == self._module_class
try:
# do a read of the current hw values
reply = self.send_command(module)
# convert them to parameters
getattr(module, 'analyze_' + self.group)(*reply)
assert module.__class__ == self._module_class
result = self.analyze(module, *reply)
for pname, value in result.items():
setattr(module, pname, value)
for pname in self.parameters:
if module.parameters[pname].readerror:
# clear errors on parameters, which were not updated.
@ -239,72 +254,62 @@ class CmdHandlerBase:
for pname in self.parameters:
module.setError(pname, e)
raise
return Done # parameters should be updated already
return Done
def get_write_func(self, pname, wfunc):
def get_write_func(self, pname, pre_wfunc=None):
"""returns the write function passed to the metaclass
may be overriden to return None, if not used
If pre_wfunc is given, it is to be called before change_<group>.
May be overriden to return None, if not used
"""
self.change = getattr(self._module_class, 'change_' + self.group)
if wfunc:
def new_wfunc(module, value, cmd=self, pname=pname, wfunc=wfunc):
if pre_wfunc:
def wfunc(module, value, hdl=self, pname=pname, wfunc=pre_wfunc):
value = wfunc(module, value)
if value is None or value is Done:
return value
cmd.write(module, {pname: value})
hdl.write(module, pname, value)
return Done
else:
def new_wfunc(module, value, cmd=self, pname=pname):
cmd.write(module, {pname: value})
def wfunc(module, value, hdl=self, pname=pname):
hdl.write(module, pname, value)
return Done
return new_wfunc
return wfunc
def write(self, module, valuedict, force_read=False):
"""write values to the module
When called from write_<param>, valuedict contains only one item,
the single parameter to be changed.
If called directly, valuedict may have more items.
"""
analyze = getattr(module, 'analyze_' + self.group)
def write(self, module, pname, value):
"""write value to the module"""
assert module.__class__ == self._module_class
force_read = False
valuedict = {pname: value}
if module.writeDict: # collect other parameters to be written
valuedict = dict(valuedict)
for p in self.parameters:
if p in self.writeDict:
valuedict[p] = self.writeDict.pop(p)
if p in module.writeDict:
valuedict[p] = module.writeDict.pop(p)
elif p not in valuedict:
force_read = True
if self.READ_BEFORE_WRITE or force_read:
# do a read of the current hw values
values = self.send_command(module)
# convert them to parameters
analyze(*values)
if not self.READ_BEFORE_WRITE:
values = () # values are not expected for change_<group>
new = Change(module, self.parameters, valuedict)
# call change_* for calculation new hw values
values = getattr(module, 'change_' + self.group)(new, *values)
change = Change(self, module, valuedict)
if force_read:
change.readValues()
values = self.change(module, change)
if values is None: # this indicates that nothing has to be written
return
# send the change command and a query command
analyze(*self.send_change(module, *values))
def change(self, module, *values):
"""write and read back values
might be called from a write method, if change_<group> is not implemented
"""
getattr(module, 'analyze_' + self.group)(*self.send_change(module, *values))
reply = self.send_change(module, *values)
result = self.analyze(module, *reply)
for k, v in result.items():
setattr(module, k, v)
class CmdHandler(CmdHandlerBase):
"""more evolved command handler
this command handler works for a syntax, where the change command syntax can be
build from the query command syntax, with the to be changed items at the second
part of the command, using the same format as for the reply.
This command handler works for a syntax, where the reply of a query command has
the same format as the arguments for the change command.
Examples: devices from LakeShore, PPMS
implementing classes have to define/override the following:

View File

@ -424,7 +424,7 @@ class Drivable(Writable):
def isDriving(self, status=None):
'''helper function (finalize is busy, not driving)'''
return 300 <= (status or self.status)[0] < 380
return 300 <= (status or self.status)[0] < 390
# improved polling: may poll faster if module is BUSY
def pollParams(self, nr=0):