improve readability be renaming PARAMS,PROPS,CMDS

and others.

Change-Id: Ie37768ed813acdf0cb0707c70ff63397ec8bfbf1
Reviewed-on: https://forge.frm2.tum.de/review/17320
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber
2018-02-14 13:32:19 +01:00
parent aba67dde7f
commit f54e8ccb45
9 changed files with 372 additions and 427 deletions

View File

@ -39,14 +39,14 @@ from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange,
EVENT_ONLY_ON_CHANGED_VALUES = False
# storage for PARAMeter settings:
# storage for Parameter settings:
# if readonly is False, the value can be changed (by code, or remote)
# if no default is given, the parameter MUST be specified in the configfile
# during startup, value is initialized with the default value or
# from the config file if specified there
class PARAM(object):
class Param(object):
def __init__(self,
description,
@ -85,7 +85,7 @@ class PARAM(object):
def copy(self):
# return a copy of ourselfs
return PARAM(description=self.description,
return Param(description=self.description,
datatype=self.datatype,
default=self.default,
unit=self.unit,
@ -116,13 +116,13 @@ class PARAM(object):
return self.datatype.export_value(self.value)
class OVERRIDE(object):
class Override(object):
def __init__(self, **kwds):
self.kwds = kwds
def apply(self, paramobj):
if isinstance(paramobj, PARAM):
if isinstance(paramobj, Param):
for k, v in self.kwds.iteritems():
if hasattr(paramobj, k):
setattr(paramobj, k, v)
@ -133,12 +133,12 @@ class OVERRIDE(object):
(k, v, paramobj))
else:
raise ProgrammingError(
"Overrides can only be applied to PARAM's, %r is none!" %
"Overrides can only be applied to Param's, %r is none!" %
paramobj)
# storage for CMDs settings (description + call signature...)
class CMD(object):
# storage for Commands settings (description + call signature...)
class Command(object):
def __init__(self, description, arguments=None, result=None):
# descriptive text for humans
@ -171,8 +171,8 @@ class ModuleMeta(type):
if '__constructed__' in attrs:
return newtype
# merge PROPERTIES, PARAM and CMDS from all sub-classes
for entry in ['PROPERTIES', 'PARAMS', 'CMDS']:
# merge properties, Param and commands from all sub-classes
for entry in ['properties', 'parameters', 'commands']:
newentry = {}
for base in reversed(bases):
if hasattr(base, entry):
@ -181,20 +181,20 @@ class ModuleMeta(type):
setattr(newtype, entry, newentry)
# apply Overrides from all sub-classes
newparams = getattr(newtype, 'PARAMS')
newparams = getattr(newtype, 'parameters')
for base in reversed(bases):
overrides = getattr(base, 'OVERRIDES', {})
overrides = getattr(base, 'overrides', {})
for n, o in overrides.iteritems():
newparams[n] = o.apply(newparams[n].copy())
for n, o in attrs.get('OVERRIDES', {}).iteritems():
for n, o in attrs.get('overrides', {}).iteritems():
newparams[n] = o.apply(newparams[n].copy())
# check validity of PARAM entries
for pname, pobj in newtype.PARAMS.items():
# check validity of Param entries
for pname, pobj in newtype.parameters.items():
# XXX: allow dicts for overriding certain aspects only.
if not isinstance(pobj, PARAM):
raise ProgrammingError('%r: PARAMs entry %r should be a '
'PARAM object!' % (name, pname))
if not isinstance(pobj, Param):
raise ProgrammingError('%r: Params entry %r should be a '
'Param object!' % (name, pname))
# XXX: create getters for the units of params ??
@ -212,7 +212,7 @@ class ModuleMeta(type):
else:
# return cached value
self.log.debug("rfunc(%s): return cached value" % pname)
value = self.PARAMS[pname].value
value = self.parameters[pname].value
setattr(self, pname, value) # important! trigger the setter
return value
@ -231,13 +231,13 @@ class ModuleMeta(type):
def wrapped_wfunc(self, value, pname=pname, wfunc=wfunc):
self.log.debug("wfunc(%s): set %r" % (pname, value))
pobj = self.PARAMS[pname]
pobj = self.parameters[pname]
value = pobj.datatype.validate(value)
if wfunc:
self.log.debug('calling %r(%r)' % (wfunc, value))
value = wfunc(self, value) or value
# XXX: use setattr or direct manipulation
# of self.PARAMS[pname]?
# of self.parameters[pname]?
setattr(self, pname, value)
return value
@ -248,33 +248,33 @@ class ModuleMeta(type):
wrapped_wfunc.__wrapped__ = True
def getter(self, pname=pname):
return self.PARAMS[pname].value
return self.parameters[pname].value
def setter(self, value, pname=pname):
pobj = self.PARAMS[pname]
pobj = self.parameters[pname]
value = pobj.datatype.validate(value)
pobj.timestamp = time.time()
if not EVENT_ONLY_ON_CHANGED_VALUES or (value != pobj.value):
pobj.value = value
# also send notification
if self.PARAMS[pname].export:
if self.parameters[pname].export:
self.log.debug('%s is now %r' % (pname, value))
self.DISPATCHER.announce_update(self, pname, pobj)
setattr(newtype, pname, property(getter, setter))
# also collect/update information about CMD's
setattr(newtype, 'CMDS', getattr(newtype, 'CMDS', {}))
# also collect/update information about Command's
setattr(newtype, 'commands', getattr(newtype, 'commands', {}))
for attrname in attrs:
if attrname.startswith('do_'):
if attrname[3:] in newtype.CMDS:
if attrname[3:] in newtype.commands:
continue
value = getattr(newtype, attrname)
if isinstance(value, types.MethodType):
argspec = inspect.getargspec(value)
if argspec[0] and argspec[0][0] == 'self':
del argspec[0][0]
newtype.CMDS[attrname[3:]] = CMD(
newtype.commands[attrname[3:]] = Command(
getattr(value, '__doc__'), argspec.args,
None) # XXX: how to find resulttype?
attrs['__constructed__'] = True
@ -294,9 +294,9 @@ class ModuleMeta(type):
class Module(object):
"""Basic Module, doesn't do much"""
__metaclass__ = ModuleMeta
# 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???
PROPERTIES = {
properties = {
'group': None, # some Modules may be grouped together
'meaning': None, # XXX: ???
'priority': None, # XXX: ???
@ -304,11 +304,11 @@ class Module(object):
'description': "The manufacturer forgot to set a meaningful description. please nag him!",
# what else?
}
# PARAMS and CMDS are auto-merged upon subclassing
# PARAMS = {
# 'description': PARAM('short description of this module and its function', datatype=StringType(), default='no specified'),
# parameter and commands are auto-merged upon subclassing
# parameters = {
# 'description': Param('short description of this module and its function', datatype=StringType(), default='no specified'),
# }
CMDS = {}
commands = {}
DISPATCHER = None
def __init__(self, logger, cfgdict, devname, dispatcher):
@ -316,48 +316,48 @@ class Module(object):
self.DISPATCHER = dispatcher
self.log = logger
self.name = devname
# make local copies of PARAMS
# make local copies of parameter
params = {}
for k, v in self.PARAMS.items()[:]:
for k, v in self.parameters.items()[:]:
params[k] = v.copy()
self.PARAMS = params
# make local copies of PROPERTIES
self.parameters = params
# make local copies of properties
props = {}
for k, v in self.PROPERTIES.items()[:]:
for k, v in self.properties.items()[:]:
props[k] = v
self.PROPERTIES = props
self.properties = props
# check and apply properties specified in cfgdict
# moduleproperties are to be specified as
# '.<propertyname>=<propertyvalue>'
for k, v in cfgdict.items():
if k[0] == '.':
if k[1:] in self.PROPERTIES:
self.PROPERTIES[k[1:]] = v
if k[1:] in self.properties:
self.properties[k[1:]] = v
del cfgdict[k]
# derive automatic properties
mycls = self.__class__
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
self.PROPERTIES['_implementation'] = myclassname
self.PROPERTIES['interface_class'] = [
self.properties['_implementation'] = myclassname
self.properties['interface_class'] = [
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')]
#self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
#self.properties['interface'] = self.properties['interfaces'][0]
# remove unset (default) module properties
for k, v in self.PROPERTIES.items():
for k, v in self.properties.items():
if v is None:
del self.PROPERTIES[k]
del self.properties[k]
# check and apply parameter_properties
# specified as '<paramname>.<propertyname> = <propertyvalue>'
for k, v in cfgdict.items()[:]:
if '.' in k[1:]:
paramname, propname = k.split('.', 1)
if paramname in self.PARAMS:
paramobj = self.PARAMS[paramname]
if paramname in self.parameters:
paramobj = self.parameters[paramname]
if propname == 'datatype':
paramobj.datatype = get_datatype(cfgdict.pop(k))
elif hasattr(paramobj, propname):
@ -365,17 +365,17 @@ class Module(object):
del cfgdict[k]
# check config for problems
# only accept config items specified in PARAMS
# only accept config items specified in parameters
for k, v in cfgdict.items():
if k not in self.PARAMS:
if k not in self.parameters:
raise ConfigError(
'Module %s:config Parameter %r '
'not unterstood! (use on of %r)' %
(self.name, k, self.PARAMS.keys()))
(self.name, k, self.parameters.keys()))
# complain if a PARAM entry has no default value and
# complain if a Param entry has no default value and
# is not specified in cfgdict
for k, v in self.PARAMS.items():
for k, v in self.parameters.items():
if k not in cfgdict:
if v.default is Ellipsis and k != 'value':
# Ellipsis is the one single value you can not specify....
@ -385,8 +385,8 @@ class Module(object):
# assume default value was given
cfgdict[k] = v.default
# replace CLASS level PARAM objects with INSTANCE level ones
self.PARAMS[k] = self.PARAMS[k].copy()
# replace CLASS level Param objects with INSTANCE level ones
self.parameters[k] = self.parameters[k].copy()
# now 'apply' config:
# pass values through the datatypes and store as attributes
@ -394,7 +394,7 @@ class Module(object):
if k == 'value':
continue
# apply datatype, complain if type does not fit
datatype = self.PARAMS[k].datatype
datatype = self.parameters[k].datatype
if datatype is not None:
# only check if datatype given
try:
@ -420,12 +420,12 @@ class Readable(Module):
providing the readonly parameter 'value' and 'status'
"""
PARAMS = {
'value': PARAM('current value of the Module', readonly=True, default=0.,
parameters = {
'value': Param('current value of the Module', readonly=True, default=0.,
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), ),
'status': PARAM('current status of the Module', default=(status.OK, ''),
'status': Param('current status of the Module', default=(status.OK, ''),
datatype=TupleOf(
EnumType(**{
'IDLE': status.OK,
@ -458,13 +458,13 @@ class Readable(Module):
def poll(self, nr):
# poll status first
fastpoll = False
if 'status' in self.PARAMS:
if 'status' in self.parameters:
stat = self.read_status(0)
# self.log.info('polling read_status -> %r' % (stat,))
fastpoll = stat[0] == status.BUSY
# if fastpoll:
# self.log.info('fastpoll!')
for pname, pobj in self.PARAMS.iteritems():
for pname, pobj in self.parameters.iteritems():
if not pobj.poll:
continue
if pname == 'status':
@ -489,15 +489,15 @@ class Writable(Readable):
providing a settable 'target' parameter to those of a Readable
"""
PARAMS = {
'target': PARAM(
parameters = {
'target': Param(
'target value of the Module',
default=0.,
readonly=False,
datatype=FloatRange(),
),
}
# XXX: CMDS ???? auto deriving working well enough?
# XXX: commands ???? auto deriving working well enough?
class Drivable(Writable):
@ -519,8 +519,8 @@ class Communicator(Module):
providing no parameters, but a 'communicate' command.
"""
CMDS = {
"communicate" : CMD("provides the simplest mean to communication",
commands = {
"communicate" : Command("provides the simplest mean to communication",
arguments=[StringType()],
result=StringType()
),

View File

@ -194,7 +194,7 @@ class Dispatcher(object):
if modulename in self._export:
# omit export=False params!
res = {}
for paramname, param in self.get_module(modulename).PARAMS.items():
for paramname, param in self.get_module(modulename).parameters.items():
if param.export:
res[paramname] = param.as_dict(only_static)
self.log.debug('list params for module %s -> %r' %
@ -208,7 +208,7 @@ class Dispatcher(object):
if modulename in self._export:
# omit export=False params!
res = {}
for cmdname, cmdobj in self.get_module(modulename).CMDS.items():
for cmdname, cmdobj in self.get_module(modulename).commands.items():
res[cmdname] = cmdobj.as_dict()
self.log.debug('list cmds for module %s -> %r' % (modulename, res))
return res
@ -229,7 +229,7 @@ class Dispatcher(object):
mod_desc['parameters'].extend([pname, param])
for cname, cmd in self.list_module_cmds(modulename).items():
mod_desc['commands'].extend([cname, cmd])
for propname, prop in module.PROPERTIES.items():
for propname, prop in module.properties.items():
mod_desc[propname] = prop
result['modules'].extend([modulename, mod_desc])
result['equipment_id'] = self.equipment_id
@ -250,7 +250,7 @@ class Dispatcher(object):
modulename,
only_static=True),
'commands': self.list_module_cmds(modulename),
'properties': module.PROPERTIES,
'properties': module.properties,
}
result['modules'][modulename] = dd
result['equipment_id'] = self.equipment_id
@ -267,7 +267,7 @@ class Dispatcher(object):
if moduleobj is None:
raise NoSuchModuleError(module=modulename)
cmdspec = moduleobj.CMDS.get(command, None)
cmdspec = moduleobj.commands.get(command, None)
if cmdspec is None:
raise NoSuchCommandError(module=modulename, command=command)
if len(cmdspec.arguments) != len(arguments):
@ -293,7 +293,7 @@ class Dispatcher(object):
if moduleobj is None:
raise NoSuchModuleError(module=modulename)
pobj = moduleobj.PARAMS.get(pname, None)
pobj = moduleobj.parameters.get(pname, None)
if pobj is None:
raise NoSuchParamError(module=modulename, parameter=pname)
if pobj.readonly:
@ -318,7 +318,7 @@ class Dispatcher(object):
if moduleobj is None:
raise NoSuchModuleError(module=modulename)
pobj = moduleobj.PARAMS.get(pname, None)
pobj = moduleobj.parameters.get(pname, None)
if pobj is None:
raise NoSuchParamError(module=modulename, parameter=pname)
@ -368,7 +368,7 @@ class Dispatcher(object):
res = self._setParamValue(msg.module, msg.parameter, msg.value)
else:
# first check if module has a target
if 'target' not in self.get_module(msg.module).PARAMS:
if 'target' not in self.get_module(msg.module).parameters:
raise ReadonlyError(module=msg.module, parameter=None)
res = self._setParamValue(msg.module, 'target', msg.value)
res.parameter = 'target'
@ -398,10 +398,10 @@ class Dispatcher(object):
self.activate_connection(conn)
# easy approach: poll all values...
for modulename, moduleobj in self._modules.items():
for pname, pobj in moduleobj.PARAMS.items():
for pname, pobj in moduleobj.parameters.items():
if not pobj.export:
continue
# WARNING: THIS READS ALL PARAMS FROM HW!
# WARNING: THIS READS ALL parameters FROM HW!
# XXX: should we send the cached values instead? (pbj.value)
# also: ignore errors here.
try:

View File

@ -24,7 +24,7 @@
import random
from time import sleep
from secop.modules import Module, Readable, Writable, Drivable, PARAM
from secop.modules import Module, Readable, Writable, Drivable, Param
from secop.lib import mkthread
from secop.protocol import status
from secop.datatypes import FloatRange
@ -32,20 +32,30 @@ from secop.datatypes import FloatRange
class SimBase(object):
def __init__(self, cfgdict):
# spice up PARAMS if requested by extra property
# spice up parameters if requested by extra property
# hint: us a comma-separated list if mor than one extra_param
# BIG FAT WARNING: changing extra params will NOT generate events!
# XXX: implement default read_* and write_* methods to handle
# read and change messages correctly
if '.extra_params' in cfgdict:
extra_params = cfgdict.pop('.extra_params')
# make a copy of self.PARAMS
self.PARAMS=dict((k,v.copy()) for k,v in self.PARAMS.items())
# make a copy of self.parameter
self.parameters = dict((k,v.copy()) for k,v in self.parameters.items())
for k in extra_params.split(','):
k = k.strip()
self.PARAMS[k] = PARAM('extra_param: %s' % k.strip(),
self.parameters[k] = Param('extra_param: %s' % k.strip(),
datatype=FloatRange(),
default=0.0)
def reader(maxage=0, pname=k):
self.log.debug('simulated reading %s' % pname)
return self.parameters[pname].value
setattr(self, 'read_' + k, reader)
def writer(newval, pname=k):
self.log.debug('simulated writing %r to %s' % (newval, pname))
self.parameters[pname].value = newval
return newval
setattr(self, 'write_' + k, writer)
def late_init(self):
self._sim_thread = mkthread(self._sim)
@ -61,7 +71,7 @@ class SimBase(object):
return True
def read_value(self, maxage=0):
if 'jitter' in self.PARAMS:
if 'jitter' in self.parameters:
return self._value + self.jitter*(0.5-random.random())
return self._value
@ -76,14 +86,14 @@ class SimReadable(SimBase, Readable):
def __init__(self, logger, cfgdict, devname, dispatcher):
SimBase.__init__(self, cfgdict)
Readable.__init__(self, logger, cfgdict, devname, dispatcher)
self._value = self.PARAMS['value'].default
self._value = self.parameters['value'].default
class SimWritable(SimBase, Writable):
def __init__(self, logger, cfgdict, devname, dispatcher):
SimBase.__init__(self, cfgdict)
Writable.__init__(self, logger, cfgdict, devname, dispatcher)
self._value = self.PARAMS['value'].default
self._value = self.parameters['value'].default
def read_value(self, maxage=0):
return self.target
def write_target(self, value):
@ -94,16 +104,16 @@ class SimDrivable(SimBase, Drivable):
def __init__(self, logger, cfgdict, devname, dispatcher):
SimBase.__init__(self, cfgdict)
Drivable.__init__(self, logger, cfgdict, devname, dispatcher)
self._value = self.PARAMS['value'].default
self._value = self.parameters['value'].default
def sim(self):
while self._value == self.target:
sleep(0.3)
self.status = status.BUSY, 'MOVING'
speed = 0
if 'ramp' in self.PARAMS:
if 'ramp' in self.parameters:
speed = self.ramp / 60. # ramp is per minute!
elif 'speed' in self.PARAMS:
elif 'speed' in self.parameters:
speed = self.speed
if speed == 0:
self._value = self.target