make most important classes available from secop

+ consmetic changes to make PyCharm more happy
+ update authorship

Change-Id: I67cb61a04e502b207be74cea4ca07931c88fdafe
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22070
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2019-12-20 14:31:50 +01:00
parent e2cc9f74b5
commit 795759786f
15 changed files with 161 additions and 148 deletions

View File

@ -19,9 +19,19 @@
# Module authors: # Module authors:
# Alexander Lenz <alexander.lenz@frm2.tum.de> # Alexander Lenz <alexander.lenz@frm2.tum.de>
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
# allow to import the most important classes from 'secop'
from secop.datatypes import *
from secop.modules import Module, Readable, Writable, Drivable, Communicator, Attached
from secop.params import Parameter, Command, Override
from secop.metaclass import Done
from secop.commandhandler import CmdHandler, CmdHandlerBase
from secop.stringio import StringIO, HasIodev
try: try:
import sip import sip
sip.setapi('QString', 2) sip.setapi('QString', 2)

View File

@ -58,7 +58,6 @@ from secop.metaclass import Done
from secop.errors import ProgrammingError from secop.errors import ProgrammingError
class CmdParser: class CmdParser:
"""helper for parsing replies """helper for parsing replies
@ -68,14 +67,13 @@ class CmdParser:
# make a map of cast functions # make a map of cast functions
CAST_MAP = {letter: cast CAST_MAP = {letter: cast
for letters, cast in ( for letters, cast in (
('d', int), ('d', int),
('s', str), # 'c' is treated separately ('s', str), # 'c' is treated separately
('o', lambda x:int(x, 8)), ('o', lambda x: int(x, 8)),
('xX', lambda x:int(x, 16)), ('xX', lambda x: int(x, 16)),
('eEfFgG', float), ('eEfFgG', float),
) for letter in letters ) for letter in letters}
}
# pattern for chacaters to be escaped # pattern for chacaters to be escaped
ESC_PAT = re.compile('([\\%s])' % '\\'.join('|^$-.+*?()[]{}<>')) ESC_PAT = re.compile('([\\%s])' % '\\'.join('|^$-.+*?()[]{}<>'))
# format pattern # format pattern
@ -94,13 +92,13 @@ class CmdParser:
casts = [] casts = []
# the first item in spl is just plain text # the first item in spl is just plain text
pat = [escaped(next(spl_iter))] pat = [escaped(next(spl_iter))]
todofmt = None # format set aside to be treated in next loop todofmt = None # format set aside to be treated in next loop
# loop over found formats and separators # loop over found formats and separators
for fmt, sep in zip(spl_iter,spl_iter): for fmt, sep in zip(spl_iter, spl_iter):
if fmt == '%%': if fmt == '%%':
if todofmt is None: if todofmt is None:
pat.append('%' + escaped(sep)) # plain text pat.append('%' + escaped(sep)) # plain text
continue continue
fmt = todofmt fmt = todofmt
todofmt = None todofmt = None
@ -108,7 +106,7 @@ class CmdParser:
elif todofmt: elif todofmt:
raise ValueError("a separator must follow '%s'" % todofmt) raise ValueError("a separator must follow '%s'" % todofmt)
cast = self.CAST_MAP.get(fmt[-1], None) cast = self.CAST_MAP.get(fmt[-1], None)
if cast is None: # special or unknown case if cast is None: # special or unknown case
if fmt != '%c': if fmt != '%c':
raise ValueError("unsupported format: '%s'" % fmt) raise ValueError("unsupported format: '%s'" % fmt)
# we do not need a separator after %c # we do not need a separator after %c
@ -116,7 +114,7 @@ class CmdParser:
casts.append(str) casts.append(str)
pat.append(escaped(sep)) pat.append(escaped(sep))
continue continue
if sep == '': # missing separator. postpone handling for '%%' case or end of pattern if sep == '': # missing separator. postpone handling for '%%' case or end of pattern
todofmt = fmt todofmt = fmt
continue continue
casts.append(cast) casts.append(cast)
@ -128,7 +126,7 @@ class CmdParser:
self.casts = casts self.casts = casts
self.pat = re.compile(''.join(pat)) self.pat = re.compile(''.join(pat))
try: try:
argformat % ((0,) * len(casts)) # validate argformat argformat % ((0,) * len(casts)) # validate argformat
except ValueError as e: except ValueError as e:
raise ValueError("%s in %r" % (e, argformat)) raise ValueError("%s in %r" % (e, argformat))
@ -154,7 +152,7 @@ class Change:
self._module = module self._module = module
self._valuedict = valuedict self._valuedict = valuedict
self._to_be_changed = set(self._valuedict) self._to_be_changed = set(self._valuedict)
self._do_read = True self._reply = None
def __getattr__(self, key): def __getattr__(self, key):
"""return attribute from module key is not in self._valuedict""" """return attribute from module key is not in self._valuedict"""
@ -171,8 +169,7 @@ class Change:
and update our parameter attributes accordingly (i.e. do not touch the new values) and update our parameter attributes accordingly (i.e. do not touch the new values)
""" """
if self._do_read: if self._reply is None:
self._do_read = False
self._reply = self._handler.send_command(self._module) self._reply = self._handler.send_command(self._module)
result = self._handler.analyze(self._module, *self._reply) result = self._handler.analyze(self._module, *self._reply)
result.update(self._valuedict) result.update(self._valuedict)
@ -218,7 +215,7 @@ class CmdHandlerBase:
a reply. a reply.
""" """
changecmd = self.make_change(module, *values) changecmd = self.make_change(module, *values)
module.sendRecv(changecmd) # ignore result module.sendRecv(changecmd) # ignore result
return self.send_command(module) return self.send_command(module)
def get_read_func(self, modclass, pname): def get_read_func(self, modclass, pname):
@ -229,7 +226,7 @@ class CmdHandlerBase:
self._module_class = self._module_class or modclass self._module_class = self._module_class or modclass
if self._module_class != modclass: if self._module_class != modclass:
raise ProgrammingError("the handler '%s' for '%s.%s' is already used in module '%s'" raise ProgrammingError("the handler '%s' for '%s.%s' is already used in module '%s'"
% (self.group, modclass.__name__, pname, self._module_class.__name__)) % (self.group, modclass.__name__, pname, self._module_class.__name__))
self.parameters.add(pname) self.parameters.add(pname)
self.analyze = getattr(modclass, 'analyze_' + self.group) self.analyze = getattr(modclass, 'analyze_' + self.group)
return self.read return self.read
@ -286,7 +283,7 @@ class CmdHandlerBase:
assert module.__class__ == self._module_class assert module.__class__ == self._module_class
force_read = False force_read = False
valuedict = {pname: value} valuedict = {pname: value}
if module.writeDict: # collect other parameters to be written if module.writeDict: # collect other parameters to be written
for p in self.parameters: for p in self.parameters:
if p in module.writeDict: if p in module.writeDict:
valuedict[p] = module.writeDict.pop(p) valuedict[p] = module.writeDict.pop(p)
@ -296,7 +293,7 @@ class CmdHandlerBase:
if force_read: if force_read:
change.readValues() change.readValues()
values = self.change(module, change) values = self.change(module, change)
if values is None: # this indicates that nothing has to be written if values is None: # this indicates that nothing has to be written
return return
# send the change command and a query command # send the change command and a query command
reply = self.send_change(module, *values) reply = self.send_change(module, *values)
@ -314,11 +311,8 @@ class CmdHandler(CmdHandlerBase):
implementing classes have to define/override the following: implementing classes have to define/override the following:
""" """
CMDARGS = [] # list of properties or parameters to be used for building CMDARGS = [] # list of properties or parameters to be used for building some of the the query and change commands
# some of the the query and change commands CMDSEPARATOR = ';' # if given, it is valid to join a command a a query with the given separator
CMDSEPARATOR = ';' # if given, it is valid to join a command a a query with
# the given separator
def __init__(self, group, querycmd, replyfmt, changecmd=None): def __init__(self, group, querycmd, replyfmt, changecmd=None):
"""initialize the command handler """initialize the command handler

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define validated data types.""" """Define validated data types."""

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Metaclass for Modules/Features""" """Define Metaclass for Modules/Features"""
@ -30,14 +31,15 @@ from secop.params import Command, Override, Parameter
from secop.datatypes import EnumType from secop.datatypes import EnumType
from secop.properties import PropertyMeta from secop.properties import PropertyMeta
EVENT_ONLY_ON_CHANGED_VALUES = False EVENT_ONLY_ON_CHANGED_VALUES = False
class Done: class Done:
"""a special return value for a read/write function """a special return value for a read/write function
indicating that the setter is triggered already""" indicating that the setter is triggered already"""
# warning: MAGIC! # warning: MAGIC!
class ModuleMeta(PropertyMeta): class ModuleMeta(PropertyMeta):
@ -68,7 +70,7 @@ class ModuleMeta(PropertyMeta):
accessibles_list.append(base.accessibles) accessibles_list.append(base.accessibles)
for accessibles in [parameters, commands, overrides]: for accessibles in [parameters, commands, overrides]:
accessibles_list.append(accessibles) accessibles_list.append(accessibles)
accessibles = {} # unordered dict of accessibles, will be sorted later accessibles = {} # unordered dict of accessibles, will be sorted later
for accessibles_dict in accessibles_list: for accessibles_dict in accessibles_list:
for key, obj in accessibles_dict.items(): for key, obj in accessibles_dict.items():
if isinstance(obj, Override): if isinstance(obj, Override):
@ -110,7 +112,7 @@ class ModuleMeta(PropertyMeta):
value = pobj.datatype(attrs[pname]) value = pobj.datatype(attrs[pname])
except BadValueError: except BadValueError:
raise ProgrammingError('parameter %s can not be set to %r' raise ProgrammingError('parameter %s can not be set to %r'
% (pname, attrs[pname])) % (pname, attrs[pname]))
newtype.accessibles[pname] = Override(default=value).apply(pobj) newtype.accessibles[pname] = Override(default=value).apply(pobj)
# check validity of Parameter entries # check validity of Parameter entries
@ -142,7 +144,7 @@ class ModuleMeta(PropertyMeta):
try: try:
value = rfunc(self) value = rfunc(self)
self.log.debug("rfunc(%s) returned %r" % (pname, value)) self.log.debug("rfunc(%s) returned %r" % (pname, value))
if value is Done: # the setter is already triggered if value is Done: # the setter is already triggered
return getattr(self, pname) return getattr(self, pname)
except Exception as e: except Exception as e:
self.log.debug("rfunc(%s) failed %r" % (pname, e)) self.log.debug("rfunc(%s) failed %r" % (pname, e))

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Baseclasses for real Modules implemented in the server""" """Define Baseclasses for real Modules implemented in the server"""
@ -187,10 +188,10 @@ class Module(HasProperties, metaclass=ModuleMeta):
# 4) complain if a Parameter entry has no default value and # 4) complain if a Parameter entry has no default value and
# is not specified in cfgdict and deal with parameters to be written. # is not specified in cfgdict and deal with parameters to be written.
self.writeDict = {} # values of parameters to be written self.writeDict = {} # values of parameters to be written
for pname, pobj in self.parameters.items(): for pname, pobj in self.parameters.items():
if pname in cfgdict: if pname in cfgdict:
if not pobj.readonly and not pobj.initwrite is False: if not pobj.readonly and pobj.initwrite is not False:
# parameters given in cfgdict have to call write_<pname> # parameters given in cfgdict have to call write_<pname>
try: try:
pobj.value = pobj.datatype(cfgdict[pname]) pobj.value = pobj.datatype(cfgdict[pname])
@ -214,7 +215,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
value = pobj.datatype(pobj.default) value = pobj.datatype(pobj.default)
except BadValueError as e: except BadValueError as e:
raise ProgrammingError('bad default for %s.%s: %s' raise ProgrammingError('bad default for %s.%s: %s'
% (name, pname, e)) % (name, pname, e))
if pobj.initwrite: if pobj.initwrite:
# we will need to call write_<pname> # we will need to call write_<pname>
# if this is not desired, the default must not be given # if this is not desired, the default must not be given
@ -264,7 +265,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
self.DISPATCHER.announce_update_error(self, pname, pobj, exception) self.DISPATCHER.announce_update_error(self, pname, pobj, exception)
def isBusy(self, status=None): def isBusy(self, status=None):
'''helper function for treating substates of BUSY correctly''' """helper function for treating substates of BUSY correctly"""
# defined even for non drivable (used for dynamic polling) # defined even for non drivable (used for dynamic polling)
return False return False
@ -276,25 +277,24 @@ class Module(HasProperties, metaclass=ModuleMeta):
self.log.debug('empty %s.initModule()' % self.__class__.__name__) self.log.debug('empty %s.initModule()' % self.__class__.__name__)
def startModule(self, started_callback): def startModule(self, started_callback):
'''runs after init of all modules """runs after init of all modules
started_callback to be called when thread spawned by late_init started_callback to be called when thread spawned by late_init
or, if not implemented, immediately or, if not implemented, immediately
might return a timeout value, if different from default might return a timeout value, if different from default
''' """
self.log.debug('empty %s.startModule()' % self.__class__.__name__) self.log.debug('empty %s.startModule()' % self.__class__.__name__)
started_callback() started_callback()
def pollOneParam(self, pname): def pollOneParam(self, pname):
"""poll parameter <pname> with proper error handling""" """poll parameter <pname> with proper error handling"""
try: try:
return getattr(self, 'read_'+ pname)() return getattr(self, 'read_' + pname)()
except SilentError as e: except SilentError:
pass pass
except SECoPError as e: except SECoPError as e:
self.log.error(str(e)) self.log.error(str(e))
except Exception as e: except Exception:
self.log.error(formatException()) self.log.error(formatException())
def writeOrPoll(self, pname): def writeOrPoll(self, pname):
@ -305,14 +305,14 @@ class Module(HasProperties, metaclass=ModuleMeta):
try: try:
if pname in self.writeDict: if pname in self.writeDict:
self.log.debug('write parameter %s', pname) self.log.debug('write parameter %s', pname)
getattr(self, 'write_'+ pname)(self.writeDict.pop(pname)) getattr(self, 'write_' + pname)(self.writeDict.pop(pname))
else: else:
getattr(self, 'read_'+ pname)() getattr(self, 'read_' + pname)()
except SilentError as e: except SilentError:
pass pass
except SECoPError as e: except SECoPError as e:
self.log.error(str(e)) self.log.error(str(e))
except Exception as e: except Exception:
self.log.error(formatException()) self.log.error(formatException())
@ -348,7 +348,7 @@ class Readable(Module):
} }
def startModule(self, started_callback): def startModule(self, started_callback):
'''start basic polling thread''' """start basic polling thread"""
if issubclass(self.pollerClass, BasicPoller): if issubclass(self.pollerClass, BasicPoller):
# use basic poller for legacy code # use basic poller for legacy code
mkthread(self.__pollThread, started_callback) mkthread(self.__pollThread, started_callback)
@ -423,15 +423,15 @@ class Drivable(Writable):
} }
overrides = { overrides = {
'status' : Override(datatype=TupleOf(EnumType(Status), StringType())), 'status': Override(datatype=TupleOf(EnumType(Status), StringType())),
} }
def isBusy(self, status=None): def isBusy(self, status=None):
'''helper function for treating substates of BUSY correctly''' """helper function for treating substates of BUSY correctly"""
return 300 <= (status or self.status)[0] < 400 return 300 <= (status or self.status)[0] < 400
def isDriving(self, status=None): def isDriving(self, status=None):
'''helper function (finalize is busy, not driving)''' """helper function (finalize is busy, not driving)"""
return 300 <= (status or self.status)[0] < 390 return 300 <= (status or self.status)[0] < 390
# improved polling: may poll faster if module is BUSY # improved polling: may poll faster if module is BUSY
@ -472,7 +472,6 @@ class Communicator(Module):
} }
class Attached(Property): class Attached(Property):
# we can not put this to properties.py, as it needs datatypes # we can not put this to properties.py, as it needs datatypes
def __init__(self, attrname=None): def __init__(self, attrname=None):

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define classes for Parameters/Commands and Overriding them""" """Define classes for Parameters/Commands and Overriding them"""

View File

@ -19,7 +19,7 @@
# Markus Zolliker <markus.zolliker@psi.ch> # Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
'''general, advanced frappy poller """general, advanced frappy poller
Usage examples: Usage examples:
any Module which want to be polled with a specific Poller must define any Module which want to be polled with a specific Poller must define
@ -31,7 +31,7 @@ Usage examples:
... ...
modules having a parameter 'iodev' with the same value will share the same poller modules having a parameter 'iodev' with the same value will share the same poller
''' """
import time import time
from threading import Event from threading import Event
@ -40,24 +40,25 @@ from secop.lib import mkthread
from secop.errors import ProgrammingError from secop.errors import ProgrammingError
# poll types: # poll types:
AUTO = 1 # equivalent to True, converted to REGULAR, SLOW or DYNAMIC AUTO = 1 # equivalent to True, converted to REGULAR, SLOW or DYNAMIC
SLOW = 2 SLOW = 2
REGULAR = 3 REGULAR = 3
DYNAMIC = 4 DYNAMIC = 4
class PollerBase: class PollerBase:
startup_timeout = 30 # default timeout for startup startup_timeout = 30 # default timeout for startup
name = 'unknown' # to be overridden in implementors __init__ method name = 'unknown' # to be overridden in implementors __init__ method
@classmethod @classmethod
def add_to_table(cls, table, module): def add_to_table(cls, table, module):
'''sort module into poller table """sort module into poller table
table is a dict, with (<pollerClass>, <name>) as the key, and the table is a dict, with (<pollerClass>, <name>) as the key, and the
poller as value. poller as value.
<name> is module.iodev or module.name, if iodev is not present <name> is module.iodev or module.name, if iodev is not present
''' """
# for modules with the same iodev, a common poller is used, # for modules with the same iodev, a common poller is used,
# modules without iodev all get their own poller # modules without iodev all get their own poller
name = getattr(module, 'iodev', module.name) name = getattr(module, 'iodev', module.name)
@ -68,26 +69,26 @@ class PollerBase:
poller.add_to_poller(module) poller.add_to_poller(module)
def start(self, started_callback): def start(self, started_callback):
'''start poller thread """start poller thread
started_callback to be called after all poll items were read at least once started_callback to be called after all poll items were read at least once
''' """
mkthread(self.run, started_callback) mkthread(self.run, started_callback)
return self.startup_timeout return self.startup_timeout
def run(self, started_callback): def run(self, started_callback):
'''poller thread function """poller thread function
started_callback to be called after all poll items were read at least once started_callback to be called after all poll items were read at least once
''' """
raise NotImplementedError raise NotImplementedError
def stop(self): def stop(self):
'''stop polling''' """stop polling"""
raise NotImplementedError raise NotImplementedError
def __bool__(self): def __bool__(self):
'''is there any poll item?''' """is there any poll item?"""
raise NotImplementedError raise NotImplementedError
def __repr__(self): def __repr__(self):
@ -95,7 +96,7 @@ class PollerBase:
class Poller(PollerBase): class Poller(PollerBase):
'''a standard poller """a standard poller
parameters may have the following polltypes: parameters may have the following polltypes:
@ -106,12 +107,12 @@ class Poller(PollerBase):
Scheduled to poll every slowfactor * module.pollinterval Scheduled to poll every slowfactor * module.pollinterval
- DYNAMIC: by default used for 'value' and 'status' - DYNAMIC: by default used for 'value' and 'status'
When busy, scheduled to poll every fastfactor * module.pollinterval When busy, scheduled to poll every fastfactor * module.pollinterval
''' """
DEFAULT_FACTORS = {SLOW: 4, DYNAMIC: 0.25, REGULAR: 1} DEFAULT_FACTORS = {SLOW: 4, DYNAMIC: 0.25, REGULAR: 1}
def __init__(self, name): def __init__(self, name):
'''create a poller''' """create a poller"""
self.queues = {polltype: [] for polltype in self.DEFAULT_FACTORS} self.queues = {polltype: [] for polltype in self.DEFAULT_FACTORS}
self._event = Event() self._event = Event()
self._stopped = False self._stopped = False
@ -148,34 +149,34 @@ class Poller(PollerBase):
module.registerReconnectCallback(self.name, self.trigger_all) module.registerReconnectCallback(self.name, self.trigger_all)
else: else:
module.log.warning("%r has 'is_connected' but no 'registerReconnectCallback'" % module) module.log.warning("%r has 'is_connected' but no 'registerReconnectCallback'" % module)
if polltype == AUTO: # covers also pobj.poll == True if polltype == AUTO: # covers also pobj.poll == True
if pname in ('value', 'status'): if pname in ('value', 'status'):
polltype = DYNAMIC polltype = DYNAMIC
elif pobj.readonly: elif pobj.readonly:
polltype = REGULAR polltype = REGULAR
else: else:
polltype = SLOW polltype = SLOW
if not polltype in factors: if polltype not in factors:
raise ProgrammingError("unknown poll type %r for parameter '%s'" raise ProgrammingError("unknown poll type %r for parameter '%s'"
% (polltype, pname)) % (polltype, pname))
if pobj.handler: if pobj.handler:
if pobj.handler in handlers: if pobj.handler in handlers:
continue # only one poller per handler continue # only one poller per handler
handlers.add(pobj.handler) handlers.add(pobj.handler)
# placeholders 0 are used for due, lastdue and idx # placeholders 0 are used for due, lastdue and idx
self.queues[polltype].append((0, 0, self.queues[polltype].append((0, 0,
(0, module, pobj, pname, factors[polltype]))) (0, module, pobj, pname, factors[polltype])))
def poll_next(self, polltype): def poll_next(self, polltype):
'''try to poll next item """try to poll next item
advance in queue until advance in queue until
- an item is found which is really due to poll. return 0 in this case - an item is found which is really due to poll. return 0 in this case
- or until the next item is not yet due. return next due time in this case - or until the next item is not yet due. return next due time in this case
''' """
queue = self.queues[polltype] queue = self.queues[polltype]
if not queue: if not queue:
return float('inf') # queue is empty return float('inf') # queue is empty
now = time.time() now = time.time()
done = False done = False
while not done: while not done:
@ -191,7 +192,7 @@ class Poller(PollerBase):
interval = module.pollinterval * factor interval = module.pollinterval * factor
mininterval = interval mininterval = interval
if due == 0: if due == 0:
due = now # do not look at timestamp after trigger_all due = now # do not look at timestamp after trigger_all
else: else:
due = max(lastdue + interval, pobj.timestamp + interval * 0.5) due = max(lastdue + interval, pobj.timestamp + interval * 0.5)
if now >= due: if now >= due:
@ -211,7 +212,7 @@ class Poller(PollerBase):
return True return True
def run(self, started_callback): def run(self, started_callback):
'''start poll loop """start poll loop
To be called as a thread. After all parameters are polled once first, To be called as a thread. After all parameters are polled once first,
started_callback is called. To be called in Module.start_module. started_callback is called. To be called in Module.start_module.
@ -221,13 +222,13 @@ class Poller(PollerBase):
If more polls are scheduled than time permits, at least every second poll is a If more polls are scheduled than time permits, at least every second poll is a
dynamic poll. After every n regular polls, one slow poll is done, if due dynamic poll. After every n regular polls, one slow poll is done, if due
(where n is the number of regular parameters). (where n is the number of regular parameters).
''' """
if not self: if not self:
# nothing to do (else we might call time.sleep(float('inf')) below # nothing to do (else we might call time.sleep(float('inf')) below
started_callback() started_callback()
return return
# do all polls once and, at the same time, insert due info # do all polls once and, at the same time, insert due info
for _, queue in sorted(self.queues.items()): # do SLOW polls first for _, queue in sorted(self.queues.items()): # do SLOW polls first
for idx, (_, _, (_, module, pobj, pname, factor)) in enumerate(queue): for idx, (_, _, (_, module, pobj, pname, factor)) in enumerate(queue):
lastdue = time.time() lastdue = time.time()
module.writeOrPoll(pname) module.writeOrPoll(pname)
@ -236,14 +237,14 @@ class Poller(PollerBase):
# are comparable. Inserting a unique idx solves the problem. # are comparable. Inserting a unique idx solves the problem.
queue[idx] = (due, lastdue, (idx, module, pobj, pname, factor)) queue[idx] = (due, lastdue, (idx, module, pobj, pname, factor))
heapify(queue) heapify(queue)
started_callback() # signal end of startup started_callback() # signal end of startup
nregular = len(self.queues[REGULAR]) nregular = len(self.queues[REGULAR])
while not self._stopped: while not self._stopped:
due = float('inf') due = float('inf')
for _ in range(nregular): for _ in range(nregular):
due = min(self.poll_next(DYNAMIC), self.poll_next(REGULAR)) due = min(self.poll_next(DYNAMIC), self.poll_next(REGULAR))
if due: if due:
break # no dynamic or regular polls due break # no dynamic or regular polls due
due = min(due, self.poll_next(DYNAMIC), self.poll_next(SLOW)) due = min(due, self.poll_next(DYNAMIC), self.poll_next(SLOW))
delay = due - time.time() delay = due - time.time()
if delay > 0: if delay > 0:
@ -255,7 +256,7 @@ class Poller(PollerBase):
self._stopped = True self._stopped = True
def __bool__(self): def __bool__(self):
'''is there any poll item?''' """is there any poll item?"""
return any(self.queues.values()) return any(self.queues.values())

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define validated data types.""" """Define validated data types."""

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Dispatcher for SECoP Messages """Dispatcher for SECoP Messages
@ -50,7 +51,6 @@ from secop.protocol.messages import COMMANDREPLY, DESCRIPTIONREPLY, \
HEARTBEATREPLY, IDENTREPLY, IDENTREQUEST, READREPLY, WRITEREPLY HEARTBEATREPLY, IDENTREPLY, IDENTREQUEST, READREPLY, WRITEREPLY
def make_update(modulename, pobj): def make_update(modulename, pobj):
if pobj.readerror: if pobj.readerror:
return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export), return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export),
@ -116,9 +116,9 @@ class Dispatcher:
if not isinstance(err, SECoPError): if not isinstance(err, SECoPError):
err = InternalError(err) err = InternalError(err)
if str(err) == str(pobj.readerror): if str(err) == str(pobj.readerror):
return # do not send updates for repeated errors return # do not send updates for repeated errors
pobj.readerror = err pobj.readerror = err
pobj.timestamp = currenttime() # indicates the first time this error appeared pobj.timestamp = currenttime() # indicates the first time this error appeared
self.broadcast_event(make_update(moduleobj.name, pobj)) self.broadcast_event(make_update(moduleobj.name, pobj))
def subscribe(self, conn, eventname): def subscribe(self, conn, eventname):
@ -272,7 +272,7 @@ class Dispatcher:
pobj = moduleobj.parameters[pname] pobj = moduleobj.parameters[pname]
if pobj.constant is not None: if pobj.constant is not None:
# really needed? we could just construct a readreply instead.... # really needed? we could just construct a readreply instead....
#raise ReadOnlyError('This parameter is constant and can not be accessed remotely.') # raise ReadOnlyError('This parameter is constant and can not be accessed remotely.')
return pobj.datatype.export_value(pobj.constant) return pobj.datatype.export_value(pobj.constant)
readfunc = getattr(moduleobj, 'read_%s' % pname, None) readfunc = getattr(moduleobj, 'read_%s' % pname, None)

