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:
# Alexander Lenz <alexander.lenz@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:
import sip
sip.setapi('QString', 2)

View File

@ -58,7 +58,6 @@ from secop.metaclass import Done
from secop.errors import ProgrammingError
class CmdParser:
"""helper for parsing replies
@ -74,8 +73,7 @@ class CmdParser:
('o', lambda x: int(x, 8)),
('xX', lambda x: int(x, 16)),
('eEfFgG', float),
) for letter in letters
}
) for letter in letters}
# pattern for chacaters to be escaped
ESC_PAT = re.compile('([\\%s])' % '\\'.join('|^$-.+*?()[]{}<>'))
# format pattern
@ -154,7 +152,7 @@ class Change:
self._module = module
self._valuedict = valuedict
self._to_be_changed = set(self._valuedict)
self._do_read = True
self._reply = None
def __getattr__(self, key):
"""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)
"""
if self._do_read:
self._do_read = False
if self._reply is None:
self._reply = self._handler.send_command(self._module)
result = self._handler.analyze(self._module, *self._reply)
result.update(self._valuedict)
@ -314,11 +311,8 @@ class CmdHandler(CmdHandlerBase):
implementing classes have to define/override the following:
"""
CMDARGS = [] # list of properties or parameters to be used for building
# 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
CMDARGS = [] # list of properties or parameters to be used for building 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
def __init__(self, group, querycmd, replyfmt, changecmd=None):
"""initialize the command handler

View File

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

View File

@ -17,6 +17,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""Define Metaclass for Modules/Features"""
@ -30,14 +31,15 @@ from secop.params import Command, Override, Parameter
from secop.datatypes import EnumType
from secop.properties import PropertyMeta
EVENT_ONLY_ON_CHANGED_VALUES = False
class Done:
"""a special return value for a read/write function
indicating that the setter is triggered already"""
# warning: MAGIC!
class ModuleMeta(PropertyMeta):

View File

@ -17,6 +17,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""Define Baseclasses for real Modules implemented in the server"""
@ -190,7 +191,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
self.writeDict = {} # values of parameters to be written
for pname, pobj in self.parameters.items():
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>
try:
pobj.value = pobj.datatype(cfgdict[pname])
@ -264,7 +265,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
self.DISPATCHER.announce_update_error(self, pname, pobj, exception)
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)
return False
@ -276,13 +277,12 @@ class Module(HasProperties, metaclass=ModuleMeta):
self.log.debug('empty %s.initModule()' % self.__class__.__name__)
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
or, if not implemented, immediately
might return a timeout value, if different from default
'''
"""
self.log.debug('empty %s.startModule()' % self.__class__.__name__)
started_callback()
@ -290,11 +290,11 @@ class Module(HasProperties, metaclass=ModuleMeta):
"""poll parameter <pname> with proper error handling"""
try:
return getattr(self, 'read_' + pname)()
except SilentError as e:
except SilentError:
pass
except SECoPError as e:
self.log.error(str(e))
except Exception as e:
except Exception:
self.log.error(formatException())
def writeOrPoll(self, pname):
@ -308,11 +308,11 @@ class Module(HasProperties, metaclass=ModuleMeta):
getattr(self, 'write_' + pname)(self.writeDict.pop(pname))
else:
getattr(self, 'read_' + pname)()
except SilentError as e:
except SilentError:
pass
except SECoPError as e:
self.log.error(str(e))
except Exception as e:
except Exception:
self.log.error(formatException())
@ -348,7 +348,7 @@ class Readable(Module):
}
def startModule(self, started_callback):
'''start basic polling thread'''
"""start basic polling thread"""
if issubclass(self.pollerClass, BasicPoller):
# use basic poller for legacy code
mkthread(self.__pollThread, started_callback)
@ -427,11 +427,11 @@ class Drivable(Writable):
}
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
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
# improved polling: may poll faster if module is BUSY
@ -472,7 +472,6 @@ class Communicator(Module):
}
class Attached(Property):
# we can not put this to properties.py, as it needs datatypes
def __init__(self, attrname=None):

View File

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

View File

