Pep8 improvements + cleanup

Change-Id: I9052e703b58e93b639c027521b47f693ae853f6e
This commit is contained in:
Enrico Faulhaber 2016-12-15 14:36:12 +01:00
parent 7320ac1538
commit 78bb3b5f96
20 changed files with 395 additions and 250 deletions

View File

@ -32,14 +32,14 @@ basepath = path.abspath(path.join(sys.path[0], '..'))
etc_path = path.join(basepath, 'etc') etc_path = path.join(basepath, 'etc')
pid_path = path.join(basepath, 'pid') pid_path = path.join(basepath, 'pid')
log_path = path.join(basepath, 'log') log_path = path.join(basepath, 'log')
#sys.path[0] = path.join(basepath, 'src') # sys.path[0] = path.join(basepath, 'src')
sys.path[0] = basepath sys.path[0] = basepath
# do not move above!
import loggers import loggers
from secop.client import ClientConsole from secop.client import ClientConsole
def parseArgv(argv): def parseArgv(argv):
parser = argparse.ArgumentParser(description="Connect to a SECoP server") parser = argparse.ArgumentParser(description="Connect to a SECoP server")
loggroup = parser.add_mutually_exclusive_group() 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') loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
loggers.initLogging('console', loglevel, log_path) loggers.initLogging('console', loglevel, log_path)
console = ClientConsole(args.name, basepath) console = ClientConsole(args.name, basepath)
try: try:

View File

@ -40,7 +40,6 @@ from secop import loggers
from secop.server import Server from secop.server import Server
def parseArgv(argv): def parseArgv(argv):
parser = argparse.ArgumentParser(description="Manage a SECoP server") parser = argparse.ArgumentParser(description="Manage a SECoP server")
loggroup = parser.add_mutually_exclusive_group() loggroup = parser.add_mutually_exclusive_group()

View File

@ -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'. 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! 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. 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?' * 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 * queries if SECoP protocol is supported and which version it is
Format is intentionally choosen to be compatible to SCPI (for this query only). 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! 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!

View File

@ -36,7 +36,7 @@ import glob
import signal import signal
CFG_DIR='/etc/secop' CFG_DIR = '/etc/secop'
PID_DIR = '/var/run' PID_DIR = '/var/run'
SRV_EXEC = 'secop-server' SRV_EXEC = 'secop-server'
@ -62,7 +62,7 @@ def parseArgv(argv):
def getServerNames(): def getServerNames():
return sorted([os.path.basename(entry)[:-4] 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): def getSrvPid(name):
@ -100,6 +100,7 @@ def determineServerStatus(name):
else: else:
print('%s: dead' % name) print('%s: dead' % name)
def main(argv=None): def main(argv=None):
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
@ -107,7 +108,7 @@ def main(argv=None):
args = parseArgv(argv[1:]) args = parseArgv(argv[1:])
actionMap = { actionMap = {
'start' : startServer, 'start': startServer,
'stop': stopServer, 'stop': stopServer,
'status': determineServerStatus, 'status': determineServerStatus,
} }

View File

@ -36,9 +36,9 @@ from secop.protocol.framing import FRAMERS
from secop.protocol.messages import * from secop.protocol.messages import *
class TCPConnection(object): class TCPConnection(object):
# disguise a TCP connection as serial one # disguise a TCP connection as serial one
def __init__(self, host, port): def __init__(self, host, port):
self._host = host self._host = host
self._port = int(port) self._port = int(port)
@ -73,15 +73,17 @@ class TCPConnection(object):
while '\n' in data: while '\n' in data:
line, data = data.split('\n', 1) line, data = data.split('\n', 1)
try: try:
self._readbuffer.put(line.strip('\r'), block=True, timeout=1) self._readbuffer.put(
line.strip('\r'), block=True, timeout=1)
except Queue.Full: except Queue.Full:
self.log.debug('rcv queue full! dropping line: %r' % line) self.log.debug(
'rcv queue full! dropping line: %r' % line)
finally: finally:
self._thread = None self._thread = None
def readline(self, block=False): def readline(self, block=False):
"""blocks until a full line was read and returns it""" """blocks until a full line was read and returns it"""
i = 10; i = 10
while i: while i:
try: try:
return self._readbuffer.get(block=True, timeout=1) return self._readbuffer.get(block=True, timeout=1)
@ -109,13 +111,13 @@ class Value(object):
u = None u = None
e = None e = None
fmtstr = '%s' fmtstr = '%s'
def __init__(self, value, qualifiers={}): def __init__(self, value, qualifiers={}):
self.value = value self.value = value
if 't' in qualifiers: if 't' in qualifiers:
self.t = parse_time(qualifiers.pop('t')) self.t = parse_time(qualifiers.pop('t'))
self.__dict__.update(qualifiers) self.__dict__.update(qualifiers)
def __repr__(self): def __repr__(self):
r = [] r = []
if self.t is not None: if self.t is not None:
@ -160,7 +162,8 @@ class Client(object):
self.single_shots = dict() self.single_shots = dict()
# mapping the modulename to a dict mapping the parameter names to their values # 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.cache = dict()
self._syncLock = threading.RLock() self._syncLock = threading.RLock()
@ -188,7 +191,7 @@ class Client(object):
self.secop_id = line self.secop_id = line
continue continue
msgtype, spec, data = self._decode_message(line) msgtype, spec, data = self._decode_message(line)
if msgtype in ('event','changed'): if msgtype in ('event', 'changed'):
# handle async stuff # handle async stuff
self._handle_event(spec, data) self._handle_event(spec, data)
if msgtype != 'event': if msgtype != 'event':
@ -202,7 +205,6 @@ class Client(object):
else: else:
self.log.error('ignoring unexpected reply %r' % line) self.log.error('ignoring unexpected reply %r' % line)
def _encode_message(self, requesttype, spec='', data=Ellipsis): def _encode_message(self, requesttype, spec='', data=Ellipsis):
"""encodes the given message to a string """encodes the given message to a string
""" """
@ -250,30 +252,37 @@ class Client(object):
try: try:
mkthread(func, data) mkthread(func, data)
except Exception as err: 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) run.add(func)
self.single_shots[spec].difference_update(run) self.single_shots[spec].difference_update(run)
def register_callback(self, module, parameter, cb): 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) self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
def unregister_callback(self, module, parameter, cb): def unregister_callback(self, module, parameter, cb):
self.log.debug('unregistering callback %r for %s:%s' % (cb, module, parameter)) self.log.debug(
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).discard(cb) '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): def communicate(self, msgtype, spec='', data=Ellipsis):
# maps each (sync) request to the corresponding reply # 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 = { REPLYMAP = {
"describe": "describing", "describe": "describing",
"do": "done", "do": "done",
"change": "changed", "change": "changed",
"activate": "active", "activate": "active",
"deactivate": "inactive", "deactivate": "inactive",
"*IDN?": "SECoP,", "*IDN?": "SECoP,",
"ping": "ping", "ping": "ping",
} }
if self.stopflag: if self.stopflag:
raise RuntimeError('alreading stopping!') raise RuntimeError('alreading stopping!')
if msgtype == 'poll': if msgtype == 'poll':
@ -282,20 +291,25 @@ class Client(object):
spec = spec + ':value' spec = spec + ':value'
event = threading.Event() event = threading.Event()
result = ['polled', spec] result = ['polled', spec]
self.single_shots.setdefault(spec, set()).add(lambda d: (result.append(d), event.set())) self.single_shots.setdefault(spec, set()).add(
self.connection.writeline(self._encode_message(msgtype, spec, data)) lambda d: (result.append(d), event.set()))
self.connection.writeline(
self._encode_message(
msgtype, spec, data))
if event.wait(10): if event.wait(10):
return tuple(result) return tuple(result)
raise RuntimeError("timeout upon waiting for reply!") raise RuntimeError("timeout upon waiting for reply!")
rply = REPLYMAP[msgtype] rply = REPLYMAP[msgtype]
if rply in self.expected_replies: 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() event = threading.Event()
self.expected_replies[rply] = [event] self.expected_replies[rply] = [event]
self.connection.writeline(self._encode_message(msgtype, spec, data)) self.connection.writeline(self._encode_message(msgtype, spec, data))
if event.wait(10): # wait 10s for reply if event.wait(10): # wait 10s for reply
result = rply, self.expected_replies[rply][1], self.expected_replies[rply][2] result = rply, self.expected_replies[rply][
1], self.expected_replies[rply][2]
del self.expected_replies[rply] del self.expected_replies[rply]
return result return result
del self.expected_replies[rply] del self.expected_replies[rply]
@ -316,9 +330,9 @@ class Client(object):
self._cache.getdefault(device, {})[param] = value self._cache.getdefault(device, {})[param] = value
# XXX: further notification-callbacks needed ??? # XXX: further notification-callbacks needed ???
def startup(self, async=False): 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 # always fill our cache
self.communicate('activate') self.communicate('activate')
# deactivate updates if not wanted # deactivate updates if not wanted
@ -343,8 +357,8 @@ class Client(object):
return self.describing_data['modules'][module]['commands'].keys() return self.describing_data['modules'][module]['commands'].keys()
def getProperties(self, module, parameter): 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): def syncCommunicate(self, msg):
return self.communicate(msg) return self.communicate(msg)

