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:
* 'event T1:value [3.479, {"t":"149128925.914882", "e":0.01924}]
* 'event T1:p [12, {"t":"149128927.193725"}]'
* 'event Vector:value [[0.01, 12.49, 3.92], {"t":"149128925.914882"}]'
* 'update T1:value [3.479, {"t":"149128925.914882", "e":0.01924}]
* 'update T1:p [12, {"t":"149128927.193725"}]'
* 'update Vector:value [[0.01, 12.49, 3.92], {"t":"149128925.914882"}]'
ERROR

View File

@ -189,28 +189,30 @@ class Client(object):
while not self.stopflag:
line = self.connection.readline()
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.secop_id = line
continue
msgtype, spec, data = self._decode_message(line)
if msgtype in ('event', 'changed'):
if msgtype in ('update', 'changed'):
# handle async stuff
self._handle_event(spec, data)
if msgtype != 'event':
if msgtype != 'update':
# handle sync stuff
if msgtype in self.expected_replies:
entry = self.expected_replies[msgtype]
entry.extend([msgtype, spec, data])
# wake up calling process
entry[0].set()
elif msgtype == "ERROR":
elif msgtype == "error":
# XXX: hack!
if len(self.expected_replies) == 1:
entry = self.expected_replies.values()[0]
entry.extend([msgtype, spec, data])
# wake up calling process
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.
self.log.error('TODO: handle ERROR replies!')
else:
@ -312,12 +314,12 @@ class Client(object):
}
if self.stopflag:
raise RuntimeError('alreading stopping!')
if msgtype == 'poll':
if msgtype == 'read':
# send a poll request and then check incoming events
if ':' not in spec:
spec = spec + ':value'
event = threading.Event()
result = ['polled', spec]
result = ['update', spec]
self.single_shots.setdefault(spec, set()).add(
lambda d: (result.append(d), event.set()))
self.connection.writeline(

View File

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

View File

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

View File

@ -45,8 +45,9 @@ DEMO_RE = re.compile(
#"""
# messagetypes:
IDENTREQUEST = '*IDN?' # literal
# literal! first part 'SECoP' is fixed!
IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
# literal! first part is fixed!
#IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
DESCRIPTIONSREQUEST = 'describe' # literal
DESCRIPTIONREPLY = 'describing' # +<id> +json
ENABLEEVENTSREQUEST = 'activate' # literal
@ -61,11 +62,11 @@ 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)
TRIGGERREQUEST = 'read'
EVENT = 'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = 'ping' # +nonce_without_space
HEARTBEATREPLY = 'pong' # +nonce_without_space
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
ERRORREPLY = 'error' # +errorclass +json_extended_info
HELPREQUEST = 'help' # literal
HELPREPLY = 'helping' # +line number +json_text
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
@ -88,6 +89,10 @@ def encode_value_data(vobj):
q['t'] = format_time(q['t'])
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):
# 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',),
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',),
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, ),
HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',),
HeartbeatReply: (HEARTBEATREPLY, 'nonce',),
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 + '()'))
if msg.parameter or msg.command else msg.module,
encode_value_data,),
@ -185,7 +190,8 @@ class DemoEncoder(MessageEncoder):
if msgspec is None and data:
return ErrorMessage(errorclass='InternalError',
errorinfo='Regex matched json, but not spec!',
is_request=True)
is_request=True,
origin=encoded)
if msgtype in self.DECODEMAP:
if msgspec and ':' in msgspec:
@ -197,13 +203,17 @@ class DemoEncoder(MessageEncoder):
data = json.loads(data)
except ValueError as err:
return ErrorMessage(errorclass='BadValue',
errorinfo=[repr(err), str(encoded)])
return self.DECODEMAP[msgtype](msgspec, data)
errorinfo=[repr(err), str(encoded)],
origin=encoded)
msg = self.DECODEMAP[msgtype](msgspec, data)
msg.setvalue("origin",encoded)
return msg
return ErrorMessage(
errorclass='SyntaxError',
errorinfo='%r: No Such Messagetype defined!' %
encoded,
is_request=True)
is_request=True,
origin=encoded)
def tests(self):
print "---- Testing encoding -----"

View File

@ -128,7 +128,7 @@ class TCPServer(SocketServer.ThreadingTCPServer):
# and encoders (to en/decode messages from frames)
self.framingCLS = FRAMERS[interfaceopts.pop('framing', 'none')]
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 encoding=%s" %
self.encodingCLS.__name__)

View File

@ -29,6 +29,7 @@ class Message(object):
is_reply = False
is_error = False
qualifiers = {}
origin = "<unknown source>"
def __init__(self, **kwds):
self.ARGS = set()
@ -68,11 +69,39 @@ class Value(object):
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()]))
['%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
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):
@ -80,8 +109,8 @@ class IdentifyReply(Message):
version_string = None
class DescribeRequest(Message):
is_request = True
class DescribeRequest(Request):
pass
class DescribeReply(Message):
@ -90,24 +119,23 @@ class DescribeReply(Message):
description = None
class ActivateRequest(Message):
is_request = True
class ActivateRequest(Request):
pass
class ActivateReply(Message):
is_reply = True
class DeactivateRequest(Message):
is_request = True
class DeactivateRequest(Request):
pass
class DeactivateReply(Message):
is_reply = True
class CommandRequest(Message):
is_request = True
class CommandRequest(Request):
command = ''
arguments = []
@ -118,8 +146,7 @@ class CommandReply(Message):
result = None
class WriteRequest(Message):
is_request = True
class WriteRequest(Request):
module = None
parameter = None
value = None
@ -132,14 +159,13 @@ class WriteReply(Message):
value = None
class PollRequest(Message):
class PollRequest(Request):
is_request = True
module = None
parameter = None
class HeartbeatRequest(Message):
is_request = True
class HeartbeatRequest(Request):
nonce = 'alive'
@ -163,16 +189,7 @@ class ErrorMessage(Message):
errorinfo = None
class HelpMessage(Message):
is_reply = True
is_request = True
class HelpMessage(Request):
is_reply = True #!sic!
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