@ -19,7 +19,7 @@
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
'''general, advanced frappy poller
"""general, advanced frappy poller
Usage examples:
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
'''
"""
import time
from threading import Event
@ -45,6 +45,7 @@ SLOW = 2
REGULAR = 3
DYNAMIC = 4
class PollerBase:
startup_timeout = 30 # default timeout for startup
@ -52,12 +53,12 @@ class PollerBase:
@classmethod
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
poller as value.
<name> is module.iodev or module.name, if iodev is not present
'''
"""
# for modules with the same iodev, a common poller is used,
# modules without iodev all get their own poller
name = getattr(module, 'iodev', module.name)
@ -68,26 +69,26 @@ class PollerBase:
poller.add_to_poller(module)
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
'''
"""
mkthread(self.run, started_callback)
return self.startup_timeout
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
'''
"""
raise NotImplementedError
def stop(self):
'''stop polling'''
"""stop polling"""
raise NotImplementedError
def __bool__(self):
'''is there any poll item?'''
"""is there any poll item?"""
raise NotImplementedError
def __repr__(self):
@ -95,7 +96,7 @@ class PollerBase:
class Poller(PollerBase):
'''a standard poller
"""a standard poller
parameters may have the following polltypes:
@ -106,12 +107,12 @@ class Poller(PollerBase):
Scheduled to poll every slowfactor * module.pollinterval
- DYNAMIC: by default used for 'value' and 'status'
When busy, scheduled to poll every fastfactor * module.pollinterval
'''
"""
DEFAULT_FACTORS = {SLOW: 4, DYNAMIC: 0.25, REGULAR: 1}
def __init__(self, name):
'''create a poller'''
"""create a poller"""
self.queues = {polltype: [] for polltype in self.DEFAULT_FACTORS}
self._event = Event()
self._stopped = False
@ -155,7 +156,7 @@ class Poller(PollerBase):
polltype = REGULAR
else:
polltype = SLOW
if not polltype in factors:
if polltype not in factors:
raise ProgrammingError("unknown poll type %r for parameter '%s'"
% (polltype, pname))
if pobj.handler:
@ -167,12 +168,12 @@ class Poller(PollerBase):
(0, module, pobj, pname, factors[polltype])))
def poll_next(self, polltype):
'''try to poll next item
"""try to poll next item
advance in queue until
- 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
'''
"""
queue = self.queues[polltype]
if not queue:
return float('inf') # queue is empty
@ -211,7 +212,7 @@ class Poller(PollerBase):
return True
def run(self, started_callback):
'''start poll loop
"""start poll loop
To be called as a thread. After all parameters are polled once first,
started_callback is called. To be called in Module.start_module.
@ -221,7 +222,7 @@ class Poller(PollerBase):
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
(where n is the number of regular parameters).
'''
"""
if not self:
# nothing to do (else we might call time.sleep(float('inf')) below
started_callback()
@ -255,7 +256,7 @@ class Poller(PollerBase):
self._stopped = True
def __bool__(self):
'''is there any poll item?'''
"""is there any poll item?"""
return any(self.queues.values())

View File

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

View File

@ -17,6 +17,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""Dispatcher for SECoP Messages
@ -50,7 +51,6 @@ from secop.protocol.messages import COMMANDREPLY, DESCRIPTIONREPLY, \
HEARTBEATREPLY, IDENTREPLY, IDENTREQUEST, READREPLY, WRITEREPLY
def make_update(modulename, pobj):
if pobj.readerror:
return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export),

View File

@ -16,6 +16,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""provides tcp interface to the SECoP Server"""
@ -45,6 +46,7 @@ SPACE = b' '
class OutputBufferOverflow(Exception):
pass
class TCPRequestHandler(socketserver.BaseRequestHandler):
def setup(self):
@ -97,7 +99,7 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
# no timeout error, but no new data -> connection closed
return
data = data + newdata
except socket.timeout as e:
except socket.timeout:
continue
except socket.error as e:
self.log.exception(e)
@ -118,7 +120,6 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
for idx, line in enumerate(HelpMessage.splitlines()):
self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line))
continue
result = None
try:
msg = decode_msg(origin)
except Exception as err:

View File

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

View File

@ -216,12 +216,12 @@ class StringIO(Communicator):
self._reconnectCallbacks.pop(key)
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
for commands without reply, join it with a query command,
wait_before is respected for end_of_lines within a command.
'''
"""
if not self.is_connected:
self.read_is_connected() # try to reconnect
try:

View File

@ -84,7 +84,7 @@ class Main(HasIodev, Module):
class ResChannel(HasIodev, Readable):
'''temperature channel on Lakeshore 336'''
"""temperature channel on Lakeshore 336"""
RES_RANGE = {key: i+1 for i, key in list(
enumerate(mag % val for mag in ['%gmOhm', '%gOhm', '%gkOhm', '%gMOhm']

View File

@ -27,9 +27,11 @@ except ImportError:
print("This Module only works with a pythoncom module on a MS Windows OS")
raise
class Error(Exception):
pass
class QDevice:
def __init__(self, classid):
self.threadlocal = threading.local()
@ -58,5 +60,6 @@ class QDevice:
return "OK"
raise Error(args[2].value.replace('\n', ' '))
if __name__ == "__main__": # test only
print(QDevice('QD.MULTIVU.PPMS.1').send('LEVEL?'))