View File

@ -69,12 +69,12 @@ class PARAM(object):
def as_dict(self): def as_dict(self):
# used for serialisation only # used for serialisation only
return dict(description = self.description, return dict(description=self.description,
unit = self.unit, unit=self.unit,
readonly = self.readonly, readonly=self.readonly,
value = self.value, value=self.value,
timestamp = self.timestamp, timestamp=self.timestamp,
validator = repr(self.validator), validator=repr(self.validator),
) )
@ -95,13 +95,15 @@ class CMD(object):
def as_dict(self): def as_dict(self):
# used for serialisation only # used for serialisation only
return dict(description = self.description, return dict(description=self.description,
arguments = repr(self.arguments), arguments=repr(self.arguments),
resulttype = repr(self.resulttype), resulttype=repr(self.resulttype),
) )
# Meta class # Meta class
# warning: MAGIC! # warning: MAGIC!
class DeviceMeta(type): class DeviceMeta(type):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
@ -183,8 +185,9 @@ class DeviceMeta(type):
argspec = inspect.getargspec(value) argspec = inspect.getargspec(value)
if argspec[0] and argspec[0][0] == 'self': if argspec[0] and argspec[0][0] == 'self':
del argspec[0][0] del argspec[0][0]
newtype.CMDS[name[2:]] = CMD(getattr(value, '__doc__'), newtype.CMDS[name[2:]] = CMD(
argspec.args, None) # XXX: find resulttype! getattr(value, '__doc__'),
argspec.args, None) # XXX: find resulttype!
attrs['__constructed__'] = True attrs['__constructed__'] = True
return newtype return newtype
@ -279,7 +282,7 @@ class Readable(Device):
'baseclass': PARAM('protocol defined interface class', 'baseclass': PARAM('protocol defined interface class',
default="Readable", validator=str), default="Readable", validator=str),
'value': PARAM('current value of the device', readonly=True, default=0.), '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, 'status': PARAM('current status of the device', default=status.OK,
validator=enum(**{'idle': status.OK, validator=enum(**{'idle': status.OK,
'BUSY': status.BUSY, 'BUSY': status.BUSY,
@ -303,7 +306,7 @@ class Readable(Device):
self._pollthread = threading.Thread(target=self._pollThread) self._pollthread = threading.Thread(target=self._pollThread)
self._pollthread.daemon = True self._pollthread.daemon = True
self._pollthread.start() self._pollthread.start()
def _pollThread(self): def _pollThread(self):
while True: while True:
time.sleep(self.pollinterval) time.sleep(self.pollinterval)
@ -312,7 +315,8 @@ class Readable(Device):
rfunc = getattr(self, 'read_%s' % pname, None) rfunc = getattr(self, 'read_%s' % pname, None)
if rfunc: if rfunc:
rfunc() rfunc()
class Driveable(Readable): class Driveable(Readable):
"""Basic Driveable device """Basic Driveable device
@ -324,5 +328,6 @@ class Driveable(Readable):
'target': PARAM('target value of the device', default=0., 'target': PARAM('target value of the device', default=0.,
readonly=False), readonly=False),
} }
def doStop(self): def doStop(self):
time.sleep(1) # for testing ! time.sleep(1) # for testing !

View File

@ -37,10 +37,14 @@ class Switch(Driveable):
'value': PARAM('current state (on or off)', 'value': PARAM('current state (on or off)',
validator=enum(on=1, off=0), default=0), validator=enum(on=1, off=0), default=0),
'target': PARAM('wanted state (on or off)', 'target': PARAM('wanted state (on or off)',
validator=enum(on=1, off=0), default=0, validator=enum(on=1, off=0),
readonly=False), 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_on_time': PARAM('seconds to wait after activating the switch',
'switch_off_time': PARAM('how long to wait after switching the switch off', validator=floatrange(0, 60), unit='s', default=10, export=False), 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): def init(self):
@ -177,8 +181,8 @@ class SampleTemp(Driveable):
validator=float, default=10), validator=float, default=10),
'sensor': PARAM("Sensor number or calibration id", 'sensor': PARAM("Sensor number or calibration id",
validator=str, readonly=True), validator=str, readonly=True),
'ramp': PARAM('moving speed in K/min', 'ramp': PARAM('moving speed in K/min', validator=floatrange(0, 100),
validator=floatrange(0, 100), unit='K/min', default=0.1, readonly=False), unit='K/min', default=0.1, readonly=False),
} }
def init(self): def init(self):

