Polishing

- unify (encoded) Errormessages. They now always contain the offeding request.
- adopt keywords to current spec
- fixed formatting of timestamp in WriteReply
- minor bugfixing + polishing

Change-Id: I0ab8836756551399643bdce3d062eedd345509f1
This commit is contained in:
Enrico Faulhaber 2017-01-19 16:02:24 +01:00
parent d442da0789
commit 8e3d0da5dd
7 changed files with 95 additions and 63 deletions

View File

@ -146,9 +146,9 @@ Events can be emitted any time from the SEC-node (except if they would interrupt
examples: examples:
* 'event T1:value [3.479, {"t":"149128925.914882", "e":0.01924}] * 'update T1:value [3.479, {"t":"149128925.914882", "e":0.01924}]
* 'event T1:p [12, {"t":"149128927.193725"}]' * 'update T1:p [12, {"t":"149128927.193725"}]'
* 'event Vector:value [[0.01, 12.49, 3.92], {"t":"149128925.914882"}]' * 'update Vector:value [[0.01, 12.49, 3.92], {"t":"149128925.914882"}]'
ERROR ERROR

View File

@ -189,28 +189,30 @@ class Client(object):
while not self.stopflag: while not self.stopflag:
line = self.connection.readline() line = self.connection.readline()
self.log.debug('got answer %r' % line) self.log.debug('got answer %r' % line)
if line.startswith(('SECoP', 'Sine2020WP7')): if line.startswith(('SECoP', 'SINE2020&ISSE,SECoP')):
self.log.info('connected to: ' + line.strip()) self.log.info('connected to: ' + line.strip())
self.secop_id = line self.secop_id = line
continue continue
msgtype, spec, data = self._decode_message(line) msgtype, spec, data = self._decode_message(line)
if msgtype in ('event', 'changed'): if msgtype in ('update', 'changed'):
# handle async stuff # handle async stuff
self._handle_event(spec, data) self._handle_event(spec, data)
if msgtype != 'event': if msgtype != 'update':
# handle sync stuff # handle sync stuff
if msgtype in self.expected_replies: if msgtype in self.expected_replies:
entry = self.expected_replies[msgtype] entry = self.expected_replies[msgtype]
entry.extend([msgtype, spec, data]) entry.extend([msgtype, spec, data])
# wake up calling process # wake up calling process
entry[0].set() entry[0].set()
elif msgtype == "ERROR": elif msgtype == "error":
# XXX: hack! # XXX: hack!
if len(self.expected_replies) == 1: if len(self.expected_replies) == 1:
entry = self.expected_replies.values()[0] entry = self.expected_replies.values()[0]
entry.extend([msgtype, spec, data]) entry.extend([msgtype, spec, data])
# wake up calling process # wake up calling process
entry[0].set() entry[0].set()
else: # try to find the right request....
print data[0] # should be the origin request
# XXX: make an assignment of ERROR to an expected reply. # XXX: make an assignment of ERROR to an expected reply.
self.log.error('TODO: handle ERROR replies!') self.log.error('TODO: handle ERROR replies!')
else: else:
@ -312,12 +314,12 @@ class Client(object):
} }
if self.stopflag: if self.stopflag:
raise RuntimeError('alreading stopping!') raise RuntimeError('alreading stopping!')
if msgtype == 'poll': if msgtype == 'read':
# send a poll request and then check incoming events # send a poll request and then check incoming events
if ':' not in spec: if ':' not in spec:
spec = spec + ':value' spec = spec + ':value'
event = threading.Event() event = threading.Event()
result = ['polled', spec] result = ['update', spec]
self.single_shots.setdefault(spec, set()).add( self.single_shots.setdefault(spec, set()).add(
lambda d: (result.append(d), event.set())) lambda d: (result.append(d), event.set()))
self.connection.writeline( self.connection.writeline(

View File

@ -32,6 +32,7 @@ import types
import inspect import inspect
import threading import threading
from secop.lib.parsing import format_time
from secop.errors import ConfigError, ProgrammingError from secop.errors import ConfigError, ProgrammingError
from secop.protocol import status from secop.protocol import status
from secop.validators import enum, vector, floatrange from secop.validators import enum, vector, floatrange
@ -73,7 +74,7 @@ class PARAM(object):
unit=self.unit, unit=self.unit,
readonly=self.readonly, readonly=self.readonly,
value=self.value, value=self.value,
timestamp=self.timestamp, timestamp=format_time(self.timestamp) if self.timestamp else None,
validator=str(self.validator) if not isinstance( validator=str(self.validator) if not isinstance(
self.validator, type) else self.validator.__name__ self.validator, type) else self.validator.__name__
) )