View File

@ -16,6 +16,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""provides tcp interface to the SECoP Server""" """provides tcp interface to the SECoP Server"""
@ -45,12 +46,13 @@ SPACE = b' '
class OutputBufferOverflow(Exception): class OutputBufferOverflow(Exception):
pass pass
class TCPRequestHandler(socketserver.BaseRequestHandler): class TCPRequestHandler(socketserver.BaseRequestHandler):
def setup(self): def setup(self):
self.log = self.server.log self.log = self.server.log
# Queue of msgObjects to send # Queue of msgObjects to send
self._queue = collections.deque() # do not use maxlen, as items might get lost self._queue = collections.deque() # do not use maxlen, as items might get lost
# self.framing = self.server.framingCLS() # self.framing = self.server.framingCLS()
# self.encoding = self.server.encodingCLS() # self.encoding = self.server.encodingCLS()
@ -80,9 +82,9 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
# send bytestring # send bytestring
outmsg = self._queue.popleft() outmsg = self._queue.popleft()
if not outmsg: if not outmsg:
outmsg = ('error','InternalError', ['<unknown origin>', 'trying to send none-data', {}]) outmsg = ('error', 'InternalError', ['<unknown origin>', 'trying to send none-data', {}])
if len(outmsg) > 3: if len(outmsg) > 3:
outmsg = ('error', 'InternalError', ['<unknown origin>', 'bad message format', {'msg':outmsg}]) outmsg = ('error', 'InternalError', ['<unknown origin>', 'bad message format', {'msg': outmsg}])
outdata = encode_msg_frame(*outmsg) outdata = encode_msg_frame(*outmsg)
try: try:
mysocket.sendall(outdata) mysocket.sendall(outdata)
@ -97,7 +99,7 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
# no timeout error, but no new data -> connection closed # no timeout error, but no new data -> connection closed
return return
data = data + newdata data = data + newdata
except socket.timeout as e: except socket.timeout:
continue continue
except socket.error as e: except socket.error as e:
self.log.exception(e) self.log.exception(e)
@ -118,13 +120,12 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
for idx, line in enumerate(HelpMessage.splitlines()): for idx, line in enumerate(HelpMessage.splitlines()):
self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line)) self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line))
continue continue
result = None
try: try:
msg = decode_msg(origin) msg = decode_msg(origin)
except Exception as err: except Exception as err:
# we have to decode 'origin' here # we have to decode 'origin' here
# use latin-1, as utf-8 or ascii may lead to encoding errors # use latin-1, as utf-8 or ascii may lead to encoding errors
msg = origin.decode('latin-1').split(' ', 3) + [None] # make sure len(msg) > 1 msg = origin.decode('latin-1').split(' ', 3) + [None] # make sure len(msg) > 1
result = (ERRORPREFIX + msg[0], msg[1], ['InternalError', str(err), result = (ERRORPREFIX + msg[0], msg[1], ['InternalError', str(err),
{'exception': formatException(), {'exception': formatException(),
'traceback': formatExtendedStack()}]) 'traceback': formatExtendedStack()}])
@ -191,10 +192,10 @@ class TCPServer(HasProperties, socketserver.ThreadingTCPServer):
allow_reuse_address = True allow_reuse_address = True
properties = { properties = {
'bindto' : Property('hostname or ip address for binding',StringType(), 'bindto': Property('hostname or ip address for binding', StringType(),
default='localhost:%d' % DEF_PORT, export=False), default='localhost:%d' % DEF_PORT, export=False),
'bindport' : Property('port number to bind',IntRange(1,65535), 'bindport': Property('port number to bind', IntRange(1, 65535),
default=DEF_PORT, export=False), default=DEF_PORT, export=False),
'detailed_errors': Property('Flag to enable detailed Errorreporting.', BoolType(), 'detailed_errors': Property('Flag to enable detailed Errorreporting.', BoolType(),
default=False, export=False), default=False, export=False),
} }
@ -202,7 +203,7 @@ class TCPServer(HasProperties, socketserver.ThreadingTCPServer):
# XXX: create configurables from Metaclass! # XXX: create configurables from Metaclass!
configurables = properties configurables = properties
def __init__(self, name, logger, options, srv): # pylint: disable=super-init-not-called def __init__(self, name, logger, options, srv): # pylint: disable=super-init-not-called
self.dispatcher = srv.dispatcher self.dispatcher = srv.dispatcher
self.name = name self.name = name
self.log = logger self.log = logger

View File

@ -18,6 +18,7 @@
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Alexander Lenz <alexander.lenz@frm2.tum.de> # Alexander Lenz <alexander.lenz@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define helpers""" """Define helpers"""
@ -56,12 +57,13 @@ class Server:
('module', None, None), ('module', None, None),
('interface', "tcp", {"tcp": "protocol.interface.tcp.TCPServer"}), ('interface', "tcp", {"tcp": "protocol.interface.tcp.TCPServer"}),
] ]
def __init__(self, name, parent_logger=None): def __init__(self, name, parent_logger=None):
cfg = getGeneralConfig() cfg = getGeneralConfig()
# also handle absolut paths # also handle absolut paths
if os.path.abspath(name) == name and os.path.exists(name) and \ if os.path.abspath(name) == name and os.path.exists(name) and \
name.endswith('.cfg'): name.endswith('.cfg'):
self._cfgfile = name self._cfgfile = name
self._pidfile = os.path.join(cfg['piddir'], self._pidfile = os.path.join(cfg['piddir'],
name[:-4].replace(os.path.sep, '_') + '.pid') name[:-4].replace(os.path.sep, '_') + '.pid')
@ -120,15 +122,13 @@ class Server:
def _processCfg(self): def _processCfg(self):
self.log.debug('Parse config file %s ...' % self._cfgfile) self.log.debug('Parse config file %s ...' % self._cfgfile)
parser = configparser.SafeConfigParser() parser = configparser.ConfigParser()
parser.optionxform = str parser.optionxform = str
if not parser.read([self._cfgfile]): if not parser.read([self._cfgfile]):
self.log.error('Couldn\'t read cfg file !') self.log.error('Couldn\'t read cfg file !')
raise ConfigError('Couldn\'t read cfg file %r' % self._cfgfile) raise ConfigError('Couldn\'t read cfg file %r' % self._cfgfile)
for kind, devtype, classmapping in self.CFGSECTIONS: for kind, devtype, classmapping in self.CFGSECTIONS:
kinds = '%ss' % kind kinds = '%ss' % kind
objs = OrderedDict() objs = OrderedDict()
@ -180,12 +180,12 @@ class Server:
raise ConfigError('cfgfile %r: needs exactly one node section!' % self._cfgfile) raise ConfigError('cfgfile %r: needs exactly one node section!' % self._cfgfile)
self.dispatcher, = tuple(self.nodes.values()) self.dispatcher, = tuple(self.nodes.values())
pollTable = dict() poll_table = dict()
# all objs created, now start them up and interconnect # all objs created, now start them up and interconnect
for modname, modobj in self.modules.items(): for modname, modobj in self.modules.items():
self.log.info('registering module %r' % modname) self.log.info('registering module %r' % modname)
self.dispatcher.register_module(modobj, modname, modobj.properties['export']) self.dispatcher.register_module(modobj, modname, modobj.properties['export'])
modobj.pollerClass.add_to_table(pollTable, modobj) modobj.pollerClass.add_to_table(poll_table, modobj)
# also call earlyInit on the modules # also call earlyInit on the modules
modobj.earlyInit() modobj.earlyInit()
@ -205,7 +205,7 @@ class Server:
# startModule must return either a timeout value or None (default 30 sec) # startModule must return either a timeout value or None (default 30 sec)
timeout = modobj.startModule(started_callback=event.set) or 30 timeout = modobj.startModule(started_callback=event.set) or 30
start_events.append((time.time() + timeout, 'module %s' % modname, event)) start_events.append((time.time() + timeout, 'module %s' % modname, event))
for poller in pollTable.values(): for poller in poll_table.values():
event = threading.Event() event = threading.Event()
# poller.start must return either a timeout value or None (default 30 sec) # poller.start must return either a timeout value or None (default 30 sec)
timeout = poller.start(started_callback=event.set) or 30 timeout = poller.start(started_callback=event.set) or 30