View File

@ -24,6 +24,7 @@
import threading import threading
class attrdict(dict): class attrdict(dict):
"""a normal dict, providing access also via attributes""" """a normal dict, providing access also via attributes"""

View File

@ -26,12 +26,17 @@ import re
import time import time
from datetime import tzinfo, timedelta, datetime 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 # based on http://stackoverflow.com/a/39418771
class LocalTimezone(tzinfo): class LocalTimezone(tzinfo):
ZERO = timedelta(0) ZERO = timedelta(0)
STDOFFSET = timedelta(seconds = -time.timezone) STDOFFSET = timedelta(seconds=-time.timezone)
if time.daylight: if time.daylight:
DSTOFFSET = timedelta(seconds = -time.altzone) DSTOFFSET = timedelta(seconds=-time.altzone)
else: else:
DSTOFFSET = STDOFFSET DSTOFFSET = STDOFFSET
@ -62,6 +67,7 @@ class LocalTimezone(tzinfo):
LocalTimezone = LocalTimezone() LocalTimezone = LocalTimezone()
def format_time(timestamp=None): def format_time(timestamp=None):
# get time in UTC # get time in UTC
if timestamp is None: if timestamp is None:
@ -70,15 +76,22 @@ def format_time(timestamp=None):
d = datetime.fromtimestamp(timestamp, LocalTimezone) d = datetime.fromtimestamp(timestamp, LocalTimezone)
return d.isoformat("T") 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): class Timezone(tzinfo):
def __init__(self, offset, name='unknown timezone'): def __init__(self, offset, name='unknown timezone'):
self.offset = offset self.offset = offset
self.name = name self.name = name
def tzname(self, dt): def tzname(self, dt):
return self.name return self.name
def utcoffset(self, dt): def utcoffset(self, dt):
return self.offset return self.offset
def dst(self, dt): def dst(self, dt):
return timedelta(0) return timedelta(0)
datetime_re = re.compile( datetime_re = re.compile(
@ -88,6 +101,7 @@ datetime_re = re.compile(
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$' r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
) )
def _parse_isostring(isostring): def _parse_isostring(isostring):
"""Parses a string and return a datetime.datetime. """Parses a string and return a datetime.datetime.
This function supports time zone offsets. When the input contains one, 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 = {k: int(v) for k, v in kw.items() if v is not None}
kw['tzinfo'] = _tzinfo kw['tzinfo'] = _tzinfo
return datetime(**kw) 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): def parse_time(isostring):
try: try:

View File

