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:
parent
e2cc9f74b5
commit
795759786f
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
@ -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"""
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
@ -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."""
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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():
|
||||||
|
@ -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?'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user