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:
@ -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])
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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__))
|
||||||
|
@ -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:
|
||||||
|
@ -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('--------------------')
|
||||||
|
@ -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)
|
||||||
|
@ -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')
|
||||||
|
@ -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():
|
||||||
|
Reference in New Issue
Block a user