@ -36,10 +36,6 @@ Interface to the modules:
- get_module(modulename) returns the requested module or None - get_module(modulename) returns the requested module or None
- remove_module(modulename_or_obj): removes the module (during shutdown) - 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 import time
@ -50,20 +46,21 @@ from errors import *
class Dispatcher(object): class Dispatcher(object):
def __init__(self, logger, options): def __init__(self, logger, options):
self.equipment_id = options.pop('equipment_id') self.equipment_id = options.pop('equipment_id')
self.log = logger self.log = logger
# map ALL modulename -> moduleobj # map ALL modulename -> moduleobj
self._dispatcher_modules = {} self._modules = {}
# list of EXPORTED modules # list of EXPORTED modules
self._dispatcher_export = [] self._export = []
# list all connections # list all connections
self._dispatcher_connections = [] self._connections = []
# active (i.e. broadcast-receiving) connections # active (i.e. broadcast-receiving) connections
self._dispatcher_active_connections = set() self._active_connections = set()
# map eventname -> list of subscribed connections # map eventname -> list of subscribed connections
self._dispatcher_subscriptions = {} self._subscriptions = {}
self._dispatcher_lock = threading.RLock() self._lock = threading.RLock()
def handle_request(self, conn, msg): def handle_request(self, conn, msg):
"""handles incoming request """handles incoming request
@ -72,7 +69,7 @@ class Dispatcher(object):
""" """
self.log.debug('Dispatcher: handling msg: %r' % msg) self.log.debug('Dispatcher: handling msg: %r' % msg)
# play thread safe ! # play thread safe !
with self._dispatcher_lock: with self._lock:
reply = None reply = None
# generate reply (coded and framed) # generate reply (coded and framed)
msgname = msg.__class__.__name__ msgname = msg.__class__.__name__
@ -90,7 +87,7 @@ class Dispatcher(object):
reply = ErrorMessage(errorclass=err.__class__.__name__, reply = ErrorMessage(errorclass=err.__class__.__name__,
errorinfo=[repr(err), str(msg)]) errorinfo=[repr(err), str(msg)])
except (ValueError, TypeError) as err: except (ValueError, TypeError) as err:
# self.log.exception(err) # self.log.exception(err)
reply = ErrorMessage(errorclass='BadValue', reply = ErrorMessage(errorclass='BadValue',
errorinfo=[repr(err), str(msg)]) errorinfo=[repr(err), str(msg)])
except Exception as err: except Exception as err:
@ -106,118 +103,123 @@ class Dispatcher(object):
def broadcast_event(self, msg, reallyall=False): def broadcast_event(self, msg, reallyall=False):
"""broadcasts a msg to all active connections""" """broadcasts a msg to all active connections"""
if reallyall: if reallyall:
listeners = self._dispatcher_connections listeners = self._connections
else: else:
if getattr(msg, 'command', None) is None: 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: else:
eventname = '%s:%s()' % (msg.module, msg.command) eventname = '%s:%s()' % (msg.module, msg.command)
listeners = self._dispatcher_subscriptions.get(eventname, []) listeners = self._subscriptions.get(eventname, [])
listeners += list(self._dispatcher_active_connections) listeners += list(self._active_connections)
for conn in listeners: for conn in listeners:
conn.queue_async_reply(msg) conn.queue_async_reply(msg)
def announce_update(self, moduleobj, pname, pobj): def announce_update(self, moduleobj, pname, pobj):
"""called by modules param setters to notify subscribers of new values """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) self.broadcast_event(msg)
def subscribe(self, conn, modulename, pname='value'): def subscribe(self, conn, modulename, pname='value'):
eventname = '%s:%s' % (modulename, pname) 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'): def unsubscribe(self, conn, modulename, pname='value'):
eventname = '%s:%s' % (modulename, pname) eventname = '%s:%s' % (modulename, pname)
if eventname in self._dispatcher_subscriptions: if eventname in self._subscriptions:
self._dispatcher_subscriptions.remove(conn) self._subscriptions.remove(conn)
def add_connection(self, conn): def add_connection(self, conn):
"""registers new connection""" """registers new connection"""
self._dispatcher_connections.append(conn) self._connections.append(conn)
def remove_connection(self, conn): def remove_connection(self, conn):
"""removes now longer functional connection""" """removes now longer functional connection"""
if conn in self._dispatcher_connections: if conn in self._connections:
self._dispatcher_connections.remove(conn) self._connections.remove(conn)
for _evt, conns in self._dispatcher_subscriptions.items(): for _evt, conns in self._subscriptions.items():
conns.discard(conn) conns.discard(conn)
def activate_connection(self, conn): def activate_connection(self, conn):
self._dispatcher_active_connections.add(conn) self._active_connections.add(conn)
def deactivate_connection(self, 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): def register_module(self, moduleobj, modulename, export=True):
self.log.debug('registering module %r as %s (export=%r)' % self.log.debug('registering module %r as %s (export=%r)' %
(moduleobj, modulename, export)) (moduleobj, modulename, export))
self._dispatcher_modules[modulename] = moduleobj self._modules[modulename] = moduleobj
if export: if export:
self._dispatcher_export.append(modulename) self._export.append(modulename)
def get_module(self, modulename): def get_module(self, modulename):
module = self._dispatcher_modules.get(modulename, modulename) module = self._modules.get(modulename, modulename)
return module return module
def remove_module(self, modulename_or_obj): def remove_module(self, modulename_or_obj):
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
modulename = moduleobj.name modulename = moduleobj.name
if modulename in self._dispatcher_export: if modulename in self._export:
self._dispatcher_export.remove(modulename) self._export.remove(modulename)
self._dispatcher_modules.pop(modulename) self._modules.pop(modulename)
# XXX: also clean _dispatcher_subscriptions # XXX: also clean _subscriptions
def list_module_names(self): def list_module_names(self):
# return a copy of our list # return a copy of our list
return self._dispatcher_export[:] return self._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
def list_module_params(self, modulename): def list_module_params(self, modulename):
self.log.debug('list_module_params(%r)' % modulename) self.log.debug('list_module_params(%r)' % modulename)
if modulename in self._dispatcher_export: if modulename in self._export:
# XXX: omit export=False params! # omit export=False params!
res = {} res = {}
for paramname, param in self.get_module(modulename).PARAMS.items(): for paramname, param in self.get_module(modulename).PARAMS.items():
if param.export == True: if param.export:
res[paramname] = param res[paramname] = param.as_dict()
self.log.debug('list params for module %s -> %r' % self.log.debug('list params for module %s -> %r' %
(modulename, res)) (modulename, res))
return res return res
self.log.debug('-> module is not to be exported!') self.log.debug('-> module is not to be exported!')
return {} 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): def _execute_command(self, modulename, command, arguments=None):
if arguments is None: if arguments is None:
arguments = [] arguments = []
@ -230,13 +232,22 @@ class Dispatcher(object):
if cmdspec is None: if cmdspec is None:
raise NoSuchCommandError(module=modulename, command=command) raise NoSuchCommandError(module=modulename, command=command)
if len(cmdspec.arguments) != len(arguments): if len(cmdspec.arguments) != len(arguments):
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 # now call func and wrap result as value
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
func = getattr(moduleobj, 'do'+command) func = getattr(moduleobj, 'do' + command)
res = func(*arguments) 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()) # res = Value(modulename, command=command, value=func(*arguments), t=time.time())
return res return res
@ -258,8 +269,16 @@ class Dispatcher(object):
else: else:
setattr(moduleobj, pname, value) setattr(moduleobj, pname, value)
if pobj.timestamp: if pobj.timestamp:
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, dict(t=pobj.timestamp)]) return WriteReply(
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, {}]) 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): def _getParamValue(self, modulename, pname):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
@ -276,10 +295,13 @@ class Dispatcher(object):
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
readfunc() readfunc()
if pobj.timestamp: 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) return Value(modulename, parameter=pname, value=pobj.value)
# now the (defined) handlers for the different requests # now the (defined) handlers for the different requests
def handle_Help(self, conn, msg): def handle_Help(self, conn, msg):
return HelpMessage() return HelpMessage()
@ -289,21 +311,24 @@ class Dispatcher(object):
def handle_Describe(self, conn, msg): def handle_Describe(self, conn, msg):
# XXX:collect descriptive data # 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): def handle_Poll(self, conn, msg):
# XXX: trigger polling and force sending event # XXX: trigger polling and force sending event
res = self._getParamValue(msg.module, msg.parameter or 'value') res = self._getParamValue(msg.module, msg.parameter or 'value')
#self.broadcast_event(res) # self.broadcast_event(res)
if conn in self._dispatcher_active_connections: if conn in self._active_connections:
return None # already send to myself return None # already send to myself
return res # send reply to inactive conns return res # send reply to inactive conns
def handle_Write(self, conn, msg): def handle_Write(self, conn, msg):
# notify all by sending WriteReply # notify all by sending WriteReply
#msg1 = WriteReply(**msg.as_dict()) #msg1 = WriteReply(**msg.as_dict())
#self.broadcast_event(msg1) # self.broadcast_event(msg1)
# try to actually write XXX: should this be done asyncron? we could just return the reply in that case # try to actually write XXX: should this be done asyncron? we could
# just return the reply in that case
if msg.parameter: if msg.parameter:
res = self._setParamValue(msg.module, msg.parameter, msg.value) res = self._setParamValue(msg.module, msg.parameter, msg.value)
else: else:
@ -312,21 +337,22 @@ class Dispatcher(object):
raise ReadonlyError(module=msg.module, parameter=None) raise ReadonlyError(module=msg.module, parameter=None)
res = self._setParamValue(msg.module, 'target', msg.value) res = self._setParamValue(msg.module, 'target', msg.value)
res.parameter = 'target' res.parameter = 'target'
#self.broadcast_event(res) # self.broadcast_event(res)
if conn in self._dispatcher_active_connections: if conn in self._active_connections:
return None # already send to myself return None # already send to myself
return res # send reply to inactive conns return res # send reply to inactive conns
def handle_Command(self, conn, msg): def handle_Command(self, conn, msg):
# notify all by sending CommandReply # notify all by sending CommandReply
#msg1 = CommandReply(**msg.as_dict()) #msg1 = CommandReply(**msg.as_dict())
#self.broadcast_event(msg1) # self.broadcast_event(msg1)
# XXX: should this be done asyncron? we could just return the reply in that case # XXX: should this be done asyncron? we could just return the reply in
# that case
# try to actually execute command # try to actually execute command
res = self._execute_command(msg.module, msg.command, msg.arguments) res = self._execute_command(msg.module, msg.command, msg.arguments)
#self.broadcast_event(res) # self.broadcast_event(res)
#if conn in self._dispatcher_active_connections: # if conn in self._active_connections:
# return None # already send to myself # return None # already send to myself
return res # send reply to inactive conns return res # send reply to inactive conns
@ -336,7 +362,7 @@ class Dispatcher(object):
def handle_Activate(self, conn, msg): def handle_Activate(self, conn, msg):
self.activate_connection(conn) self.activate_connection(conn)
# easy approach: poll all values... # 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(): for pname, pobj in moduleobj.PARAMS.items():
# WARNING: THIS READS ALL PARAMS FROM HW! # WARNING: THIS READS ALL PARAMS FROM HW!
# XXX: should we send the cached values instead? (pbj.value) # XXX: should we send the cached values instead? (pbj.value)
@ -349,7 +375,7 @@ class Dispatcher(object):
res = Value(module=modulename, parameter=pname, res = Value(module=modulename, parameter=pname,
value=pobj.value, t=pobj.timestamp, value=pobj.value, t=pobj.timestamp,
unit=pobj.unit) 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) self.broadcast_event(res)
conn.queue_async_reply(ActivateReply(**msg.as_dict())) conn.queue_async_reply(ActivateReply(**msg.as_dict()))
return None return None
@ -369,6 +395,4 @@ class Dispatcher(object):
""" """
self.log.error('IGN: got unhandled request %s' % msg) self.log.error('IGN: got unhandled request %s' % msg)
return ErrorMessage(errorclass="InternalError", return ErrorMessage(errorclass="InternalError",
errorstring = 'Got Unhandled Request %r' % msg) errorstring='Got Unhandled Request %r' % msg)

