From f54e8ccb4540a8f8d08860f37ac0251adf1bbd6d Mon Sep 17 00:00:00 2001 From: Enrico Faulhaber Date: Wed, 14 Feb 2018 13:32:19 +0100 Subject: [PATCH] improve readability be renaming PARAMS,PROPS,CMDS and others. Change-Id: Ie37768ed813acdf0cb0707c70ff63397ec8bfbf1 Reviewed-on: https://forge.frm2.tum.de/review/17320 Tested-by: JenkinsCodeReview Reviewed-by: Enrico Faulhaber --- secop/modules.py | 136 ++++++------- secop/protocol/dispatcher.py | 20 +- secop/simulation.py | 32 +-- secop_demo/cryo.py | 73 ++++--- secop_demo/modules.py | 82 ++++---- secop_demo/test.py | 12 +- secop_ess/epics.py | 44 ++--- secop_mlz/amagnet.py | 30 +-- secop_mlz/entangle.py | 370 ++++++++++++++--------------------- 9 files changed, 372 insertions(+), 427 deletions(-) diff --git a/secop/modules.py b/secop/modules.py index ffa1ca5..6bf897e 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -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 # '.=' 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 '. = ' 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() ), diff --git a/secop/protocol/dispatcher.py b/secop/protocol/dispatcher.py index 0728a2a..f17a63d 100644 --- a/secop/protocol/dispatcher.py +++ b/secop/protocol/dispatcher.py @@ -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: diff --git a/secop/simulation.py b/secop/simulation.py index dcb3834..38cf5d2 100644 --- a/secop/simulation.py +++ b/secop/simulation.py @@ -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 diff --git a/secop_demo/cryo.py b/secop_demo/cryo.py index 278a663..9a5b1d4 100644 --- a/secop_demo/cryo.py +++ b/secop_demo/cryo.py @@ -23,9 +23,8 @@ from math import atan import time import random -import threading -from secop.modules import Drivable, CMD, PARAM +from secop.modules import Drivable, Command, Param from secop.protocol import status from secop.datatypes import FloatRange, EnumType, TupleOf from secop.lib import clamp, mkthread @@ -42,90 +41,90 @@ class Cryostat(CryoBase): - cooling power - thermal transfer between regulation and samplen """ - PARAMS = dict( - jitter=PARAM("amount of random noise on readout values", + parameters = dict( + jitter=Param("amount of random noise on readout values", datatype=FloatRange(0, 1), unit="K", default=0.1, readonly=False, export=False, ), - T_start=PARAM("starting temperature for simulation", + T_start=Param("starting temperature for simulation", datatype=FloatRange(0), default=10, export=False, ), - looptime=PARAM("timestep for simulation", + looptime=Param("timestep for simulation", datatype=FloatRange(0.01, 10), unit="s", default=1, readonly=False, export=False, ), - ramp=PARAM("ramping speed of the setpoint", + ramp=Param("ramping speed of the setpoint", datatype=FloatRange(0, 1e3), unit="K/min", default=1, readonly=False, ), - setpoint=PARAM("current setpoint during ramping else target", + setpoint=Param("current setpoint during ramping else target", datatype=FloatRange(), default=1, unit='K', ), - maxpower=PARAM("Maximum heater power", + maxpower=Param("Maximum heater power", datatype=FloatRange(0), default=1, unit="W", readonly=False, group='heater_settings', ), - heater=PARAM("current heater setting", + heater=Param("current heater setting", datatype=FloatRange(0, 100), default=0, unit="%", group='heater_settings', ), - heaterpower=PARAM("current heater power", + heaterpower=Param("current heater power", datatype=FloatRange(0), default=0, unit="W", group='heater_settings', ), - target=PARAM("target temperature", + target=Param("target temperature", datatype=FloatRange(0), default=0, unit="K", readonly=False, ), - value=PARAM("regulation temperature", + value=Param("regulation temperature", datatype=FloatRange(0), default=0, unit="K", ), - pid=PARAM("regulation coefficients", + pid=Param("regulation coefficients", datatype=TupleOf(FloatRange(0), FloatRange(0, 100), FloatRange(0, 100)), default=(40, 10, 2), readonly=False, group='pid', ), - p=PARAM("regulation coefficient 'p'", + p=Param("regulation coefficient 'p'", datatype=FloatRange(0), default=40, unit="%/K", readonly=False, group='pid', ), - i=PARAM("regulation coefficient 'i'", + i=Param("regulation coefficient 'i'", datatype=FloatRange(0, 100), default=10, readonly=False, group='pid', ), - d=PARAM("regulation coefficient 'd'", + d=Param("regulation coefficient 'd'", datatype=FloatRange(0, 100), default=2, readonly=False, group='pid', ), - mode=PARAM("mode of regulation", + mode=Param("mode of regulation", datatype=EnumType('ramp', 'pid', 'openloop'), default='ramp', readonly=False, ), - pollinterval=PARAM("polling interval", + pollinterval=Param("polling interval", datatype=FloatRange(0), default=5, ), - tolerance=PARAM("temperature range for stability checking", + tolerance=Param("temperature range for stability checking", datatype=FloatRange(0, 100), default=0.1, unit='K', readonly=False, group='stability', ), - window=PARAM("time window for stability checking", + window=Param("time window for stability checking", datatype=FloatRange(1, 900), default=30, unit='s', readonly=False, group='stability', ), - timeout=PARAM("max waiting time for stabilisation check", + timeout=Param("max waiting time for stabilisation check", datatype=FloatRange(1, 36000), default=900, unit='s', readonly=False, group='stability', ), ) - CMDS = dict( - stop=CMD( + commands = dict( + stop=Command( "Stop ramping the setpoint\n\nby setting the current setpoint as new target", [], None), @@ -236,7 +235,7 @@ class Cryostat(CryoBase): lastflow = 0 last_heaters = (0, 0) delta = 0 - I = D = 0 + _I = _D = 0 lastD = 0 damper = 1 lastmode = self.mode @@ -283,32 +282,32 @@ class Cryostat(CryoBase): kp = self.p / 10. # LakeShore P = 10*k_p ki = kp * abs(self.i) / 500. # LakeShore I = 500/T_i kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d - P = kp * error - I += ki * error * h - D = kd * delta / h + _P = kp * error + _I += ki * error * h + _D = kd * delta / h # avoid reset windup - I = clamp(I, 0., 100.) # I is in % + _I = clamp(_I, 0., 100.) # _I is in % # avoid jumping heaterpower if switching back to pid mode if lastmode != self.mode: # adjust some values upon switching back on - I = self.heater - P - D + _I = self.heater - _P - _D - v = P + I + D + v = _P + _I + _D # in damping mode, use a weighted sum of old + new heaterpower if damper > 1: v = ((damper**2 - 1) * self.heater + v) / damper**2 # damp oscillations due to D switching signs - if D * lastD < -0.2: + if _D * lastD < -0.2: v = (v + heater) / 2. # clamp new heater power to 0..100% heater = clamp(v, 0., 100.) - lastD = D + lastD = _D self.log.debug('PID: P = %.2f, I = %.2f, D = %.2f, ' - 'heater = %.2f' % (P, I, D, heater)) + 'heater = %.2f' % (_P, _I, _D, heater)) # check for turn-around points to detect oscillations -> # increase damper @@ -351,9 +350,9 @@ class Cryostat(CryoBase): window.pop(0) # obtain min/max deviation = 0 - for _, T in window: - if abs(T - self.target) > deviation: - deviation = abs(T - self.target) + for _, _T in window: + if abs(_T - self.target) > deviation: + deviation = abs(_T - self.target) if (len(window) < 3) or deviation > self.tolerance: self.status = status.BUSY, 'unstable' elif self.setpoint == self.target: diff --git a/secop_demo/modules.py b/secop_demo/modules.py index dee5727..3ef1299 100644 --- a/secop_demo/modules.py +++ b/secop_demo/modules.py @@ -24,7 +24,7 @@ import time import random import threading -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.protocol import status @@ -32,19 +32,19 @@ from secop.protocol import status class Switch(Drivable): """switch it on or off.... """ - PARAMS = { - 'value': PARAM('current state (on or off)', + parameters = { + 'value': Param('current state (on or off)', datatype=EnumType(on=1, off=0), default=0, ), - 'target': PARAM('wanted state (on or off)', + 'target': Param('wanted state (on or off)', datatype=EnumType(on=1, off=0), default=0, readonly=False, ), - 'switch_on_time': PARAM('seconds to wait after activating the switch', + 'switch_on_time': Param('seconds to wait after activating the switch', datatype=FloatRange(0, 60), unit='s', default=10, export=False, ), - 'switch_off_time': PARAM('cool-down time in seconds', + 'switch_off_time': Param('cool-down time in seconds', datatype=FloatRange(0, 60), unit='s', default=10, export=False, ), @@ -77,7 +77,7 @@ class Switch(Drivable): return status.BUSY, info def _update(self): - started = self.PARAMS['target'].timestamp + started = self.parameters['target'].timestamp info = '' if self.target > self.value: info = 'waiting for ON' @@ -97,23 +97,23 @@ class Switch(Drivable): class MagneticField(Drivable): """a liquid magnet """ - PARAMS = { - 'value': PARAM('current field in T', + parameters = { + 'value': Param('current field in T', unit='T', datatype=FloatRange(-15, 15), default=0, ), - 'target': PARAM('target field in T', + 'target': Param('target field in T', unit='T', datatype=FloatRange(-15, 15), default=0, readonly=False, ), - 'ramp': PARAM('ramping speed', + 'ramp': Param('ramping speed', unit='T/min', datatype=FloatRange(0, 1), default=0.1, readonly=False, ), - 'mode': PARAM('what to do after changing field', + 'mode': Param('what to do after changing field', default=1, datatype=EnumType(persistent=1, hold=0), readonly=False, ), - 'heatswitch': PARAM('name of heat switch device', + 'heatswitch': Param('name of heat switch device', datatype=StringType(), export=False, ), } @@ -184,11 +184,11 @@ class MagneticField(Drivable): class CoilTemp(Readable): """a coil temperature """ - PARAMS = { - 'value': PARAM('Coil temperatur', + parameters = { + 'value': Param('Coil temperatur', unit='K', datatype=FloatRange(), default=0, ), - 'sensor': PARAM("Sensor number or calibration id", + 'sensor': Param("Sensor number or calibration id", datatype=StringType(), readonly=True, ), } @@ -200,14 +200,14 @@ class CoilTemp(Readable): class SampleTemp(Drivable): """a sample temperature """ - PARAMS = { - 'value': PARAM('Sample temperature', + parameters = { + 'value': Param('Sample temperature', unit='K', datatype=FloatRange(), default=10, ), - 'sensor': PARAM("Sensor number or calibration id", + 'sensor': Param("Sensor number or calibration id", datatype=StringType(), readonly=True, ), - 'ramp': PARAM('moving speed in K/min', + 'ramp': Param('moving speed in K/min', datatype=FloatRange(0, 100), unit='K/min', default=0.1, readonly=False, ), @@ -241,20 +241,23 @@ class SampleTemp(Drivable): class Label(Readable): - """ + """Displays the status of a cryomagnet + by composing its (stringtype) value from the status/value + of several subdevices. used for demoing connections between + modules. """ - PARAMS = { - 'system': PARAM("Name of the magnet system", + parameters = { + 'system': Param("Name of the magnet system", datatype=StringType, export=False, ), - 'subdev_mf': PARAM("name of subdevice for magnet status", + 'subdev_mf': Param("name of subdevice for magnet status", datatype=StringType, export=False, ), - 'subdev_ts': PARAM("name of subdevice for sample temp", + 'subdev_ts': Param("name of subdevice for sample temp", datatype=StringType, export=False, ), - 'value': PARAM("final value of label string", + 'value': Param("final value of label string", datatype=StringType, ), } @@ -265,7 +268,7 @@ class Label(Readable): dev_ts = self.DISPATCHER.get_module(self.subdev_ts) if dev_ts: strings.append('at %.3f %s' % - (dev_ts.read_value(), dev_ts.PARAMS['value'].unit)) + (dev_ts.read_value(), dev_ts.parameters['value'].unit)) else: strings.append('No connection to sample temp!') @@ -274,7 +277,7 @@ class Label(Readable): mf_stat = dev_mf.read_status() mf_mode = dev_mf.mode mf_val = dev_mf.value - mf_unit = dev_mf.PARAMS['value'].unit + mf_unit = dev_mf.parameters['value'].unit if mf_stat[0] == status.OK: state = 'Persistent' if mf_mode else 'Non-persistent' else: @@ -287,21 +290,28 @@ class Label(Readable): class DatatypesTest(Readable): + """for demoing all datatypes """ - """ - PARAMS = { - 'enum': PARAM( + parameters = { + 'enum': Param( 'enum', datatype=EnumType( - 'boo', 'faar', z=9), readonly=False, default=1), 'tupleof': PARAM( + 'boo', 'faar', z=9), readonly=False, default=1), 'tupleof': Param( 'tuple of int, float and str', datatype=TupleOf( IntRange(), FloatRange(), StringType()), readonly=False, default=( - 1, 2.3, 'a')), 'arrayof': PARAM( + 1, 2.3, 'a')), 'arrayof': Param( 'array: 2..3 times bool', datatype=ArrayOf( BoolType(), 2, 3), readonly=False, default=[ - 1, 0, 1]), 'intrange': PARAM( + 1, 0, 1]), 'intrange': Param( 'intrange', datatype=IntRange( - 2, 9), readonly=False, default=4), 'floatrange': PARAM( + 2, 9), readonly=False, default=4), 'floatrange': Param( 'floatrange', datatype=FloatRange( - -1, 1), readonly=False, default=0, ), 'struct': PARAM( + -1, 1), readonly=False, default=0, ), 'struct': Param( 'struct(a=str, b=int, c=bool)', datatype=StructOf( a=StringType(), b=IntRange(), c=BoolType()), ), } + + +class ArrayTest(Readable): + parameters = { + "x" : Param('value', datatype=ArrayOf(FloatRange(),100000,100000), + default = 100000 * [0]), + } diff --git a/secop_demo/test.py b/secop_demo/test.py index befec99..beae877 100644 --- a/secop_demo/test.py +++ b/secop_demo/test.py @@ -22,7 +22,7 @@ import random -from secop.modules import Readable, Drivable, Communicator, PARAM +from secop.modules import Readable, Drivable, Communicator, Param from secop.datatypes import FloatRange, StringType @@ -43,8 +43,8 @@ class Heater(Drivable): class name indicates it to be some heating element, but the implementation may do anything """ - PARAMS = { - 'maxheaterpower': PARAM('maximum allowed heater power', + parameters = { + 'maxheaterpower': Param('maximum allowed heater power', datatype=FloatRange(0, 100), unit='W', ), } @@ -62,15 +62,15 @@ class Temp(Drivable): class name indicates it to be some temperature controller, but the implementation may do anything """ - PARAMS = { - 'sensor': PARAM( + parameters = { + 'sensor': Param( "Sensor number or calibration id", datatype=StringType( 8, 16), readonly=True, ), - 'target': PARAM( + 'target': Param( "Target temperature", default=300.0, datatype=FloatRange(0), diff --git a/secop_ess/epics.py b/secop_ess/epics.py index 597cc08..eb2113e 100644 --- a/secop_ess/epics.py +++ b/secop_ess/epics.py @@ -20,10 +20,8 @@ # Erik Dahlbäck # ***************************************************************************** -import random - -from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType -from secop.modules import Readable, Module, Drivable, PARAM +from secop.datatypes import EnumType, FloatRange, StringType +from secop.modules import Readable, Drivable, Param from secop.protocol import status try: @@ -59,18 +57,18 @@ except ImportError: class EpicsReadable(Readable): """EpicsDrivable handles a Drivable interfacing to EPICS v4""" - # Commmon PARAMS for all EPICS devices - PARAMS = { - 'value': PARAM('EPICS generic value', + # Commmon parameter for all EPICS devices + parameters = { + 'value': Param('EPICS generic value', datatype=FloatRange(), default=300.0,), - 'epics_version': PARAM("EPICS version used, v3 or v4", + 'epics_version': Param("EPICS version used, v3 or v4", datatype=EnumType(v3=3, v4=4),), # 'private' parameters: not remotely accessible - 'value_pv': PARAM('EPICS pv_name of value', + 'value_pv': Param('EPICS pv_name of value', datatype=StringType(), default="unset", export=False), - 'status_pv': PARAM('EPICS pv_name of status', + 'status_pv': Param('EPICS pv_name of status', datatype=StringType(), default="unset", export=False), } @@ -119,20 +117,20 @@ class EpicsReadable(Readable): class EpicsDrivable(Drivable): """EpicsDrivable handles a Drivable interfacing to EPICS v4""" - # Commmon PARAMS for all EPICS devices - PARAMS = { - 'target': PARAM('EPICS generic target', datatype=FloatRange(), + # Commmon parameter for all EPICS devices + parameters = { + 'target': Param('EPICS generic target', datatype=FloatRange(), default=300.0, readonly=False), - 'value': PARAM('EPICS generic value', datatype=FloatRange(), + 'value': Param('EPICS generic value', datatype=FloatRange(), default=300.0,), - 'epics_version': PARAM("EPICS version used, v3 or v4", + 'epics_version': Param("EPICS version used, v3 or v4", datatype=StringType(),), # 'private' parameters: not remotely accessible - 'target_pv': PARAM('EPICS pv_name of target', datatype=StringType(), + 'target_pv': Param('EPICS pv_name of target', datatype=StringType(), default="unset", export=False), - 'value_pv': PARAM('EPICS pv_name of value', datatype=StringType(), + 'value_pv': Param('EPICS pv_name of value', datatype=StringType(), default="unset", export=False), - 'status_pv': PARAM('EPICS pv_name of status', datatype=StringType(), + 'status_pv': Param('EPICS pv_name of status', datatype=StringType(), default="unset", export=False), } @@ -188,22 +186,22 @@ class EpicsDrivable(Drivable): 'Moving') -"""Temperature control loop""" +# """Temperature control loop""" # should also derive from secop.core.temperaturecontroller, once its # features are agreed upon class EpicsTempCtrl(EpicsDrivable): - PARAMS = { + parameters = { # TODO: restrict possible values with oneof datatype - 'heaterrange': PARAM('Heater range', datatype=StringType(), + 'heaterrange': Param('Heater range', datatype=StringType(), default='Off', readonly=False,), - 'tolerance': PARAM('allowed deviation between value and target', + 'tolerance': Param('allowed deviation between value and target', datatype=FloatRange(1e-6, 1e6), default=0.1, readonly=False,), # 'private' parameters: not remotely accessible - 'heaterrange_pv': PARAM('EPICS pv_name of heater range', + 'heaterrange_pv': Param('EPICS pv_name of heater range', datatype=StringType(), default="unset", export=False,), } diff --git a/secop_mlz/amagnet.py b/secop_mlz/amagnet.py index 8e71378..47871da 100644 --- a/secop_mlz/amagnet.py +++ b/secop_mlz/amagnet.py @@ -32,7 +32,7 @@ from secop.lib.sequence import SequencerMixin, Step from secop.protocol import status from secop.datatypes import StringType, TupleOf, FloatRange, ArrayOf, StructOf from secop.errors import DisabledError, ConfigError -from secop.modules import PARAM, Drivable +from secop.modules import Param, Drivable class GarfieldMagnet(SequencerMixin, Drivable): @@ -48,31 +48,31 @@ class GarfieldMagnet(SequencerMixin, Drivable): the symmetry setting selects which. """ - PARAMS = { - 'subdev_currentsource': PARAM('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False), - 'subdev_enable': PARAM('Switch to set for on/off', datatype=StringType(), readonly=True, export=False), - 'subdev_polswitch': PARAM('Switch to set for polarity', datatype=StringType(), readonly=True, export=False), - 'subdev_symmetry': PARAM('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False), - 'userlimits': PARAM('User defined limits of device value', + parameters = { + 'subdev_currentsource': Param('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False), + 'subdev_enable': Param('Switch to set for on/off', datatype=StringType(), readonly=True, export=False), + 'subdev_polswitch': Param('Switch to set for polarity', datatype=StringType(), readonly=True, export=False), + 'subdev_symmetry': Param('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False), + 'userlimits': Param('User defined limits of device value', unit='main', datatype=TupleOf(FloatRange(), FloatRange()), default=(float('-Inf'), float('+Inf')), readonly=False, poll=10), - 'abslimits': PARAM('Absolute limits of device value', + 'abslimits': Param('Absolute limits of device value', unit='main', datatype=TupleOf(FloatRange(), FloatRange()), default=(-0.5, 0.5), poll=True, ), - 'precision': PARAM('Precision of the device value (allowed deviation ' + 'precision': Param('Precision of the device value (allowed deviation ' 'of stable values from target)', unit='main', datatype=FloatRange(0.001), default=0.001, readonly=False, ), - 'ramp': PARAM('Target rate of field change per minute', readonly=False, + 'ramp': Param('Target rate of field change per minute', readonly=False, unit='main/min', datatype=FloatRange(), default=1.0), - 'calibration': PARAM('Coefficients for calibration ' + 'calibration': Param('Coefficients for calibration ' 'function: [c0, c1, c2, c3, c4] calculates ' 'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)' ' in T', poll=1, datatype=ArrayOf(FloatRange(), 5, 5), default=(1.0, 0.0, 0.0, 0.0, 0.0)), - 'calibrationtable': PARAM('Map of Coefficients for calibration per symmetry setting', + 'calibrationtable': Param('Map of Coefficients for calibration per symmetry setting', datatype=StructOf(symmetric=ArrayOf(FloatRange(), 5, 5), short=ArrayOf( FloatRange(), 5, 5), @@ -175,7 +175,7 @@ class GarfieldMagnet(SequencerMixin, Drivable): def read_abslimits(self, maxage=0): maxfield = self._current2field(self._currentsource.abslimits[1]) # limit to configured value (if any) - maxfield = min(maxfield, max(self.PARAMS['abslimits'].default)) + maxfield = min(maxfield, max(self.parameters['abslimits'].default)) return -maxfield, maxfield def read_ramp(self, maxage=0): @@ -203,10 +203,10 @@ class GarfieldMagnet(SequencerMixin, Drivable): # safe to switch self._polswitch.write_target( '+1' if polarity > 0 else str(polarity)) - return 0 + return if self._currentsource.value < 0.1: self._polswitch.write_target('0') - return current_pol + return # unsafe to switch, go to safe state first self._currentsource.write_target(0) diff --git a/secop_mlz/entangle.py b/secop_mlz/entangle.py index 0e609d1..35159ab 100644 --- a/secop_mlz/entangle.py +++ b/secop_mlz/entangle.py @@ -22,26 +22,25 @@ # ***************************************************************************** # This is based upon the entangle-nicos integration -""" -This module contains the MLZ SECoP - TANGO integration. +"""This module contains the MLZ SECoP - TANGO integration. Here we support devices which fulfill the official MLZ TANGO interface for the respective device classes. """ import re -import sys from time import sleep, time as currenttime import threading import PyTango -import numpy -from secop.lib import lazy_property, mkthread +from secop.lib import lazy_property from secop.protocol import status -from secop.datatypes import * -from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError -from secop.modules import PARAM, CMD, OVERRIDE, Module, Readable, Drivable +from secop.parse import Parser +from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \ + ArrayOf, EnumType +from secop.errors import ConfigError, ProgrammingError, CommunicationError, HardwareError +from secop.modules import Param, Command, Override, Module, Readable, Drivable ##### @@ -158,13 +157,13 @@ class PyTangoDevice(Module): execution and attribute operations with logging and exception mapping. """ - PARAMS = { - 'comtries': PARAM('Maximum retries for communication', + parameters = { + 'comtries': Param('Maximum retries for communication', datatype=IntRange(1, 100), default=3, readonly=False, group='communication'), - 'comdelay': PARAM('Delay between retries', datatype=FloatRange(0), unit='s', default=0.1, + 'comdelay': Param('Delay between retries', datatype=FloatRange(0), unit='s', default=0.1, readonly=False, group='communication'), - 'tangodevice': PARAM('Tango device name', + 'tangodevice': Param('Tango device name', datatype=StringType(), readonly=True, # export=True, # for testing only export=False, @@ -186,7 +185,7 @@ class PyTangoDevice(Module): def _com_retry(self, info, function, *args, **kwds): """Try communicating with the hardware/device. - PARAMeter "info" is passed to _com_return and _com_raise methods that + Parameter "info" is passed to _com_return and _com_raise methods that process the return value or exception raised after maximum tries. """ tries = self.comtries @@ -380,7 +379,7 @@ class AnalogInput(PyTangoDevice, Readable): # prefer configured unit if nothing is set on the Tango device, else # update if attrInfo.unit != 'No unit': - self.PARAMS['value'].unit = attrInfo.unit + self.parameters['value'].unit = attrInfo.unit def read_value(self, maxage=0): return self._dev.value @@ -397,8 +396,8 @@ class Sensor(AnalogInput): # note: we don't transport the formula to secop.... # we support the adjust method - CMDS = { - 'setposition' : CMD('Set the position to the given value.', + commands = { + 'setposition' : Command('Set the position to the given value.', arguments=[FloatRange()], result=None ), @@ -409,8 +408,7 @@ class Sensor(AnalogInput): class AnalogOutput(PyTangoDevice, Drivable): - """ - The AnalogOutput handles all devices which set an analogue value. + """The AnalogOutput handles all devices which set an analogue value. The main application field is the output of any signal which may be considered as continously in a range. The values may have nearly any @@ -421,41 +419,25 @@ class AnalogOutput(PyTangoDevice, Drivable): controllers, ... """ - PARAMS = { - 'userlimits': PARAM( - 'User defined limits of device value', - unit='main', - datatype=TupleOf( - FloatRange(), - FloatRange()), - default=( - float('-Inf'), - float('+Inf')), - readonly=False, - poll=10), - 'abslimits': PARAM( - 'Absolute limits of device value', - unit='main', - datatype=TupleOf( - FloatRange(), - FloatRange()), - ), - 'precision': PARAM( - 'Precision of the device value (allowed deviation ' - 'of stable values from target)', - unit='main', - datatype=FloatRange(1e-38), - readonly=False, - ), - 'window': PARAM( - 'Time window for checking stabilization if > 0', - unit='s', - default=60.0, - datatype=FloatRange( - 0, - 900), - readonly=False, - ), + parameters = { + 'userlimits': Param('User defined limits of device value', + datatype=TupleOf(FloatRange(), FloatRange()), + default=(float('-Inf'),float('+Inf')), + unit='main', readonly=False, poll=10, + ), + 'abslimits': Param('Absolute limits of device value', + datatype=TupleOf(FloatRange(), FloatRange()), + unit='main', + ), + 'precision': Param('Precision of the device value (allowed deviation ' + 'of stable values from target)', + unit='main', datatype=FloatRange(1e-38), + readonly=False, + ), + 'window': Param('Time window for checking stabilization if > 0', + unit='s', default=60.0, readonly=False, + datatype=FloatRange(0, 900), + ), } def init(self): @@ -470,7 +452,7 @@ class AnalogOutput(PyTangoDevice, Drivable): # prefer configured unit if nothing is set on the Tango device, else # update if attrInfo.unit != 'No unit': - self.PARAMS['value'].unit = attrInfo.unit + self.parameters['value'].unit = attrInfo.unit def poll(self, nr): super(AnalogOutput, self).poll(nr) @@ -563,9 +545,8 @@ class AnalogOutput(PyTangoDevice, Drivable): class Actuator(AnalogOutput): - """ - The actuator interface describes all analog devices which DO something in a - defined way. + """The aAtuator interface describes all analog devices which DO something + in a defined way. The difference to AnalogOutput is that there is a speed attribute, and the value attribute is converted from the “raw value” with a formula and @@ -573,22 +554,18 @@ class Actuator(AnalogOutput): """ # for secop: support the speed and ramp parameters - PARAMS = { - 'speed': PARAM( - 'The speed of changing the value', - unit='main/s', - readonly=False, - datatype=FloatRange(0)), - 'ramp': PARAM( - 'The speed of changing the value', - unit='main/min', - readonly=False, - datatype=FloatRange(0), - poll=30), + parameters = { + 'speed': Param('The speed of changing the value', + unit='main/s', readonly=False, datatype=FloatRange(0), + ), + 'ramp': Param('The speed of changing the value', + unit='main/min', readonly=False, datatype=FloatRange(0), + poll=30, + ), } - CMDS = { - 'setposition' : CMD('Set the position to the given value.', + commands = { + 'setposition' : Command('Set the position to the given value.', arguments=[FloatRange()], result=None ), @@ -612,28 +589,22 @@ class Actuator(AnalogOutput): class Motor(Actuator): - """ - This class implements a motor device (in a sense of a real motor + """This class implements a motor device (in a sense of a real motor (stepper motor, servo motor, ...)). It has the ability to move a real object from one place to another place. """ - PARAMS = { - 'refpos': PARAM( - 'Reference position', - datatype=FloatRange(), - unit='main'), - 'accel': PARAM( - 'Acceleration', - datatype=FloatRange(), - readonly=False, - unit='main/s^2'), - 'decel': PARAM( - 'Deceleration', - datatype=FloatRange(), - readonly=False, - unit='main/s^2'), + parameters = { + 'refpos': Param('Reference position', + datatype=FloatRange(), unit='main', + ), + 'accel': Param('Acceleration', + datatype=FloatRange(), readonly=False, unit='main/s^2', + ), + 'decel': Param('Deceleration', + datatype=FloatRange(), readonly=False, unit='main/s^2', + ), } def read_refpos(self, maxage=0): @@ -657,35 +628,35 @@ class Motor(Actuator): class TemperatureController(Actuator): - """ - A temperature control loop device. + """A temperature control loop device. """ - PARAMS = { - 'p': PARAM('Proportional control PARAMeter', datatype=FloatRange(), + parameters = { + 'p': Param('Proportional control Parameter', datatype=FloatRange(), readonly=False, group='pid', ), - 'i': PARAM('Integral control PARAMeter', datatype=FloatRange(), + 'i': Param('Integral control Parameter', datatype=FloatRange(), readonly=False, group='pid', ), - 'd': PARAM('Derivative control PARAMeter', datatype=FloatRange(), + 'd': Param('Derivative control Parameter', datatype=FloatRange(), readonly=False, group='pid', ), - 'pid': PARAM('pid control PARAMeters', datatype=TupleOf(FloatRange(), FloatRange(), FloatRange()), + 'pid': Param('pid control Parameters', + datatype=TupleOf(FloatRange(), FloatRange(), FloatRange()), readonly=False, group='pid', poll=30, ), - 'setpoint': PARAM('Current setpoint', datatype=FloatRange(), poll=1, + 'setpoint': Param('Current setpoint', datatype=FloatRange(), poll=1, ), - 'heateroutput': PARAM('Heater output', datatype=FloatRange(), poll=1, + 'heateroutput': Param('Heater output', datatype=FloatRange(), poll=1, ), - 'ramp': PARAM('Temperature ramp', unit='main/min', + 'ramp': Param('Temperature ramp', unit='main/min', datatype=FloatRange(), readonly=False, poll=30), } - OVERRIDES = { + overrides = { # We want this to be freely user-settable, and not produce a warning # on startup, so select a usually sensible default. - 'precision': OVERRIDE(default=0.1), + 'precision': Override(default=0.1), } def read_ramp(self, maxage=0): @@ -732,16 +703,15 @@ class TemperatureController(Actuator): class PowerSupply(Actuator): - """ - A power supply (voltage and current) device. + """A power supply (voltage and current) device. """ - PARAMS = { - 'ramp': PARAM('Current/voltage ramp', unit='main/min', + parameters = { + 'ramp': Param('Current/voltage ramp', unit='main/min', datatype=FloatRange(), readonly=False, poll=30,), - 'voltage': PARAM('Actual voltage', unit='V', + 'voltage': Param('Actual voltage', unit='V', datatype=FloatRange(), poll=-5), - 'current': PARAM('Actual current', unit='A', + 'current': Param('Actual current', unit='A', datatype=FloatRange(), poll=-5), } @@ -759,12 +729,11 @@ class PowerSupply(Actuator): class DigitalInput(PyTangoDevice, Readable): - """ - A device reading a bitfield. + """A device reading a bitfield. """ - OVERRIDES = { - 'value': OVERRIDE(datatype=IntRange()), + overrides = { + 'value': Override(datatype=IntRange()), } def read_value(self, maxage=0): @@ -772,19 +741,22 @@ class DigitalInput(PyTangoDevice, Readable): class NamedDigitalInput(DigitalInput): - """ - A DigitalInput with numeric values mapped to names. + """A DigitalInput with numeric values mapped to names. """ - PARAMS = { - 'mapping': PARAM('A dictionary mapping state names to integers', + parameters = { + 'mapping': Param('A dictionary mapping state names to integers', datatype=StringType(), export=False), # XXX:!!! } def init(self): super(NamedDigitalInput, self).init() try: - self.PARAMS['value'].datatype = EnumType(**eval(self.mapping)) + mapping, rem = Parser().parse(self.mapping) + if rem: + raise ValueError('Illegal Value for mapping, ' + 'trailing garbage: %r' % rem) + self.parameters['value'].datatype = EnumType(**mapping) except Exception as e: raise ValueError('Illegal Value for mapping: %r' % e) @@ -794,26 +766,21 @@ class NamedDigitalInput(DigitalInput): class PartialDigitalInput(NamedDigitalInput): - """ - Base class for a TANGO DigitalInput with only a part of the full + """Base class for a TANGO DigitalInput with only a part of the full bit width accessed. """ - PARAMS = { - 'startbit': PARAM( - 'Number of the first bit', - datatype=IntRange(0), - default=0), - 'bitwidth': PARAM( - 'Number of bits', - datatype=IntRange(0), - default=1), + parameters = { + 'startbit': Param('Number of the first bit', + datatype=IntRange(0), default=0), + 'bitwidth': Param('Number of bits', + datatype=IntRange(0), default=1), } def init(self): super(PartialDigitalInput, self).init() self._mask = (1 << self.bitwidth) - 1 - #self.PARAMS['value'].datatype = IntRange(0, self._mask) + #self.parameters['value'].datatype = IntRange(0, self._mask) def read_value(self, maxage=0): raw_value = self._dev.value @@ -822,14 +789,13 @@ class PartialDigitalInput(NamedDigitalInput): class DigitalOutput(PyTangoDevice, Drivable): - """ - A devices that can set and read a digital value corresponding to a + """A device that can set and read a digital value corresponding to a bitfield. """ - OVERRIDES = { - 'value': OVERRIDE(datatype=IntRange()), - 'target': OVERRIDE(datatype=IntRange()), + overrides = { + 'value': Override(datatype=IntRange()), + 'target': Override(datatype=IntRange()), } def read_value(self, maxage=0): @@ -845,88 +811,74 @@ class DigitalOutput(PyTangoDevice, Drivable): class NamedDigitalOutput(DigitalOutput): - """ - A DigitalOutput with numeric values mapped to names. + """A DigitalOutput with numeric values mapped to names. """ -# PARAMS = { -# 'mapping': PARAM('A dictionary mapping state names to integers', +# parameters = { +# 'mapping': Param('A dictionary mapping state names to integers', # datatype=EnumType(), export=False), # XXX: !!! # } # # def init(self): # super(NamedDigitalOutput, self).init() # try: # XXX: !!! -# self.PARAMS['value'].datatype = EnumType(**eval(self.mapping)) +# self.parameters['value'].datatype = EnumType(**eval(self.mapping)) # except Exception as e: # raise ValueError('Illegal Value for mapping: %r' % e) - def write_target(self, target): + def write_target(self, value): # map from enum-str to integer value - self._dev.value = self.PARAMS[ - 'target'].datatype.reversed.get(target, target) + self._dev.value = self.parameters[ + 'target'].datatype.reversed.get(value, value) self.read_value() class PartialDigitalOutput(NamedDigitalOutput): - """ - Base class for a TANGO DigitalOutput with only a part of the full + """Base class for a TANGO DigitalOutput with only a part of the full bit width accessed. """ - PARAMS = { - 'startbit': PARAM( - 'Number of the first bit', - datatype=IntRange(0), - default=0), - 'bitwidth': PARAM( - 'Number of bits', - datatype=IntRange(0), - default=1), + parameters = { + 'startbit': Param('Number of the first bit', + datatype=IntRange(0), default=0), + 'bitwidth': Param('Number of bits', + datatype=IntRange(0), default=1), } - def init(self, mode): + def init(self): super(PartialDigitalOutput, self).init() self._mask = (1 << self.bitwidth) - 1 - #self.PARAMS['value'].datatype = IntRange(0, self._mask) - #self.PARAMS['target'].datatype = IntRange(0, self._mask) + #self.parameters['value'].datatype = IntRange(0, self._mask) + #self.parameters['target'].datatype = IntRange(0, self._mask) def read_value(self, maxage=0): raw_value = self._dev.value value = (raw_value >> self.startbit) & self._mask return value # mapping is done by datatype upon export() - def write_target(self, target): + def write_target(self, value): curvalue = self._dev.value newvalue = (curvalue & ~(self._mask << self.startbit)) | \ - (target << self.startbit) + (value << self.startbit) self._dev.value = newvalue self.read_value() class StringIO(PyTangoDevice, Module): - """ - StringIO abstracts communication over a hardware bus that sends and + """StringIO abstracts communication over a hardware bus that sends and receives strings. """ - PARAMS = { - 'bustimeout': PARAM( - 'Communication timeout', - datatype=FloatRange(), - readonly=False, - unit='s', - group='communication'), - 'endofline': PARAM( - 'End of line', - datatype=StringType(), - readonly=False, - group='communication'), - 'startofline': PARAM( - 'Start of line', - datatype=StringType(), - readonly=False, - group='communication'), + parameters = { + 'bustimeout': Param('Communication timeout', + datatype=FloatRange(), readonly=False, + unit='s', group='communication'), + 'endofline': Param('End of line', + datatype=StringType(), readonly=False, + group='communication'), + 'startofline': Param('Start of line', + datatype=StringType(), readonly=False, + group='communication'), } def read_bustimeout(self, maxage=0): @@ -947,52 +899,28 @@ class StringIO(PyTangoDevice, Module): def write_startofline(self, value): self._dev.startOfLine = value - CMDS = { - 'communicate': CMD( - 'Send a string and return the reply', - arguments=[ - StringType()], - result=StringType()), - 'flush': CMD( - 'Flush output buffer', - arguments=[], - result=None), - 'read': CMD( - 'read some characters from input buffer', - arguments=[ - IntRange()], - result=StringType()), - 'write': CMD( - 'write some chars to output', - arguments=[ - StringType()], - result=None), - 'readLine': CMD( - 'Read sol - a whole line - eol', - arguments=[], - result=StringType()), - 'writeLine': CMD( - 'write sol + a whole line + eol', - arguments=[ - StringType()], - result=None), - 'availablechars': CMD( - 'return number of chars in input buffer', - arguments=[], - result=IntRange(0)), - 'availablelines': CMD( - 'return number of lines in input buffer', - arguments=[], - result=IntRange(0)), - 'multicommunicate': CMD( - 'perform a sequence of communications', - arguments=[ - ArrayOf( - TupleOf( - StringType(), - IntRange()),100)], - result=ArrayOf( - StringType(),100)), + commands = { + 'communicate': Command('Send a string and return the reply', + arguments=[StringType()], + result=StringType()), + 'flush': Command('Flush output buffer', + arguments=[], result=None), + 'read': Command('read some characters from input buffer', + arguments=[IntRange()], result=StringType()), + 'write': Command('write some chars to output', + arguments=[StringType()], result=None), + 'readLine': Command('Read sol - a whole line - eol', + arguments=[], result=StringType()), + 'writeLine': Command('write sol + a whole line + eol', + arguments=[StringType()], result=None), + 'availablechars': Command('return number of chars in input buffer', + arguments=[], result=IntRange(0)), + 'availablelines': Command('return number of lines in input buffer', + arguments=[], result=IntRange(0)), + 'multicommunicate': Command('perform a sequence of communications', + arguments=[ArrayOf( + TupleOf(StringType(), IntRange()),100)], + result=ArrayOf(StringType(),100)), } def do_communicate(self, value=StringType()):