rework message syntax to conform to latest decisions

needs a bigger rework, since READREPLY and EVENTREPLY are now different....
Also the format of the error-reply got changed :(

Change-Id: I1760743238227730ee49aaf92b54e0ff5f25423b
Reviewed-on: https://forge.frm2.tum.de/review/20246
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber
2019-03-27 15:29:19 +01:00
parent 94619723a9
commit 94959f2e9b
9 changed files with 107 additions and 101 deletions

View File

@ -34,12 +34,14 @@ import mlzlog
import serial import serial
from secop.datatypes import CommandType, EnumType, get_datatype from secop.datatypes import CommandType, EnumType, get_datatype
#from secop.protocol.encoding import ENCODERS
#from secop.protocol.framing import FRAMERS
#from secop.protocol.messages import *
from secop.errors import EXCEPTIONS from secop.errors import EXCEPTIONS
from secop.lib import formatException, formatExtendedStack, mkthread from secop.lib import formatException, formatExtendedStack, mkthread
from secop.lib.parsing import format_time, parse_time from secop.lib.parsing import format_time, parse_time
from secop.protocol.messages import BUFFERREQUEST, COMMANDREQUEST, \
DESCRIPTIONREPLY, DESCRIPTIONREQUEST, DISABLEEVENTSREQUEST, \
ENABLEEVENTSREQUEST, ERRORPREFIX, EVENTREPLY, \
HEARTBEATREQUEST, HELPREQUEST, IDENTREQUEST, READREPLY, \
READREQUEST, REQUEST2REPLY, WRITEREPLY, WRITEREQUEST
try: try:
# py3 # py3
@ -76,8 +78,8 @@ class TCPConnection(object):
try: try:
data = u'' data = u''
while True: while True:
newdata = b''
try: try:
newdata = b''
dlist = [self._io.fileno()] dlist = [self._io.fileno()]
rlist, wlist, xlist = select(dlist, dlist, dlist, 1) rlist, wlist, xlist = select(dlist, dlist, dlist, 1)
if dlist[0] in rlist + wlist: if dlist[0] in rlist + wlist:
@ -114,7 +116,7 @@ class TCPConnection(object):
try: try:
return self._readbuffer.get(block=True, timeout=1) return self._readbuffer.get(block=True, timeout=1)
except queue.Empty: except queue.Empty:
continue pass
if not block: if not block:
i -= 1 i -= 1
@ -244,7 +246,7 @@ class Client(object):
self.secop_id = line self.secop_id = line
continue continue
msgtype, spec, data = self.decode_message(line) msgtype, spec, data = self.decode_message(line)
if msgtype in ('event', 'update', 'changed'): if msgtype in (EVENTREPLY, READREPLY, WRITEREPLY):
# handle async stuff # handle async stuff
self._handle_event(spec, data) self._handle_event(spec, data)
# handle sync stuff # handle sync stuff
@ -252,23 +254,23 @@ class Client(object):
def _handle_sync_reply(self, msgtype, spec, data): def _handle_sync_reply(self, msgtype, spec, data):
# handle sync stuff # handle sync stuff
if msgtype == "error": if msgtype.startswith(ERRORPREFIX):
# find originating msgtype and map to expected_reply_type # find originating msgtype and map to expected_reply_type
# errormessages carry to offending request as the first # errormessages carry to offending request as the first
# result in the resultist # result in the resultist
_msgtype, _spec, _data = self.decode_message(data[0]) request = msgtype[len(ERRORPREFIX):]
_reply = self._get_reply_from_request(_msgtype) reply = REQUEST2REPLY.get(request, request)
entry = self.expected_replies.get((_reply, _spec), None) entry = self.expected_replies.get((reply, spec), None)
if entry: if entry:
self.log.error("request %r resulted in Error %r" % self.log.error("request %r resulted in Error %r" %
(data[0], spec)) ("%s %s" % (request, spec), (data[0], data[1])))
entry.extend([True, EXCEPTIONS[spec](*data)]) entry.extend([True, EXCEPTIONS[data[0]](*data[1:])])
entry[0].set() entry[0].set()
return return
self.log.error("got an unexpected error %s %r" % (spec, data[0])) self.log.error("got an unexpected %s %r" % (msgtype,data[0:1]))
return return
if msgtype == "describing": if msgtype == DESCRIPTIONREPLY:
entry = self.expected_replies.get((msgtype, ''), None) entry = self.expected_replies.get((msgtype, ''), None)
else: else:
entry = self.expected_replies.get((msgtype, spec), None) entry = self.expected_replies.get((msgtype, spec), None)
@ -291,7 +293,7 @@ class Client(object):
return req return req
def decode_message(self, msg): def decode_message(self, msg):
"""return a decoded message tripel""" """return a decoded message triple"""
msg = msg.strip() msg = msg.strip()
if ' ' not in msg: if ' ' not in msg:
return msg, '', None return msg, '', None
@ -359,7 +361,7 @@ class Client(object):
raise RuntimeError('Error decoding substruct of descriptive data: %r\n%r' % (err, data)) raise RuntimeError('Error decoding substruct of descriptive data: %r\n%r' % (err, data))
def _issueDescribe(self): def _issueDescribe(self):
_, _, describing_data = self._communicate('describe') _, _, describing_data = self._communicate(DESCRIPTIONREQUEST)
try: try:
describing_data = self._decode_substruct( describing_data = self._decode_substruct(
['modules'], describing_data) ['modules'], describing_data)
@ -368,12 +370,6 @@ class Client(object):
['accessibles'], module) ['accessibles'], module)
self.describing_data = describing_data self.describing_data = describing_data
# import pprint
# def r(stuff):
# if isinstance(stuff, dict):
# return dict((k,r(v)) for k,v in stuff.items())
# return stuff
# pprint.pprint(r(describing_data))
for module, moduleData in self.describing_data['modules'].items(): for module, moduleData in self.describing_data['modules'].items():
for aname, adata in moduleData['accessibles'].items(): for aname, adata in moduleData['accessibles'].items():
@ -403,21 +399,6 @@ class Client(object):
def register_shutdown_callback(self, func, arg): def register_shutdown_callback(self, func, arg):
self.connection.callbacks.append((func, arg)) self.connection.callbacks.append((func, arg))
def _get_reply_from_request(self, requesttype):
# maps each (sync) request to the corresponding reply
# XXX: should go to the encoder! and be imported here
REPLYMAP = { # pylint: disable=C0103
"describe": "describing",
"do": "done",
"change": "changed",
"activate": "active",
"deactivate": "inactive",
"read": "update",
#"*IDN?": "SECoP,", # XXX: !!!
"ping": "pong",
}
return REPLYMAP.get(requesttype, requesttype)
def communicate(self, msgtype, spec='', data=None): def communicate(self, msgtype, spec='', data=None):
# only return the data portion.... # only return the data portion....
return self._communicate(msgtype, spec, data)[2] return self._communicate(msgtype, spec, data)[2]
@ -426,15 +407,17 @@ class Client(object):
self.log.debug('communicate: %r %r %r' % (msgtype, spec, data)) self.log.debug('communicate: %r %r %r' % (msgtype, spec, data))
if self.stopflag: if self.stopflag:
raise RuntimeError('alreading stopping!') raise RuntimeError('alreading stopping!')
if msgtype == "*IDN?": if msgtype == IDENTREQUEST:
return self.secop_id return self.secop_id
# sanitize input # sanitize input
msgtype = str(msgtype) msgtype = str(msgtype)
spec = str(spec) spec = str(spec)
if msgtype not in ('*IDN?', 'describe', 'activate', 'deactivate', 'do', if msgtype not in (DESCRIPTIONREQUEST, ENABLEEVENTSREQUEST,
'change', 'read', 'ping', 'help'): DISABLEEVENTSREQUEST, COMMANDREQUEST,
WRITEREQUEST, BUFFERREQUEST,
READREQUEST, HEARTBEATREQUEST, HELPREQUEST):
raise EXCEPTIONS['Protocol'](args=[ raise EXCEPTIONS['Protocol'](args=[
self.encode_message(msgtype, spec, data), self.encode_message(msgtype, spec, data),
dict( dict(
@ -443,13 +426,13 @@ class Client(object):
]) ])
# handle syntactic sugar # handle syntactic sugar
if msgtype == 'change' and ':' not in spec: if msgtype == WRITEREQUEST and ':' not in spec:
spec = spec + ':target' spec = spec + ':target'
if msgtype == 'read' and ':' not in spec: if msgtype == READREQUEST and ':' not in spec:
spec = spec + ':value' spec = spec + ':value'
# check if such a request is already out # check if such a request is already out
rply = self._get_reply_from_request(msgtype) rply = REQUEST2REPLY[msgtype]
if (rply, spec) in self.expected_replies: if (rply, spec) in self.expected_replies:
raise RuntimeError( raise RuntimeError(
"can not have more than one requests of the same type at the same time!" "can not have more than one requests of the same type at the same time!"
@ -487,7 +470,7 @@ class Client(object):
def quit(self): def quit(self):
# after calling this the client is dysfunctional! # after calling this the client is dysfunctional!
self.communicate('deactivate') self.communicate(DISABLEEVENTSREQUEST)
self.stopflag = True self.stopflag = True
if self._thread and self._thread.is_alive(): if self._thread and self._thread.is_alive():
self.thread.join(self._thread) self.thread.join(self._thread)
@ -495,10 +478,10 @@ class Client(object):
def startup(self, _async=False): def startup(self, _async=False):
self._issueDescribe() self._issueDescribe()
# always fill our cache # always fill our cache
self.communicate('activate') self.communicate(ENABLEEVENTSREQUEST)
# deactivate updates if not wanted # deactivate updates if not wanted
if not _async: if not _async:
self.communicate('deactivate') self.communicate(DISABLEEVENTSREQUEST)
def queryCache(self, module, parameter=None): def queryCache(self, module, parameter=None):
result = self._cache.get(module, {}) result = self._cache.get(module, {})
@ -509,7 +492,7 @@ class Client(object):
return result return result
def getParameter(self, module, parameter): def getParameter(self, module, parameter):
return self.communicate('read', '%s:%s' % (module, parameter)) return self.communicate(READREQUEST, '%s:%s' % (module, parameter))
def setParameter(self, module, parameter, value): def setParameter(self, module, parameter, value):
datatype = self._getDescribingParameterData(module, datatype = self._getDescribingParameterData(module,
@ -517,7 +500,7 @@ class Client(object):
value = datatype.from_string(value) value = datatype.from_string(value)
value = datatype.export_value(value) value = datatype.export_value(value)
self.communicate('change', '%s:%s' % (module, parameter), value) self.communicate(WRITEREQUEST, '%s:%s' % (module, parameter), value)
@property @property
def describingData(self): def describingData(self):
@ -559,7 +542,7 @@ class Client(object):
def execCommand(self, module, command, args): def execCommand(self, module, command, args):
# ignore reply message + reply specifier, only return data # ignore reply message + reply specifier, only return data
return self._communicate('do', '%s:%s' % (module, command), list(args) if args else None)[2] return self._communicate(COMMANDREQUEST, '%s:%s' % (module, command), list(args) if args else None)[2]
def getProperties(self, module, parameter): def getProperties(self, module, parameter):
return self.describing_data['modules'][module]['accessibles'][parameter] return self.describing_data['modules'][module]['accessibles'][parameter]
@ -574,4 +557,4 @@ class Client(object):
def ping(self, pingctr=[0]): # pylint: disable=W0102 def ping(self, pingctr=[0]): # pylint: disable=W0102
pingctr[0] = pingctr[0] + 1 pingctr[0] = pingctr[0] + 1
self.communicate("ping", pingctr[0]) self.communicate(HEARTBEATREQUEST, pingctr[0])

View File

@ -121,9 +121,10 @@ class ModuleMeta(type):
if isinstance(v.datatype, EnumType) and not v.datatype._enum.name: if isinstance(v.datatype, EnumType) and not v.datatype._enum.name:
v.datatype._enum.name = k v.datatype._enum.name = k
# newtype.accessibles will be used in 2 places only: # newtype.accessibles will be used in 3 places only:
# 1) for inheritance (see above) # 1) for inheritance (see above)
# 2) for the describing message # 2) for the describing message
# 3) by code needing to access the Parameter/Command object (i.e. checking datatypes)
newtype.accessibles = OrderedDict(sorted(accessibles.items(), key=lambda item: item[1].ctr)) newtype.accessibles = OrderedDict(sorted(accessibles.items(), key=lambda item: item[1].ctr))
# check validity of Parameter entries # check validity of Parameter entries
@ -143,7 +144,12 @@ class ModuleMeta(type):
def wrapped_rfunc(self, maxage=0, pname=pname, rfunc=rfunc): def wrapped_rfunc(self, maxage=0, pname=pname, rfunc=rfunc):
if rfunc: if rfunc:
self.log.debug("rfunc(%s): call %r" % (pname, rfunc)) self.log.debug("rfunc(%s): call %r" % (pname, rfunc))
value = rfunc(self, maxage) try:
value = rfunc(self, maxage)
except Exception as e:
pobj = self.accessibles[pname]
self.DISPATCHER.announce_update_error(self, pname, pobj, e)
raise e
else: else:
# return cached value # return cached value
self.log.debug("rfunc(%s): return cached value" % pname) self.log.debug("rfunc(%s): return cached value" % pname)
@ -170,7 +176,11 @@ class ModuleMeta(type):
value = pobj.datatype.validate(value) value = pobj.datatype.validate(value)
if wfunc: if wfunc:
self.log.debug('calling %r(%r)' % (wfunc, value)) self.log.debug('calling %r(%r)' % (wfunc, value))
returned_value = wfunc(self, value) try:
returned_value = wfunc(self, value)
except Exception as e:
self.DISPATCHER.announce_update_error(self, pname, pobj, e)
raise e
if returned_value is not None: if returned_value is not None:
value = returned_value value = returned_value
# XXX: use setattr or direct manipulation # XXX: use setattr or direct manipulation

View File

@ -26,14 +26,14 @@ from __future__ import division, print_function
import sys import sys
import time import time
from secop.datatypes import EnumType, FloatRange, StringType, TupleOf, \ from secop.datatypes import EnumType, FloatRange, \
get_datatype StringType, TupleOf, get_datatype
from secop.errors import ConfigError, ProgrammingError from secop.errors import ConfigError, ProgrammingError
from secop.lib import formatException, formatExtendedStack, mkthread, \ from secop.lib import formatException, \
unset_value formatExtendedStack, mkthread, unset_value
from secop.lib.enum import Enum from secop.lib.enum import Enum
from secop.metaclass import ModuleMeta, add_metaclass from secop.metaclass import ModuleMeta, add_metaclass
from secop.params import Command, Override, Parameter, PREDEFINED_ACCESSIBLES from secop.params import PREDEFINED_ACCESSIBLES, Command, Override, Parameter
# XXX: connect with 'protocol'-Modules. # XXX: connect with 'protocol'-Modules.
# Idea: every Module defined herein is also a 'protocol'-Module, # Idea: every Module defined herein is also a 'protocol'-Module,

View File

@ -27,8 +27,6 @@ from secop.datatypes import CommandType, DataType
from secop.errors import ProgrammingError from secop.errors import ProgrammingError
from secop.lib import unset_value from secop.lib import unset_value
EVENT_ONLY_ON_CHANGED_VALUES = False
class CountedObj(object): class CountedObj(object):
ctr = [0] ctr = [0]
@ -193,6 +191,9 @@ class Override(CountedObj):
if isinstance(obj, Accessible): if isinstance(obj, Accessible):
props = obj.__dict__.copy() props = obj.__dict__.copy()
for key in self.kwds: for key in self.kwds:
if key == 'unit':
# XXX: HACK!
continue
if key not in props and key not in type(obj).valid_properties: if key not in props and key not in type(obj).valid_properties:
raise ProgrammingError( "%s is not a valid %s property" % raise ProgrammingError( "%s is not a valid %s property" %
(key, type(obj).__name__)) (key, type(obj).__name__))

View File

@ -42,15 +42,15 @@ import threading
from time import time as currenttime from time import time as currenttime
from secop.errors import SECoPServerError as InternalError from secop.errors import SECoPServerError as InternalError
from secop.errors import BadValueError, NoSuchCommandError, \ from secop.errors import BadValueError, NoSuchCommandError, NoSuchModuleError, \
NoSuchModuleError, NoSuchParameterError, ProtocolError, ReadOnlyError NoSuchParameterError, ProtocolError, ReadOnlyError, SECoPError
from secop.params import Parameter from secop.params import Parameter
from secop.protocol.messages import COMMANDREPLY, DESCRIPTIONREPLY, \ from secop.protocol.messages import COMMANDREPLY, DESCRIPTIONREPLY, \
DISABLEEVENTSREPLY, ENABLEEVENTSREPLY, EVENTREPLY, \ DISABLEEVENTSREPLY, ENABLEEVENTSREPLY, ERRORPREFIX, EVENTREPLY, \
HEARTBEATREPLY, IDENTREPLY, IDENTREQUEST, WRITEREPLY HEARTBEATREPLY, IDENTREPLY, IDENTREQUEST, READREPLY, WRITEREPLY
try: try:
unicode('a') unicode
except NameError: except NameError:
# no unicode on py3 # no unicode on py3
unicode = str # pylint: disable=redefined-builtin unicode = str # pylint: disable=redefined-builtin
@ -104,6 +104,19 @@ class Dispatcher(object):
[pobj.export_value(), dict(t=pobj.timestamp)]) [pobj.export_value(), dict(t=pobj.timestamp)])
self.broadcast_event(msg) self.broadcast_event(msg)
def announce_update_error(self, moduleobj, pname, pobj, err):
"""called by modules param setters/getters to notify subscribers
of problems
"""
# argument pname is no longer used here - should we remove it?
if not isinstance(err, SECoPError):
err = InternalError(err)
msg = (ERRORPREFIX + EVENTREPLY, u'%s:%s' % (moduleobj.name, pobj.export),
# error-report !
[err.name, repr(err), dict(t=currenttime())])
self.broadcast_event(msg)
def subscribe(self, conn, eventname): def subscribe(self, conn, eventname):
self._subscriptions.setdefault(eventname, set()).add(conn) self._subscriptions.setdefault(eventname, set()).add(conn)
@ -140,7 +153,7 @@ class Dispatcher(object):
return self._modules[modulename] return self._modules[modulename]
elif modulename in list(self._modules.values()): elif modulename in list(self._modules.values()):
return modulename return modulename
raise NoSuchModuleError('Module does not exist on this SEC-Node!') raise NoSuchModuleError(u'Module does not exist on this SEC-Node!')
def remove_module(self, modulename_or_obj): def remove_module(self, modulename_or_obj):
moduleobj = self.get_module(modulename_or_obj) moduleobj = self.get_module(modulename_or_obj)
@ -188,18 +201,18 @@ class Dispatcher(object):
result[u'modules'].append([modulename, mod_desc]) result[u'modules'].append([modulename, mod_desc])
result[u'equipment_id'] = self.equipment_id result[u'equipment_id'] = self.equipment_id
result[u'firmware'] = u'FRAPPY - The Python Framework for SECoP' result[u'firmware'] = u'FRAPPY - The Python Framework for SECoP'
result[u'version'] = u'2018.09' result[u'version'] = u'2019.03'
result.update(self.nodeprops) result.update(self.nodeprops)
return result return result
def _execute_command(self, modulename, command, argument=None): def _execute_command(self, modulename, command, argument=None):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError('Module does not exist on this SEC-Node!') raise NoSuchModuleError(u'Module does not exist on this SEC-Node!')
cmdspec = moduleobj.accessibles.get(command, None) cmdspec = moduleobj.accessibles.get(command, None)
if cmdspec is None: if cmdspec is None:
raise NoSuchCommandError('Module has no such command!') raise NoSuchCommandError(u'Module has no such command!')
if argument is None and cmdspec.datatype.argtype is not None: if argument is None and cmdspec.datatype.argtype is not None:
raise BadValueError(u'Command needs an argument!') raise BadValueError(u'Command needs an argument!')
@ -216,16 +229,16 @@ class Dispatcher(object):
def _setParameterValue(self, modulename, exportedname, value): def _setParameterValue(self, modulename, exportedname, value):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError('Module does not exist on this SEC-Node!') raise NoSuchModuleError(u'Module does not exist on this SEC-Node!')
pname = moduleobj.accessiblename2attr.get(exportedname, None) pname = moduleobj.accessiblename2attr.get(exportedname, None)
pobj = moduleobj.accessibles.get(pname, None) pobj = moduleobj.accessibles.get(pname, None)
if pobj is None or not isinstance(pobj, Parameter): if pobj is None or not isinstance(pobj, Parameter):
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!') raise NoSuchParameterError(u'Module has no such parameter on this SEC-Node!')
if pobj.constant is not None: if pobj.constant is not None:
raise ReadOnlyError('This parameter is constant and can not be accessed remotely.') raise ReadOnlyError(u'This parameter is constant and can not be accessed remotely.')
if pobj.readonly: if pobj.readonly:
raise ReadOnlyError('This parameter can not be changed remotely.') raise ReadOnlyError(u'This parameter can not be changed remotely.')
writefunc = getattr(moduleobj, u'write_%s' % pname, None) writefunc = getattr(moduleobj, u'write_%s' % pname, None)
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
@ -240,14 +253,14 @@ class Dispatcher(object):
def _getParameterValue(self, modulename, exportedname): def _getParameterValue(self, modulename, exportedname):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
if moduleobj is None: if moduleobj is None:
raise NoSuchModuleError('Module does not exist on this SEC-Node!') raise NoSuchModuleError(u'Module does not exist on this SEC-Node!')
pname = moduleobj.accessiblename2attr.get(exportedname, None) pname = moduleobj.accessiblename2attr.get(exportedname, None)
pobj = moduleobj.accessibles.get(pname, None) pobj = moduleobj.accessibles.get(pname, None)
if pobj is None or not isinstance(pobj, Parameter): if pobj is None or not isinstance(pobj, Parameter):
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!') raise NoSuchParameterError(u'Module has no such parameter on this SEC-Node!')
if pobj.constant is not None: if pobj.constant is not None:
raise ReadOnlyError('This parameter is constant and can not be accessed remotely.') raise ReadOnlyError(u'This parameter is constant and can not be accessed remotely.')
readfunc = getattr(moduleobj, u'read_%s' % pname, None) readfunc = getattr(moduleobj, u'read_%s' % pname, None)
if readfunc: if readfunc:
@ -297,12 +310,12 @@ class Dispatcher(object):
def handle_read(self, conn, specifier, data): def handle_read(self, conn, specifier, data):
if data: if data:
raise ProtocolError('poll request don\'t take data!') raise ProtocolError('read requests don\'t take data!')
modulename, pname = specifier, u'value' modulename, pname = specifier, u'value'
if ':' in specifier: if ':' in specifier:
modulename, pname = specifier.split(':', 1) modulename, pname = specifier.split(':', 1)
# XXX: trigger polling and force sending event ??? # XXX: trigger polling and force sending event ???
return (EVENTREPLY, specifier, list(self._getParameterValue(modulename, pname))) return (READREPLY, specifier, list(self._getParameterValue(modulename, pname)))
def handle_change(self, conn, specifier, data): def handle_change(self, conn, specifier, data):
modulename, pname = specifier, u'value' modulename, pname = specifier, u'value'
@ -318,12 +331,12 @@ class Dispatcher(object):
def handle_ping(self, conn, specifier, data): def handle_ping(self, conn, specifier, data):
if data: if data:
raise ProtocolError('poll request don\'t take data!') raise ProtocolError('ping requests don\'t take data!')
return (HEARTBEATREPLY, specifier, [None, {u't':currenttime()}]) return (HEARTBEATREPLY, specifier, [None, {u't':currenttime()}])
def handle_activate(self, conn, specifier, data): def handle_activate(self, conn, specifier, data):
if data: if data:
raise ProtocolError('activate request don\'t take data!') raise ProtocolError('activate requests don\'t take data!')
if specifier: if specifier:
modulename, exportedname = specifier, None modulename, exportedname = specifier, None
if ':' in specifier: if ':' in specifier:
@ -368,6 +381,8 @@ class Dispatcher(object):
return (ENABLEEVENTSREPLY, specifier, None) if specifier else (ENABLEEVENTSREPLY, None, None) return (ENABLEEVENTSREPLY, specifier, None) if specifier else (ENABLEEVENTSREPLY, None, None)
def handle_deactivate(self, conn, specifier, data): def handle_deactivate(self, conn, specifier, data):
if data:
raise ProtocolError('deactivate requests don\'t take data!')
if specifier: if specifier:
self.unsubscribe(conn, specifier) self.unsubscribe(conn, specifier)
else: else:

View File

@ -29,7 +29,8 @@ from secop.errors import SECoPError
from secop.lib import formatException, \ from secop.lib import formatException, \
formatExtendedStack, formatExtendedTraceback formatExtendedStack, formatExtendedTraceback
from secop.protocol.interface import decode_msg, encode_msg_frame, get_msg from secop.protocol.interface import decode_msg, encode_msg_frame, get_msg
from secop.protocol.messages import HELPREPLY, HELPREQUEST, HelpMessage from secop.protocol.messages import ERRORPREFIX, \
HELPREPLY, HELPREQUEST, HelpMessage
try: try:
import socketserver # py3 import socketserver # py3
@ -113,29 +114,23 @@ class TCPRequestHandler(socketserver.BaseRequestHandler):
if origin is None: if origin is None:
break # no more messages to process break # no more messages to process
origin = origin.strip() origin = origin.strip()
if origin and origin[0] == CR:
origin = origin[1:]
if origin and origin[-1] == CR:
origin = origin[:-1]
if origin in (HELPREQUEST, ''): # empty string -> send help message if origin in (HELPREQUEST, ''): # empty string -> send help message
for idx, line in enumerate(HelpMessage.splitlines()): for idx, line in enumerate(HelpMessage.splitlines()):
self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line)) self.queue_async_reply((HELPREPLY, '%d' % (idx+1), line))
continue continue
msg = decode_msg(origin)
result = None result = None
try: try:
msg = decode_msg(origin)
result = serverobj.dispatcher.handle_request(self, msg) result = serverobj.dispatcher.handle_request(self, msg)
if (msg[0] == 'read') and result:
# read should only trigger async_replies
self.queue_async_reply(('error', 'InternalError', [origin,
'read should only trigger async data units']))
except SECoPError as err: except SECoPError as err:
result = ('error', err.name, [origin, str(err), {'exception': formatException(), result = (ERRORPREFIX + msg[0], msg[1], [err.name, str(err),
'traceback': formatExtendedStack()}]) {'exception': formatException(),
'traceback': formatExtendedStack()}])
except Exception as err: except Exception as err:
# create Error Obj instead # create Error Obj instead
result = ('error', 'InternalError', [origin, str(err), {'exception': formatException(), result = (ERRORPREFIX + msg[0], msg[1], ['InternalError', str(err),
'traceback': formatExtendedStack()}]) {'exception': formatException(),
'traceback': formatExtendedStack()}])
print('--------------------') print('--------------------')
print(formatException()) print(formatException())
print('--------------------') print('--------------------')

View File

@ -26,7 +26,7 @@ from __future__ import division, print_function
IDENTREQUEST = u'*IDN?' # literal IDENTREQUEST = u'*IDN?' # literal
# literal! first part is fixed! # literal! first part is fixed!
IDENTREPLY = u'SINE2020&ISSE,SECoP,V2018-11-07,v1.0\\beta' IDENTREPLY = u'SINE2020&ISSE,SECoP,V2019-03-20,v1.0 RC1'
DESCRIPTIONREQUEST = u'describe' # literal DESCRIPTIONREQUEST = u'describe' # literal
DESCRIPTIONREPLY = u'describing' # +<id> +json DESCRIPTIONREPLY = u'describing' # +<id> +json
@ -52,13 +52,15 @@ BUFFERREQUEST = u'buffer'
BUFFERREPLY = u'buffered' BUFFERREPLY = u'buffered'
# +module[:parameter] -> NO direct reply, calls POLL internally! # +module[:parameter] -> NO direct reply, calls POLL internally!
POLLREQUEST = u'read' READREQUEST = u'read'
READREPLY = u'reply' # See Issue 54
EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict) EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = u'ping' # +nonce_without_space HEARTBEATREQUEST = u'ping' # +nonce_without_space
HEARTBEATREPLY = u'pong' # +nonce_without_space HEARTBEATREPLY = u'pong' # +nonce_without_space
ERRORREPLY = u'error' # +errorclass +json_extended_info ERRORPREFIX = u'error_' # + specifier + json_extended_info(error_report)
HELPREQUEST = u'help' # literal HELPREQUEST = u'help' # literal
HELPREPLY = u'helping' # +line number +json_text HELPREPLY = u'helping' # +line number +json_text
@ -72,7 +74,7 @@ REQUEST2REPLY = {
COMMANDREQUEST: COMMANDREPLY, COMMANDREQUEST: COMMANDREPLY,
WRITEREQUEST: WRITEREPLY, WRITEREQUEST: WRITEREPLY,
BUFFERREQUEST: BUFFERREPLY, BUFFERREQUEST: BUFFERREPLY,
POLLREQUEST: EVENTREPLY, READREQUEST: READREPLY,
HEARTBEATREQUEST: HEARTBEATREPLY, HEARTBEATREQUEST: HEARTBEATREPLY,
HELPREQUEST: HELPREPLY, HELPREQUEST: HELPREPLY,
} }
@ -88,6 +90,6 @@ HelpMessage = u"""Try one of the following:
'%s <nonce>' to request a heartbeat response '%s <nonce>' to request a heartbeat response
'%s' to activate async updates '%s' to activate async updates
'%s' to deactivate updates '%s' to deactivate updates
""" % (IDENTREQUEST, DESCRIPTIONREQUEST, POLLREQUEST, """ % (IDENTREQUEST, DESCRIPTIONREQUEST, READREQUEST,
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST, WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST) ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)

View File

@ -28,7 +28,7 @@ from math import atan
from secop.datatypes import EnumType, FloatRange, TupleOf from secop.datatypes import EnumType, FloatRange, TupleOf
from secop.lib import clamp, mkthread from secop.lib import clamp, mkthread
from secop.modules import Drivable, Parameter, Command, Override from secop.modules import Command, Drivable, Override, Parameter
# test custom property (value.test can be changed in config file) # test custom property (value.test can be changed in config file)
Parameter.add_property('test') Parameter.add_property('test')

View File

@ -28,7 +28,7 @@ import pytest
from secop.datatypes import ArrayOf, BLOBType, BoolType, \ from secop.datatypes import ArrayOf, BLOBType, BoolType, \
DataType, EnumType, FloatRange, IntRange, ProgrammingError, \ DataType, EnumType, FloatRange, IntRange, ProgrammingError, \
StringType, StructOf, TupleOf, get_datatype, ScaledInteger ScaledInteger, StringType, StructOf, TupleOf, get_datatype
def test_DataType(): def test_DataType():