View File

@ -98,7 +98,7 @@ class DemoEncoder(MessageEncoder):
MessageEncoder.__init__(self, *args, **kwds) MessageEncoder.__init__(self, *args, **kwds)
self.result = [] # for decoding self.result = [] # for decoding
self.expect_lines = 1 self.expect_lines = 1
#self.tests() # self.tests()
def encode(self, msg): def encode(self, msg):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
@ -179,8 +179,13 @@ class DemoEncoder(MessageEncoder):
return '\n'.join(result) return '\n'.join(result)
if isinstance(msg, ErrorMessage): if isinstance(msg, ErrorMessage):
return ('%s %s' % (devspec(msg, 'error %s' % return (
msg.errortype), msg.errorstring)).strip() '%s %s' %
(devspec(
msg,
'error %s' %
msg.errortype),
msg.errorstring)).strip()
return 'Can not handle object %r!' % msg return 'Can not handle object %r!' % msg
@ -292,7 +297,11 @@ class DemoEncoder(MessageEncoder):
# construct messageobj # construct messageobj
if msgtype in MESSAGE: if msgtype in MESSAGE:
return MESSAGE[msgtype]( 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", return ErrorMessage(errortype="SyntaxError",
errorstring="Can't handle %r" % encoded) errorstring="Can't handle %r" % encoded)

View File

