Pep8 improvements + cleanup
Change-Id: I9052e703b58e93b639c027521b47f693ae853f6e
This commit is contained in:
parent
7320ac1538
commit
78bb3b5f96
@ -35,11 +35,11 @@ log_path = path.join(basepath, 'log')
|
||||
# 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:
|
||||
|
@ -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()
|
||||
|
@ -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!
|
||||
|
@ -100,6 +100,7 @@ def determineServerStatus(name):
|
||||
else:
|
||||
print('%s: dead' % name)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
@ -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)
|
||||
@ -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()
|
||||
@ -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,21 +252,28 @@ 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",
|
||||
@ -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]
|
||||
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)
|
||||
|
||||
|
@ -102,6 +102,8 @@ class CMD(object):
|
||||
|
||||
# Meta class
|
||||
# warning: MAGIC!
|
||||
|
||||
|
||||
class DeviceMeta(type):
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
@ -183,7 +185,8 @@ 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__'),
|
||||
newtype.CMDS[name[2:]] = CMD(
|
||||
getattr(value, '__doc__'),
|
||||
argspec.args, None) # XXX: find resulttype!
|
||||
attrs['__constructed__'] = True
|
||||
return newtype
|
||||
@ -313,6 +316,7 @@ class Readable(Device):
|
||||
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 !
|
||||
|
@ -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):
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
class attrdict(dict):
|
||||
"""a normal dict, providing access also via attributes"""
|
||||
|
||||
|
@ -26,7 +26,12 @@ 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)
|
||||
@ -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'(?P<tzinfo>Z|[+-]\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:
|
||||
|
@ -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__
|
||||
@ -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)
|
||||
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,13 +311,15 @@ 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:
|
||||
if conn in self._active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
@ -303,7 +327,8 @@ class Dispatcher(object):
|
||||
# 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
|
||||
# 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:
|
||||
@ -313,7 +338,7 @@ class Dispatcher(object):
|
||||
res = self._setParamValue(msg.module, 'target', msg.value)
|
||||
res.parameter = 'target'
|
||||
# self.broadcast_event(res)
|
||||
if conn in self._dispatcher_active_connections:
|
||||
if conn in self._active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
@ -321,12 +346,13 @@ class Dispatcher(object):
|
||||
# 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
|
||||
# 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:
|
||||
# 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)
|
||||
@ -370,5 +396,3 @@ class Dispatcher(object):
|
||||
self.log.error('IGN: got unhandled request %s' % msg)
|
||||
return ErrorMessage(errorclass="InternalError",
|
||||
errorstring='Got Unhandled Request %r' % msg)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -34,16 +34,19 @@ import ast
|
||||
import re
|
||||
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!
|
||||
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:
|
||||
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' # +<id> +json
|
||||
ENABLEEVENTSREQUEST = 'activate' # literal
|
||||
@ -51,10 +54,14 @@ 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
|
||||
@ -65,7 +72,9 @@ 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
|
||||
# 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,6 +82,7 @@ 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 = {
|
||||
@ -99,7 +109,8 @@ class DemoEncoder(MessageEncoder):
|
||||
}
|
||||
DECODEMAP = {
|
||||
IDENTREQUEST: lambda spec, data: IdentifyRequest(),
|
||||
IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness
|
||||
# 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(),
|
||||
@ -139,18 +150,19 @@ class DemoEncoder(MessageEncoder):
|
||||
""" % (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]))
|
||||
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
|
||||
match = DEMO_RE.match(encoded)
|
||||
@ -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,
|
||||
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='<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 self.decode(e)
|
||||
print
|
||||
@ -205,4 +229,3 @@ class DemoEncoder(MessageEncoder):
|
||||
print self.encode(d)
|
||||
print
|
||||
print "---- Testing done -----"
|
||||
|
||||
|
@ -35,7 +35,6 @@ except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
|
||||
class PickleEncoder(MessageEncoder):
|
||||
|
||||
def encode(self, messageobj):
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
|
||||
class SECOPError(RuntimeError):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.args = args
|
||||
for k, v in kwds.items():
|
||||
|
@ -80,5 +80,3 @@ class DemoFramer(Framer):
|
||||
def reset(self):
|
||||
self.data = b''
|
||||
self.decoded = []
|
||||
|
||||
|
||||
|
@ -70,5 +70,3 @@ class RLEFramer(Framer):
|
||||
def reset(self):
|
||||
self.data = b''
|
||||
self.frames_to_go = 0
|
||||
|
||||
|
||||
|
@ -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):
|
||||
@ -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)
|
||||
|
@ -50,7 +50,8 @@ class Message(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.parameter = parameter
|
||||
self.command = command
|
||||
@ -65,71 +66,87 @@ 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 !
|
||||
is_reply = True
|
||||
@ -138,6 +155,7 @@ class EventMessage(Message):
|
||||
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')
|
||||
|
@ -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....")
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user