View File

@ -43,7 +43,7 @@ import threading
from messages import * from messages import *
from errors import * from errors import *
from secop.lib.parsing import format_time
class Dispatcher(object): class Dispatcher(object):
@ -84,16 +84,15 @@ class Dispatcher(object):
reply = handler(conn, msg) reply = handler(conn, msg)
except SECOPError as err: except SECOPError as err:
self.log.exception(err) self.log.exception(err)
reply = ErrorMessage(errorclass=err.__class__.__name__, reply = msg.get_error(errorclass=err.__class__.__name__,
errorinfo=[repr(err), str(msg)]) errorinfo=[repr(err), str(msg)])
except (ValueError, TypeError) as err: except (ValueError, TypeError) as err:
# self.log.exception(err) reply = msg.get_error(errorclass='BadValue',
reply = ErrorMessage(errorclass='BadValue', errorinfo=[repr(err), str(msg)])
errorinfo=[repr(err), str(msg)])
except Exception as err: except Exception as err:
self.log.exception(err) self.log.exception(err)
reply = ErrorMessage(errorclass='InternalError', reply = msg.get_error(errorclass='InternalError',
errorinfo=[repr(err), str(msg)]) errorinfo=[repr(err), str(msg)])
else: else:
self.log.debug('Can not handle msg %r' % msg) self.log.debug('Can not handle msg %r' % msg)
reply = self.unhandled(conn, msg) reply = self.unhandled(conn, msg)
@ -159,8 +158,11 @@ class Dispatcher(object):
self._export.append(modulename) self._export.append(modulename)
def get_module(self, modulename): def get_module(self, modulename):
module = self._modules.get(modulename, modulename) if modulename in self._modules:
return module return self._modules[modulename]
elif modulename in self._modules.values():
return modulename
raise NoSuchModuleError(module=str(modulename))
def remove_module(self, modulename_or_obj): def remove_module(self, modulename_or_obj):
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
@ -226,7 +228,7 @@ class Dispatcher(object):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchmoduleError(module=modulename) raise NoSuchModuleError(module=modulename)
cmdspec = moduleobj.CMDS.get(command, None) cmdspec = moduleobj.CMDS.get(command, None)
if cmdspec is None: if cmdspec is None:
@ -249,7 +251,7 @@ class Dispatcher(object):
def _setParamValue(self, modulename, pname, value): def _setParamValue(self, modulename, pname, value):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchmoduleError(module=modulename) raise NoSuchModuleError(module=modulename)
pobj = moduleobj.PARAMS.get(pname, None) pobj = moduleobj.PARAMS.get(pname, None)
if pobj is None: if pobj is None:
@ -267,7 +269,7 @@ class Dispatcher(object):
return WriteReply( return WriteReply(
module=modulename, parameter=pname, value=[ module=modulename, parameter=pname, value=[
pobj.value, dict( pobj.value, dict(
t=pobj.timestamp)]) t=format_time(pobj.timestamp))])
return WriteReply( return WriteReply(
module=modulename, module=modulename,
parameter=pname, parameter=pname,
@ -389,5 +391,5 @@ class Dispatcher(object):
(no handle_<messagename> method was defined) (no handle_<messagename> method was defined)
""" """
self.log.error('IGN: got unhandled request %s' % msg) self.log.error('IGN: got unhandled request %s' % msg)
return ErrorMessage(errorclass="InternalError", return msg.get_error(errorclass="InternalError",
errorstring='Got Unhandled Request %r' % msg) errorinfo="Unhandled Request")

View File

@ -45,8 +45,9 @@ DEMO_RE = re.compile(
#""" #"""
# messagetypes: # messagetypes:
IDENTREQUEST = '*IDN?' # literal IDENTREQUEST = '*IDN?' # literal
# literal! first part 'SECoP' is fixed! # literal! first part is fixed!
IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1' #IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
DESCRIPTIONSREQUEST = 'describe' # literal DESCRIPTIONSREQUEST = 'describe' # literal
DESCRIPTIONREPLY = 'describing' # +<id> +json DESCRIPTIONREPLY = 'describing' # +<id> +json
ENABLEEVENTSREQUEST = 'activate' # literal ENABLEEVENTSREQUEST = 'activate' # literal
@ -61,11 +62,11 @@ WRITEREQUEST = 'change'
# +module[:parameter] +json_value # send with the read back value # +module[:parameter] +json_value # send with the read back value
WRITEREPLY = 'changed' WRITEREPLY = 'changed'
# +module[:parameter] -> NO direct reply, calls TRIGGER internally! # +module[:parameter] -> NO direct reply, calls TRIGGER internally!
TRIGGERREQUEST = 'poll' TRIGGERREQUEST = 'read'
EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict) EVENT = 'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = 'ping' # +nonce_without_space HEARTBEATREQUEST = 'ping' # +nonce_without_space
HEARTBEATREPLY = 'pong' # +nonce_without_space HEARTBEATREPLY = 'pong' # +nonce_without_space
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info ERRORREPLY = 'error' # +errorclass +json_extended_info
HELPREQUEST = 'help' # literal HELPREQUEST = 'help' # literal
HELPREPLY = 'helping' # +line number +json_text HELPREPLY = 'helping' # +line number +json_text
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand', ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
@ -88,6 +89,10 @@ def encode_value_data(vobj):
q['t'] = format_time(q['t']) q['t'] = format_time(q['t'])
return vobj.value, q return vobj.value, q
def encode_error_msg(emsg):
# note: result is JSON-ified....
return [emsg.origin, dict( (k,getattr(emsg, k)) for k in emsg.ARGS if k != 'origin')]
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
# map of msg to msgtype string as defined above. # map of msg to msgtype string as defined above.
@ -103,12 +108,12 @@ class DemoEncoder(MessageEncoder):
CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',), CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), encode_cmd_result,), CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), encode_cmd_result,),
WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value', ),
PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ), PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',), HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',),
HeartbeatReply: (HEARTBEATREPLY, 'nonce',), HeartbeatReply: (HEARTBEATREPLY, 'nonce',),
HelpMessage: (HELPREQUEST, ), HelpMessage: (HELPREQUEST, ),
ErrorMessage: (ERRORREPLY, 'errorclass', 'errorinfo',), ErrorMessage: (ERRORREPLY, "errorclass", encode_error_msg,),
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()'))
if msg.parameter or msg.command else msg.module, if msg.parameter or msg.command else msg.module,
encode_value_data,), encode_value_data,),
@ -185,7 +190,8 @@ class DemoEncoder(MessageEncoder):
if msgspec is None and data: if msgspec is None and data:
return ErrorMessage(errorclass='InternalError', return ErrorMessage(errorclass='InternalError',
errorinfo='Regex matched json, but not spec!', errorinfo='Regex matched json, but not spec!',
is_request=True) is_request=True,
origin=encoded)
if msgtype in self.DECODEMAP: if msgtype in self.DECODEMAP:
if msgspec and ':' in msgspec: if msgspec and ':' in msgspec:
@ -197,13 +203,17 @@ class DemoEncoder(MessageEncoder):
data = json.loads(data) data = json.loads(data)
except ValueError as err: except ValueError as err:
return ErrorMessage(errorclass='BadValue', return ErrorMessage(errorclass='BadValue',
errorinfo=[repr(err), str(encoded)]) errorinfo=[repr(err), str(encoded)],
return self.DECODEMAP[msgtype](msgspec, data) origin=encoded)
msg = self.DECODEMAP[msgtype](msgspec, data)
msg.setvalue("origin",encoded)
return msg
return ErrorMessage( return ErrorMessage(
errorclass='SyntaxError', errorclass='SyntaxError',
errorinfo='%r: No Such Messagetype defined!' % errorinfo='%r: No Such Messagetype defined!' %
encoded, encoded,
is_request=True) is_request=True,
origin=encoded)
def tests(self): def tests(self):
print "---- Testing encoding -----" print "---- Testing encoding -----"