@ -34,38 +34,47 @@ import ast
import re import re
import json import json
# each message is like <messagetype> [ \space <messageargs> [ \space <json> ]] \lf # each message is like <messagetype> [ \space <messageargs> [ \space
# <json> ]] \lf
# note: the regex allow <> for spec for testing only! # note: the regex allow <> for spec for testing only!
DEMO_RE = re.compile( DEMO_RE = re.compile(
r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""", re.X) r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""",
re.X)
#""" #"""
# messagetypes: # messagetypes:
IDENTREQUEST = '*IDN?' # literal 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 DESCRIPTIONSREQUEST = 'describe' # literal
DESCRIPTIONREPLY = 'describing' # +<id> +json DESCRIPTIONREPLY = 'describing' # +<id> +json
ENABLEEVENTSREQUEST = 'activate' # literal ENABLEEVENTSREQUEST = 'activate' # literal
ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer
DISABLEEVENTSREQUEST = 'deactivate' # literal DISABLEEVENTSREQUEST = 'deactivate' # literal
DISABLEEVENTSREPLY = 'inactive' # literal DISABLEEVENTSREPLY = 'inactive' # literal
COMMANDREQUEST = 'do' # +module:command +json args (if needed) COMMANDREQUEST = 'do' # +module:command +json args (if needed)
COMMANDREPLY = 'done' # +module:command +json args (if needed) # send after the command finished ! # +module:command +json args (if needed) # send after the command finished !
WRITEREQUEST = 'change' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally! COMMANDREPLY = 'done'
WRITEREPLY = 'changed' # +module[:parameter] +json_value # send with the read back value # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
TRIGGERREQUEST = 'poll' # +module[:parameter] -> 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) EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = 'ping' # +nonce_without_space HEARTBEATREQUEST = 'ping' # +nonce_without_space
HEARTBEATREPLY = 'pong' # +nonce_without_space HEARTBEATREPLY = 'pong' # +nonce_without_space
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
HELPREQUEST = 'help' # literal HELPREQUEST = 'help' # literal
HELPREPLY = 'helping' # +line number +json_text HELPREPLY = 'helping' # +line number +json_text
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand', ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed', 'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
'IsBusy', 'IsError', 'SyntaxError', 'InternalError', 'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
'CommandRunning', 'Disabled',] 'CommandRunning', 'Disabled', ]
# note: above strings need to be unique in the sense, that none is/or starts with another # note: above strings need to be unique in the sense, that none is/or
# starts with another
def encode_value_data(vobj): def encode_value_data(vobj):
q = vobj.qualifiers.copy() q = vobj.qualifiers.copy()
@ -73,55 +82,57 @@ def encode_value_data(vobj):
q['t'] = format_time(q['t']) q['t'] = format_time(q['t'])
return vobj.value, q return vobj.value, q
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
# map of msg to msgtype string as defined above. # map of msg to msgtype string as defined above.
ENCODEMAP = { ENCODEMAP = {
IdentifyRequest : (IDENTREQUEST,), IdentifyRequest: (IDENTREQUEST,),
IdentifyReply : (IDENTREPLY,), IdentifyReply: (IDENTREPLY,),
DescribeRequest : (DESCRIPTIONSREQUEST,), DescribeRequest: (DESCRIPTIONSREQUEST,),
DescribeReply : (DESCRIPTIONREPLY, 'equipment_id', 'description',), DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',),
ActivateRequest : (ENABLEEVENTSREQUEST,), ActivateRequest: (ENABLEEVENTSREQUEST,),
ActivateReply : (ENABLEEVENTSREPLY,), ActivateReply: (ENABLEEVENTSREPLY,),
DeactivateRequest: (DISABLEEVENTSREQUEST,), DeactivateRequest: (DISABLEEVENTSREQUEST,),
DeactivateReply : (DISABLEEVENTSREPLY,), DeactivateReply: (DISABLEEVENTSREPLY,),
CommandRequest : (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',), CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
CommandReply : (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',), 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',), 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',), 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, ), PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
HeartbeatRequest : (HEARTBEATREQUEST, 'nonce',), HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',),
HeartbeatReply : (HEARTBEATREPLY, 'nonce',), HeartbeatReply: (HEARTBEATREPLY, 'nonce',),
HelpMessage: (HELPREQUEST, ), HelpMessage: (HELPREQUEST, ),
ErrorMessage : (ERRORREPLY, 'errorclass', 'errorinfo',), ErrorMessage: (ERRORREPLY, 'errorclass', 'errorinfo',),
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()')) Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()'))
if msg.parameter or msg.command else msg.module, if msg.parameter or msg.command else msg.module,
encode_value_data,), encode_value_data,),
} }
DECODEMAP = { DECODEMAP = {
IDENTREQUEST : lambda spec, data: IdentifyRequest(), IDENTREQUEST: lambda spec, data: IdentifyRequest(),
IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness # handled specially, listed here for completeness
DESCRIPTIONSREQUEST : lambda spec, data: DescribeRequest(), IDENTREPLY: lambda spec, data: IdentifyReply(encoded),
DESCRIPTIONREPLY : lambda spec, data: DescribeReply(equipment_id=spec[0], description=data), DESCRIPTIONSREQUEST: lambda spec, data: DescribeRequest(),
ENABLEEVENTSREQUEST : lambda spec, data: ActivateRequest(), DESCRIPTIONREPLY: lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
ENABLEEVENTSREPLY: lambda spec, data:ActivateReply(), ENABLEEVENTSREQUEST: lambda spec, data: ActivateRequest(),
DISABLEEVENTSREQUEST: lambda spec, data:DeactivateRequest(), ENABLEEVENTSREPLY: lambda spec, data: ActivateReply(),
DISABLEEVENTSREPLY: lambda spec, data:DeactivateReply(), DISABLEEVENTSREQUEST: lambda spec, data: DeactivateRequest(),
COMMANDREQUEST: lambda spec, data:CommandRequest(module=spec[0], command=spec[1], arguments=data), 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), 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), 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), WRITEREPLY: lambda spec, data: WriteReply(module=spec[0], parameter=spec[1], value=data),
TRIGGERREQUEST:lambda spec, data:PollRequest(module=spec[0], parameter=spec[1]), TRIGGERREQUEST: lambda spec, data: PollRequest(module=spec[0], parameter=spec[1]),
HEARTBEATREQUEST:lambda spec, data:HeartbeatRequest(nonce=spec[0]), HEARTBEATREQUEST: lambda spec, data: HeartbeatRequest(nonce=spec[0]),
HEARTBEATREPLY:lambda spec, data:HeartbeatReply(nonce=spec[0]), HEARTBEATREPLY: lambda spec, data: HeartbeatReply(nonce=spec[0]),
HELPREQUEST: lambda spec, data:HelpMessage(), HELPREQUEST: lambda spec, data: HelpMessage(),
# HELPREPLY: lambda spec, data:None, # ignore this # HELPREPLY: lambda spec, data:None, # ignore this
ERRORREPLY:lambda spec, data:ErrorMessage(errorclass=spec[0], errorinfo=data), 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 {}), 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): def __init__(self, *args, **kwds):
MessageEncoder.__init__(self, *args, **kwds) MessageEncoder.__init__(self, *args, **kwds)
#self.tests() # self.tests()
def encode(self, msg): def encode(self, msg):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
@ -136,20 +147,21 @@ class DemoEncoder(MessageEncoder):
'%s <nonce>' to request a heartbeat response '%s <nonce>' to request a heartbeat response
'%s' to activate async updates '%s' to activate async updates
'%s' to deactivate updates '%s' to deactivate updates
""" %(IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST, """ % (IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST, WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST) ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
return '\n'.join('%s %d %s' %(HELPREPLY, i+1, l.strip()) for i,l in enumerate(text.split('\n')[:-1])) 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(): for msgcls, parts in self.ENCODEMAP.items():
if isinstance(msg, msgcls): if isinstance(msg, msgcls):
# resolve lambdas # 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: if len(parts) > 1:
parts[1] = str(parts[1]) parts[1] = str(parts[1])
if len(parts) == 3: if len(parts) == 3:
parts[2] = json.dumps(parts[2]) parts[2] = json.dumps(parts[2])
return ' '.join(parts) return ' '.join(parts)
def decode(self, encoded): def decode(self, encoded):
# first check beginning # first check beginning
@ -158,17 +170,17 @@ class DemoEncoder(MessageEncoder):
print repr(encoded), repr(IDENTREPLY) print repr(encoded), repr(IDENTREPLY)
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts... if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
return IdentifyReply(version_string=encoded) return IdentifyReply(version_string=encoded)
return HelpMessage() return HelpMessage()
return ErrorMessage(errorclass='SyntaxError', return ErrorMessage(errorclass='SyntaxError',
errorinfo='Regex did not match!', errorinfo='Regex did not match!',
is_request=True) is_request=True)
msgtype, msgspec, data = match.groups() msgtype, msgspec, data = match.groups()
if msgspec is None and data: if msgspec is None and data:
return ErrorMessage(errorclass='InternalError', return ErrorMessage(errorclass='InternalError',
errorinfo='Regex matched json, but not spec!', errorinfo='Regex matched json, but not spec!',
is_request=True) is_request=True)
if msgtype in self.DECODEMAP: if msgtype in self.DECODEMAP:
if msgspec and ':' in msgspec: if msgspec and ':' in msgspec:
msgspec = msgspec.split(':', 1) msgspec = msgspec.split(':', 1)
@ -181,16 +193,28 @@ class DemoEncoder(MessageEncoder):
return ErrorMessage(errorclass='BadValue', return ErrorMessage(errorclass='BadValue',
errorinfo=[repr(err), str(encoded)]) errorinfo=[repr(err), str(encoded)])
return self.DECODEMAP[msgtype](msgspec, data) return self.DECODEMAP[msgtype](msgspec, data)
return ErrorMessage(errorclass='SyntaxError', return ErrorMessage(
errorinfo='%r: No Such Messagetype defined!' % encoded, errorclass='SyntaxError',
is_request=True) errorinfo='%r: No Such Messagetype defined!' %
encoded,
is_request=True)
def tests(self): def tests(self):
print "---- Testing encoding -----" print "---- Testing encoding -----"
for msgclass, parts in sorted(self.ENCODEMAP.items()): for msgclass, parts in sorted(self.ENCODEMAP.items()):
print msgclass print msgclass
e=self.encode(msgclass(module='<module>',parameter='<paramname>',value=2.718,equipment_id='<id>',description='descriptive data',command='<cmd>',arguments='<arguments>',nonce='<nonce>',errorclass='InternalError',errorinfo='nix')) e = self.encode(
msgclass(
module='<module>',
parameter='<paramname>',
value=2.718,
equipment_id='<id>',
description='descriptive data',
command='<cmd>',
arguments='<arguments>',
nonce='<nonce>',
errorclass='InternalError',
errorinfo='nix'))
print e print e
print self.decode(e) print self.decode(e)
print print
@ -200,9 +224,8 @@ class DemoEncoder(MessageEncoder):
if msgtype == EVENT: if msgtype == EVENT:
msg = '%s a:b [3,{"t":193868}]' % msgtype msg = '%s a:b [3,{"t":193868}]' % msgtype
print msg print msg
d=self.decode(msg) d = self.decode(msg)
print d print d
print self.encode(d) print self.encode(d)
print print
print "---- Testing done -----" print "---- Testing done -----"

