
Also implement lots of fixes and improvements. fixes: #3381 Change-Id: Ibe6664da00756ae5813b90f190295045808b2ff0
388 lines
15 KiB
Python
388 lines
15 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# *****************************************************************************
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Module authors:
|
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
|
#
|
|
# *****************************************************************************
|
|
"""Encoding/decoding Messages"""
|
|
|
|
# implement as class as they may need some internal 'state' later on
|
|
# (think compressors)
|
|
|
|
from __future__ import print_function
|
|
|
|
from secop.protocol.encoding import MessageEncoder
|
|
from secop.protocol.messages import *
|
|
from secop.protocol.errors import ProtocolError
|
|
|
|
import ast
|
|
import re
|
|
|
|
|
|
def floatify(s):
|
|
try:
|
|
return int(s)
|
|
except (ValueError, TypeError):
|
|
try:
|
|
return float(s)
|
|
except (ValueError, TypeError):
|
|
return s
|
|
|
|
|
|
def devspec(msg, result=''):
|
|
if isinstance(msg, Message):
|
|
devs = ','.join(msg.devs)
|
|
pars = ','.join(msg.pars)
|
|
props = ','.join(msg.props)
|
|
else:
|
|
devs = msg.dev
|
|
pars = msg.par
|
|
props = msg.prop
|
|
if devs:
|
|
result = '%s %s' % (result, devs)
|
|
if pars:
|
|
result = '%s:%s' % (result, pars)
|
|
if props:
|
|
result = '%s:%s' % (result, props)
|
|
return result.strip()
|
|
|
|
|
|
def encode_value(value, prefix='', targetvalue='', cmd=''):
|
|
result = [prefix]
|
|
if value.dev:
|
|
result.append(' ')
|
|
result.append(value.dev)
|
|
if value.param:
|
|
result.append(':%s' % value.param)
|
|
if value.prop:
|
|
result.append(':%s' % value.prop)
|
|
# only needed for WriteMessages
|
|
if targetvalue:
|
|
result.append('=%s' % repr(targetvalue))
|
|
# only needed for CommandMessages
|
|
if cmd:
|
|
result.append(':%s' % cmd)
|
|
if value.value != Ellipsis:
|
|
# results always have a ';'
|
|
result.append('=%s;' % repr(value.value))
|
|
result.append(';'.join('%s=%s' % (qn, repr(qv))
|
|
for qn, qv in value.qualifiers.items()))
|
|
return ''.join(result).strip()
|
|
|
|
|
|
DEMO_RE_ERROR = re.compile(
|
|
r"""^error\s(?P<errortype>\w+)\s(?P<msgtype>\w+)?(?:\s(?P<devs>\*|[\w,]+)(?:\:(?P<pars>\*|[\w,]+)(?:\:(?P<props>\*|[\w,]+))?)?)?(?:(?:\=(?P<target>[^=;\s"]*))|(?:\((?P<cmdargs>[^\)]*)\)))?(?:\s"(?P<errorstring>[^"]*)")$""",
|
|
re.X)
|
|
DEMO_RE_OTHER = re.compile(
|
|
r"""^(?P<msgtype>\w+)(?:\s(?P<devs>\*|[\w,]+)(?:\:(?P<pars>\*|[\w,]+)(?:\:(?P<props>\*|[\w,]+))?)?)?(?:(?:\=(?P<target>[^=;]*))|(?::(?P<cmd>\w+)\((?P<args>[^\)]*)\)))?(?:=(?P<readback>[^;]+);(?P<qualifiers>.*))?$""",
|
|
re.X)
|
|
|
|
|
|
class DemoEncoder(MessageEncoder):
|
|
|
|
def __init__(self, *args, **kwds):
|
|
MessageEncoder.__init__(self, *args, **kwds)
|
|
self.result = [] # for decoding
|
|
self.expect_lines = 1
|
|
# self.tests()
|
|
|
|
def encode(self, msg):
|
|
"""msg object -> transport layer message"""
|
|
# fun for Humans
|
|
if isinstance(msg, HelpMessage):
|
|
r = ['#5']
|
|
r.append("help Try one of the following:")
|
|
r.append("help 'list' to query a list of modules")
|
|
r.append("help 'read <module>' to read a module")
|
|
r.append("help 'list <module>' to query a list of parameters")
|
|
r.append("help ... more to come")
|
|
return '\n'.join(r)
|
|
|
|
if isinstance(msg, (ListMessage, SubscribeMessage, UnsubscribeMessage,
|
|
TriggerMessage)):
|
|
msgtype = msg.MSGTYPE
|
|
if msg.result:
|
|
if msg.devs:
|
|
# msg.result is always a list!
|
|
return "%s=%s" % (devspec(msg, msgtype),
|
|
','.join(map(str, msg.result)))
|
|
return "%s=%s" % (msgtype, ','.join(map(str, msg.result)))
|
|
return devspec(msg, msgtype).strip()
|
|
|
|
if isinstance(msg, (ReadMessage, PollMessage, EventMessage)):
|
|
msgtype = msg.MSGTYPE
|
|
result = []
|
|
if len(msg.result or []) > 1:
|
|
result.append("#%d" % len(msg.result))
|
|
for val in msg.result or []:
|
|
# encode 1..N replies
|
|
result.append(encode_value(val, msgtype))
|
|
if not msg.result:
|
|
# encode a request (no results -> reply, else an error would
|
|
# have been sent)
|
|
result.append(devspec(msg, msgtype))
|
|
return '\n'.join(result)
|
|
|
|
if isinstance(msg, WriteMessage):
|
|
result = []
|
|
if len(msg.result or []) > 1:
|
|
result.append("#%d" % len(msg.result))
|
|
for val in msg.result or []:
|
|
# encode 1..N replies
|
|
result.append(
|
|
encode_value(
|
|
val, 'write', targetvalue=msg.target))
|
|
if not msg.result:
|
|
# encode a request (no results -> reply, else an error would
|
|
# have been sent)
|
|
result.append('%s=%r' % (devspec(msg, 'write'), msg.target))
|
|
return '\n'.join(result)
|
|
|
|
if isinstance(msg, CommandMessage):
|
|
result = []
|
|
if len(msg.result or []) > 1:
|
|
result.append("#%d" % len(msg.result))
|
|
for val in msg.result or []:
|
|
# encode 1..N replies
|
|
result.append(
|
|
encode_value(
|
|
val,
|
|
'command',
|
|
cmd='%s(%s)' % (msg.cmd, ','.join(msg.args))))
|
|
if not msg.result:
|
|
# encode a request (no results -> reply, else an error would
|
|
# have been sent)
|
|
result.append('%s:%s(%s)' % (devspec(msg, 'command'), msg.cmd,
|
|
','.join(msg.args)))
|
|
return '\n'.join(result)
|
|
|
|
if isinstance(msg, ErrorMessage):
|
|
return ('%s %s' % (devspec(msg, 'error %s' % msg.errortype),
|
|
msg.errorstring)).strip()
|
|
|
|
return 'Can not handle object %r!' % msg
|
|
|
|
def decode(self, encoded):
|
|
if encoded.startswith('#'):
|
|
# XXX: check if last message was complete
|
|
self.expect_lines = int(encoded[1:])
|
|
if self.result:
|
|
# XXX: also flag an error?
|
|
self.result = []
|
|
return None
|
|
|
|
if encoded == '':
|
|
return HelpMessage()
|
|
# now decode the message and append to self.result
|
|
msg = self.decode_single_message(encoded)
|
|
if msg:
|
|
# XXX: check if messagetype is the same as the already existing,
|
|
# else error
|
|
self.result.append(msg)
|
|
else:
|
|
# XXX: flag an error?
|
|
return HelpMessage()
|
|
|
|
self.expect_lines -= 1
|
|
if self.expect_lines <= 0:
|
|
# reconstruct a multi-reply-message from the entries
|
|
# return the first message, but extend the result list first
|
|
# if there is only 1 message, just return this
|
|
res = self.result.pop(0)
|
|
while self.result:
|
|
m = self.result.pop(0)
|
|
res.result.append(m.result[0])
|
|
self.expect_lines = 1
|
|
return res
|
|
|
|
# no complete message yet
|
|
return None
|
|
|
|
def decode_single_message(self, encoded):
|
|
# just decode a single message line
|
|
|
|
# 1) check for error msgs (more specific first)
|
|
m = DEMO_RE_ERROR.match(encoded)
|
|
if m:
|
|
return ErrorMessage(**m.groupdict())
|
|
|
|
# 2) check for 'normal' message
|
|
m = DEMO_RE_OTHER.match(encoded)
|
|
if m:
|
|
mgroups = m.groupdict()
|
|
msgtype = mgroups.pop('msgtype')
|
|
|
|
# reformat devspec
|
|
def helper(stuff, sep=','):
|
|
if not stuff:
|
|
return []
|
|
if sep in stuff:
|
|
return stuff.split(sep)
|
|
return [stuff]
|
|
|
|
devs = helper(mgroups.pop('devs'))
|
|
pars = helper(mgroups.pop('pars'))
|
|
props = helper(mgroups.pop('props'))
|
|
|
|
# sugar for listing stuff:
|
|
# map list -> list *
|
|
# map list x -> list x:*
|
|
# map list x:y -> list x:y:*
|
|
if msgtype == LIST:
|
|
if not devs:
|
|
devs = ['*']
|
|
elif devs[0] != '*':
|
|
if not pars:
|
|
pars = ['*']
|
|
elif pars[0] != '*':
|
|
if not props:
|
|
props = ['*']
|
|
|
|
# reformat cmdargs
|
|
args = ast.literal_eval(mgroups.pop('args') or '()')
|
|
if msgtype == COMMAND:
|
|
mgroups['args'] = args
|
|
|
|
# reformat qualifiers
|
|
print(mgroups)
|
|
quals = dict(
|
|
qual.split('=', 1)
|
|
for qual in helper(mgroups.pop('qualifiers', ';')))
|
|
|
|
# reformat value
|
|
result = []
|
|
readback = mgroups.pop('readback')
|
|
if readback or quals:
|
|
valargs = dict()
|
|
if devs:
|
|
valargs['dev'] = devs[0]
|
|
if pars:
|
|
valargs['par'] = pars[0]
|
|
if props:
|
|
valargs['prop'] = props[0]
|
|
result = [Value(floatify(readback), quals, **valargs)]
|
|
if msgtype == LIST and result:
|
|
result = [n.strip() for n in readback.split(',')]
|
|
|
|
# construct messageobj
|
|
if msgtype in MESSAGE:
|
|
return MESSAGE[msgtype](devs=devs,
|
|
pars=pars,
|
|
props=props,
|
|
result=result,
|
|
**mgroups)
|
|
|
|
return ErrorMessage(
|
|
errortype="SyntaxError", errorstring="Can't handle %r" % encoded)
|
|
|
|
def tests(self):
|
|
testmsg = [
|
|
'list',
|
|
'list *',
|
|
'list device',
|
|
'list device:param1,param2',
|
|
'list *:*',
|
|
'list *=ts,tcoil,mf,lhe,ln2;',
|
|
'read blub=12;t=3',
|
|
'command ts:stop()',
|
|
'command mf:quench(1,"now")',
|
|
'error GibbetNich query x:y:z=9 "blubub blah"',
|
|
'#3',
|
|
'read blub:a=12;t=3',
|
|
'read blub:b=13;t=3.1',
|
|
'read blub:c=14;t=3.3',
|
|
]
|
|
for m in testmsg:
|
|
print(repr(m))
|
|
print(self.decode(m))
|
|
print()
|
|
|
|
|
|
DEMO_RE_MZ = re.compile(
|
|
r"""^(?P<type>[a-z]+)? # request type word (read/write/list/...)
|
|
\ ? # optional space
|
|
(?P<device>[a-z][a-z0-9_]*)? # optional devicename
|
|
(?:\:(?P<param>[a-z0-9_]*) # optional ':'+paramname
|
|
(?:\:(?P<prop>[a-z0-9_]*))?)? # optinal ':' + propname
|
|
(?:(?P<op>[=\?])(?P<value>[^;]+)(?:\;(?P<quals>.*))?)?$""",
|
|
re.X)
|
|
|
|
|
|
class DemoEncoder_MZ(MessageEncoder):
|
|
|
|
def decode(sef, encoded):
|
|
m = DEMO_RE_MZ.match(encoded)
|
|
if m:
|
|
print("implement me !")
|
|
return HelpRequest()
|
|
|
|
def encode(self, msg):
|
|
"""msg object -> transport layer message"""
|
|
# fun for Humans
|
|
if isinstance(msg, HelpReply):
|
|
r = []
|
|
r.append("Try one of the following:")
|
|
r.append("'list' to query a list of modules")
|
|
r.append("'read <module>' to read a module")
|
|
r.append("'list <module>' to query a list of parameters")
|
|
r.append("... more to come")
|
|
return '\n'.join(r)
|
|
|
|
return {
|
|
ListDevicesRequest: lambda msg: "list",
|
|
ListDevicesReply: lambda msg: "list=%s" % ','.join(sorted(msg.list_of_devices)),
|
|
GetVersionRequest: lambda msg: "version",
|
|
GetVersionReply: lambda msg: "version=%r" % msg.version,
|
|
ListDeviceParamsRequest: lambda msg: "list %s" % msg.device,
|
|
# do not include a '.' as param name!
|
|
ListDeviceParamsReply: lambda msg: "list %s=%s" % (msg.device, ','.join(sorted(msg.params.keys()))),
|
|
ReadValueRequest: lambda msg: "read %s" % msg.device,
|
|
ReadValueReply: lambda msg: "read %s=%r" % (msg.device, msg.value),
|
|
WriteValueRequest: lambda msg: "write %s=%r" % (msg.device, msg.value),
|
|
WriteValueReply: lambda msg: "write %s=%r" % (msg.device, msg.readback_value),
|
|
ReadParamRequest: lambda msg: "read %s:%s" % (msg.device, msg.param),
|
|
ReadParamReply: lambda msg: "read %s:%s=%r" % (msg.device, msg.param, msg.value),
|
|
WriteParamRequest: lambda msg: "write %s:%s=%r" % (msg.device, msg.param, msg.value),
|
|
WriteParamReply: lambda msg: "write %s:%s=%r" % (msg.device, msg.param, msg.readback_value),
|
|
# extensions
|
|
ReadAllDevicesRequest: lambda msg: "",
|
|
ReadAllDevicesReply: lambda msg: "",
|
|
ListParamPropsRequest: lambda msg: "readprop %s:%s" % (msg.device, msg.param),
|
|
ListParamPropsReply: lambda msg: ["readprop %s:%s" % (msg.device, msg.param)] + ["%s:%s:%s=%s" % (msg.device, msg.param, k, v) for k, v in sorted(msg.props.items())],
|
|
ReadPropertyRequest: lambda msg: "readprop %s:%s:%s" % (msg.device, msg.param, msg.prop),
|
|
ReadPropertyReply: lambda msg: "readprop %s:%s:%s=%s" % (msg.device, msg.param, msg.prop, msg.value),
|
|
AsyncDataUnit: lambda msg: "",
|
|
SubscribeRequest: lambda msg: "subscribe %s:%s" % (msg.device, msg.param) if msg.param else ("subscribe %s" % msg.device),
|
|
SubscribeReply: lambda msg: "subscribe %s:%s" % (msg.device, msg.param) if msg.param else ("subscribe %s" % msg.device),
|
|
UnSubscribeRequest: lambda msg: "",
|
|
UnSubscribeReply: lambda msg: "",
|
|
CommandRequest: lambda msg: "command %s:%s" % (msg.device, msg.command),
|
|
CommandReply: lambda msg: "command %s:%s" % (msg.device, msg.command),
|
|
# errors
|
|
ErrorReply: lambda msg: "",
|
|
InternalError: lambda msg: "",
|
|
ProtocolError: lambda msg: "",
|
|
CommandFailedError: lambda msg: "error CommandError %s:%s %s" % (msg.device, msg.param, msg.error),
|
|
NoSuchCommandError: lambda msg: "error NoSuchCommand %s:%s" % (msg.device, msg.param, msg.error),
|
|
NoSuchDeviceError: lambda msg: "error NoSuchModule %s" % msg.device,
|
|
NoSuchParamError: lambda msg: "error NoSuchParameter %s:%s" % (msg.device, msg.param),
|
|
ParamReadonlyError: lambda msg: "",
|
|
UnsupportedFeatureError: lambda msg: "",
|
|
InvalidParamValueError: lambda msg: "",
|
|
}[msg.__class__](msg)
|