View File

@ -128,7 +128,7 @@ class TCPServer(SocketServer.ThreadingTCPServer):
# and encoders (to en/decode messages from frames) # and encoders (to en/decode messages from frames)
self.framingCLS = FRAMERS[interfaceopts.pop('framing', 'none')] self.framingCLS = FRAMERS[interfaceopts.pop('framing', 'none')]
self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')] self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum)) self.log.info("TCPServer binding to %s:%d" % (bindto, portnum))
self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__) self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
self.log.debug("TCPServer using encoding=%s" % self.log.debug("TCPServer using encoding=%s" %
self.encodingCLS.__name__) self.encodingCLS.__name__)

View File

@ -29,6 +29,7 @@ class Message(object):
is_reply = False is_reply = False
is_error = False is_error = False
qualifiers = {} qualifiers = {}
origin = "<unknown source>"
def __init__(self, **kwds): def __init__(self, **kwds):
self.ARGS = set() self.ARGS = set()
@ -68,11 +69,39 @@ class Value(object):
devspec = '%s:%s()' % (devspec, self.command) devspec = '%s:%s()' % (devspec, self.command)
return '%s:Value(%s)' % (devspec, ', '.join( return '%s:Value(%s)' % (devspec, ', '.join(
[repr(self.value)] + [repr(self.value)] +
['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()])) ['%s=%s' % (k, format_time(v) if k=="timestamp" else repr(v)) for k, v in self.qualifiers.items()]))
class IdentifyRequest(Message): class Request(Message):
is_request = True is_request = True
def get_reply(self):
"""returns a Reply object prefilled with the attributes from this request."""
m = Message()
m.is_request = False
m.is_reply = True
m.is_error = False
m.qualifiers = self.qualifiers
m.origin = self.origin
for k in self.ARGS:
m.setvalue(k, self.__dict__[k])
return m
def get_error(self, errorclass, errorinfo):
"""returns a Reply object prefilled with the attributes from this request."""
m = ErrorMessage()
m.qualifiers = self.qualifiers
m.origin = self.origin
for k in self.ARGS:
m.setvalue(k, self.__dict__[k])
m.setvalue("errorclass", errorclass[:-5]
if errorclass.endswith('rror')
else errorclass)
m.setvalue("errorinfo", errorinfo)
return m
class IdentifyRequest(Request):
pass
class IdentifyReply(Message): class IdentifyReply(Message):
@ -80,8 +109,8 @@ class IdentifyReply(Message):
version_string = None version_string = None
class DescribeRequest(Message): class DescribeRequest(Request):
is_request = True pass
class DescribeReply(Message): class DescribeReply(Message):
@ -90,24 +119,23 @@ class DescribeReply(Message):
description = None description = None
class ActivateRequest(Message): class ActivateRequest(Request):
is_request = True pass
class ActivateReply(Message): class ActivateReply(Message):
is_reply = True is_reply = True
class DeactivateRequest(Message): class DeactivateRequest(Request):
is_request = True pass
class DeactivateReply(Message): class DeactivateReply(Message):
is_reply = True is_reply = True
class CommandRequest(Message): class CommandRequest(Request):
is_request = True
command = '' command = ''
arguments = [] arguments = []
@ -118,8 +146,7 @@ class CommandReply(Message):
result = None result = None
class WriteRequest(Message): class WriteRequest(Request):
is_request = True
module = None module = None
parameter = None parameter = None
value = None value = None
@ -132,14 +159,13 @@ class WriteReply(Message):
value = None value = None
class PollRequest(Message): class PollRequest(Request):
is_request = True is_request = True
module = None module = None
parameter = None parameter = None
class HeartbeatRequest(Message): class HeartbeatRequest(Request):
is_request = True
nonce = 'alive' nonce = 'alive'
@ -163,16 +189,7 @@ class ErrorMessage(Message):
errorinfo = None errorinfo = None
class HelpMessage(Message): class HelpMessage(Request):
is_reply = True is_reply = True #!sic!
is_request = True
if __name__ == '__main__':
print("Minimal testing of messages....")
m = Message(MSGTYPE='test', a=1, b=2, c='x')
print m
print ReadMessage(devs=['a'], result=[Value(12.3)])
print "OK"
print