View File

@ -35,7 +35,6 @@ except ImportError:
import pickle import pickle
class PickleEncoder(MessageEncoder): class PickleEncoder(MessageEncoder):
def encode(self, messageobj): def encode(self, messageobj):

View File

@ -24,9 +24,10 @@
class SECOPError(RuntimeError): class SECOPError(RuntimeError):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
self.args = args self.args = args
for k,v in kwds.items(): for k, v in kwds.items():
setattr(self, k, v) setattr(self, k, v)

View File

@ -80,5 +80,3 @@ class DemoFramer(Framer):
def reset(self): def reset(self):
self.data = b'' self.data = b''
self.decoded = [] self.decoded = []

View File

@ -70,5 +70,3 @@ class RLEFramer(Framer):
def reset(self): def reset(self):
self.data = b'' self.data = b''
self.frames_to_go = 0 self.frames_to_go = 0

View File

@ -34,6 +34,7 @@ from secop.protocol.encoding import ENCODERS
from secop.protocol.framing import FRAMERS from secop.protocol.framing import FRAMERS
from secop.protocol.messages import HelpMessage from secop.protocol.messages import HelpMessage
class TCPRequestHandler(SocketServer.BaseRequestHandler): class TCPRequestHandler(SocketServer.BaseRequestHandler):
def setup(self): def setup(self):
@ -49,7 +50,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
clientaddr = self.client_address clientaddr = self.client_address
serverobj = self.server serverobj = self.server
self.log.debug("handling new connection from %s" % repr(clientaddr)) self.log.debug("handling new connection from %s" % repr(clientaddr))
# notify dispatcher of us # notify dispatcher of us
serverobj.dispatcher.add_connection(self) serverobj.dispatcher.add_connection(self)
@ -81,7 +82,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
# dispatcher will queue the reply before returning # dispatcher will queue the reply before returning
frames = self.framing.decode(data) frames = self.framing.decode(data)
if frames is not None: if frames is not None:
if not frames: # empty list if not frames: # empty list
self.queue_reply(HelpMessage(MSGTYPE=reply)) self.queue_reply(HelpMessage(MSGTYPE=reply))
for frame in frames: for frame in frames:
reply = None reply = None
@ -129,7 +130,8 @@ class TCPServer(SocketServer.ThreadingTCPServer):
self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')] self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum)) 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 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), SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
TCPRequestHandler, TCPRequestHandler,
bind_and_activate=True) bind_and_activate=True)

