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

View File

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

View File

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

View File

@ -23,9 +23,8 @@
from math import atan from math import atan
import time import time
import random 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.protocol import status
from secop.datatypes import FloatRange, EnumType, TupleOf from secop.datatypes import FloatRange, EnumType, TupleOf
from secop.lib import clamp, mkthread from secop.lib import clamp, mkthread
@ -42,90 +41,90 @@ class Cryostat(CryoBase):
- cooling power - cooling power
- thermal transfer between regulation and samplen - thermal transfer between regulation and samplen
""" """
PARAMS = dict( parameters = dict(
jitter=PARAM("amount of random noise on readout values", jitter=Param("amount of random noise on readout values",
datatype=FloatRange(0, 1), unit="K", datatype=FloatRange(0, 1), unit="K",
default=0.1, readonly=False, export=False, 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, datatype=FloatRange(0), default=10,
export=False, export=False,
), ),
looptime=PARAM("timestep for simulation", looptime=Param("timestep for simulation",
datatype=FloatRange(0.01, 10), unit="s", default=1, datatype=FloatRange(0.01, 10), unit="s", default=1,
readonly=False, export=False, 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, datatype=FloatRange(0, 1e3), unit="K/min", default=1,
readonly=False, readonly=False,
), ),
setpoint=PARAM("current setpoint during ramping else target", setpoint=Param("current setpoint during ramping else target",
datatype=FloatRange(), default=1, unit='K', datatype=FloatRange(), default=1, unit='K',
), ),
maxpower=PARAM("Maximum heater power", maxpower=Param("Maximum heater power",
datatype=FloatRange(0), default=1, unit="W", datatype=FloatRange(0), default=1, unit="W",
readonly=False, readonly=False,
group='heater_settings', group='heater_settings',
), ),
heater=PARAM("current heater setting", heater=Param("current heater setting",
datatype=FloatRange(0, 100), default=0, unit="%", datatype=FloatRange(0, 100), default=0, unit="%",
group='heater_settings', group='heater_settings',
), ),
heaterpower=PARAM("current heater power", heaterpower=Param("current heater power",
datatype=FloatRange(0), default=0, unit="W", datatype=FloatRange(0), default=0, unit="W",
group='heater_settings', group='heater_settings',
), ),
target=PARAM("target temperature", target=Param("target temperature",
datatype=FloatRange(0), default=0, unit="K", datatype=FloatRange(0), default=0, unit="K",
readonly=False, readonly=False,
), ),
value=PARAM("regulation temperature", value=Param("regulation temperature",
datatype=FloatRange(0), default=0, unit="K", datatype=FloatRange(0), default=0, unit="K",
), ),
pid=PARAM("regulation coefficients", pid=Param("regulation coefficients",
datatype=TupleOf(FloatRange(0), FloatRange(0, 100), datatype=TupleOf(FloatRange(0), FloatRange(0, 100),
FloatRange(0, 100)), FloatRange(0, 100)),
default=(40, 10, 2), readonly=False, default=(40, 10, 2), readonly=False,
group='pid', group='pid',
), ),
p=PARAM("regulation coefficient 'p'", p=Param("regulation coefficient 'p'",
datatype=FloatRange(0), default=40, unit="%/K", readonly=False, datatype=FloatRange(0), default=40, unit="%/K", readonly=False,
group='pid', group='pid',
), ),
i=PARAM("regulation coefficient 'i'", i=Param("regulation coefficient 'i'",
datatype=FloatRange(0, 100), default=10, readonly=False, datatype=FloatRange(0, 100), default=10, readonly=False,
group='pid', group='pid',
), ),
d=PARAM("regulation coefficient 'd'", d=Param("regulation coefficient 'd'",
datatype=FloatRange(0, 100), default=2, readonly=False, datatype=FloatRange(0, 100), default=2, readonly=False,
group='pid', group='pid',
), ),
mode=PARAM("mode of regulation", mode=Param("mode of regulation",
datatype=EnumType('ramp', 'pid', 'openloop'), datatype=EnumType('ramp', 'pid', 'openloop'),
default='ramp', default='ramp',
readonly=False, readonly=False,
), ),
pollinterval=PARAM("polling interval", pollinterval=Param("polling interval",
datatype=FloatRange(0), default=5, 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', datatype=FloatRange(0, 100), default=0.1, unit='K',
readonly=False, readonly=False,
group='stability', group='stability',
), ),
window=PARAM("time window for stability checking", window=Param("time window for stability checking",
datatype=FloatRange(1, 900), default=30, unit='s', datatype=FloatRange(1, 900), default=30, unit='s',
readonly=False, readonly=False,
group='stability', 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', datatype=FloatRange(1, 36000), default=900, unit='s',
readonly=False, readonly=False,
group='stability', group='stability',
), ),
) )
CMDS = dict( commands = dict(
stop=CMD( stop=Command(
"Stop ramping the setpoint\n\nby setting the current setpoint as new target", "Stop ramping the setpoint\n\nby setting the current setpoint as new target",
[], [],
None), None),
@ -236,7 +235,7 @@ class Cryostat(CryoBase):
lastflow = 0 lastflow = 0
last_heaters = (0, 0) last_heaters = (0, 0)
delta = 0 delta = 0
I = D = 0 _I = _D = 0
lastD = 0 lastD = 0
damper = 1 damper = 1
lastmode = self.mode lastmode = self.mode
@ -283,32 +282,32 @@ class Cryostat(CryoBase):
kp = self.p / 10. # LakeShore P = 10*k_p kp = self.p / 10. # LakeShore P = 10*k_p
ki = kp * abs(self.i) / 500. # LakeShore I = 500/T_i ki = kp * abs(self.i) / 500. # LakeShore I = 500/T_i
kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d
P = kp * error _P = kp * error
I += ki * error * h _I += ki * error * h
D = kd * delta / h _D = kd * delta / h
# avoid reset windup # 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 # avoid jumping heaterpower if switching back to pid mode
if lastmode != self.mode: if lastmode != self.mode:
# adjust some values upon switching back on # 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 # in damping mode, use a weighted sum of old + new heaterpower
if damper > 1: if damper > 1:
v = ((damper**2 - 1) * self.heater + v) / damper**2 v = ((damper**2 - 1) * self.heater + v) / damper**2
# damp oscillations due to D switching signs # damp oscillations due to D switching signs
if D * lastD < -0.2: if _D * lastD < -0.2:
v = (v + heater) / 2. v = (v + heater) / 2.
# clamp new heater power to 0..100% # clamp new heater power to 0..100%
heater = clamp(v, 0., 100.) heater = clamp(v, 0., 100.)
lastD = D lastD = _D
self.log.debug('PID: P = %.2f, I = %.2f, D = %.2f, ' 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 -> # check for turn-around points to detect oscillations ->
# increase damper # increase damper
@ -351,9 +350,9 @@ class Cryostat(CryoBase):
window.pop(0) window.pop(0)
# obtain min/max # obtain min/max
deviation = 0 deviation = 0
for _, T in window: for _, _T in window:
if abs(T - self.target) > deviation: if abs(_T - self.target) > deviation:
deviation = abs(T - self.target) deviation = abs(_T - self.target)
if (len(window) < 3) or deviation > self.tolerance: if (len(window) < 3) or deviation > self.tolerance:
self.status = status.BUSY, 'unstable' self.status = status.BUSY, 'unstable'
elif self.setpoint == self.target: elif self.setpoint == self.target:

View File

@ -24,7 +24,7 @@ import time
import random import random
import threading 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.datatypes import EnumType, FloatRange, IntRange, ArrayOf, StringType, TupleOf, StructOf, BoolType
from secop.protocol import status from secop.protocol import status
@ -32,19 +32,19 @@ from secop.protocol import status
class Switch(Drivable): class Switch(Drivable):
"""switch it on or off.... """switch it on or off....
""" """
PARAMS = { parameters = {
'value': PARAM('current state (on or off)', 'value': Param('current state (on or off)',
datatype=EnumType(on=1, off=0), default=0, 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, datatype=EnumType(on=1, off=0), default=0,
readonly=False, 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', datatype=FloatRange(0, 60), unit='s',
default=10, export=False, 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', datatype=FloatRange(0, 60), unit='s',
default=10, export=False, default=10, export=False,
), ),
@ -77,7 +77,7 @@ class Switch(Drivable):
return status.BUSY, info return status.BUSY, info
def _update(self): def _update(self):
started = self.PARAMS['target'].timestamp started = self.parameters['target'].timestamp
info = '' info = ''
if self.target > self.value: if self.target > self.value:
info = 'waiting for ON' info = 'waiting for ON'
@ -97,23 +97,23 @@ class Switch(Drivable):
class MagneticField(Drivable): class MagneticField(Drivable):
"""a liquid magnet """a liquid magnet
""" """
PARAMS = { parameters = {
'value': PARAM('current field in T', 'value': Param('current field in T',
unit='T', datatype=FloatRange(-15, 15), default=0, 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, unit='T', datatype=FloatRange(-15, 15), default=0,
readonly=False, readonly=False,
), ),
'ramp': PARAM('ramping speed', 'ramp': Param('ramping speed',
unit='T/min', datatype=FloatRange(0, 1), default=0.1, unit='T/min', datatype=FloatRange(0, 1), default=0.1,
readonly=False, 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), default=1, datatype=EnumType(persistent=1, hold=0),
readonly=False, readonly=False,
), ),
'heatswitch': PARAM('name of heat switch device', 'heatswitch': Param('name of heat switch device',
datatype=StringType(), export=False, datatype=StringType(), export=False,
), ),
} }
@ -184,11 +184,11 @@ class MagneticField(Drivable):
class CoilTemp(Readable): class CoilTemp(Readable):
"""a coil temperature """a coil temperature
""" """
PARAMS = { parameters = {
'value': PARAM('Coil temperatur', 'value': Param('Coil temperatur',
unit='K', datatype=FloatRange(), default=0, unit='K', datatype=FloatRange(), default=0,
), ),
'sensor': PARAM("Sensor number or calibration id", 'sensor': Param("Sensor number or calibration id",
datatype=StringType(), readonly=True, datatype=StringType(), readonly=True,
), ),
} }
@ -200,14 +200,14 @@ class CoilTemp(Readable):
class SampleTemp(Drivable): class SampleTemp(Drivable):
"""a sample temperature """a sample temperature
""" """
PARAMS = { parameters = {
'value': PARAM('Sample temperature', 'value': Param('Sample temperature',
unit='K', datatype=FloatRange(), default=10, unit='K', datatype=FloatRange(), default=10,
), ),
'sensor': PARAM("Sensor number or calibration id", 'sensor': Param("Sensor number or calibration id",
datatype=StringType(), readonly=True, 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, datatype=FloatRange(0, 100), unit='K/min', default=0.1,
readonly=False, readonly=False,
), ),
@ -241,20 +241,23 @@ class SampleTemp(Drivable):
class Label(Readable): 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 = { parameters = {
'system': PARAM("Name of the magnet system", 'system': Param("Name of the magnet system",
datatype=StringType, export=False, 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, 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, datatype=StringType, export=False,
), ),
'value': PARAM("final value of label string", 'value': Param("final value of label string",
datatype=StringType, datatype=StringType,
), ),
} }
@ -265,7 +268,7 @@ class Label(Readable):
dev_ts = self.DISPATCHER.get_module(self.subdev_ts) dev_ts = self.DISPATCHER.get_module(self.subdev_ts)
if dev_ts: if dev_ts:
strings.append('at %.3f %s' % strings.append('at %.3f %s' %
(dev_ts.read_value(), dev_ts.PARAMS['value'].unit)) (dev_ts.read_value(), dev_ts.parameters['value'].unit))
else: else:
strings.append('No connection to sample temp!') strings.append('No connection to sample temp!')
@ -274,7 +277,7 @@ class Label(Readable):
mf_stat = dev_mf.read_status() mf_stat = dev_mf.read_status()
mf_mode = dev_mf.mode mf_mode = dev_mf.mode
mf_val = dev_mf.value 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: if mf_stat[0] == status.OK:
state = 'Persistent' if mf_mode else 'Non-persistent' state = 'Persistent' if mf_mode else 'Non-persistent'
else: else:
@ -287,21 +290,28 @@ class Label(Readable):
class DatatypesTest(Readable): class DatatypesTest(Readable):
"""for demoing all datatypes
""" """
""" parameters = {
PARAMS = { 'enum': Param(
'enum': PARAM(
'enum', datatype=EnumType( '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( 'tuple of int, float and str', datatype=TupleOf(
IntRange(), FloatRange(), StringType()), readonly=False, default=( 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( 'array: 2..3 times bool', datatype=ArrayOf(
BoolType(), 2, 3), readonly=False, default=[ BoolType(), 2, 3), readonly=False, default=[
1, 0, 1]), 'intrange': PARAM( 1, 0, 1]), 'intrange': Param(
'intrange', datatype=IntRange( 'intrange', datatype=IntRange(
2, 9), readonly=False, default=4), 'floatrange': PARAM( 2, 9), readonly=False, default=4), 'floatrange': Param(
'floatrange', datatype=FloatRange( '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( 'struct(a=str, b=int, c=bool)', datatype=StructOf(
a=StringType(), b=IntRange(), c=BoolType()), ), } 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 import random
from secop.modules import Readable, Drivable, Communicator, PARAM from secop.modules import Readable, Drivable, Communicator, Param
from secop.datatypes import FloatRange, StringType from secop.datatypes import FloatRange, StringType
@ -43,8 +43,8 @@ class Heater(Drivable):
class name indicates it to be some heating element, class name indicates it to be some heating element,
but the implementation may do anything but the implementation may do anything
""" """
PARAMS = { parameters = {
'maxheaterpower': PARAM('maximum allowed heater power', 'maxheaterpower': Param('maximum allowed heater power',
datatype=FloatRange(0, 100), unit='W', datatype=FloatRange(0, 100), unit='W',
), ),
} }
@ -62,15 +62,15 @@ class Temp(Drivable):
class name indicates it to be some temperature controller, class name indicates it to be some temperature controller,
but the implementation may do anything but the implementation may do anything
""" """
PARAMS = { parameters = {
'sensor': PARAM( 'sensor': Param(
"Sensor number or calibration id", "Sensor number or calibration id",
datatype=StringType( datatype=StringType(
8, 8,
16), 16),
readonly=True, readonly=True,
), ),
'target': PARAM( 'target': Param(
"Target temperature", "Target temperature",
default=300.0, default=300.0,
datatype=FloatRange(0), datatype=FloatRange(0),

View File

@ -20,10 +20,8 @@
# Erik Dahlbäck <erik.dahlback@esss.se> # Erik Dahlbäck <erik.dahlback@esss.se>
# ***************************************************************************** # *****************************************************************************
import random from secop.datatypes import EnumType, FloatRange, StringType
from secop.modules import Readable, Drivable, Param
from secop.datatypes import EnumType, TupleOf, FloatRange, get_datatype, StringType
from secop.modules import Readable, Module, Drivable, PARAM
from secop.protocol import status from secop.protocol import status
try: try:
@ -59,18 +57,18 @@ except ImportError:
class EpicsReadable(Readable): class EpicsReadable(Readable):
"""EpicsDrivable handles a Drivable interfacing to EPICS v4""" """EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices # Commmon parameter for all EPICS devices
PARAMS = { parameters = {
'value': PARAM('EPICS generic value', 'value': Param('EPICS generic value',
datatype=FloatRange(), datatype=FloatRange(),
default=300.0,), 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),), datatype=EnumType(v3=3, v4=4),),
# 'private' parameters: not remotely accessible # 'private' parameters: not remotely accessible
'value_pv': PARAM('EPICS pv_name of value', 'value_pv': Param('EPICS pv_name of value',
datatype=StringType(), datatype=StringType(),
default="unset", export=False), default="unset", export=False),
'status_pv': PARAM('EPICS pv_name of status', 'status_pv': Param('EPICS pv_name of status',
datatype=StringType(), datatype=StringType(),
default="unset", export=False), default="unset", export=False),
} }
@ -119,20 +117,20 @@ class EpicsReadable(Readable):
class EpicsDrivable(Drivable): class EpicsDrivable(Drivable):
"""EpicsDrivable handles a Drivable interfacing to EPICS v4""" """EpicsDrivable handles a Drivable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices # Commmon parameter for all EPICS devices
PARAMS = { parameters = {
'target': PARAM('EPICS generic target', datatype=FloatRange(), 'target': Param('EPICS generic target', datatype=FloatRange(),
default=300.0, readonly=False), default=300.0, readonly=False),
'value': PARAM('EPICS generic value', datatype=FloatRange(), 'value': Param('EPICS generic value', datatype=FloatRange(),
default=300.0,), default=300.0,),
'epics_version': PARAM("EPICS version used, v3 or v4", 'epics_version': Param("EPICS version used, v3 or v4",
datatype=StringType(),), datatype=StringType(),),
# 'private' parameters: not remotely accessible # '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), 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), 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), default="unset", export=False),
} }
@ -188,22 +186,22 @@ class EpicsDrivable(Drivable):
'Moving') 'Moving')
"""Temperature control loop""" # """Temperature control loop"""
# should also derive from secop.core.temperaturecontroller, once its # should also derive from secop.core.temperaturecontroller, once its
# features are agreed upon # features are agreed upon
class EpicsTempCtrl(EpicsDrivable): class EpicsTempCtrl(EpicsDrivable):
PARAMS = { parameters = {
# TODO: restrict possible values with oneof datatype # TODO: restrict possible values with oneof datatype
'heaterrange': PARAM('Heater range', datatype=StringType(), 'heaterrange': Param('Heater range', datatype=StringType(),
default='Off', readonly=False,), 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, datatype=FloatRange(1e-6, 1e6), default=0.1,
readonly=False,), readonly=False,),
# 'private' parameters: not remotely accessible # '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,), 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.protocol import status
from secop.datatypes import StringType, TupleOf, FloatRange, ArrayOf, StructOf from secop.datatypes import StringType, TupleOf, FloatRange, ArrayOf, StructOf
from secop.errors import DisabledError, ConfigError from secop.errors import DisabledError, ConfigError
from secop.modules import PARAM, Drivable from secop.modules import Param, Drivable
class GarfieldMagnet(SequencerMixin, Drivable): class GarfieldMagnet(SequencerMixin, Drivable):
@ -48,31 +48,31 @@ class GarfieldMagnet(SequencerMixin, Drivable):
the symmetry setting selects which. the symmetry setting selects which.
""" """
PARAMS = { parameters = {
'subdev_currentsource': PARAM('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False), '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_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_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), 'subdev_symmetry': Param('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False),
'userlimits': PARAM('User defined limits of device value', 'userlimits': Param('User defined limits of device value',
unit='main', datatype=TupleOf(FloatRange(), FloatRange()), unit='main', datatype=TupleOf(FloatRange(), FloatRange()),
default=(float('-Inf'), float('+Inf')), readonly=False, poll=10), 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()), unit='main', datatype=TupleOf(FloatRange(), FloatRange()),
default=(-0.5, 0.5), poll=True, 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)', 'of stable values from target)',
unit='main', datatype=FloatRange(0.001), default=0.001, readonly=False, 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), 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 ' 'function: [c0, c1, c2, c3, c4] calculates '
'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)' 'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
' in T', poll=1, ' in T', poll=1,
datatype=ArrayOf(FloatRange(), 5, 5), datatype=ArrayOf(FloatRange(), 5, 5),
default=(1.0, 0.0, 0.0, 0.0, 0.0)), 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), datatype=StructOf(symmetric=ArrayOf(FloatRange(), 5, 5),
short=ArrayOf( short=ArrayOf(
FloatRange(), 5, 5), FloatRange(), 5, 5),
@ -175,7 +175,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
def read_abslimits(self, maxage=0): def read_abslimits(self, maxage=0):
maxfield = self._current2field(self._currentsource.abslimits[1]) maxfield = self._current2field(self._currentsource.abslimits[1])
# limit to configured value (if any) # 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 return -maxfield, maxfield
def read_ramp(self, maxage=0): def read_ramp(self, maxage=0):
@ -203,10 +203,10 @@ class GarfieldMagnet(SequencerMixin, Drivable):
# safe to switch # safe to switch
self._polswitch.write_target( self._polswitch.write_target(
'+1' if polarity > 0 else str(polarity)) '+1' if polarity > 0 else str(polarity))
return 0 return
if self._currentsource.value < 0.1: if self._currentsource.value < 0.1:
self._polswitch.write_target('0') self._polswitch.write_target('0')
return current_pol return
# unsafe to switch, go to safe state first # unsafe to switch, go to safe state first
self._currentsource.write_target(0) self._currentsource.write_target(0)

View File

@ -22,26 +22,25 @@
# ***************************************************************************** # *****************************************************************************
# This is based upon the entangle-nicos integration # 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 Here we support devices which fulfill the official
MLZ TANGO interface for the respective device classes. MLZ TANGO interface for the respective device classes.
""" """
import re import re
import sys
from time import sleep, time as currenttime from time import sleep, time as currenttime
import threading import threading
import PyTango import PyTango
import numpy
from secop.lib import lazy_property, mkthread from secop.lib import lazy_property
from secop.protocol import status from secop.protocol import status
from secop.datatypes import * from secop.parse import Parser
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
from secop.modules import PARAM, CMD, OVERRIDE, Module, Readable, Drivable 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. execution and attribute operations with logging and exception mapping.
""" """
PARAMS = { parameters = {
'comtries': PARAM('Maximum retries for communication', 'comtries': Param('Maximum retries for communication',
datatype=IntRange(1, 100), default=3, readonly=False, group='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'), readonly=False, group='communication'),
'tangodevice': PARAM('Tango device name', 'tangodevice': Param('Tango device name',
datatype=StringType(), readonly=True, datatype=StringType(), readonly=True,
# export=True, # for testing only # export=True, # for testing only
export=False, export=False,
@ -186,7 +185,7 @@ class PyTangoDevice(Module):
def _com_retry(self, info, function, *args, **kwds): def _com_retry(self, info, function, *args, **kwds):
"""Try communicating with the hardware/device. """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. process the return value or exception raised after maximum tries.
""" """
tries = self.comtries tries = self.comtries
@ -380,7 +379,7 @@ class AnalogInput(PyTangoDevice, Readable):
# prefer configured unit if nothing is set on the Tango device, else # prefer configured unit if nothing is set on the Tango device, else
# update # update
if attrInfo.unit != 'No unit': if attrInfo.unit != 'No unit':
self.PARAMS['value'].unit = attrInfo.unit self.parameters['value'].unit = attrInfo.unit
def read_value(self, maxage=0): def read_value(self, maxage=0):
return self._dev.value return self._dev.value
@ -397,8 +396,8 @@ class Sensor(AnalogInput):
# note: we don't transport the formula to secop.... # note: we don't transport the formula to secop....
# we support the adjust method # we support the adjust method
CMDS = { commands = {
'setposition' : CMD('Set the position to the given value.', 'setposition' : Command('Set the position to the given value.',
arguments=[FloatRange()], arguments=[FloatRange()],
result=None result=None
), ),
@ -409,8 +408,7 @@ class Sensor(AnalogInput):
class AnalogOutput(PyTangoDevice, Drivable): 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 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 considered as continously in a range. The values may have nearly any
@ -421,40 +419,24 @@ class AnalogOutput(PyTangoDevice, Drivable):
controllers, ... controllers, ...
""" """
PARAMS = { parameters = {
'userlimits': PARAM( 'userlimits': Param('User defined limits of device value',
'User defined limits of device value', datatype=TupleOf(FloatRange(), FloatRange()),
unit='main', default=(float('-Inf'),float('+Inf')),
datatype=TupleOf( unit='main', readonly=False, poll=10,
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( 'abslimits': Param('Absolute limits of device value',
'Precision of the device value (allowed deviation ' datatype=TupleOf(FloatRange(), FloatRange()),
unit='main',
),
'precision': Param('Precision of the device value (allowed deviation '
'of stable values from target)', 'of stable values from target)',
unit='main', unit='main', datatype=FloatRange(1e-38),
datatype=FloatRange(1e-38),
readonly=False, readonly=False,
), ),
'window': PARAM( 'window': Param('Time window for checking stabilization if > 0',
'Time window for checking stabilization if > 0', unit='s', default=60.0, readonly=False,
unit='s', datatype=FloatRange(0, 900),
default=60.0,
datatype=FloatRange(
0,
900),
readonly=False,
), ),
} }
@ -470,7 +452,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
# prefer configured unit if nothing is set on the Tango device, else # prefer configured unit if nothing is set on the Tango device, else
# update # update
if attrInfo.unit != 'No unit': if attrInfo.unit != 'No unit':
self.PARAMS['value'].unit = attrInfo.unit self.parameters['value'].unit = attrInfo.unit
def poll(self, nr): def poll(self, nr):
super(AnalogOutput, self).poll(nr) super(AnalogOutput, self).poll(nr)
@ -563,9 +545,8 @@ class AnalogOutput(PyTangoDevice, Drivable):
class Actuator(AnalogOutput): class Actuator(AnalogOutput):
""" """The aAtuator interface describes all analog devices which DO something
The actuator interface describes all analog devices which DO something in a in a defined way.
defined way.
The difference to AnalogOutput is that there is a speed attribute, and the 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 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 # for secop: support the speed and ramp parameters
PARAMS = { parameters = {
'speed': PARAM( 'speed': Param('The speed of changing the value',
'The speed of changing the value', unit='main/s', readonly=False, datatype=FloatRange(0),
unit='main/s', ),
readonly=False, 'ramp': Param('The speed of changing the value',
datatype=FloatRange(0)), unit='main/min', readonly=False, datatype=FloatRange(0),
'ramp': PARAM( poll=30,
'The speed of changing the value', ),
unit='main/min',
readonly=False,
datatype=FloatRange(0),
poll=30),
} }
CMDS = { commands = {
'setposition' : CMD('Set the position to the given value.', 'setposition' : Command('Set the position to the given value.',
arguments=[FloatRange()], arguments=[FloatRange()],
result=None result=None
), ),
@ -612,28 +589,22 @@ class Actuator(AnalogOutput):
class Motor(Actuator): 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, ...)). (stepper motor, servo motor, ...)).
It has the ability to move a real object from one place to another place. It has the ability to move a real object from one place to another place.
""" """
PARAMS = { parameters = {
'refpos': PARAM( 'refpos': Param('Reference position',
'Reference position', datatype=FloatRange(), unit='main',
datatype=FloatRange(), ),
unit='main'), 'accel': Param('Acceleration',
'accel': PARAM( datatype=FloatRange(), readonly=False, unit='main/s^2',
'Acceleration', ),
datatype=FloatRange(), 'decel': Param('Deceleration',
readonly=False, datatype=FloatRange(), readonly=False, unit='main/s^2',
unit='main/s^2'), ),
'decel': PARAM(
'Deceleration',
datatype=FloatRange(),
readonly=False,
unit='main/s^2'),
} }
def read_refpos(self, maxage=0): def read_refpos(self, maxage=0):
@ -657,35 +628,35 @@ class Motor(Actuator):
class TemperatureController(Actuator): class TemperatureController(Actuator):
""" """A temperature control loop device.
A temperature control loop device.
""" """
PARAMS = { parameters = {
'p': PARAM('Proportional control PARAMeter', datatype=FloatRange(), 'p': Param('Proportional control Parameter', datatype=FloatRange(),
readonly=False, group='pid', readonly=False, group='pid',
), ),
'i': PARAM('Integral control PARAMeter', datatype=FloatRange(), 'i': Param('Integral control Parameter', datatype=FloatRange(),
readonly=False, group='pid', readonly=False, group='pid',
), ),
'd': PARAM('Derivative control PARAMeter', datatype=FloatRange(), 'd': Param('Derivative control Parameter', datatype=FloatRange(),
readonly=False, group='pid', 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, 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), datatype=FloatRange(), readonly=False, poll=30),
} }
OVERRIDES = { overrides = {
# We want this to be freely user-settable, and not produce a warning # We want this to be freely user-settable, and not produce a warning
# on startup, so select a usually sensible default. # on startup, so select a usually sensible default.
'precision': OVERRIDE(default=0.1), 'precision': Override(default=0.1),
} }
def read_ramp(self, maxage=0): def read_ramp(self, maxage=0):
@ -732,16 +703,15 @@ class TemperatureController(Actuator):
class PowerSupply(Actuator): class PowerSupply(Actuator):
""" """A power supply (voltage and current) device.
A power supply (voltage and current) device.
""" """
PARAMS = { parameters = {
'ramp': PARAM('Current/voltage ramp', unit='main/min', 'ramp': Param('Current/voltage ramp', unit='main/min',
datatype=FloatRange(), readonly=False, poll=30,), datatype=FloatRange(), readonly=False, poll=30,),
'voltage': PARAM('Actual voltage', unit='V', 'voltage': Param('Actual voltage', unit='V',
datatype=FloatRange(), poll=-5), datatype=FloatRange(), poll=-5),
'current': PARAM('Actual current', unit='A', 'current': Param('Actual current', unit='A',
datatype=FloatRange(), poll=-5), datatype=FloatRange(), poll=-5),
} }
@ -759,12 +729,11 @@ class PowerSupply(Actuator):
class DigitalInput(PyTangoDevice, Readable): class DigitalInput(PyTangoDevice, Readable):
""" """A device reading a bitfield.
A device reading a bitfield.
""" """
OVERRIDES = { overrides = {
'value': OVERRIDE(datatype=IntRange()), 'value': Override(datatype=IntRange()),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):
@ -772,19 +741,22 @@ class DigitalInput(PyTangoDevice, Readable):
class NamedDigitalInput(DigitalInput): class NamedDigitalInput(DigitalInput):
""" """A DigitalInput with numeric values mapped to names.
A DigitalInput with numeric values mapped to names.
""" """
PARAMS = { parameters = {
'mapping': PARAM('A dictionary mapping state names to integers', 'mapping': Param('A dictionary mapping state names to integers',
datatype=StringType(), export=False), # XXX:!!! datatype=StringType(), export=False), # XXX:!!!
} }
def init(self): def init(self):
super(NamedDigitalInput, self).init() super(NamedDigitalInput, self).init()
try: 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: except Exception as e:
raise ValueError('Illegal Value for mapping: %r' % e) raise ValueError('Illegal Value for mapping: %r' % e)
@ -794,26 +766,21 @@ class NamedDigitalInput(DigitalInput):
class PartialDigitalInput(NamedDigitalInput): 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. bit width accessed.
""" """
PARAMS = { parameters = {
'startbit': PARAM( 'startbit': Param('Number of the first bit',
'Number of the first bit', datatype=IntRange(0), default=0),
datatype=IntRange(0), 'bitwidth': Param('Number of bits',
default=0), datatype=IntRange(0), default=1),
'bitwidth': PARAM(
'Number of bits',
datatype=IntRange(0),
default=1),
} }
def init(self): def init(self):
super(PartialDigitalInput, self).init() super(PartialDigitalInput, self).init()
self._mask = (1 << self.bitwidth) - 1 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): def read_value(self, maxage=0):
raw_value = self._dev.value raw_value = self._dev.value
@ -822,14 +789,13 @@ class PartialDigitalInput(NamedDigitalInput):
class DigitalOutput(PyTangoDevice, Drivable): class DigitalOutput(PyTangoDevice, Drivable):
""" """A device that can set and read a digital value corresponding to a
A devices that can set and read a digital value corresponding to a
bitfield. bitfield.
""" """
OVERRIDES = { overrides = {
'value': OVERRIDE(datatype=IntRange()), 'value': Override(datatype=IntRange()),
'target': OVERRIDE(datatype=IntRange()), 'target': Override(datatype=IntRange()),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):
@ -845,87 +811,73 @@ class DigitalOutput(PyTangoDevice, Drivable):
class NamedDigitalOutput(DigitalOutput): class NamedDigitalOutput(DigitalOutput):
""" """A DigitalOutput with numeric values mapped to names.
A DigitalOutput with numeric values mapped to names.
""" """
# PARAMS = { # parameters = {
# 'mapping': PARAM('A dictionary mapping state names to integers', # 'mapping': Param('A dictionary mapping state names to integers',
# datatype=EnumType(), export=False), # XXX: !!! # datatype=EnumType(), export=False), # XXX: !!!
# } # }
# #
# def init(self): # def init(self):
# super(NamedDigitalOutput, self).init() # super(NamedDigitalOutput, self).init()
# try: # XXX: !!! # try: # XXX: !!!
# self.PARAMS['value'].datatype = EnumType(**eval(self.mapping)) # self.parameters['value'].datatype = EnumType(**eval(self.mapping))
# except Exception as e: # except Exception as e:
# raise ValueError('Illegal Value for mapping: %r' % 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 # map from enum-str to integer value
self._dev.value = self.PARAMS[ self._dev.value = self.parameters[
'target'].datatype.reversed.get(target, target) 'target'].datatype.reversed.get(value, value)
self.read_value() self.read_value()
class PartialDigitalOutput(NamedDigitalOutput): 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. bit width accessed.
""" """
PARAMS = { parameters = {
'startbit': PARAM( 'startbit': Param('Number of the first bit',
'Number of the first bit', datatype=IntRange(0), default=0),
datatype=IntRange(0), 'bitwidth': Param('Number of bits',
default=0), datatype=IntRange(0), default=1),
'bitwidth': PARAM(
'Number of bits',
datatype=IntRange(0),
default=1),
} }
def init(self, mode): def init(self):
super(PartialDigitalOutput, self).init() super(PartialDigitalOutput, self).init()
self._mask = (1 << self.bitwidth) - 1 self._mask = (1 << self.bitwidth) - 1
#self.PARAMS['value'].datatype = IntRange(0, self._mask) #self.parameters['value'].datatype = IntRange(0, self._mask)
#self.PARAMS['target'].datatype = IntRange(0, self._mask) #self.parameters['target'].datatype = IntRange(0, self._mask)
def read_value(self, maxage=0): def read_value(self, maxage=0):
raw_value = self._dev.value raw_value = self._dev.value
value = (raw_value >> self.startbit) & self._mask value = (raw_value >> self.startbit) & self._mask
return value # mapping is done by datatype upon export() return value # mapping is done by datatype upon export()
def write_target(self, target): def write_target(self, value):
curvalue = self._dev.value curvalue = self._dev.value
newvalue = (curvalue & ~(self._mask << self.startbit)) | \ newvalue = (curvalue & ~(self._mask << self.startbit)) | \
(target << self.startbit) (value << self.startbit)
self._dev.value = newvalue self._dev.value = newvalue
self.read_value() self.read_value()
class StringIO(PyTangoDevice, Module): 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. receives strings.
""" """
PARAMS = { parameters = {
'bustimeout': PARAM( 'bustimeout': Param('Communication timeout',
'Communication timeout', datatype=FloatRange(), readonly=False,
datatype=FloatRange(), unit='s', group='communication'),
readonly=False, 'endofline': Param('End of line',
unit='s', datatype=StringType(), readonly=False,
group='communication'), group='communication'),
'endofline': PARAM( 'startofline': Param('Start of line',
'End of line', datatype=StringType(), readonly=False,
datatype=StringType(),
readonly=False,
group='communication'),
'startofline': PARAM(
'Start of line',
datatype=StringType(),
readonly=False,
group='communication'), group='communication'),
} }
@ -947,52 +899,28 @@ class StringIO(PyTangoDevice, Module):
def write_startofline(self, value): def write_startofline(self, value):
self._dev.startOfLine = value self._dev.startOfLine = value
CMDS = { commands = {
'communicate': CMD( 'communicate': Command('Send a string and return the reply',
'Send a string and return the reply', arguments=[StringType()],
arguments=[
StringType()],
result=StringType()), result=StringType()),
'flush': CMD( 'flush': Command('Flush output buffer',
'Flush output buffer', arguments=[], result=None),
arguments=[], 'read': Command('read some characters from input buffer',
result=None), arguments=[IntRange()], result=StringType()),
'read': CMD( 'write': Command('write some chars to output',
'read some characters from input buffer', arguments=[StringType()], result=None),
arguments=[ 'readLine': Command('Read sol - a whole line - eol',
IntRange()], arguments=[], result=StringType()),
result=StringType()), 'writeLine': Command('write sol + a whole line + eol',
'write': CMD( arguments=[StringType()], result=None),
'write some chars to output', 'availablechars': Command('return number of chars in input buffer',
arguments=[ arguments=[], result=IntRange(0)),
StringType()], 'availablelines': Command('return number of lines in input buffer',
result=None), arguments=[], result=IntRange(0)),
'readLine': CMD( 'multicommunicate': Command('perform a sequence of communications',
'Read sol - a whole line - eol', arguments=[ArrayOf(
arguments=[], TupleOf(StringType(), IntRange()),100)],
result=StringType()), result=ArrayOf(StringType(),100)),
'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)),
} }
def do_communicate(self, value=StringType()): def do_communicate(self, value=StringType()):