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

View File

@ -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:

View File

@ -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]),
}

View File

@ -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),

View File

@ -20,10 +20,8 @@
# Erik Dahlbäck <erik.dahlback@esss.se>
# *****************************************************************************
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,),
}

View File

@ -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)

View File

@ -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()):