View File

@ -45,18 +45,19 @@ class Message(object):
def as_dict(self): def as_dict(self):
"""returns set parameters as dict""" """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): 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.module = module
self.parameter = parameter self.parameter = parameter
self.command = command self.command = command
self.value = value self.value = value
self.qualifiers = qualifiers self.qualifiers = qualifiers
self.msgtype = 'update' # 'changed' or 'done' self.msgtype = 'update' # 'changed' or 'done'
def __repr__(self): def __repr__(self):
devspec = self.module devspec = self.module
@ -65,79 +66,96 @@ class Value(object):
elif self.command: elif self.command:
devspec = '%s:%s()' % (devspec, self.command) devspec = '%s:%s()' % (devspec, self.command)
return '%s:Value(%s)' % (devspec, ', '.join( 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): class IdentifyRequest(Message):
is_request = True is_request = True
class IdentifyReply(Message): class IdentifyReply(Message):
is_reply = True is_reply = True
version_string = None version_string = None
class DescribeRequest(Message): class DescribeRequest(Message):
is_request = True is_request = True
class DescribeReply(Message): class DescribeReply(Message):
is_reply = True is_reply = True
equipment_id = None equipment_id = None
description = None description = None
class ActivateRequest(Message): class ActivateRequest(Message):
is_request = True is_request = True
class ActivateReply(Message): class ActivateReply(Message):
is_reply = True is_reply = True
class DeactivateRequest(Message): class DeactivateRequest(Message):
is_request = True is_request = True
class DeactivateReply(Message): class DeactivateReply(Message):
is_reply = True is_reply = True
class CommandRequest(Message): class CommandRequest(Message):
is_request = True is_request = True
command = '' command = ''
arguments = [] arguments = []
class CommandReply(Message): class CommandReply(Message):
is_reply = True is_reply = True
command = '' command = ''
result = None result = None
class WriteRequest(Message): class WriteRequest(Message):
is_request = True is_request = True
module = None module = None
parameter = None parameter = None
value = None value = None
class WriteReply(Message): class WriteReply(Message):
is_reply = True is_reply = True
module = None module = None
parameter = None parameter = None
value = None value = None
class PollRequest(Message): class PollRequest(Message):
is_request = True is_request = True
module = None module = None
parameter = None parameter = None
class HeartbeatRequest(Message): class HeartbeatRequest(Message):
is_request = True is_request = True
nonce = 'alive' nonce = 'alive'
class HeartbeatReply(Message): class HeartbeatReply(Message):
is_reply = True is_reply = True
nonce = 'undefined' nonce = 'undefined'
class EventMessage(Message): class EventMessage(Message):
# use Value directly for Replies ! # use Value directly for Replies !
is_reply = True is_reply = True
module = None module = None
parameter = None parameter = None
command = None command = None
value = None # Value object ! (includes qualifiers!) value = None # Value object ! (includes qualifiers!)
class ErrorMessage(Message): class ErrorMessage(Message):
is_error = True is_error = True
errorclass = 'InternalError' errorclass = 'InternalError'
@ -149,7 +167,6 @@ class HelpMessage(Message):
is_request = True is_request = True
if __name__ == '__main__': if __name__ == '__main__':
print("Minimal testing of messages....") print("Minimal testing of messages....")
m = Message(MSGTYPE='test', a=1, b=2, c='x') m = Message(MSGTYPE='test', a=1, b=2, c='x')

View File

@ -110,8 +110,10 @@ class Value(object):
devspec = '%s:%s' % (devspec, self.param) devspec = '%s:%s' % (devspec, self.param)
if self.prop: if self.prop:
devspec = '%s:%s' % (devspec, self.prop) devspec = '%s:%s' % (devspec, self.prop)
return '%s:Value(%s)' % (devspec, ', '.join( return '%s:Value(%s)' % (
[repr(self.value)] + ['%s=%r' % (k, v) for k, v in self.qualifiers.items()])) devspec, ', '.join(
[repr(self.value)] +
['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
class ListMessage(Message): class ListMessage(Message):
@ -165,32 +167,59 @@ class HelpMessage(Message):
class NoSuchDeviceError(ErrorMessage): class NoSuchDeviceError(ErrorMessage):
def __init__(self, *devs): 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): class NoSuchParamError(ErrorMessage):
def __init__(self, dev, *params): 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): class ParamReadonlyError(ErrorMessage):
def __init__(self, dev, *params): 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): class InvalidParamValueError(ErrorMessage):
def __init__(self, dev, param, value, e): 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): class InternalError(ErrorMessage):
def __init__(self, err, **kwds): 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, MESSAGE = dict(
PollMessage, CommandMessage, WriteMessage, ReadMessage, ListMessage]) (cls.MSGTYPE, cls)
for cls
in
[HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
UnsubscribeMessage, SubscribeMessage, PollMessage, CommandMessage,
WriteMessage, ReadMessage, ListMessage])
if __name__ == '__main__': if __name__ == '__main__':
print("Minimal testing of messages....") print("Minimal testing of messages....")

View File

@ -85,7 +85,9 @@ class Server(object):
time.sleep(1) time.sleep(1)
for t in self._threads: for t in self._threads:
if not t.is_alive(): 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() t.join()
self._threads.discard(t) self._threads.discard(t)
@ -132,7 +134,9 @@ class Server(object):
if parser.has_option('equipment', 'id'): if parser.has_option('equipment', 'id'):
equipment_id = parser.get('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._processInterfaceOptions(interfaceopts)
self._processDeviceOptions(deviceopts) self._processDeviceOptions(deviceopts)