View File

@ -193,7 +193,7 @@ class StringIO(Communicator):
if not re.match(regexp, reply): if not re.match(regexp, reply):
self.closeConnection() self.closeConnection()
raise CommunicationFailedError('bad response: %s does not match %s' % raise CommunicationFailedError('bad response: %s does not match %s' %
(reply, regexp)) (reply, regexp))
def registerReconnectCallback(self, name, func): def registerReconnectCallback(self, name, func):
"""register reconnect callback """register reconnect callback
@ -216,14 +216,14 @@ class StringIO(Communicator):
self._reconnectCallbacks.pop(key) self._reconnectCallbacks.pop(key)
def do_communicate(self, command): def do_communicate(self, command):
'''send a command and receive a reply """send a command and receive a reply
using end_of_line, encoding and self._lock using end_of_line, encoding and self._lock
for commands without reply, join it with a query command, for commands without reply, join it with a query command,
wait_before is respected for end_of_lines within a command. wait_before is respected for end_of_lines within a command.
''' """
if not self.is_connected: if not self.is_connected:
self.read_is_connected() # try to reconnect self.read_is_connected() # try to reconnect
try: try:
with self._lock: with self._lock:
# read garbage and wait before send # read garbage and wait before send
@ -235,7 +235,7 @@ class StringIO(Communicator):
for cmd in cmds: for cmd in cmds:
if self.wait_before: if self.wait_before:
time.sleep(self.wait_before) time.sleep(self.wait_before)
if garbage is None: # read garbage only once if garbage is None: # read garbage only once
garbage = b'' garbage = b''
data = self.readWithTimeout(0) data = self.readWithTimeout(0)
while data: while data:

View File

@ -84,12 +84,12 @@ class Main(HasIodev, Module):
class ResChannel(HasIodev, Readable): class ResChannel(HasIodev, Readable):
'''temperature channel on Lakeshore 336''' """temperature channel on Lakeshore 336"""
RES_RANGE = {key: i+1 for i, key in list( RES_RANGE = {key: i+1 for i, key in list(
enumerate(mag % val for mag in ['%gmOhm', '%gOhm', '%gkOhm', '%gMOhm'] enumerate(mag % val for mag in ['%gmOhm', '%gOhm', '%gkOhm', '%gMOhm']
for val in [2, 6.32, 20, 63.2, 200, 632]))[:-2]} for val in [2, 6.32, 20, 63.2, 200, 632]))[:-2]}
RES_SCALE = [2 * 10 ** (0.5 * i) for i in range(-7,16)] # RES_SCALE[0] is not used RES_SCALE = [2 * 10 ** (0.5 * i) for i in range(-7, 16)] # RES_SCALE[0] is not used
CUR_RANGE = {key: i + 1 for i, key in list( CUR_RANGE = {key: i + 1 for i, key in list(
enumerate(mag % val for mag in ['%gpA', '%gnA', '%guA', '%gmA'] enumerate(mag % val for mag in ['%gpA', '%gnA', '%guA', '%gmA']
for val in [1, 3.16, 10, 31.6, 100, 316]))[:-2]} for val in [1, 3.16, 10, 31.6, 100, 316]))[:-2]}
@ -162,7 +162,7 @@ class ResChannel(HasIodev, Readable):
lim = 0.2 lim = 0.2
while rng > self.minrange and abs(result) < lim * self.RES_SCALE[rng]: while rng > self.minrange and abs(result) < lim * self.RES_SCALE[rng]:
rng -= 1 rng -= 1
lim -= 0.05 # not more than 4 steps at once lim -= 0.05 # not more than 4 steps at once
# effectively: <0.16 %: 4 steps, <1%: 3 steps, <5%: 2 steps, <20%: 1 step # effectively: <0.16 %: 4 steps, <1%: 3 steps, <5%: 2 steps, <20%: 1 step
if lim != 0.2: if lim != 0.2:
self.log.info('chan %d: lowered range to %.3g' % self.log.info('chan %d: lowered range to %.3g' %
@ -182,7 +182,7 @@ class ResChannel(HasIodev, Readable):
if not self.enabled: if not self.enabled:
return [self.Status.DISABLED, 'disabled'] return [self.Status.DISABLED, 'disabled']
result = int(self.sendRecv('RDGST?%d' % self.channel)) result = int(self.sendRecv('RDGST?%d' % self.channel))
result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities) result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities)
statustext = STATUS_TEXT[result] statustext = STATUS_TEXT[result]
if statustext: if statustext:
return [self.Status.ERROR, statustext] return [self.Status.ERROR, statustext]
@ -205,13 +205,13 @@ class ResChannel(HasIodev, Readable):
def change_rdgrng(self, change): def change_rdgrng(self, change):
iscur, exc, rng, autorange, excoff = change.readValues() iscur, exc, rng, autorange, excoff = change.readValues()
if change.doesInclude('vexc'): # in case vext is changed, do not consider iexc if change.doesInclude('vexc'): # in case vext is changed, do not consider iexc
change.iexc = 0 change.iexc = 0
if change.iexc != 0: # we need '!= 0' here, as bool(enum) is always True! if change.iexc != 0: # we need '!= 0' here, as bool(enum) is always True!
iscur = 1 iscur = 1
exc = change.iexc exc = change.iexc
excoff = 0 excoff = 0
elif change.vexc != 0: # we need '!= 0' here, as bool(enum) is always True! elif change.vexc != 0: # we need '!= 0' here, as bool(enum) is always True!
iscur = 0 iscur = 0
exc = change.vexc exc = change.vexc
excoff = 0 excoff = 0
@ -240,5 +240,5 @@ class ResChannel(HasIodev, Readable):
def change_filter(self, change): def change_filter(self, change):
_, settle, window = change.readValues() _, settle, window = change.readValues()
if change.filter: if change.filter:
return 1, change.filter, 80 # always use 80% filter return 1, change.filter, 80 # always use 80% filter
return 0, settle, window return 0, settle, window

View File

@ -53,7 +53,7 @@ except ImportError:
class CmdHandler(secop.commandhandler.CmdHandler): class CmdHandler(secop.commandhandler.CmdHandler):
CMDARGS = ['no'] CMDARGS = ['no']
CMDSEPARATOR = None # no command chaining CMDSEPARATOR = None # no command chaining
READ_BEFORE_WRITE = False READ_BEFORE_WRITE = False
def __init__(self, name, querycmd, replyfmt): def __init__(self, name, querycmd, replyfmt):
@ -68,7 +68,7 @@ class Main(Communicator):
'pollinterval': Parameter('poll interval', readonly=False, 'pollinterval': Parameter('poll interval', readonly=False,
datatype=FloatRange(), default=2), datatype=FloatRange(), default=2),
'communicate': Override('GBIP command'), 'communicate': Override('GBIP command'),
'data': Parameter('internal', poll=True, export=True, # export for test only 'data': Parameter('internal', poll=True, export=True, # export for test only
default="", readonly=True, datatype=StringType()), default="", readonly=True, datatype=StringType()),
} }
properties = { properties = {
@ -100,7 +100,7 @@ class Main(Communicator):
return reply return reply
def read_data(self): def read_data(self):
mask = 1 # always get packed_status mask = 1 # always get packed_status
for channelname, channel in self.modules.items(): for channelname, channel in self.modules.items():
if channel.enabled: if channel.enabled:
mask |= 1 << self._channel_to_index.get(channelname, 0) mask |= 1 << self._channel_to_index.get(channelname, 0)
@ -108,7 +108,7 @@ class Main(Communicator):
data = self.do_communicate('GETDAT? %d' % mask) data = self.do_communicate('GETDAT? %d' % mask)
reply = data.split(',') reply = data.split(',')
mask = int(reply.pop(0)) mask = int(reply.pop(0))
reply.pop(0) # pop timestamp reply.pop(0) # pop timestamp
result = {} result = {}
for bitpos, channelname in enumerate(self._channel_names): for bitpos, channelname in enumerate(self._channel_names):
if mask & (1 << bitpos): if mask & (1 << bitpos):
@ -118,10 +118,10 @@ class Main(Communicator):
if 'ts' in result: if 'ts' in result:
result['temp'] = result['ts'] result['temp'] = result['ts']
packed_status = int(result['packed_status']) packed_status = int(result['packed_status'])
result['chamber'] = None # 'chamber' must be in result for status, but value is ignored result['chamber'] = None # 'chamber' must be in result for status, but value is ignored
for channelname, channel in self.modules.items(): for channelname, channel in self.modules.items():
channel.update_value_status(result.get(channelname, None), packed_status) channel.update_value_status(result.get(channelname, None), packed_status)
return data # return data as string return data # return data as string
class PpmsMixin(HasIodev, Module): class PpmsMixin(HasIodev, Module):
@ -130,9 +130,9 @@ class PpmsMixin(HasIodev, Module):
} }
pollerClass = Poller pollerClass = Poller
enabled = True # default, if no parameter enable is defined enabled = True # default, if no parameter enable is defined
_last_target_change = 0 # used by several modules _last_target_change = 0 # used by several modules
_last_settings = None # used by several modules _last_settings = None # used by several modules
slow_pollfactor = 1 slow_pollfactor = 1
def initModule(self): def initModule(self):
@ -188,7 +188,7 @@ class Channel(PpmsMixin, Readable):
datatype=StringType(), export=False, default=''), datatype=StringType(), export=False, default=''),
'no': 'no':
Property('channel number', Property('channel number',
datatype=IntRange(1, 4), export=False), datatype=IntRange(1, 4), export=False),
} }
def earlyInit(self): def earlyInit(self):
@ -208,7 +208,7 @@ class UserChannel(Channel):
properties = { properties = {
'no': 'no':
Property('channel number', Property('channel number',
datatype=IntRange(0, 0), export=False, default=0), datatype=IntRange(0, 0), export=False, default=0),
} }
@ -307,7 +307,7 @@ class Level(PpmsMixin, Readable):
self.status = [self.Status.IDLE, ''] self.status = [self.Status.IDLE, '']
else: else:
self.status = [self.Status.ERROR, 'old reading'] self.status = [self.Status.ERROR, 'old reading']
return dict(value = level) return dict(value=level)
class Chamber(PpmsMixin, Drivable): class Chamber(PpmsMixin, Drivable):
@ -448,7 +448,7 @@ class Temp(PpmsMixin, Drivable):
channel = 'temp' channel = 'temp'
_stopped = False _stopped = False
_expected_target = 0 _expected_target = 0
_last_change = 0 # 0 means no target change is pending _last_change = 0 # 0 means no target change is pending
def earlyInit(self): def earlyInit(self):
self.setProperty('general_stop', False) self.setProperty('general_stop', False)
@ -469,7 +469,7 @@ class Temp(PpmsMixin, Drivable):
else: else:
self.status = [self.Status.IDLE, 'stopped(%s)' % status[1]] self.status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
return return
if self._last_change: # there was a change, which is not yet confirmed by hw if self._last_change: # there was a change, which is not yet confirmed by hw
if now > self._last_change + 5: if now > self._last_change + 5:
self._last_change = 0 # give up waiting for busy self._last_change = 0 # give up waiting for busy
elif self.isDriving(status) and status != self._status_before_change: elif self.isDriving(status) and status != self._status_before_change:
@ -510,12 +510,12 @@ class Temp(PpmsMixin, Drivable):
def write_approachmode(self, value): def write_approachmode(self, value):
if self.isDriving(): if self.isDriving():
return value return value
return None # change_temp will not be called, as this would trigger an unnecessary T change return None # change_temp will not be called, as this would trigger an unnecessary T change
def write_ramp(self, value): def write_ramp(self, value):
if self.isDriving(): if self.isDriving():
return value return value
return None # change_temp will not be called, as this would trigger an unnecessary T change return None # change_temp will not be called, as this would trigger an unnecessary T change
def calc_expected(self, target, ramp): def calc_expected(self, target, ramp):
self._expected_target = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp) self._expected_target = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
@ -545,7 +545,7 @@ class Field(PpmsMixin, Drivable):
FINALIZING = 390, FINALIZING = 390,
) )
# pylint: disable=invalid-name # pylint: disable=invalid-name
PersistentMode = Enum('PersistentMode', persistent = 0, driven = 1) PersistentMode = Enum('PersistentMode', persistent=0, driven=1)
ApproachMode = Enum('ApproachMode', linear=0, no_overshoot=1, oscillate=2) ApproachMode = Enum('ApproachMode', linear=0, no_overshoot=1, oscillate=2)
parameters = { parameters = {
@ -554,7 +554,7 @@ class Field(PpmsMixin, Drivable):
'status': 'status':
Override(datatype=StatusType(Status), poll=True), Override(datatype=StatusType(Status), poll=True),
'target': 'target':
Override(datatype=FloatRange(-15,15,unit='T'), handler=field), Override(datatype=FloatRange(-15, 15, unit='T'), handler=field),
'ramp': 'ramp':
Parameter('ramping speed', readonly=False, handler=field, Parameter('ramping speed', readonly=False, handler=field,
datatype=FloatRange(0.064, 1.19, unit='T/min')), datatype=FloatRange(0.064, 1.19, unit='T/min')),
@ -584,7 +584,7 @@ class Field(PpmsMixin, Drivable):
channel = 'field' channel = 'field'
_stopped = False _stopped = False
_last_target = 0 _last_target = 0
_last_change= 0 # means no target change is pending _last_change = 0 # means no target change is pending
def update_value_status(self, value, packed_status): def update_value_status(self, value, packed_status):
"""update value and status""" """update value and status"""
@ -602,8 +602,8 @@ class Field(PpmsMixin, Drivable):
else: else:
self.status = [status[0], 'stopped (%s)' % status[1]] self.status = [status[0], 'stopped (%s)' % status[1]]
return return
elif self._last_change: # there was a change, which is not yet confirmed by hw elif self._last_change: # there was a change, which is not yet confirmed by hw
if status_code == 1: # persistent mode if status_code == 1: # persistent mode
# leads are ramping (ppms has no extra status code for this!) # leads are ramping (ppms has no extra status code for this!)
if now < self._last_change + 30: if now < self._last_change + 30:
status = [self.Status.PREPARING, 'ramping leads'] status = [self.Status.PREPARING, 'ramping leads']
@ -637,7 +637,7 @@ class Field(PpmsMixin, Drivable):
else: else:
# changed ramp or approachmode # changed ramp or approachmode
if not self.isDriving(): if not self.isDriving():
return None # nothing to be written, as this would trigger a ramp up of leads current return None # nothing to be written, as this would trigger a ramp up of leads current
return change.target * 1e+4, change.ramp / 6e-3, change.approachmode, change.persistentmode return change.target * 1e+4, change.ramp / 6e-3, change.approachmode, change.persistentmode
def do_stop(self): def do_stop(self):
@ -685,7 +685,7 @@ class Position(PpmsMixin, Drivable):
channel = 'position' channel = 'position'
_stopped = False _stopped = False
_last_target = 0 _last_target = 0
_last_change = 0 # means no target change is pending _last_change = 0 # means no target change is pending
def update_value_status(self, value, packed_status): def update_value_status(self, value, packed_status):
"""update value and status""" """update value and status"""
@ -703,10 +703,10 @@ class Position(PpmsMixin, Drivable):
self._stopped = False self._stopped = False
else: else:
status = [self.Status.IDLE, 'stopped(%s)' % status[1]] status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
if self._last_change: # there was a change, which is not yet confirmed by hw if self._last_change: # there was a change, which is not yet confirmed by hw
now = time.time() now = time.time()
if now > self._last_change + 5: if now > self._last_change + 5:
self._last_change = 0 # give up waiting for busy self._last_change = 0 # give up waiting for busy
elif self.isDriving() and status != self._status_before_change: elif self.isDriving() and status != self._status_before_change:
self._last_change = 0 self._last_change = 0
self.log.debug('time needed to change to busy: %.3g', now - self._last_change) self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
@ -727,7 +727,7 @@ class Position(PpmsMixin, Drivable):
return change.target, 0, speed return change.target, 0, speed
def write_target(self, target): def write_target(self, target):
self._last_target = self.target # save for stop command self._last_target = self.target # save for stop command
self._stopped = False self._stopped = False
self._last_change = 0 self._last_change = 0
self._status_before_change = self.status self._status_before_change = self.status
@ -736,7 +736,7 @@ class Position(PpmsMixin, Drivable):
def write_speed(self, value): def write_speed(self, value):
if self.isDriving(): if self.isDriving():
return value return value
return None # change_move not called: as this would trigger an unnecessary move return None # change_move not called: as this would trigger an unnecessary move
def do_stop(self): def do_stop(self):
if not self.isDriving(): if not self.isDriving():

View File

@ -27,9 +27,11 @@ except ImportError:
print("This Module only works with a pythoncom module on a MS Windows OS") print("This Module only works with a pythoncom module on a MS Windows OS")
raise raise
class Error(Exception): class Error(Exception):
pass pass
class QDevice: class QDevice:
def __init__(self, classid): def __init__(self, classid):
self.threadlocal = threading.local() self.threadlocal = threading.local()
@ -44,19 +46,20 @@ class QDevice:
self.threadlocal.mvu = mvu self.threadlocal.mvu = mvu
args = [ args = [
win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, command), win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, command),
win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, ""), # reply win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, ""), # reply
win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, ""), # error win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, ""), # error
win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0), # ? win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0), # ?
win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)] # ? win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, 0)] # ?
err = mvu.SendPpmsCommand(*args) err = mvu.SendPpmsCommand(*args)
# win32com does invert the order of results! # win32com does invert the order of results!
if err == 0: if err == 0:
#print '<', args[3].value # print '<', args[3].value
return args[3].value return args[3].value
if err == 1: if err == 1:
#print '<done' # print '<done'
return "OK" return "OK"
raise Error(args[2].value.replace('\n', ' ')) raise Error(args[2].value.replace('\n', ' '))
if __name__ == "__main__": # test only
if __name__ == "__main__": # test only
print(QDevice('QD.MULTIVU.PPMS.1').send('LEVEL?')) print(QDevice('QD.MULTIVU.PPMS.1').send('LEVEL?'))