From 78bb3b5f96ac832248f451b5d87cf82d31b676b1 Mon Sep 17 00:00:00 2001 From: Enrico Faulhaber Date: Thu, 15 Dec 2016 14:36:12 +0100 Subject: [PATCH] Pep8 improvements + cleanup Change-Id: I9052e703b58e93b639c027521b47f693ae853f6e --- bin/secop-console | 5 +- bin/secop-server | 1 - doc/SECoP_Messages.md | 5 +- etc/secop-server | 7 +- secop/client/baseclient.py | 74 +++++----- secop/devices/core.py | 33 +++-- secop/devices/demo.py | 16 ++- secop/lib/__init__.py | 1 + secop/lib/parsing.py | 23 +++- secop/protocol/dispatcher.py | 210 ++++++++++++++++------------- secop/protocol/encoding/demo_v3.py | 17 ++- secop/protocol/encoding/demo_v4.py | 153 ++++++++++++--------- secop/protocol/encoding/pickle.py | 1 - secop/protocol/errors.py | 3 +- secop/protocol/framing/demo.py | 2 - secop/protocol/framing/rle.py | 2 - secop/protocol/interface/tcp.py | 8 +- secop/protocol/messages.py | 29 +++- secop/protocol/messages_old.py | 47 +++++-- secop/server.py | 8 +- 20 files changed, 395 insertions(+), 250 deletions(-) diff --git a/bin/secop-console b/bin/secop-console index c3248c2..6fd9b55 100755 --- a/bin/secop-console +++ b/bin/secop-console @@ -32,14 +32,14 @@ basepath = path.abspath(path.join(sys.path[0], '..')) etc_path = path.join(basepath, 'etc') pid_path = path.join(basepath, 'pid') log_path = path.join(basepath, 'log') -#sys.path[0] = path.join(basepath, 'src') +# sys.path[0] = path.join(basepath, 'src') sys.path[0] = basepath +# do not move above! import loggers from secop.client import ClientConsole - def parseArgv(argv): parser = argparse.ArgumentParser(description="Connect to a SECoP server") loggroup = parser.add_mutually_exclusive_group() @@ -64,7 +64,6 @@ def main(argv=None): loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info') loggers.initLogging('console', loglevel, log_path) - console = ClientConsole(args.name, basepath) try: diff --git a/bin/secop-server b/bin/secop-server index bcce4f9..84b1917 100755 --- a/bin/secop-server +++ b/bin/secop-server @@ -40,7 +40,6 @@ from secop import loggers from secop.server import Server - def parseArgv(argv): parser = argparse.ArgumentParser(description="Manage a SECoP server") loggroup = parser.add_mutually_exclusive_group() diff --git a/doc/SECoP_Messages.md b/doc/SECoP_Messages.md index ca19a00..7550f81 100644 --- a/doc/SECoP_Messages.md +++ b/doc/SECoP_Messages.md @@ -23,7 +23,8 @@ In replies the SEC-node (in the playground) will always use the correct paramete On change-requests the parameter is assumed to be 'target', on trigger-requests it is assumed to be 'value'. Clients should not rely on this and explicitly state the parametername! -All keywords are defined to be identifiers in the sense, that they are not longer than 63 characters and consist only of letters, digits and underscore and do not start with a digit. (i.e. T_9 is ok, whereas t{9} is not!) +All names and keywords are defined to be identifiers in the sense, that they are not longer than 63 characters and consist only of letters, digits and underscore and do not start with a digit. (i.e. T_9 is ok, whereas t{9} is not!) +No rule is without exception, there is exactly ONE special case: the identify request consists of the literal string '*IDN?\n' and its answer is formatted like an valid SCPI response for *IDN?. We rely on the underlying transport to not split messages, i.e. all messages are transported as a whole and no message interrupts another. @@ -58,7 +59,7 @@ Identify -------- * Request: type A: '*IDN?' - * Reply: special: 'Sine2020WP7.1&ISSE, SECoP, V2016-11-30, rc1' + * Reply: special: 'SECoP, SECoPTCP, V2016-11-30, rc1' * queries if SECoP protocol is supported and which version it is Format is intentionally choosen to be compatible to SCPI (for this query only). It is NOT intended to transport information about the manufacturer of the hardware, but to identify this as a SECoP device and transfer the protocol version! diff --git a/etc/secop-server b/etc/secop-server index b0ce626..1484e6b 100755 --- a/etc/secop-server +++ b/etc/secop-server @@ -36,7 +36,7 @@ import glob import signal -CFG_DIR='/etc/secop' +CFG_DIR = '/etc/secop' PID_DIR = '/var/run' SRV_EXEC = 'secop-server' @@ -62,7 +62,7 @@ def parseArgv(argv): def getServerNames(): return sorted([os.path.basename(entry)[:-4] - for entry in glob.glob(os.path.join(CFG_DIR, '*.cfg'))]) + for entry in glob.glob(os.path.join(CFG_DIR, '*.cfg'))]) def getSrvPid(name): @@ -100,6 +100,7 @@ def determineServerStatus(name): else: print('%s: dead' % name) + def main(argv=None): if argv is None: argv = sys.argv @@ -107,7 +108,7 @@ def main(argv=None): args = parseArgv(argv[1:]) actionMap = { - 'start' : startServer, + 'start': startServer, 'stop': stopServer, 'status': determineServerStatus, } diff --git a/secop/client/baseclient.py b/secop/client/baseclient.py index 617a2ff..62c6a65 100644 --- a/secop/client/baseclient.py +++ b/secop/client/baseclient.py @@ -36,9 +36,9 @@ from secop.protocol.framing import FRAMERS from secop.protocol.messages import * - class TCPConnection(object): # disguise a TCP connection as serial one + def __init__(self, host, port): self._host = host self._port = int(port) @@ -73,15 +73,17 @@ class TCPConnection(object): while '\n' in data: line, data = data.split('\n', 1) try: - self._readbuffer.put(line.strip('\r'), block=True, timeout=1) + self._readbuffer.put( + line.strip('\r'), block=True, timeout=1) except Queue.Full: - self.log.debug('rcv queue full! dropping line: %r' % line) + self.log.debug( + 'rcv queue full! dropping line: %r' % line) finally: self._thread = None def readline(self, block=False): """blocks until a full line was read and returns it""" - i = 10; + i = 10 while i: try: return self._readbuffer.get(block=True, timeout=1) @@ -109,13 +111,13 @@ class Value(object): u = None e = None fmtstr = '%s' - + def __init__(self, value, qualifiers={}): self.value = value if 't' in qualifiers: self.t = parse_time(qualifiers.pop('t')) self.__dict__.update(qualifiers) - + def __repr__(self): r = [] if self.t is not None: @@ -160,7 +162,8 @@ class Client(object): self.single_shots = dict() # mapping the modulename to a dict mapping the parameter names to their values - # note: the module value is stored as the value of the parameter value of the module + # note: the module value is stored as the value of the parameter value + # of the module self.cache = dict() self._syncLock = threading.RLock() @@ -188,7 +191,7 @@ class Client(object): self.secop_id = line continue msgtype, spec, data = self._decode_message(line) - if msgtype in ('event','changed'): + if msgtype in ('event', 'changed'): # handle async stuff self._handle_event(spec, data) if msgtype != 'event': @@ -202,7 +205,6 @@ class Client(object): else: self.log.error('ignoring unexpected reply %r' % line) - def _encode_message(self, requesttype, spec='', data=Ellipsis): """encodes the given message to a string """ @@ -250,30 +252,37 @@ class Client(object): try: mkthread(func, data) except Exception as err: - self.log.exception('Exception in Single-shot Callback!', err) + self.log.exception( + 'Exception in Single-shot Callback!', err) run.add(func) self.single_shots[spec].difference_update(run) def register_callback(self, module, parameter, cb): - self.log.debug('registering callback %r for %s:%s' % (cb, module, parameter)) + self.log.debug( + 'registering callback %r for %s:%s' % + (cb, module, parameter)) self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb) def unregister_callback(self, module, parameter, cb): - self.log.debug('unregistering callback %r for %s:%s' % (cb, module, parameter)) - self.callbacks.setdefault('%s:%s' % (module, parameter), set()).discard(cb) + self.log.debug( + 'unregistering callback %r for %s:%s' % + (cb, module, parameter)) + self.callbacks.setdefault('%s:%s' % + (module, parameter), set()).discard(cb) def communicate(self, msgtype, spec='', data=Ellipsis): # maps each (sync) request to the corresponding reply - # XXX: should go to the encoder! and be imported here (or make a translating method) + # XXX: should go to the encoder! and be imported here (or make a + # translating method) REPLYMAP = { - "describe": "describing", - "do": "done", - "change": "changed", - "activate": "active", + "describe": "describing", + "do": "done", + "change": "changed", + "activate": "active", "deactivate": "inactive", - "*IDN?": "SECoP,", - "ping": "ping", - } + "*IDN?": "SECoP,", + "ping": "ping", + } if self.stopflag: raise RuntimeError('alreading stopping!') if msgtype == 'poll': @@ -282,20 +291,25 @@ class Client(object): spec = spec + ':value' event = threading.Event() result = ['polled', spec] - self.single_shots.setdefault(spec, set()).add(lambda d: (result.append(d), event.set())) - self.connection.writeline(self._encode_message(msgtype, spec, data)) + self.single_shots.setdefault(spec, set()).add( + lambda d: (result.append(d), event.set())) + self.connection.writeline( + self._encode_message( + msgtype, spec, data)) if event.wait(10): return tuple(result) raise RuntimeError("timeout upon waiting for reply!") rply = REPLYMAP[msgtype] if rply in self.expected_replies: - raise RuntimeError("can not have more than one requests of the same type at the same time!") + raise RuntimeError( + "can not have more than one requests of the same type at the same time!") event = threading.Event() self.expected_replies[rply] = [event] self.connection.writeline(self._encode_message(msgtype, spec, data)) - if event.wait(10): # wait 10s for reply - result = rply, self.expected_replies[rply][1], self.expected_replies[rply][2] + if event.wait(10): # wait 10s for reply + result = rply, self.expected_replies[rply][ + 1], self.expected_replies[rply][2] del self.expected_replies[rply] return result del self.expected_replies[rply] @@ -316,9 +330,9 @@ class Client(object): self._cache.getdefault(device, {})[param] = value # XXX: further notification-callbacks needed ??? - def startup(self, async=False): - _, self.equipment_id, self.describing_data = self.communicate('describe') + _, self.equipment_id, self.describing_data = self.communicate( + 'describe') # always fill our cache self.communicate('activate') # deactivate updates if not wanted @@ -343,8 +357,8 @@ class Client(object): return self.describing_data['modules'][module]['commands'].keys() def getProperties(self, module, parameter): - return self.describing_data['modules'][module]['parameters'][parameter].items() + return self.describing_data['modules'][ + module]['parameters'][parameter].items() def syncCommunicate(self, msg): return self.communicate(msg) - diff --git a/secop/devices/core.py b/secop/devices/core.py index 3ae83ca..e0b8e1d 100644 --- a/secop/devices/core.py +++ b/secop/devices/core.py @@ -69,12 +69,12 @@ class PARAM(object): def as_dict(self): # used for serialisation only - return dict(description = self.description, - unit = self.unit, - readonly = self.readonly, - value = self.value, - timestamp = self.timestamp, - validator = repr(self.validator), + return dict(description=self.description, + unit=self.unit, + readonly=self.readonly, + value=self.value, + timestamp=self.timestamp, + validator=repr(self.validator), ) @@ -95,13 +95,15 @@ class CMD(object): def as_dict(self): # used for serialisation only - return dict(description = self.description, - arguments = repr(self.arguments), - resulttype = repr(self.resulttype), + return dict(description=self.description, + arguments=repr(self.arguments), + resulttype=repr(self.resulttype), ) # Meta class # warning: MAGIC! + + class DeviceMeta(type): def __new__(mcs, name, bases, attrs): @@ -183,8 +185,9 @@ class DeviceMeta(type): argspec = inspect.getargspec(value) if argspec[0] and argspec[0][0] == 'self': del argspec[0][0] - newtype.CMDS[name[2:]] = CMD(getattr(value, '__doc__'), - argspec.args, None) # XXX: find resulttype! + newtype.CMDS[name[2:]] = CMD( + getattr(value, '__doc__'), + argspec.args, None) # XXX: find resulttype! attrs['__constructed__'] = True return newtype @@ -279,7 +282,7 @@ class Readable(Device): 'baseclass': PARAM('protocol defined interface class', default="Readable", validator=str), 'value': PARAM('current value of the device', readonly=True, default=0.), - 'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1,120),), + 'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),), 'status': PARAM('current status of the device', default=status.OK, validator=enum(**{'idle': status.OK, 'BUSY': status.BUSY, @@ -303,7 +306,7 @@ class Readable(Device): self._pollthread = threading.Thread(target=self._pollThread) self._pollthread.daemon = True self._pollthread.start() - + def _pollThread(self): while True: time.sleep(self.pollinterval) @@ -312,7 +315,8 @@ class Readable(Device): rfunc = getattr(self, 'read_%s' % pname, None) if rfunc: rfunc() - + + class Driveable(Readable): """Basic Driveable device @@ -324,5 +328,6 @@ class Driveable(Readable): 'target': PARAM('target value of the device', default=0., readonly=False), } + def doStop(self): time.sleep(1) # for testing ! diff --git a/secop/devices/demo.py b/secop/devices/demo.py index a77b630..efa891b 100644 --- a/secop/devices/demo.py +++ b/secop/devices/demo.py @@ -37,10 +37,14 @@ class Switch(Driveable): 'value': PARAM('current state (on or off)', validator=enum(on=1, off=0), default=0), 'target': PARAM('wanted state (on or off)', - validator=enum(on=1, off=0), default=0, - readonly=False), - 'switch_on_time': PARAM('how long to wait after switching the switch on', validator=floatrange(0, 60), unit='s', default=10, export=False), - 'switch_off_time': PARAM('how long to wait after switching the switch off', validator=floatrange(0, 60), unit='s', default=10, export=False), + validator=enum(on=1, off=0), + default=0, readonly=False), + 'switch_on_time': PARAM('seconds to wait after activating the switch', + validator=floatrange(0, 60), unit='s', + default=10, export=False), + 'switch_off_time': PARAM('cool-down time in seconds', + validator=floatrange(0, 60), unit='s', + default=10, export=False), } def init(self): @@ -177,8 +181,8 @@ class SampleTemp(Driveable): validator=float, default=10), 'sensor': PARAM("Sensor number or calibration id", validator=str, readonly=True), - 'ramp': PARAM('moving speed in K/min', - validator=floatrange(0, 100), unit='K/min', default=0.1, readonly=False), + 'ramp': PARAM('moving speed in K/min', validator=floatrange(0, 100), + unit='K/min', default=0.1, readonly=False), } def init(self): diff --git a/secop/lib/__init__.py b/secop/lib/__init__.py index 2a81cb4..1dd7b08 100644 --- a/secop/lib/__init__.py +++ b/secop/lib/__init__.py @@ -24,6 +24,7 @@ import threading + class attrdict(dict): """a normal dict, providing access also via attributes""" diff --git a/secop/lib/parsing.py b/secop/lib/parsing.py index 9f33fae..fb23c52 100644 --- a/secop/lib/parsing.py +++ b/secop/lib/parsing.py @@ -26,12 +26,17 @@ import re import time from datetime import tzinfo, timedelta, datetime +# format_time and parse_time could be simplified with external dateutil lib +# http://stackoverflow.com/a/15228038 + # based on http://stackoverflow.com/a/39418771 + + class LocalTimezone(tzinfo): ZERO = timedelta(0) - STDOFFSET = timedelta(seconds = -time.timezone) + STDOFFSET = timedelta(seconds=-time.timezone) if time.daylight: - DSTOFFSET = timedelta(seconds = -time.altzone) + DSTOFFSET = timedelta(seconds=-time.altzone) else: DSTOFFSET = STDOFFSET @@ -62,6 +67,7 @@ class LocalTimezone(tzinfo): LocalTimezone = LocalTimezone() + def format_time(timestamp=None): # get time in UTC if timestamp is None: @@ -70,15 +76,22 @@ def format_time(timestamp=None): d = datetime.fromtimestamp(timestamp, LocalTimezone) return d.isoformat("T") +# Solution based on +# https://bugs.python.org/review/15873/diff/16581/Lib/datetime.py#newcode1418Lib/datetime.py:1418 + class Timezone(tzinfo): + def __init__(self, offset, name='unknown timezone'): self.offset = offset self.name = name + def tzname(self, dt): return self.name + def utcoffset(self, dt): return self.offset + def dst(self, dt): return timedelta(0) datetime_re = re.compile( @@ -88,6 +101,7 @@ datetime_re = re.compile( r'(?PZ|[+-]\d{2}(?::?\d{2})?)?$' ) + def _parse_isostring(isostring): """Parses a string and return a datetime.datetime. This function supports time zone offsets. When the input contains one, @@ -111,7 +125,10 @@ def _parse_isostring(isostring): kw = {k: int(v) for k, v in kw.items() if v is not None} kw['tzinfo'] = _tzinfo return datetime(**kw) - raise ValueError("%s is not a valid ISO8601 string I can parse!" % isostring) + raise ValueError( + "%s is not a valid ISO8601 string I can parse!" % + isostring) + def parse_time(isostring): try: diff --git a/secop/protocol/dispatcher.py b/secop/protocol/dispatcher.py index e2864e7..cc63836 100644 --- a/secop/protocol/dispatcher.py +++ b/secop/protocol/dispatcher.py @@ -36,10 +36,6 @@ Interface to the modules: - get_module(modulename) returns the requested module or None - remove_module(modulename_or_obj): removes the module (during shutdown) -internal stuff which may be called - - list_modules(): return a list of modules + descriptive data as dict - - list_module_params(): - return a list of paramnames for this module + descriptive data """ import time @@ -50,20 +46,21 @@ from errors import * class Dispatcher(object): + def __init__(self, logger, options): self.equipment_id = options.pop('equipment_id') self.log = logger # map ALL modulename -> moduleobj - self._dispatcher_modules = {} + self._modules = {} # list of EXPORTED modules - self._dispatcher_export = [] + self._export = [] # list all connections - self._dispatcher_connections = [] + self._connections = [] # active (i.e. broadcast-receiving) connections - self._dispatcher_active_connections = set() + self._active_connections = set() # map eventname -> list of subscribed connections - self._dispatcher_subscriptions = {} - self._dispatcher_lock = threading.RLock() + self._subscriptions = {} + self._lock = threading.RLock() def handle_request(self, conn, msg): """handles incoming request @@ -72,7 +69,7 @@ class Dispatcher(object): """ self.log.debug('Dispatcher: handling msg: %r' % msg) # play thread safe ! - with self._dispatcher_lock: + with self._lock: reply = None # generate reply (coded and framed) msgname = msg.__class__.__name__ @@ -90,7 +87,7 @@ class Dispatcher(object): reply = ErrorMessage(errorclass=err.__class__.__name__, errorinfo=[repr(err), str(msg)]) except (ValueError, TypeError) as err: -# self.log.exception(err) + # self.log.exception(err) reply = ErrorMessage(errorclass='BadValue', errorinfo=[repr(err), str(msg)]) except Exception as err: @@ -106,118 +103,123 @@ class Dispatcher(object): def broadcast_event(self, msg, reallyall=False): """broadcasts a msg to all active connections""" if reallyall: - listeners = self._dispatcher_connections + listeners = self._connections else: if getattr(msg, 'command', None) is None: - eventname = '%s:%s' % (msg.module, msg.parameter if msg.parameter else 'value') + eventname = '%s:%s' % ( + msg.module, msg.parameter if msg.parameter else 'value') else: eventname = '%s:%s()' % (msg.module, msg.command) - listeners = self._dispatcher_subscriptions.get(eventname, []) - listeners += list(self._dispatcher_active_connections) + listeners = self._subscriptions.get(eventname, []) + listeners += list(self._active_connections) for conn in listeners: conn.queue_async_reply(msg) def announce_update(self, moduleobj, pname, pobj): """called by modules param setters to notify subscribers of new values """ - msg = Value(moduleobj.name, parameter=pname, value=pobj.value, t=pobj.timestamp) + msg = Value( + moduleobj.name, + parameter=pname, + value=pobj.value, + t=pobj.timestamp) self.broadcast_event(msg) def subscribe(self, conn, modulename, pname='value'): eventname = '%s:%s' % (modulename, pname) - self._dispatcher_subscriptions.setdefault(eventname, set()).add(conn) + self._subscriptions.setdefault(eventname, set()).add(conn) def unsubscribe(self, conn, modulename, pname='value'): eventname = '%s:%s' % (modulename, pname) - if eventname in self._dispatcher_subscriptions: - self._dispatcher_subscriptions.remove(conn) + if eventname in self._subscriptions: + self._subscriptions.remove(conn) def add_connection(self, conn): """registers new connection""" - self._dispatcher_connections.append(conn) + self._connections.append(conn) def remove_connection(self, conn): """removes now longer functional connection""" - if conn in self._dispatcher_connections: - self._dispatcher_connections.remove(conn) - for _evt, conns in self._dispatcher_subscriptions.items(): + if conn in self._connections: + self._connections.remove(conn) + for _evt, conns in self._subscriptions.items(): conns.discard(conn) def activate_connection(self, conn): - self._dispatcher_active_connections.add(conn) + self._active_connections.add(conn) def deactivate_connection(self, conn): - self._dispatcher_active_connections.discard(conn) + self._active_connections.discard(conn) def register_module(self, moduleobj, modulename, export=True): self.log.debug('registering module %r as %s (export=%r)' % (moduleobj, modulename, export)) - self._dispatcher_modules[modulename] = moduleobj + self._modules[modulename] = moduleobj if export: - self._dispatcher_export.append(modulename) + self._export.append(modulename) def get_module(self, modulename): - module = self._dispatcher_modules.get(modulename, modulename) + module = self._modules.get(modulename, modulename) return module def remove_module(self, modulename_or_obj): moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj modulename = moduleobj.name - if modulename in self._dispatcher_export: - self._dispatcher_export.remove(modulename) - self._dispatcher_modules.pop(modulename) - # XXX: also clean _dispatcher_subscriptions + if modulename in self._export: + self._export.remove(modulename) + self._modules.pop(modulename) + # XXX: also clean _subscriptions def list_module_names(self): # return a copy of our list - return self._dispatcher_export[:] - - def list_modules(self): - dn = [] - dd = {} - for modulename in self._dispatcher_export: - dn.append(modulename) - module = self.get_module(modulename) - descriptive_data = { - 'class': module.__class__.__name__, - #'bases': module.__bases__, - 'parameters': module.PARAMS.keys(), - 'commands': module.CMDS.keys(), - # XXX: what else? - } - dd[modulename] = descriptive_data - return dn, dd - - def get_descriptive_data(self): - # XXX: be lazy and cache this? - result = {} - for modulename in self._dispatcher_export: - module = self.get_module(modulename) - dd = {'class' : module.__class__.__name__, - 'bases' : [b.__name__ for b in module.__class__.__bases__], - 'parameters': dict((pn,po.as_dict()) for pn,po in module.PARAMS.items()), - 'commands': dict((cn,co.as_dict()) for cn,co in module.CMDS.items()), - 'baseclass' : 'Readable', - } - result.setdefault('modules', {})[modulename] = dd - result['equipment_id'] = self.equipment_id - # XXX: what else? - return result + return self._export[:] def list_module_params(self, modulename): self.log.debug('list_module_params(%r)' % modulename) - if modulename in self._dispatcher_export: - # XXX: omit export=False params! + if modulename in self._export: + # omit export=False params! res = {} for paramname, param in self.get_module(modulename).PARAMS.items(): - if param.export == True: - res[paramname] = param + if param.export: + res[paramname] = param.as_dict() self.log.debug('list params for module %s -> %r' % (modulename, res)) return res self.log.debug('-> module is not to be exported!') return {} + def list_module_cmds(self, modulename): + self.log.debug('list_module_cmds(%r)' % modulename) + if modulename in self._export: + # omit export=False params! + res = {} + for cmdname, cmdobj in self.get_module(modulename).CMDS.items(): + res[cmdname] = cmdobj.as_dict() + self.log.debug('list cmds for module %s -> %r' % + (modulename, res)) + return res + self.log.debug('-> module is not to be exported!') + return {} + + def get_descriptive_data(self): + # XXX: be lazy and cache this? + result = {'modules':{}} + for modulename in self._export: + module = self.get_module(modulename) + # some of these need rework ! + dd = {'class': module.__class__.__name__, + 'bases': [b.__name__ for b in module.__class__.__bases__], + 'parameters': self.list_module_params(modulename), + 'commands': self.list_module_cmds(modulename), + 'baseclass': 'Readable', + } + result['modules'][modulename] = dd + result['equipment_id'] = self.equipment_id + result['firmware'] = 'The SECoP playground' + result['version'] = "2016.12" + # XXX: what else? + return result + def _execute_command(self, modulename, command, arguments=None): if arguments is None: arguments = [] @@ -230,13 +232,22 @@ class Dispatcher(object): if cmdspec is None: raise NoSuchCommandError(module=modulename, command=command) if len(cmdspec.arguments) != len(arguments): - raise BadValueError(module=modulename, command=command, reason='Wrong number of arguments!') + raise BadValueError( + module=modulename, + command=command, + reason='Wrong number of arguments!') # now call func and wrap result as value # note: exceptions are handled in handle_request, not here! - func = getattr(moduleobj, 'do'+command) + func = getattr(moduleobj, 'do' + command) res = func(*arguments) - res = CommandReply(module=modulename, command=command, result=[res, dict(t=time.time())]) + res = CommandReply( + module=modulename, + command=command, + result=[ + res, + dict( + t=time.time())]) # res = Value(modulename, command=command, value=func(*arguments), t=time.time()) return res @@ -258,8 +269,16 @@ class Dispatcher(object): else: setattr(moduleobj, pname, value) if pobj.timestamp: - return WriteReply(module=modulename, parameter=pname, value=[pobj.value, dict(t=pobj.timestamp)]) - return WriteReply(module=modulename, parameter=pname, value=[pobj.value, {}]) + return WriteReply( + module=modulename, parameter=pname, value=[ + pobj.value, dict( + t=pobj.timestamp)]) + return WriteReply( + module=modulename, + parameter=pname, + value=[ + pobj.value, + {}]) def _getParamValue(self, modulename, pname): moduleobj = self.get_module(modulename) @@ -276,10 +295,13 @@ class Dispatcher(object): # note: exceptions are handled in handle_request, not here! readfunc() if pobj.timestamp: - return Value(modulename, parameter=pname, value=pobj.value, t=pobj.timestamp) + return Value( + modulename, + parameter=pname, + value=pobj.value, + t=pobj.timestamp) return Value(modulename, parameter=pname, value=pobj.value) - # now the (defined) handlers for the different requests def handle_Help(self, conn, msg): return HelpMessage() @@ -289,21 +311,24 @@ class Dispatcher(object): def handle_Describe(self, conn, msg): # XXX:collect descriptive data - return DescribeReply(equipment_id = self.equipment_id, description = self.get_descriptive_data()) + return DescribeReply( + equipment_id=self.equipment_id, + description=self.get_descriptive_data()) def handle_Poll(self, conn, msg): # XXX: trigger polling and force sending event res = self._getParamValue(msg.module, msg.parameter or 'value') - #self.broadcast_event(res) - if conn in self._dispatcher_active_connections: + # self.broadcast_event(res) + if conn in self._active_connections: return None # already send to myself return res # send reply to inactive conns def handle_Write(self, conn, msg): # notify all by sending WriteReply #msg1 = WriteReply(**msg.as_dict()) - #self.broadcast_event(msg1) - # try to actually write XXX: should this be done asyncron? we could just return the reply in that case + # self.broadcast_event(msg1) + # try to actually write XXX: should this be done asyncron? we could + # just return the reply in that case if msg.parameter: res = self._setParamValue(msg.module, msg.parameter, msg.value) else: @@ -312,21 +337,22 @@ class Dispatcher(object): raise ReadonlyError(module=msg.module, parameter=None) res = self._setParamValue(msg.module, 'target', msg.value) res.parameter = 'target' - #self.broadcast_event(res) - if conn in self._dispatcher_active_connections: + # self.broadcast_event(res) + if conn in self._active_connections: return None # already send to myself return res # send reply to inactive conns def handle_Command(self, conn, msg): # notify all by sending CommandReply #msg1 = CommandReply(**msg.as_dict()) - #self.broadcast_event(msg1) - # XXX: should this be done asyncron? we could just return the reply in that case + # self.broadcast_event(msg1) + # XXX: should this be done asyncron? we could just return the reply in + # that case # try to actually execute command res = self._execute_command(msg.module, msg.command, msg.arguments) - #self.broadcast_event(res) - #if conn in self._dispatcher_active_connections: + # self.broadcast_event(res) + # if conn in self._active_connections: # return None # already send to myself return res # send reply to inactive conns @@ -336,7 +362,7 @@ class Dispatcher(object): def handle_Activate(self, conn, msg): self.activate_connection(conn) # easy approach: poll all values... - for modulename, moduleobj in self._dispatcher_modules.items(): + for modulename, moduleobj in self._modules.items(): for pname, pobj in moduleobj.PARAMS.items(): # WARNING: THIS READS ALL PARAMS FROM HW! # XXX: should we send the cached values instead? (pbj.value) @@ -349,7 +375,7 @@ class Dispatcher(object): res = Value(module=modulename, parameter=pname, value=pobj.value, t=pobj.timestamp, unit=pobj.unit) - if res.value != Ellipsis: # means we do not have a value at all so skip this + if res.value != Ellipsis: # means we do not have a value at all so skip this self.broadcast_event(res) conn.queue_async_reply(ActivateReply(**msg.as_dict())) return None @@ -369,6 +395,4 @@ class Dispatcher(object): """ self.log.error('IGN: got unhandled request %s' % msg) return ErrorMessage(errorclass="InternalError", - errorstring = 'Got Unhandled Request %r' % msg) - - + errorstring='Got Unhandled Request %r' % msg) diff --git a/secop/protocol/encoding/demo_v3.py b/secop/protocol/encoding/demo_v3.py index d51e441..ab0b682 100644 --- a/secop/protocol/encoding/demo_v3.py +++ b/secop/protocol/encoding/demo_v3.py @@ -98,7 +98,7 @@ class DemoEncoder(MessageEncoder): MessageEncoder.__init__(self, *args, **kwds) self.result = [] # for decoding self.expect_lines = 1 - #self.tests() + # self.tests() def encode(self, msg): """msg object -> transport layer message""" @@ -179,8 +179,13 @@ class DemoEncoder(MessageEncoder): return '\n'.join(result) if isinstance(msg, ErrorMessage): - return ('%s %s' % (devspec(msg, 'error %s' % - msg.errortype), msg.errorstring)).strip() + return ( + '%s %s' % + (devspec( + msg, + 'error %s' % + msg.errortype), + msg.errorstring)).strip() return 'Can not handle object %r!' % msg @@ -292,7 +297,11 @@ class DemoEncoder(MessageEncoder): # construct messageobj if msgtype in MESSAGE: return MESSAGE[msgtype]( - devs=devs, pars=pars, props=props, result=result, **mgroups) + devs=devs, + pars=pars, + props=props, + result=result, + **mgroups) return ErrorMessage(errortype="SyntaxError", errorstring="Can't handle %r" % encoded) diff --git a/secop/protocol/encoding/demo_v4.py b/secop/protocol/encoding/demo_v4.py index efc3ec2..54f6c13 100644 --- a/secop/protocol/encoding/demo_v4.py +++ b/secop/protocol/encoding/demo_v4.py @@ -34,38 +34,47 @@ import ast import re import json -# each message is like [ \space [ \space ]] \lf +# each message is like [ \space [ \space +# ]] \lf # note: the regex allow <> for spec for testing only! DEMO_RE = re.compile( - r"""^(?P[\*\?\w]+)(?:\s(?P[\w:<>]+)(?:\s(?P.*))?)?$""", re.X) + r"""^(?P[\*\?\w]+)(?:\s(?P[\w:<>]+)(?:\s(?P.*))?)?$""", + re.X) #""" # messagetypes: IDENTREQUEST = '*IDN?' # literal -IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1' # literal! first part 'SECoP' is fixed! +# literal! first part 'SECoP' is fixed! +IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1' DESCRIPTIONSREQUEST = 'describe' # literal DESCRIPTIONREPLY = 'describing' # + +json -ENABLEEVENTSREQUEST = 'activate' # literal +ENABLEEVENTSREQUEST = 'activate' # literal ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer DISABLEEVENTSREQUEST = 'deactivate' # literal DISABLEEVENTSREPLY = 'inactive' # literal COMMANDREQUEST = 'do' # +module:command +json args (if needed) -COMMANDREPLY = 'done' # +module:command +json args (if needed) # send after the command finished ! -WRITEREQUEST = 'change' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally! -WRITEREPLY = 'changed' # +module[:parameter] +json_value # send with the read back value -TRIGGERREQUEST = 'poll' # +module[:parameter] -> NO direct reply, calls TRIGGER internally! +# +module:command +json args (if needed) # send after the command finished ! +COMMANDREPLY = 'done' +# +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally! +WRITEREQUEST = 'change' +# +module[:parameter] +json_value # send with the read back value +WRITEREPLY = 'changed' +# +module[:parameter] -> NO direct reply, calls TRIGGER internally! +TRIGGERREQUEST = 'poll' EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict) HEARTBEATREQUEST = 'ping' # +nonce_without_space HEARTBEATREPLY = 'pong' # +nonce_without_space ERRORREPLY = 'ERROR' # +errorclass +json_extended_info -HELPREQUEST = 'help' # literal +HELPREQUEST = 'help' # literal HELPREPLY = 'helping' # +line number +json_text ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand', 'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed', 'IsBusy', 'IsError', 'SyntaxError', 'InternalError', - 'CommandRunning', 'Disabled',] -# note: above strings need to be unique in the sense, that none is/or starts with another + 'CommandRunning', 'Disabled', ] +# note: above strings need to be unique in the sense, that none is/or +# starts with another + def encode_value_data(vobj): q = vobj.qualifiers.copy() @@ -73,55 +82,57 @@ def encode_value_data(vobj): q['t'] = format_time(q['t']) return vobj.value, q + class DemoEncoder(MessageEncoder): # map of msg to msgtype string as defined above. ENCODEMAP = { - IdentifyRequest : (IDENTREQUEST,), - IdentifyReply : (IDENTREPLY,), - DescribeRequest : (DESCRIPTIONSREQUEST,), - DescribeReply : (DESCRIPTIONREPLY, 'equipment_id', 'description',), - ActivateRequest : (ENABLEEVENTSREQUEST,), - ActivateReply : (ENABLEEVENTSREPLY,), + IdentifyRequest: (IDENTREQUEST,), + IdentifyReply: (IDENTREPLY,), + DescribeRequest: (DESCRIPTIONSREQUEST,), + DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',), + ActivateRequest: (ENABLEEVENTSREQUEST,), + ActivateReply: (ENABLEEVENTSREPLY,), DeactivateRequest: (DISABLEEVENTSREQUEST,), - DeactivateReply : (DISABLEEVENTSREPLY,), - CommandRequest : (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',), - CommandReply : (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',), - WriteRequest : (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), - WriteReply : (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), - PollRequest : (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ), - HeartbeatRequest : (HEARTBEATREQUEST, 'nonce',), - HeartbeatReply : (HEARTBEATREPLY, 'nonce',), + DeactivateReply: (DISABLEEVENTSREPLY,), + CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',), + CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',), + WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), + WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), + PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ), + HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',), + HeartbeatReply: (HEARTBEATREPLY, 'nonce',), HelpMessage: (HELPREQUEST, ), - ErrorMessage : (ERRORREPLY, 'errorclass', 'errorinfo',), - Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()')) - if msg.parameter or msg.command else msg.module, - encode_value_data,), + ErrorMessage: (ERRORREPLY, 'errorclass', 'errorinfo',), + Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) + if msg.parameter or msg.command else msg.module, + encode_value_data,), } DECODEMAP = { - IDENTREQUEST : lambda spec, data: IdentifyRequest(), - IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness - DESCRIPTIONSREQUEST : lambda spec, data: DescribeRequest(), - DESCRIPTIONREPLY : lambda spec, data: DescribeReply(equipment_id=spec[0], description=data), - ENABLEEVENTSREQUEST : lambda spec, data: ActivateRequest(), - ENABLEEVENTSREPLY: lambda spec, data:ActivateReply(), - DISABLEEVENTSREQUEST: lambda spec, data:DeactivateRequest(), - DISABLEEVENTSREPLY: lambda spec, data:DeactivateReply(), - COMMANDREQUEST: lambda spec, data:CommandRequest(module=spec[0], command=spec[1], arguments=data), + IDENTREQUEST: lambda spec, data: IdentifyRequest(), + # handled specially, listed here for completeness + IDENTREPLY: lambda spec, data: IdentifyReply(encoded), + DESCRIPTIONSREQUEST: lambda spec, data: DescribeRequest(), + DESCRIPTIONREPLY: lambda spec, data: DescribeReply(equipment_id=spec[0], description=data), + ENABLEEVENTSREQUEST: lambda spec, data: ActivateRequest(), + ENABLEEVENTSREPLY: lambda spec, data: ActivateReply(), + DISABLEEVENTSREQUEST: lambda spec, data: DeactivateRequest(), + DISABLEEVENTSREPLY: lambda spec, data: DeactivateReply(), + COMMANDREQUEST: lambda spec, data: CommandRequest(module=spec[0], command=spec[1], arguments=data), COMMANDREPLY: lambda spec, data: CommandReply(module=spec[0], command=spec[1], result=data), WRITEREQUEST: lambda spec, data: WriteRequest(module=spec[0], parameter=spec[1], value=data), - WRITEREPLY:lambda spec, data:WriteReply(module=spec[0], parameter=spec[1], value=data), - TRIGGERREQUEST:lambda spec, data:PollRequest(module=spec[0], parameter=spec[1]), - HEARTBEATREQUEST:lambda spec, data:HeartbeatRequest(nonce=spec[0]), - HEARTBEATREPLY:lambda spec, data:HeartbeatReply(nonce=spec[0]), - HELPREQUEST: lambda spec, data:HelpMessage(), -# HELPREPLY: lambda spec, data:None, # ignore this - ERRORREPLY:lambda spec, data:ErrorMessage(errorclass=spec[0], errorinfo=data), - EVENT:lambda spec, data:Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data)>1 else {}), - } + WRITEREPLY: lambda spec, data: WriteReply(module=spec[0], parameter=spec[1], value=data), + TRIGGERREQUEST: lambda spec, data: PollRequest(module=spec[0], parameter=spec[1]), + HEARTBEATREQUEST: lambda spec, data: HeartbeatRequest(nonce=spec[0]), + HEARTBEATREPLY: lambda spec, data: HeartbeatReply(nonce=spec[0]), + HELPREQUEST: lambda spec, data: HelpMessage(), + # HELPREPLY: lambda spec, data:None, # ignore this + ERRORREPLY: lambda spec, data: ErrorMessage(errorclass=spec[0], errorinfo=data), + EVENT: lambda spec, data: Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data) > 1 else {}), + } def __init__(self, *args, **kwds): MessageEncoder.__init__(self, *args, **kwds) - #self.tests() + # self.tests() def encode(self, msg): """msg object -> transport layer message""" @@ -136,20 +147,21 @@ class DemoEncoder(MessageEncoder): '%s ' to request a heartbeat response '%s' to activate async updates '%s' to deactivate updates - """ %(IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST, - WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST, - ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST) - return '\n'.join('%s %d %s' %(HELPREPLY, i+1, l.strip()) for i,l in enumerate(text.split('\n')[:-1])) + """ % (IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST, + WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST, + ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST) + return '\n'.join('%s %d %s' % (HELPREPLY, i + 1, l.strip()) + for i, l in enumerate(text.split('\n')[:-1])) for msgcls, parts in self.ENCODEMAP.items(): if isinstance(msg, msgcls): # resolve lambdas - parts = [parts[0]] + [p(msg) if callable(p) else getattr(msg, p) for p in parts[1:]] + parts = [parts[0]] + [p(msg) if callable(p) + else getattr(msg, p) for p in parts[1:]] if len(parts) > 1: parts[1] = str(parts[1]) if len(parts) == 3: parts[2] = json.dumps(parts[2]) return ' '.join(parts) - def decode(self, encoded): # first check beginning @@ -158,17 +170,17 @@ class DemoEncoder(MessageEncoder): print repr(encoded), repr(IDENTREPLY) if encoded == IDENTREPLY: # XXX:better just check the first 2 parts... return IdentifyReply(version_string=encoded) - + return HelpMessage() - return ErrorMessage(errorclass='SyntaxError', + return ErrorMessage(errorclass='SyntaxError', errorinfo='Regex did not match!', is_request=True) msgtype, msgspec, data = match.groups() if msgspec is None and data: - return ErrorMessage(errorclass='InternalError', + return ErrorMessage(errorclass='InternalError', errorinfo='Regex matched json, but not spec!', is_request=True) - + if msgtype in self.DECODEMAP: if msgspec and ':' in msgspec: msgspec = msgspec.split(':', 1) @@ -181,16 +193,28 @@ class DemoEncoder(MessageEncoder): return ErrorMessage(errorclass='BadValue', errorinfo=[repr(err), str(encoded)]) return self.DECODEMAP[msgtype](msgspec, data) - return ErrorMessage(errorclass='SyntaxError', - errorinfo='%r: No Such Messagetype defined!' % encoded, - is_request=True) - + return ErrorMessage( + errorclass='SyntaxError', + errorinfo='%r: No Such Messagetype defined!' % + encoded, + is_request=True) def tests(self): print "---- Testing encoding -----" for msgclass, parts in sorted(self.ENCODEMAP.items()): print msgclass - e=self.encode(msgclass(module='',parameter='',value=2.718,equipment_id='',description='descriptive data',command='',arguments='',nonce='',errorclass='InternalError',errorinfo='nix')) + e = self.encode( + msgclass( + module='', + parameter='', + value=2.718, + equipment_id='', + description='descriptive data', + command='', + arguments='', + nonce='', + errorclass='InternalError', + errorinfo='nix')) print e print self.decode(e) print @@ -200,9 +224,8 @@ class DemoEncoder(MessageEncoder): if msgtype == EVENT: msg = '%s a:b [3,{"t":193868}]' % msgtype print msg - d=self.decode(msg) + d = self.decode(msg) print d print self.encode(d) print print "---- Testing done -----" - diff --git a/secop/protocol/encoding/pickle.py b/secop/protocol/encoding/pickle.py index 152bbe0..1d87e1d 100644 --- a/secop/protocol/encoding/pickle.py +++ b/secop/protocol/encoding/pickle.py @@ -35,7 +35,6 @@ except ImportError: import pickle - class PickleEncoder(MessageEncoder): def encode(self, messageobj): diff --git a/secop/protocol/errors.py b/secop/protocol/errors.py index f0364f7..b2e9da5 100644 --- a/secop/protocol/errors.py +++ b/secop/protocol/errors.py @@ -24,9 +24,10 @@ class SECOPError(RuntimeError): + def __init__(self, *args, **kwds): self.args = args - for k,v in kwds.items(): + for k, v in kwds.items(): setattr(self, k, v) diff --git a/secop/protocol/framing/demo.py b/secop/protocol/framing/demo.py index 60ae7a1..1272245 100644 --- a/secop/protocol/framing/demo.py +++ b/secop/protocol/framing/demo.py @@ -80,5 +80,3 @@ class DemoFramer(Framer): def reset(self): self.data = b'' self.decoded = [] - - diff --git a/secop/protocol/framing/rle.py b/secop/protocol/framing/rle.py index e9c80a3..f68894e 100644 --- a/secop/protocol/framing/rle.py +++ b/secop/protocol/framing/rle.py @@ -70,5 +70,3 @@ class RLEFramer(Framer): def reset(self): self.data = b'' self.frames_to_go = 0 - - diff --git a/secop/protocol/interface/tcp.py b/secop/protocol/interface/tcp.py index cfbdacf..e9ccfbd 100644 --- a/secop/protocol/interface/tcp.py +++ b/secop/protocol/interface/tcp.py @@ -34,6 +34,7 @@ from secop.protocol.encoding import ENCODERS from secop.protocol.framing import FRAMERS from secop.protocol.messages import HelpMessage + class TCPRequestHandler(SocketServer.BaseRequestHandler): def setup(self): @@ -49,7 +50,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler): clientaddr = self.client_address serverobj = self.server self.log.debug("handling new connection from %s" % repr(clientaddr)) - + # notify dispatcher of us serverobj.dispatcher.add_connection(self) @@ -81,7 +82,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler): # dispatcher will queue the reply before returning frames = self.framing.decode(data) if frames is not None: - if not frames: # empty list + if not frames: # empty list self.queue_reply(HelpMessage(MSGTYPE=reply)) for frame in frames: reply = None @@ -129,7 +130,8 @@ class TCPServer(SocketServer.ThreadingTCPServer): self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')] self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum)) self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__) - self.log.debug("TCPServer using encoding=%s" % self.encodingCLS.__name__) + self.log.debug("TCPServer using encoding=%s" % + self.encodingCLS.__name__) SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum), TCPRequestHandler, bind_and_activate=True) diff --git a/secop/protocol/messages.py b/secop/protocol/messages.py index 83aac2b..ede948a 100644 --- a/secop/protocol/messages.py +++ b/secop/protocol/messages.py @@ -45,18 +45,19 @@ class Message(object): def as_dict(self): """returns set parameters as dict""" - return dict(map(lambda k:(k, getattr(self,k)),self.ARGS)) + return dict(map(lambda k: (k, getattr(self, k)), self.ARGS)) class Value(object): - def __init__(self, module, parameter=None, command=None, value=Ellipsis, **qualifiers): + def __init__(self, module, parameter=None, command=None, value=Ellipsis, + **qualifiers): self.module = module self.parameter = parameter self.command = command self.value = value self.qualifiers = qualifiers - self.msgtype = 'update' # 'changed' or 'done' + self.msgtype = 'update' # 'changed' or 'done' def __repr__(self): devspec = self.module @@ -65,79 +66,96 @@ class Value(object): elif self.command: devspec = '%s:%s()' % (devspec, self.command) return '%s:Value(%s)' % (devspec, ', '.join( - [repr(self.value)] + ['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()])) + [repr(self.value)] + + ['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()])) class IdentifyRequest(Message): is_request = True + class IdentifyReply(Message): is_reply = True version_string = None + class DescribeRequest(Message): is_request = True + class DescribeReply(Message): is_reply = True equipment_id = None description = None + class ActivateRequest(Message): is_request = True + class ActivateReply(Message): is_reply = True + class DeactivateRequest(Message): is_request = True + class DeactivateReply(Message): is_reply = True + class CommandRequest(Message): is_request = True command = '' arguments = [] + class CommandReply(Message): is_reply = True command = '' result = None + class WriteRequest(Message): is_request = True module = None parameter = None value = None + class WriteReply(Message): is_reply = True module = None parameter = None value = None + class PollRequest(Message): is_request = True module = None parameter = None + class HeartbeatRequest(Message): is_request = True nonce = 'alive' + class HeartbeatReply(Message): is_reply = True nonce = 'undefined' + class EventMessage(Message): -# use Value directly for Replies ! + # use Value directly for Replies ! is_reply = True module = None parameter = None command = None value = None # Value object ! (includes qualifiers!) + class ErrorMessage(Message): is_error = True errorclass = 'InternalError' @@ -149,7 +167,6 @@ class HelpMessage(Message): is_request = True - if __name__ == '__main__': print("Minimal testing of messages....") m = Message(MSGTYPE='test', a=1, b=2, c='x') diff --git a/secop/protocol/messages_old.py b/secop/protocol/messages_old.py index 3af3917..60e22b9 100644 --- a/secop/protocol/messages_old.py +++ b/secop/protocol/messages_old.py @@ -110,8 +110,10 @@ class Value(object): devspec = '%s:%s' % (devspec, self.param) if self.prop: devspec = '%s:%s' % (devspec, self.prop) - return '%s:Value(%s)' % (devspec, ', '.join( - [repr(self.value)] + ['%s=%r' % (k, v) for k, v in self.qualifiers.items()])) + return '%s:Value(%s)' % ( + devspec, ', '.join( + [repr(self.value)] + + ['%s=%r' % (k, v) for k, v in self.qualifiers.items()])) class ListMessage(Message): @@ -165,32 +167,59 @@ class HelpMessage(Message): class NoSuchDeviceError(ErrorMessage): + def __init__(self, *devs): - ErrorMessage.__init__(self, devs=devs, errorstring="Device %r does not exist" % devs[0], errortype='NoSuchDevice') + ErrorMessage.__init__( + self, devs=devs, errorstring="Device %r does not exist" % + devs[0], errortype='NoSuchDevice') class NoSuchParamError(ErrorMessage): + def __init__(self, dev, *params): - ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r has no parameter %r" % (dev, params[0]), errortype='NoSuchParam') + ErrorMessage.__init__( + self, devs=(dev,), + params=params, errorstring="Device %r has no parameter %r" % + (dev, params[0]), + errortype='NoSuchParam') class ParamReadonlyError(ErrorMessage): + def __init__(self, dev, *params): - ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r, parameter %r is not writeable!" % (dev, params[0]), errortype='ParamReadOnly') + ErrorMessage.__init__( + self, devs=(dev,), + params=params, + errorstring="Device %r, parameter %r is not writeable!" % + (dev, params[0]), + errortype='ParamReadOnly') class InvalidParamValueError(ErrorMessage): + def __init__(self, dev, param, value, e): - ErrorMessage.__init__(self, devs=(dev,), params=params, values=(value), errorstring=str(e), errortype='InvalidParamValueError') + ErrorMessage.__init__( + self, devs=(dev,), + params=params, values=(value), + errorstring=str(e), + errortype='InvalidParamValueError') class InternalError(ErrorMessage): + def __init__(self, err, **kwds): - ErrorMessage.__init__(self, errorstring=str(err), errortype='InternalError', **kwds) + ErrorMessage.__init__( + self, errorstring=str(err), + errortype='InternalError', **kwds) -MESSAGE = dict((cls.MSGTYPE, cls) for cls in [HelpMessage, ErrorMessage, EventMessage, TriggerMessage, UnsubscribeMessage, SubscribeMessage, - PollMessage, CommandMessage, WriteMessage, ReadMessage, ListMessage]) +MESSAGE = dict( + (cls.MSGTYPE, cls) + for cls + in + [HelpMessage, ErrorMessage, EventMessage, TriggerMessage, + UnsubscribeMessage, SubscribeMessage, PollMessage, CommandMessage, + WriteMessage, ReadMessage, ListMessage]) if __name__ == '__main__': print("Minimal testing of messages....") diff --git a/secop/server.py b/secop/server.py index a70c395..5663b48 100644 --- a/secop/server.py +++ b/secop/server.py @@ -85,7 +85,9 @@ class Server(object): time.sleep(1) for t in self._threads: if not t.is_alive(): - self.log.debug('thread %r died (%d still running)' % (t,len(self._threads))) + self.log.debug( + 'thread %r died (%d still running)' % + (t, len(self._threads))) t.join() self._threads.discard(t) @@ -132,7 +134,9 @@ class Server(object): if parser.has_option('equipment', 'id'): equipment_id = parser.get('equipment', 'id') - self._dispatcher = self._buildObject('Dispatcher', Dispatcher, dict(equipment_id=equipment_id)) + self._dispatcher = self._buildObject( + 'Dispatcher', Dispatcher, dict( + equipment_id=equipment_id)) self._processInterfaceOptions(interfaceopts) self._processDeviceOptions(deviceopts)