Pep8 improvements + cleanup
Change-Id: I9052e703b58e93b639c027521b47f693ae853f6e
This commit is contained in:
@@ -36,9 +36,9 @@ from secop.protocol.framing import FRAMERS
|
||||
from secop.protocol.messages import *
|
||||
|
||||
|
||||
|
||||
class TCPConnection(object):
|
||||
# disguise a TCP connection as serial one
|
||||
|
||||
def __init__(self, host, port):
|
||||
self._host = host
|
||||
self._port = int(port)
|
||||
@@ -73,15 +73,17 @@ class TCPConnection(object):
|
||||
while '\n' in data:
|
||||
line, data = data.split('\n', 1)
|
||||
try:
|
||||
self._readbuffer.put(line.strip('\r'), block=True, timeout=1)
|
||||
self._readbuffer.put(
|
||||
line.strip('\r'), block=True, timeout=1)
|
||||
except Queue.Full:
|
||||
self.log.debug('rcv queue full! dropping line: %r' % line)
|
||||
self.log.debug(
|
||||
'rcv queue full! dropping line: %r' % line)
|
||||
finally:
|
||||
self._thread = None
|
||||
|
||||
def readline(self, block=False):
|
||||
"""blocks until a full line was read and returns it"""
|
||||
i = 10;
|
||||
i = 10
|
||||
while i:
|
||||
try:
|
||||
return self._readbuffer.get(block=True, timeout=1)
|
||||
@@ -109,13 +111,13 @@ class Value(object):
|
||||
u = None
|
||||
e = None
|
||||
fmtstr = '%s'
|
||||
|
||||
|
||||
def __init__(self, value, qualifiers={}):
|
||||
self.value = value
|
||||
if 't' in qualifiers:
|
||||
self.t = parse_time(qualifiers.pop('t'))
|
||||
self.__dict__.update(qualifiers)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
r = []
|
||||
if self.t is not None:
|
||||
@@ -160,7 +162,8 @@ class Client(object):
|
||||
self.single_shots = dict()
|
||||
|
||||
# mapping the modulename to a dict mapping the parameter names to their values
|
||||
# note: the module value is stored as the value of the parameter value of the module
|
||||
# note: the module value is stored as the value of the parameter value
|
||||
# of the module
|
||||
self.cache = dict()
|
||||
|
||||
self._syncLock = threading.RLock()
|
||||
@@ -188,7 +191,7 @@ class Client(object):
|
||||
self.secop_id = line
|
||||
continue
|
||||
msgtype, spec, data = self._decode_message(line)
|
||||
if msgtype in ('event','changed'):
|
||||
if msgtype in ('event', 'changed'):
|
||||
# handle async stuff
|
||||
self._handle_event(spec, data)
|
||||
if msgtype != 'event':
|
||||
@@ -202,7 +205,6 @@ class Client(object):
|
||||
else:
|
||||
self.log.error('ignoring unexpected reply %r' % line)
|
||||
|
||||
|
||||
def _encode_message(self, requesttype, spec='', data=Ellipsis):
|
||||
"""encodes the given message to a string
|
||||
"""
|
||||
@@ -250,30 +252,37 @@ class Client(object):
|
||||
try:
|
||||
mkthread(func, data)
|
||||
except Exception as err:
|
||||
self.log.exception('Exception in Single-shot Callback!', err)
|
||||
self.log.exception(
|
||||
'Exception in Single-shot Callback!', err)
|
||||
run.add(func)
|
||||
self.single_shots[spec].difference_update(run)
|
||||
|
||||
def register_callback(self, module, parameter, cb):
|
||||
self.log.debug('registering callback %r for %s:%s' % (cb, module, parameter))
|
||||
self.log.debug(
|
||||
'registering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
|
||||
|
||||
def unregister_callback(self, module, parameter, cb):
|
||||
self.log.debug('unregistering callback %r for %s:%s' % (cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).discard(cb)
|
||||
self.log.debug(
|
||||
'unregistering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' %
|
||||
(module, parameter), set()).discard(cb)
|
||||
|
||||
def communicate(self, msgtype, spec='', data=Ellipsis):
|
||||
# maps each (sync) request to the corresponding reply
|
||||
# XXX: should go to the encoder! and be imported here (or make a translating method)
|
||||
# XXX: should go to the encoder! and be imported here (or make a
|
||||
# translating method)
|
||||
REPLYMAP = {
|
||||
"describe": "describing",
|
||||
"do": "done",
|
||||
"change": "changed",
|
||||
"activate": "active",
|
||||
"describe": "describing",
|
||||
"do": "done",
|
||||
"change": "changed",
|
||||
"activate": "active",
|
||||
"deactivate": "inactive",
|
||||
"*IDN?": "SECoP,",
|
||||
"ping": "ping",
|
||||
}
|
||||
"*IDN?": "SECoP,",
|
||||
"ping": "ping",
|
||||
}
|
||||
if self.stopflag:
|
||||
raise RuntimeError('alreading stopping!')
|
||||
if msgtype == 'poll':
|
||||
@@ -282,20 +291,25 @@ class Client(object):
|
||||
spec = spec + ':value'
|
||||
event = threading.Event()
|
||||
result = ['polled', spec]
|
||||
self.single_shots.setdefault(spec, set()).add(lambda d: (result.append(d), event.set()))
|
||||
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
||||
self.single_shots.setdefault(spec, set()).add(
|
||||
lambda d: (result.append(d), event.set()))
|
||||
self.connection.writeline(
|
||||
self._encode_message(
|
||||
msgtype, spec, data))
|
||||
if event.wait(10):
|
||||
return tuple(result)
|
||||
raise RuntimeError("timeout upon waiting for reply!")
|
||||
|
||||
rply = REPLYMAP[msgtype]
|
||||
if rply in self.expected_replies:
|
||||
raise RuntimeError("can not have more than one requests of the same type at the same time!")
|
||||
raise RuntimeError(
|
||||
"can not have more than one requests of the same type at the same time!")
|
||||
event = threading.Event()
|
||||
self.expected_replies[rply] = [event]
|
||||
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
||||
if event.wait(10): # wait 10s for reply
|
||||
result = rply, self.expected_replies[rply][1], self.expected_replies[rply][2]
|
||||
if event.wait(10): # wait 10s for reply
|
||||
result = rply, self.expected_replies[rply][
|
||||
1], self.expected_replies[rply][2]
|
||||
del self.expected_replies[rply]
|
||||
return result
|
||||
del self.expected_replies[rply]
|
||||
@@ -316,9 +330,9 @@ class Client(object):
|
||||
self._cache.getdefault(device, {})[param] = value
|
||||
# XXX: further notification-callbacks needed ???
|
||||
|
||||
|
||||
def startup(self, async=False):
|
||||
_, self.equipment_id, self.describing_data = self.communicate('describe')
|
||||
_, self.equipment_id, self.describing_data = self.communicate(
|
||||
'describe')
|
||||
# always fill our cache
|
||||
self.communicate('activate')
|
||||
# deactivate updates if not wanted
|
||||
@@ -343,8 +357,8 @@ class Client(object):
|
||||
return self.describing_data['modules'][module]['commands'].keys()
|
||||
|
||||
def getProperties(self, module, parameter):
|
||||
return self.describing_data['modules'][module]['parameters'][parameter].items()
|
||||
return self.describing_data['modules'][
|
||||
module]['parameters'][parameter].items()
|
||||
|
||||
def syncCommunicate(self, msg):
|
||||
return self.communicate(msg)
|
||||
|
||||
|
||||
@@ -69,12 +69,12 @@ class PARAM(object):
|
||||
|
||||
def as_dict(self):
|
||||
# used for serialisation only
|
||||
return dict(description = self.description,
|
||||
unit = self.unit,
|
||||
readonly = self.readonly,
|
||||
value = self.value,
|
||||
timestamp = self.timestamp,
|
||||
validator = repr(self.validator),
|
||||
return dict(description=self.description,
|
||||
unit=self.unit,
|
||||
readonly=self.readonly,
|
||||
value=self.value,
|
||||
timestamp=self.timestamp,
|
||||
validator=repr(self.validator),
|
||||
)
|
||||
|
||||
|
||||
@@ -95,13 +95,15 @@ class CMD(object):
|
||||
|
||||
def as_dict(self):
|
||||
# used for serialisation only
|
||||
return dict(description = self.description,
|
||||
arguments = repr(self.arguments),
|
||||
resulttype = repr(self.resulttype),
|
||||
return dict(description=self.description,
|
||||
arguments=repr(self.arguments),
|
||||
resulttype=repr(self.resulttype),
|
||||
)
|
||||
|
||||
# Meta class
|
||||
# warning: MAGIC!
|
||||
|
||||
|
||||
class DeviceMeta(type):
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
@@ -183,8 +185,9 @@ class DeviceMeta(type):
|
||||
argspec = inspect.getargspec(value)
|
||||
if argspec[0] and argspec[0][0] == 'self':
|
||||
del argspec[0][0]
|
||||
newtype.CMDS[name[2:]] = CMD(getattr(value, '__doc__'),
|
||||
argspec.args, None) # XXX: find resulttype!
|
||||
newtype.CMDS[name[2:]] = CMD(
|
||||
getattr(value, '__doc__'),
|
||||
argspec.args, None) # XXX: find resulttype!
|
||||
attrs['__constructed__'] = True
|
||||
return newtype
|
||||
|
||||
@@ -279,7 +282,7 @@ class Readable(Device):
|
||||
'baseclass': PARAM('protocol defined interface class',
|
||||
default="Readable", validator=str),
|
||||
'value': PARAM('current value of the device', readonly=True, default=0.),
|
||||
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1,120),),
|
||||
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),),
|
||||
'status': PARAM('current status of the device', default=status.OK,
|
||||
validator=enum(**{'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
@@ -303,7 +306,7 @@ class Readable(Device):
|
||||
self._pollthread = threading.Thread(target=self._pollThread)
|
||||
self._pollthread.daemon = True
|
||||
self._pollthread.start()
|
||||
|
||||
|
||||
def _pollThread(self):
|
||||
while True:
|
||||
time.sleep(self.pollinterval)
|
||||
@@ -312,7 +315,8 @@ class Readable(Device):
|
||||
rfunc = getattr(self, 'read_%s' % pname, None)
|
||||
if rfunc:
|
||||
rfunc()
|
||||
|
||||
|
||||
|
||||
class Driveable(Readable):
|
||||
"""Basic Driveable device
|
||||
|
||||
@@ -324,5 +328,6 @@ class Driveable(Readable):
|
||||
'target': PARAM('target value of the device', default=0.,
|
||||
readonly=False),
|
||||
}
|
||||
|
||||
def doStop(self):
|
||||
time.sleep(1) # for testing !
|
||||
|
||||
@@ -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,12 +26,17 @@ import re
|
||||
import time
|
||||
from datetime import tzinfo, timedelta, datetime
|
||||
|
||||
# format_time and parse_time could be simplified with external dateutil lib
|
||||
# http://stackoverflow.com/a/15228038
|
||||
|
||||
# based on http://stackoverflow.com/a/39418771
|
||||
|
||||
|
||||
class LocalTimezone(tzinfo):
|
||||
ZERO = timedelta(0)
|
||||
STDOFFSET = timedelta(seconds = -time.timezone)
|
||||
STDOFFSET = timedelta(seconds=-time.timezone)
|
||||
if time.daylight:
|
||||
DSTOFFSET = timedelta(seconds = -time.altzone)
|
||||
DSTOFFSET = timedelta(seconds=-time.altzone)
|
||||
else:
|
||||
DSTOFFSET = STDOFFSET
|
||||
|
||||
@@ -62,6 +67,7 @@ class LocalTimezone(tzinfo):
|
||||
|
||||
LocalTimezone = LocalTimezone()
|
||||
|
||||
|
||||
def format_time(timestamp=None):
|
||||
# get time in UTC
|
||||
if timestamp is None:
|
||||
@@ -70,15 +76,22 @@ def format_time(timestamp=None):
|
||||
d = datetime.fromtimestamp(timestamp, LocalTimezone)
|
||||
return d.isoformat("T")
|
||||
|
||||
# Solution based on
|
||||
# https://bugs.python.org/review/15873/diff/16581/Lib/datetime.py#newcode1418Lib/datetime.py:1418
|
||||
|
||||
|
||||
class Timezone(tzinfo):
|
||||
|
||||
def __init__(self, offset, name='unknown timezone'):
|
||||
self.offset = offset
|
||||
self.name = name
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.name
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self.offset
|
||||
|
||||
def dst(self, dt):
|
||||
return timedelta(0)
|
||||
datetime_re = re.compile(
|
||||
@@ -88,6 +101,7 @@ datetime_re = re.compile(
|
||||
r'(?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__
|
||||
@@ -90,7 +87,7 @@ class Dispatcher(object):
|
||||
reply = ErrorMessage(errorclass=err.__class__.__name__,
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
except (ValueError, TypeError) as err:
|
||||
# self.log.exception(err)
|
||||
# self.log.exception(err)
|
||||
reply = ErrorMessage(errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
except Exception as err:
|
||||
@@ -106,118 +103,123 @@ class Dispatcher(object):
|
||||
def broadcast_event(self, msg, reallyall=False):
|
||||
"""broadcasts a msg to all active connections"""
|
||||
if reallyall:
|
||||
listeners = self._dispatcher_connections
|
||||
listeners = self._connections
|
||||
else:
|
||||
if getattr(msg, 'command', None) is None:
|
||||
eventname = '%s:%s' % (msg.module, msg.parameter if msg.parameter else 'value')
|
||||
eventname = '%s:%s' % (
|
||||
msg.module, msg.parameter if msg.parameter else 'value')
|
||||
else:
|
||||
eventname = '%s:%s()' % (msg.module, msg.command)
|
||||
listeners = self._dispatcher_subscriptions.get(eventname, [])
|
||||
listeners += list(self._dispatcher_active_connections)
|
||||
listeners = self._subscriptions.get(eventname, [])
|
||||
listeners += list(self._active_connections)
|
||||
for conn in listeners:
|
||||
conn.queue_async_reply(msg)
|
||||
|
||||
def announce_update(self, moduleobj, pname, pobj):
|
||||
"""called by modules param setters to notify subscribers of new values
|
||||
"""
|
||||
msg = Value(moduleobj.name, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
||||
msg = Value(
|
||||
moduleobj.name,
|
||||
parameter=pname,
|
||||
value=pobj.value,
|
||||
t=pobj.timestamp)
|
||||
self.broadcast_event(msg)
|
||||
|
||||
def subscribe(self, conn, modulename, pname='value'):
|
||||
eventname = '%s:%s' % (modulename, pname)
|
||||
self._dispatcher_subscriptions.setdefault(eventname, set()).add(conn)
|
||||
self._subscriptions.setdefault(eventname, set()).add(conn)
|
||||
|
||||
def unsubscribe(self, conn, modulename, pname='value'):
|
||||
eventname = '%s:%s' % (modulename, pname)
|
||||
if eventname in self._dispatcher_subscriptions:
|
||||
self._dispatcher_subscriptions.remove(conn)
|
||||
if eventname in self._subscriptions:
|
||||
self._subscriptions.remove(conn)
|
||||
|
||||
def add_connection(self, conn):
|
||||
"""registers new connection"""
|
||||
self._dispatcher_connections.append(conn)
|
||||
self._connections.append(conn)
|
||||
|
||||
def remove_connection(self, conn):
|
||||
"""removes now longer functional connection"""
|
||||
if conn in self._dispatcher_connections:
|
||||
self._dispatcher_connections.remove(conn)
|
||||
for _evt, conns in self._dispatcher_subscriptions.items():
|
||||
if conn in self._connections:
|
||||
self._connections.remove(conn)
|
||||
for _evt, conns in self._subscriptions.items():
|
||||
conns.discard(conn)
|
||||
|
||||
def activate_connection(self, conn):
|
||||
self._dispatcher_active_connections.add(conn)
|
||||
self._active_connections.add(conn)
|
||||
|
||||
def deactivate_connection(self, conn):
|
||||
self._dispatcher_active_connections.discard(conn)
|
||||
self._active_connections.discard(conn)
|
||||
|
||||
def register_module(self, moduleobj, modulename, export=True):
|
||||
self.log.debug('registering module %r as %s (export=%r)' %
|
||||
(moduleobj, modulename, export))
|
||||
self._dispatcher_modules[modulename] = moduleobj
|
||||
self._modules[modulename] = moduleobj
|
||||
if export:
|
||||
self._dispatcher_export.append(modulename)
|
||||
self._export.append(modulename)
|
||||
|
||||
def get_module(self, modulename):
|
||||
module = self._dispatcher_modules.get(modulename, modulename)
|
||||
module = self._modules.get(modulename, modulename)
|
||||
return module
|
||||
|
||||
def remove_module(self, modulename_or_obj):
|
||||
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
|
||||
modulename = moduleobj.name
|
||||
if modulename in self._dispatcher_export:
|
||||
self._dispatcher_export.remove(modulename)
|
||||
self._dispatcher_modules.pop(modulename)
|
||||
# XXX: also clean _dispatcher_subscriptions
|
||||
if modulename in self._export:
|
||||
self._export.remove(modulename)
|
||||
self._modules.pop(modulename)
|
||||
# XXX: also clean _subscriptions
|
||||
|
||||
def list_module_names(self):
|
||||
# return a copy of our list
|
||||
return self._dispatcher_export[:]
|
||||
|
||||
def list_modules(self):
|
||||
dn = []
|
||||
dd = {}
|
||||
for modulename in self._dispatcher_export:
|
||||
dn.append(modulename)
|
||||
module = self.get_module(modulename)
|
||||
descriptive_data = {
|
||||
'class': module.__class__.__name__,
|
||||
#'bases': module.__bases__,
|
||||
'parameters': module.PARAMS.keys(),
|
||||
'commands': module.CMDS.keys(),
|
||||
# XXX: what else?
|
||||
}
|
||||
dd[modulename] = descriptive_data
|
||||
return dn, dd
|
||||
|
||||
def get_descriptive_data(self):
|
||||
# XXX: be lazy and cache this?
|
||||
result = {}
|
||||
for modulename in self._dispatcher_export:
|
||||
module = self.get_module(modulename)
|
||||
dd = {'class' : module.__class__.__name__,
|
||||
'bases' : [b.__name__ for b in module.__class__.__bases__],
|
||||
'parameters': dict((pn,po.as_dict()) for pn,po in module.PARAMS.items()),
|
||||
'commands': dict((cn,co.as_dict()) for cn,co in module.CMDS.items()),
|
||||
'baseclass' : 'Readable',
|
||||
}
|
||||
result.setdefault('modules', {})[modulename] = dd
|
||||
result['equipment_id'] = self.equipment_id
|
||||
# XXX: what else?
|
||||
return result
|
||||
return self._export[:]
|
||||
|
||||
def list_module_params(self, modulename):
|
||||
self.log.debug('list_module_params(%r)' % modulename)
|
||||
if modulename in self._dispatcher_export:
|
||||
# XXX: omit export=False params!
|
||||
if modulename in self._export:
|
||||
# omit export=False params!
|
||||
res = {}
|
||||
for paramname, param in self.get_module(modulename).PARAMS.items():
|
||||
if param.export == True:
|
||||
res[paramname] = param
|
||||
if param.export:
|
||||
res[paramname] = param.as_dict()
|
||||
self.log.debug('list params for module %s -> %r' %
|
||||
(modulename, res))
|
||||
return res
|
||||
self.log.debug('-> module is not to be exported!')
|
||||
return {}
|
||||
|
||||
def list_module_cmds(self, modulename):
|
||||
self.log.debug('list_module_cmds(%r)' % modulename)
|
||||
if modulename in self._export:
|
||||
# omit export=False params!
|
||||
res = {}
|
||||
for cmdname, cmdobj in self.get_module(modulename).CMDS.items():
|
||||
res[cmdname] = cmdobj.as_dict()
|
||||
self.log.debug('list cmds for module %s -> %r' %
|
||||
(modulename, res))
|
||||
return res
|
||||
self.log.debug('-> module is not to be exported!')
|
||||
return {}
|
||||
|
||||
def get_descriptive_data(self):
|
||||
# XXX: be lazy and cache this?
|
||||
result = {'modules':{}}
|
||||
for modulename in self._export:
|
||||
module = self.get_module(modulename)
|
||||
# some of these need rework !
|
||||
dd = {'class': module.__class__.__name__,
|
||||
'bases': [b.__name__ for b in module.__class__.__bases__],
|
||||
'parameters': self.list_module_params(modulename),
|
||||
'commands': self.list_module_cmds(modulename),
|
||||
'baseclass': 'Readable',
|
||||
}
|
||||
result['modules'][modulename] = dd
|
||||
result['equipment_id'] = self.equipment_id
|
||||
result['firmware'] = 'The SECoP playground'
|
||||
result['version'] = "2016.12"
|
||||
# XXX: what else?
|
||||
return result
|
||||
|
||||
def _execute_command(self, modulename, command, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = []
|
||||
@@ -230,13 +232,22 @@ class Dispatcher(object):
|
||||
if cmdspec is None:
|
||||
raise NoSuchCommandError(module=modulename, command=command)
|
||||
if len(cmdspec.arguments) != len(arguments):
|
||||
raise BadValueError(module=modulename, command=command, reason='Wrong number of arguments!')
|
||||
raise BadValueError(
|
||||
module=modulename,
|
||||
command=command,
|
||||
reason='Wrong number of arguments!')
|
||||
|
||||
# now call func and wrap result as value
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
func = getattr(moduleobj, 'do'+command)
|
||||
func = getattr(moduleobj, 'do' + command)
|
||||
res = func(*arguments)
|
||||
res = CommandReply(module=modulename, command=command, result=[res, dict(t=time.time())])
|
||||
res = CommandReply(
|
||||
module=modulename,
|
||||
command=command,
|
||||
result=[
|
||||
res,
|
||||
dict(
|
||||
t=time.time())])
|
||||
# res = Value(modulename, command=command, value=func(*arguments), t=time.time())
|
||||
return res
|
||||
|
||||
@@ -258,8 +269,16 @@ class Dispatcher(object):
|
||||
else:
|
||||
setattr(moduleobj, pname, value)
|
||||
if pobj.timestamp:
|
||||
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, dict(t=pobj.timestamp)])
|
||||
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, {}])
|
||||
return WriteReply(
|
||||
module=modulename, parameter=pname, value=[
|
||||
pobj.value, dict(
|
||||
t=pobj.timestamp)])
|
||||
return WriteReply(
|
||||
module=modulename,
|
||||
parameter=pname,
|
||||
value=[
|
||||
pobj.value,
|
||||
{}])
|
||||
|
||||
def _getParamValue(self, modulename, pname):
|
||||
moduleobj = self.get_module(modulename)
|
||||
@@ -276,10 +295,13 @@ class Dispatcher(object):
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
readfunc()
|
||||
if pobj.timestamp:
|
||||
return Value(modulename, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
||||
return Value(
|
||||
modulename,
|
||||
parameter=pname,
|
||||
value=pobj.value,
|
||||
t=pobj.timestamp)
|
||||
return Value(modulename, parameter=pname, value=pobj.value)
|
||||
|
||||
|
||||
# now the (defined) handlers for the different requests
|
||||
def handle_Help(self, conn, msg):
|
||||
return HelpMessage()
|
||||
@@ -289,21 +311,24 @@ class Dispatcher(object):
|
||||
|
||||
def handle_Describe(self, conn, msg):
|
||||
# XXX:collect descriptive data
|
||||
return DescribeReply(equipment_id = self.equipment_id, description = self.get_descriptive_data())
|
||||
return DescribeReply(
|
||||
equipment_id=self.equipment_id,
|
||||
description=self.get_descriptive_data())
|
||||
|
||||
def handle_Poll(self, conn, msg):
|
||||
# XXX: trigger polling and force sending event
|
||||
res = self._getParamValue(msg.module, msg.parameter or 'value')
|
||||
#self.broadcast_event(res)
|
||||
if conn in self._dispatcher_active_connections:
|
||||
# self.broadcast_event(res)
|
||||
if conn in self._active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
def handle_Write(self, conn, msg):
|
||||
# notify all by sending WriteReply
|
||||
#msg1 = WriteReply(**msg.as_dict())
|
||||
#self.broadcast_event(msg1)
|
||||
# try to actually write XXX: should this be done asyncron? we could just return the reply in that case
|
||||
# self.broadcast_event(msg1)
|
||||
# try to actually write XXX: should this be done asyncron? we could
|
||||
# just return the reply in that case
|
||||
if msg.parameter:
|
||||
res = self._setParamValue(msg.module, msg.parameter, msg.value)
|
||||
else:
|
||||
@@ -312,21 +337,22 @@ class Dispatcher(object):
|
||||
raise ReadonlyError(module=msg.module, parameter=None)
|
||||
res = self._setParamValue(msg.module, 'target', msg.value)
|
||||
res.parameter = 'target'
|
||||
#self.broadcast_event(res)
|
||||
if conn in self._dispatcher_active_connections:
|
||||
# self.broadcast_event(res)
|
||||
if conn in self._active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
def handle_Command(self, conn, msg):
|
||||
# notify all by sending CommandReply
|
||||
#msg1 = CommandReply(**msg.as_dict())
|
||||
#self.broadcast_event(msg1)
|
||||
# XXX: should this be done asyncron? we could just return the reply in that case
|
||||
# self.broadcast_event(msg1)
|
||||
# XXX: should this be done asyncron? we could just return the reply in
|
||||
# that case
|
||||
|
||||
# try to actually execute command
|
||||
res = self._execute_command(msg.module, msg.command, msg.arguments)
|
||||
#self.broadcast_event(res)
|
||||
#if conn in self._dispatcher_active_connections:
|
||||
# self.broadcast_event(res)
|
||||
# if conn in self._active_connections:
|
||||
# return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
@@ -336,7 +362,7 @@ class Dispatcher(object):
|
||||
def handle_Activate(self, conn, msg):
|
||||
self.activate_connection(conn)
|
||||
# easy approach: poll all values...
|
||||
for modulename, moduleobj in self._dispatcher_modules.items():
|
||||
for modulename, moduleobj in self._modules.items():
|
||||
for pname, pobj in moduleobj.PARAMS.items():
|
||||
# WARNING: THIS READS ALL PARAMS FROM HW!
|
||||
# XXX: should we send the cached values instead? (pbj.value)
|
||||
@@ -349,7 +375,7 @@ class Dispatcher(object):
|
||||
res = Value(module=modulename, parameter=pname,
|
||||
value=pobj.value, t=pobj.timestamp,
|
||||
unit=pobj.unit)
|
||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||
self.broadcast_event(res)
|
||||
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
||||
return None
|
||||
@@ -369,6 +395,4 @@ class Dispatcher(object):
|
||||
"""
|
||||
self.log.error('IGN: got unhandled request %s' % msg)
|
||||
return ErrorMessage(errorclass="InternalError",
|
||||
errorstring = 'Got Unhandled Request %r' % msg)
|
||||
|
||||
|
||||
errorstring='Got Unhandled Request %r' % msg)
|
||||
|
||||
@@ -98,7 +98,7 @@ class DemoEncoder(MessageEncoder):
|
||||
MessageEncoder.__init__(self, *args, **kwds)
|
||||
self.result = [] # for decoding
|
||||
self.expect_lines = 1
|
||||
#self.tests()
|
||||
# self.tests()
|
||||
|
||||
def encode(self, msg):
|
||||
"""msg object -> transport layer message"""
|
||||
@@ -179,8 +179,13 @@ class DemoEncoder(MessageEncoder):
|
||||
return '\n'.join(result)
|
||||
|
||||
if isinstance(msg, ErrorMessage):
|
||||
return ('%s %s' % (devspec(msg, 'error %s' %
|
||||
msg.errortype), msg.errorstring)).strip()
|
||||
return (
|
||||
'%s %s' %
|
||||
(devspec(
|
||||
msg,
|
||||
'error %s' %
|
||||
msg.errortype),
|
||||
msg.errorstring)).strip()
|
||||
|
||||
return 'Can not handle object %r!' % msg
|
||||
|
||||
@@ -292,7 +297,11 @@ class DemoEncoder(MessageEncoder):
|
||||
# construct messageobj
|
||||
if msgtype in MESSAGE:
|
||||
return MESSAGE[msgtype](
|
||||
devs=devs, pars=pars, props=props, result=result, **mgroups)
|
||||
devs=devs,
|
||||
pars=pars,
|
||||
props=props,
|
||||
result=result,
|
||||
**mgroups)
|
||||
|
||||
return ErrorMessage(errortype="SyntaxError",
|
||||
errorstring="Can't handle %r" % encoded)
|
||||
|
||||
@@ -34,38 +34,47 @@ 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
|
||||
ENABLEEVENTSREQUEST = 'activate' # literal
|
||||
ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer
|
||||
DISABLEEVENTSREQUEST = 'deactivate' # literal
|
||||
DISABLEEVENTSREPLY = 'inactive' # literal
|
||||
COMMANDREQUEST = 'do' # +module:command +json args (if needed)
|
||||
COMMANDREPLY = 'done' # +module:command +json args (if needed) # send after the command finished !
|
||||
WRITEREQUEST = 'change' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
||||
WRITEREPLY = 'changed' # +module[:parameter] +json_value # send with the read back value
|
||||
TRIGGERREQUEST = 'poll' # +module[:parameter] -> NO direct reply, calls TRIGGER internally!
|
||||
# +module:command +json args (if needed) # send after the command finished !
|
||||
COMMANDREPLY = 'done'
|
||||
# +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
||||
WRITEREQUEST = 'change'
|
||||
# +module[:parameter] +json_value # send with the read back value
|
||||
WRITEREPLY = 'changed'
|
||||
# +module[:parameter] -> NO direct reply, calls TRIGGER internally!
|
||||
TRIGGERREQUEST = 'poll'
|
||||
EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict)
|
||||
HEARTBEATREQUEST = 'ping' # +nonce_without_space
|
||||
HEARTBEATREPLY = 'pong' # +nonce_without_space
|
||||
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
|
||||
HELPREQUEST = 'help' # literal
|
||||
HELPREQUEST = 'help' # literal
|
||||
HELPREPLY = 'helping' # +line number +json_text
|
||||
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
||||
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
||||
'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
|
||||
'CommandRunning', 'Disabled',]
|
||||
# note: above strings need to be unique in the sense, that none is/or starts with another
|
||||
'CommandRunning', 'Disabled', ]
|
||||
# note: above strings need to be unique in the sense, that none is/or
|
||||
# starts with another
|
||||
|
||||
|
||||
def encode_value_data(vobj):
|
||||
q = vobj.qualifiers.copy()
|
||||
@@ -73,55 +82,57 @@ def encode_value_data(vobj):
|
||||
q['t'] = format_time(q['t'])
|
||||
return vobj.value, q
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
# map of msg to msgtype string as defined above.
|
||||
ENCODEMAP = {
|
||||
IdentifyRequest : (IDENTREQUEST,),
|
||||
IdentifyReply : (IDENTREPLY,),
|
||||
DescribeRequest : (DESCRIPTIONSREQUEST,),
|
||||
DescribeReply : (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
||||
ActivateRequest : (ENABLEEVENTSREQUEST,),
|
||||
ActivateReply : (ENABLEEVENTSREPLY,),
|
||||
IdentifyRequest: (IDENTREQUEST,),
|
||||
IdentifyReply: (IDENTREPLY,),
|
||||
DescribeRequest: (DESCRIPTIONSREQUEST,),
|
||||
DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
||||
ActivateRequest: (ENABLEEVENTSREQUEST,),
|
||||
ActivateReply: (ENABLEEVENTSREPLY,),
|
||||
DeactivateRequest: (DISABLEEVENTSREQUEST,),
|
||||
DeactivateReply : (DISABLEEVENTSREPLY,),
|
||||
CommandRequest : (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||
CommandReply : (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',),
|
||||
WriteRequest : (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||
WriteReply : (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||
PollRequest : (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
|
||||
HeartbeatRequest : (HEARTBEATREQUEST, 'nonce',),
|
||||
HeartbeatReply : (HEARTBEATREPLY, 'nonce',),
|
||||
DeactivateReply: (DISABLEEVENTSREPLY,),
|
||||
CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||
CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',),
|
||||
WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||
WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||
PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
|
||||
HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',),
|
||||
HeartbeatReply: (HEARTBEATREPLY, 'nonce',),
|
||||
HelpMessage: (HELPREQUEST, ),
|
||||
ErrorMessage : (ERRORREPLY, 'errorclass', 'errorinfo',),
|
||||
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()'))
|
||||
if msg.parameter or msg.command else msg.module,
|
||||
encode_value_data,),
|
||||
ErrorMessage: (ERRORREPLY, 'errorclass', 'errorinfo',),
|
||||
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()'))
|
||||
if msg.parameter or msg.command else msg.module,
|
||||
encode_value_data,),
|
||||
}
|
||||
DECODEMAP = {
|
||||
IDENTREQUEST : lambda spec, data: IdentifyRequest(),
|
||||
IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness
|
||||
DESCRIPTIONSREQUEST : lambda spec, data: DescribeRequest(),
|
||||
DESCRIPTIONREPLY : lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
|
||||
ENABLEEVENTSREQUEST : lambda spec, data: ActivateRequest(),
|
||||
ENABLEEVENTSREPLY: lambda spec, data:ActivateReply(),
|
||||
DISABLEEVENTSREQUEST: lambda spec, data:DeactivateRequest(),
|
||||
DISABLEEVENTSREPLY: lambda spec, data:DeactivateReply(),
|
||||
COMMANDREQUEST: lambda spec, data:CommandRequest(module=spec[0], command=spec[1], arguments=data),
|
||||
IDENTREQUEST: lambda spec, data: IdentifyRequest(),
|
||||
# handled specially, listed here for completeness
|
||||
IDENTREPLY: lambda spec, data: IdentifyReply(encoded),
|
||||
DESCRIPTIONSREQUEST: lambda spec, data: DescribeRequest(),
|
||||
DESCRIPTIONREPLY: lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
|
||||
ENABLEEVENTSREQUEST: lambda spec, data: ActivateRequest(),
|
||||
ENABLEEVENTSREPLY: lambda spec, data: ActivateReply(),
|
||||
DISABLEEVENTSREQUEST: lambda spec, data: DeactivateRequest(),
|
||||
DISABLEEVENTSREPLY: lambda spec, data: DeactivateReply(),
|
||||
COMMANDREQUEST: lambda spec, data: CommandRequest(module=spec[0], command=spec[1], arguments=data),
|
||||
COMMANDREPLY: lambda spec, data: CommandReply(module=spec[0], command=spec[1], result=data),
|
||||
WRITEREQUEST: lambda spec, data: WriteRequest(module=spec[0], parameter=spec[1], value=data),
|
||||
WRITEREPLY:lambda spec, data:WriteReply(module=spec[0], parameter=spec[1], value=data),
|
||||
TRIGGERREQUEST:lambda spec, data:PollRequest(module=spec[0], parameter=spec[1]),
|
||||
HEARTBEATREQUEST:lambda spec, data:HeartbeatRequest(nonce=spec[0]),
|
||||
HEARTBEATREPLY:lambda spec, data:HeartbeatReply(nonce=spec[0]),
|
||||
HELPREQUEST: lambda spec, data:HelpMessage(),
|
||||
# HELPREPLY: lambda spec, data:None, # ignore this
|
||||
ERRORREPLY:lambda spec, data:ErrorMessage(errorclass=spec[0], errorinfo=data),
|
||||
EVENT:lambda spec, data:Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data)>1 else {}),
|
||||
}
|
||||
WRITEREPLY: lambda spec, data: WriteReply(module=spec[0], parameter=spec[1], value=data),
|
||||
TRIGGERREQUEST: lambda spec, data: PollRequest(module=spec[0], parameter=spec[1]),
|
||||
HEARTBEATREQUEST: lambda spec, data: HeartbeatRequest(nonce=spec[0]),
|
||||
HEARTBEATREPLY: lambda spec, data: HeartbeatReply(nonce=spec[0]),
|
||||
HELPREQUEST: lambda spec, data: HelpMessage(),
|
||||
# HELPREPLY: lambda spec, data:None, # ignore this
|
||||
ERRORREPLY: lambda spec, data: ErrorMessage(errorclass=spec[0], errorinfo=data),
|
||||
EVENT: lambda spec, data: Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data) > 1 else {}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
MessageEncoder.__init__(self, *args, **kwds)
|
||||
#self.tests()
|
||||
# self.tests()
|
||||
|
||||
def encode(self, msg):
|
||||
"""msg object -> transport layer message"""
|
||||
@@ -136,20 +147,21 @@ class DemoEncoder(MessageEncoder):
|
||||
'%s <nonce>' to request a heartbeat response
|
||||
'%s' to activate async updates
|
||||
'%s' to deactivate updates
|
||||
""" %(IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
|
||||
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
|
||||
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
||||
return '\n'.join('%s %d %s' %(HELPREPLY, i+1, l.strip()) for i,l in enumerate(text.split('\n')[:-1]))
|
||||
""" % (IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
|
||||
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
|
||||
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
||||
return '\n'.join('%s %d %s' % (HELPREPLY, i + 1, l.strip())
|
||||
for i, l in enumerate(text.split('\n')[:-1]))
|
||||
for msgcls, parts in self.ENCODEMAP.items():
|
||||
if isinstance(msg, msgcls):
|
||||
# resolve lambdas
|
||||
parts = [parts[0]] + [p(msg) if callable(p) else getattr(msg, p) for p in parts[1:]]
|
||||
parts = [parts[0]] + [p(msg) if callable(p)
|
||||
else getattr(msg, p) for p in parts[1:]]
|
||||
if len(parts) > 1:
|
||||
parts[1] = str(parts[1])
|
||||
if len(parts) == 3:
|
||||
parts[2] = json.dumps(parts[2])
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
def decode(self, encoded):
|
||||
# first check beginning
|
||||
@@ -158,17 +170,17 @@ class DemoEncoder(MessageEncoder):
|
||||
print repr(encoded), repr(IDENTREPLY)
|
||||
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
||||
return IdentifyReply(version_string=encoded)
|
||||
|
||||
|
||||
return HelpMessage()
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
errorinfo='Regex did not match!',
|
||||
is_request=True)
|
||||
msgtype, msgspec, data = match.groups()
|
||||
if msgspec is None and data:
|
||||
return ErrorMessage(errorclass='InternalError',
|
||||
return ErrorMessage(errorclass='InternalError',
|
||||
errorinfo='Regex matched json, but not spec!',
|
||||
is_request=True)
|
||||
|
||||
|
||||
if msgtype in self.DECODEMAP:
|
||||
if msgspec and ':' in msgspec:
|
||||
msgspec = msgspec.split(':', 1)
|
||||
@@ -181,16 +193,28 @@ class DemoEncoder(MessageEncoder):
|
||||
return ErrorMessage(errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(encoded)])
|
||||
return self.DECODEMAP[msgtype](msgspec, data)
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
errorinfo='%r: No Such Messagetype defined!' % encoded,
|
||||
is_request=True)
|
||||
|
||||
return ErrorMessage(
|
||||
errorclass='SyntaxError',
|
||||
errorinfo='%r: No Such Messagetype defined!' %
|
||||
encoded,
|
||||
is_request=True)
|
||||
|
||||
def tests(self):
|
||||
print "---- Testing encoding -----"
|
||||
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
||||
print msgclass
|
||||
e=self.encode(msgclass(module='<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
|
||||
@@ -200,9 +224,8 @@ class DemoEncoder(MessageEncoder):
|
||||
if msgtype == EVENT:
|
||||
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
||||
print msg
|
||||
d=self.decode(msg)
|
||||
d = self.decode(msg)
|
||||
print d
|
||||
print self.encode(d)
|
||||
print
|
||||
print "---- Testing done -----"
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
|
||||
class PickleEncoder(MessageEncoder):
|
||||
|
||||
def encode(self, messageobj):
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
|
||||
|
||||
class SECOPError(RuntimeError):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.args = args
|
||||
for k,v in kwds.items():
|
||||
for k, v in kwds.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -49,7 +50,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
clientaddr = self.client_address
|
||||
serverobj = self.server
|
||||
self.log.debug("handling new connection from %s" % repr(clientaddr))
|
||||
|
||||
|
||||
# notify dispatcher of us
|
||||
serverobj.dispatcher.add_connection(self)
|
||||
|
||||
@@ -81,7 +82,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
# dispatcher will queue the reply before returning
|
||||
frames = self.framing.decode(data)
|
||||
if frames is not None:
|
||||
if not frames: # empty list
|
||||
if not frames: # empty list
|
||||
self.queue_reply(HelpMessage(MSGTYPE=reply))
|
||||
for frame in frames:
|
||||
reply = None
|
||||
@@ -129,7 +130,8 @@ class TCPServer(SocketServer.ThreadingTCPServer):
|
||||
self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
|
||||
self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum))
|
||||
self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
|
||||
self.log.debug("TCPServer using encoding=%s" % self.encodingCLS.__name__)
|
||||
self.log.debug("TCPServer using encoding=%s" %
|
||||
self.encodingCLS.__name__)
|
||||
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
|
||||
TCPRequestHandler,
|
||||
bind_and_activate=True)
|
||||
|
||||
@@ -45,18 +45,19 @@ class Message(object):
|
||||
|
||||
def as_dict(self):
|
||||
"""returns set parameters as dict"""
|
||||
return dict(map(lambda k:(k, getattr(self,k)),self.ARGS))
|
||||
return dict(map(lambda k: (k, getattr(self, k)), self.ARGS))
|
||||
|
||||
|
||||
class Value(object):
|
||||
|
||||
def __init__(self, module, parameter=None, command=None, value=Ellipsis, **qualifiers):
|
||||
def __init__(self, module, parameter=None, command=None, value=Ellipsis,
|
||||
**qualifiers):
|
||||
self.module = module
|
||||
self.parameter = parameter
|
||||
self.command = command
|
||||
self.value = value
|
||||
self.qualifiers = qualifiers
|
||||
self.msgtype = 'update' # 'changed' or 'done'
|
||||
self.msgtype = 'update' # 'changed' or 'done'
|
||||
|
||||
def __repr__(self):
|
||||
devspec = self.module
|
||||
@@ -65,79 +66,96 @@ class Value(object):
|
||||
elif self.command:
|
||||
devspec = '%s:%s()' % (devspec, self.command)
|
||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
||||
[repr(self.value)] + ['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()]))
|
||||
[repr(self.value)] +
|
||||
['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()]))
|
||||
|
||||
|
||||
class IdentifyRequest(Message):
|
||||
is_request = True
|
||||
|
||||
|
||||
class IdentifyReply(Message):
|
||||
is_reply = True
|
||||
version_string = None
|
||||
|
||||
|
||||
class DescribeRequest(Message):
|
||||
is_request = True
|
||||
|
||||
|
||||
class DescribeReply(Message):
|
||||
is_reply = True
|
||||
equipment_id = None
|
||||
description = None
|
||||
|
||||
|
||||
class ActivateRequest(Message):
|
||||
is_request = True
|
||||
|
||||
|
||||
class ActivateReply(Message):
|
||||
is_reply = True
|
||||
|
||||
|
||||
class DeactivateRequest(Message):
|
||||
is_request = True
|
||||
|
||||
|
||||
class DeactivateReply(Message):
|
||||
is_reply = True
|
||||
|
||||
|
||||
class CommandRequest(Message):
|
||||
is_request = True
|
||||
command = ''
|
||||
arguments = []
|
||||
|
||||
|
||||
class CommandReply(Message):
|
||||
is_reply = True
|
||||
command = ''
|
||||
result = None
|
||||
|
||||
|
||||
class WriteRequest(Message):
|
||||
is_request = True
|
||||
module = None
|
||||
parameter = None
|
||||
value = None
|
||||
|
||||
|
||||
class WriteReply(Message):
|
||||
is_reply = True
|
||||
module = None
|
||||
parameter = None
|
||||
value = None
|
||||
|
||||
|
||||
class PollRequest(Message):
|
||||
is_request = True
|
||||
module = None
|
||||
parameter = None
|
||||
|
||||
|
||||
class HeartbeatRequest(Message):
|
||||
is_request = True
|
||||
nonce = 'alive'
|
||||
|
||||
|
||||
class HeartbeatReply(Message):
|
||||
is_reply = True
|
||||
nonce = 'undefined'
|
||||
|
||||
|
||||
class EventMessage(Message):
|
||||
# use Value directly for Replies !
|
||||
# use Value directly for Replies !
|
||||
is_reply = True
|
||||
module = None
|
||||
parameter = None
|
||||
command = None
|
||||
value = None # Value object ! (includes qualifiers!)
|
||||
|
||||
|
||||
class ErrorMessage(Message):
|
||||
is_error = True
|
||||
errorclass = 'InternalError'
|
||||
@@ -149,7 +167,6 @@ class HelpMessage(Message):
|
||||
is_request = True
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Minimal testing of messages....")
|
||||
m = Message(MSGTYPE='test', a=1, b=2, c='x')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user