ppms driver now uses command handlers
this is a simplicifcation for the ppms driver - the derivation of a changecmd from a querycmd is moved to CmdHandler.__init__ - the special treatment of handlers when writing configured parameters has moved to CmdHandler.write - introduced Drivable.isDriving, changed Module.isBusy Change-Id: I8862ecda9c8cc998bb018bd960f31c9488146707 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22033 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
@ -136,22 +136,26 @@ class CmdParser:
|
||||
return [c(v) for c, v in zip(self.casts, match.groups())]
|
||||
|
||||
|
||||
class ChangeWrapper:
|
||||
"""Wrapper around a module
|
||||
class Change:
|
||||
"""contains new values for the call to change_<group>
|
||||
|
||||
A ChangeWrapper instance is used as the 'new' argument for the change_<group> message.
|
||||
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>' indicates, whether <parameter> is to be changed.
|
||||
setting new.<parameter> does not yet set the value on the module.
|
||||
In addition '<parameter>' in new indicates, whether <parameter> is to be changed.
|
||||
new.<parameter> can not be changed
|
||||
"""
|
||||
def __init__(self, module, valuedict):
|
||||
self._module = module
|
||||
for pname, value in valuedict.items():
|
||||
setattr(self, pname, value)
|
||||
def __init__(self, module, parameters, valuedict):
|
||||
self.__dict__.update(valuedict, _module=module, _parameters=parameters)
|
||||
|
||||
def __getattr__(self, key):
|
||||
def __getattr__(self, pname):
|
||||
"""get current values from _module for unchanged parameters"""
|
||||
return getattr(self._module, key)
|
||||
if not pname in self._parameters:
|
||||
raise AttributeError("parameter '%s' is not within the handlers group"
|
||||
% pname)
|
||||
return getattr(self._module, pname)
|
||||
|
||||
def __setattr__(self, pname, value):
|
||||
raise AttributeError("can't set attribute ")
|
||||
|
||||
def __contains__(self, pname):
|
||||
"""check whether a specific parameter is to be changed"""
|
||||
@ -237,35 +241,49 @@ class CmdHandlerBase:
|
||||
raise
|
||||
return Done # parameters should be updated already
|
||||
|
||||
def get_write_func(self, pname):
|
||||
def get_write_func(self, pname, wfunc):
|
||||
"""returns the write function passed to the metaclass
|
||||
|
||||
return None if not used.
|
||||
may be overriden to return None, if not used
|
||||
"""
|
||||
|
||||
def wfunc(module, value, cmd=self, pname=pname):
|
||||
cmd.write(module, {pname: value})
|
||||
return Done
|
||||
if wfunc:
|
||||
def new_wfunc(module, value, cmd=self, pname=pname, wfunc=wfunc):
|
||||
value = wfunc(module, value)
|
||||
if value is None or value is Done:
|
||||
return value
|
||||
cmd.write(module, {pname: value})
|
||||
return Done
|
||||
else:
|
||||
def new_wfunc(module, value, cmd=self, pname=pname):
|
||||
cmd.write(module, {pname: value})
|
||||
return Done
|
||||
|
||||
return wfunc
|
||||
return new_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 parameter to be changed.
|
||||
When called from initialization, valuedict may have more items.
|
||||
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)
|
||||
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)
|
||||
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 = ()
|
||||
# create wrapper object 'new' with changed parameter 'pname'
|
||||
new = ChangeWrapper(module, valuedict)
|
||||
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)
|
||||
if values is None: # this indicates that nothing has to be written
|
||||
@ -273,6 +291,13 @@ class CmdHandlerBase:
|
||||
# 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))
|
||||
|
||||
|
||||
class CmdHandler(CmdHandlerBase):
|
||||
"""more evolved command handler
|
||||
@ -290,16 +315,19 @@ class CmdHandler(CmdHandlerBase):
|
||||
# the given separator
|
||||
|
||||
|
||||
def __init__(self, group, querycmd, replyfmt):
|
||||
def __init__(self, group, querycmd, replyfmt, changecmd=None):
|
||||
"""initialize the command handler
|
||||
|
||||
group: the handler group (used for analyze_<group> and change_<group>)
|
||||
querycmd: the command for a query, may contain named formats for cmdargs
|
||||
replyfmt: the format for reading the reply with some scanf like behaviour
|
||||
changecmd: the first part of the change command (without values), may be
|
||||
omitted if no write happens
|
||||
"""
|
||||
super().__init__(group)
|
||||
self.querycmd = querycmd
|
||||
self.replyfmt = CmdParser(replyfmt)
|
||||
self.changecmd = changecmd
|
||||
|
||||
def parse_reply(self, reply):
|
||||
"""return values from a raw reply"""
|
||||
@ -310,11 +338,8 @@ class CmdHandler(CmdHandlerBase):
|
||||
return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
|
||||
|
||||
def make_change(self, module, *values):
|
||||
"""make a change command from a query command"""
|
||||
changecmd = self.querycmd.replace('?', ' ')
|
||||
if not self.querycmd.endswith('?'):
|
||||
changecmd += ','
|
||||
changecmd %= {k: getattr(module, k, None) for k in self.CMDARGS}
|
||||
"""make a change command"""
|
||||
changecmd = self.changecmd % {k: getattr(module, k, None) for k in self.CMDARGS}
|
||||
return changecmd + self.replyfmt.format(*values)
|
||||
|
||||
def send_change(self, module, *values):
|
||||
|
@ -112,12 +112,12 @@ class ModuleMeta(PropertyMeta):
|
||||
# skip commands for now
|
||||
continue
|
||||
rfunc = attrs.get('read_' + pname, None)
|
||||
handler = pobj.handler.get_read_func(newtype, pname) if pobj.handler else None
|
||||
if handler:
|
||||
rfunc_handler = pobj.handler.get_read_func(newtype, pname) if pobj.handler else None
|
||||
if rfunc_handler:
|
||||
if rfunc:
|
||||
raise ProgrammingError("parameter '%s' can not have a handler "
|
||||
"and read_%s" % (pname, pname))
|
||||
rfunc = handler
|
||||
rfunc = rfunc_handler
|
||||
else:
|
||||
for base in bases:
|
||||
if rfunc is not None:
|
||||
@ -151,17 +151,13 @@ class ModuleMeta(PropertyMeta):
|
||||
|
||||
if not pobj.readonly:
|
||||
wfunc = attrs.get('write_' + pname, None)
|
||||
handler = pobj.handler.get_write_func(pname) if pobj.handler else None
|
||||
if handler:
|
||||
if wfunc:
|
||||
raise ProgrammingError("parameter '%s' can not have a handler "
|
||||
"and write_%s" % (pname, pname))
|
||||
wfunc = handler
|
||||
else:
|
||||
for base in bases:
|
||||
if wfunc is not None:
|
||||
break
|
||||
wfunc = getattr(base, 'write_' + pname, None)
|
||||
# if a handler and write_<param> is present, wfunc will be called
|
||||
# by the handler first
|
||||
wfunc = pobj.handler.get_write_func(pname, wfunc) if pobj.handler else wfunc
|
||||
for base in bases:
|
||||
if wfunc is not None:
|
||||
break
|
||||
wfunc = getattr(base, 'write_' + pname, None)
|
||||
|
||||
if wfunc is None or getattr(wfunc, '__wrapped__', False) is False:
|
||||
|
||||
|
@ -255,7 +255,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
||||
pobj = self.parameters[pname]
|
||||
self.DISPATCHER.announce_update_error(self, pname, pobj, exception)
|
||||
|
||||
def isBusy(self):
|
||||
def isBusy(self, status=None):
|
||||
'''helper function for treating substates of BUSY correctly'''
|
||||
# defined even for non drivable (used for dynamic polling)
|
||||
return False
|
||||
@ -295,21 +295,11 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
||||
with proper error handling
|
||||
"""
|
||||
try:
|
||||
pobj = self.parameters[pname]
|
||||
if pobj.handler:
|
||||
pnames = pobj.handler.parameters
|
||||
valuedict = {n: self.writeDict.pop(n) for n in pnames if n in self.writeDict}
|
||||
if valuedict:
|
||||
self.log.debug('write parameters %r', valuedict)
|
||||
pobj.handler.write(self, valuedict, force_read=True)
|
||||
return
|
||||
pobj.handler.read(self)
|
||||
if pname in self.writeDict:
|
||||
self.log.debug('write parameter %s', pname)
|
||||
getattr(self, 'write_'+ pname)(self.writeDict.pop(pname))
|
||||
else:
|
||||
if pname in self.writeDict:
|
||||
self.log.debug('write parameter %s', pname)
|
||||
getattr(self, 'write_'+ pname)(self.writeDict.pop(pname))
|
||||
else:
|
||||
getattr(self, 'read_'+ pname)()
|
||||
getattr(self, 'read_'+ pname)()
|
||||
except SilentError as e:
|
||||
pass
|
||||
except SECoPError as e:
|
||||
@ -428,9 +418,13 @@ class Drivable(Writable):
|
||||
'status' : Override(datatype=TupleOf(EnumType(Status), StringType())),
|
||||
}
|
||||
|
||||
def isBusy(self):
|
||||
def isBusy(self, status=None):
|
||||
'''helper function for treating substates of BUSY correctly'''
|
||||
return 300 <= self.status[0] < 400
|
||||
return 300 <= (status or self.status)[0] < 400
|
||||
|
||||
def isDriving(self, status=None):
|
||||
'''helper function (finalize is busy, not driving)'''
|
||||
return 300 <= (status or self.status)[0] < 380
|
||||
|
||||
# improved polling: may poll faster if module is BUSY
|
||||
def pollParams(self, nr=0):
|
||||
|
Reference in New Issue
Block a user