reworking messages
1) start 'bin/secop-server test' 2) connect to localhost port 10767 3) enter help<enter> 4) enjoy Change-Id: I488d5f9cdca8c91c583691ab23f541a4a8759f4e
This commit is contained in:
22
secop/protocol/__init__.py
Normal file
22
secop/protocol/__init__.py
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""SECoP protocl specific stuff"""
|
76
secop/protocol/device.py
Normal file
76
secop/protocol/device.py
Normal file
@ -0,0 +1,76 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Device classes
|
||||
|
||||
"""
|
||||
# XXX: is this still needed ???
|
||||
# see devices.core ....
|
||||
|
||||
from secop.lib import attrdict
|
||||
from secop.protocol import status
|
||||
|
||||
|
||||
# XXX: deriving PARS/CMDS should be done in a suitable metaclass....
|
||||
class Device(object):
|
||||
"""Minimalist Device
|
||||
|
||||
all others derive from this"""
|
||||
name = None
|
||||
|
||||
def read_status(self):
|
||||
raise NotImplementedError('All Devices need a Status!')
|
||||
|
||||
def read_name(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Readable(Device):
|
||||
"""A Readable Device"""
|
||||
unit = ''
|
||||
|
||||
def read_value(self):
|
||||
raise NotImplementedError('A Readable MUST provide a value')
|
||||
|
||||
def read_unit(self):
|
||||
return self.unit
|
||||
|
||||
|
||||
class Writeable(Readable):
|
||||
"""Writeable can be told to change it's vallue"""
|
||||
target = None
|
||||
|
||||
def read_target(self):
|
||||
return self.target
|
||||
|
||||
def write_target(self, target):
|
||||
self.target = target
|
||||
|
||||
|
||||
class Driveable(Writeable):
|
||||
"""A Moveable which may take a while to reach its target,
|
||||
|
||||
hence stopping it may be desired"""
|
||||
|
||||
def do_stop(self):
|
||||
raise NotImplementedError('A Driveable MUST implement the STOP() '
|
||||
'command')
|
359
secop/protocol/dispatcher.py
Normal file
359
secop/protocol/dispatcher.py
Normal file
@ -0,0 +1,359 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Dispatcher for SECoP Messages
|
||||
|
||||
Interface to the service offering part:
|
||||
|
||||
- 'handle_request(connectionobj, data)' handles incoming request
|
||||
will call 'queue_request(data)' on connectionobj before returning
|
||||
- 'add_connection(connectionobj)' registers new connection
|
||||
- 'remove_connection(connectionobj)' removes now longer functional connection
|
||||
- may at any time call 'queue_async_request(connobj, data)' on the connobj
|
||||
|
||||
Interface to the modules:
|
||||
- add_module(modulename, moduleobj, export=True) registers a new module under the
|
||||
given name, may also register it for exporting (making accessible)
|
||||
- get_module(modulename) returns the requested module or None
|
||||
- remove_module(modulename_or_obj): removes the module (during shutdown)
|
||||
|
||||
internal stuff which may be called
|
||||
- list_modules(): return a list of modules + descriptive data as dict
|
||||
- list_module_params():
|
||||
return a list of paramnames for this module + descriptive data
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
|
||||
from messages import *
|
||||
from errors import *
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
def __init__(self, logger, options):
|
||||
self.equipment_id = options.pop('equipment_id')
|
||||
self.log = logger
|
||||
# map ALL modulename -> moduleobj
|
||||
self._dispatcher_modules = {}
|
||||
# list of EXPORTED modules
|
||||
self._dispatcher_export = []
|
||||
# list all connections
|
||||
self._dispatcher_connections = []
|
||||
# active (i.e. broadcast-receiving) connections
|
||||
self._dispatcher_active_connections = set()
|
||||
# map eventname -> list of subscribed connections
|
||||
self._dispatcher_subscriptions = {}
|
||||
self._dispatcher_lock = threading.RLock()
|
||||
|
||||
def handle_request(self, conn, msg):
|
||||
"""handles incoming request
|
||||
|
||||
will call 'queue.request(data)' on conn to send reply before returning
|
||||
"""
|
||||
self.log.debug('Dispatcher: handling msg: %r' % msg)
|
||||
# play thread safe !
|
||||
with self._dispatcher_lock:
|
||||
reply = None
|
||||
# generate reply (coded and framed)
|
||||
msgname = msg.__class__.__name__
|
||||
if msgname.endswith('Request'):
|
||||
msgname = msgname[:-len('Request')]
|
||||
if msgname.endswith('Message'):
|
||||
msgname = msgname[:-len('Message')]
|
||||
self.log.debug('Looking for handle_%s' % msgname)
|
||||
handler = getattr(self, 'handle_%s' % msgname, None)
|
||||
if handler:
|
||||
try:
|
||||
reply = handler(conn, msg)
|
||||
except SECOPError as err:
|
||||
self.log.exception(err)
|
||||
reply = ErrorMessage(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)])
|
||||
except Exception as err:
|
||||
self.log.exception(err)
|
||||
reply = ErrorMessage(errorclass='InternalError',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
else:
|
||||
self.log.debug('Can not handle msg %r' % msg)
|
||||
reply = self.unhandled(conn, msg)
|
||||
if reply:
|
||||
conn.queue_reply(reply)
|
||||
|
||||
def broadcast_event(self, msg, reallyall=False):
|
||||
"""broadcasts a msg to all active connections"""
|
||||
if reallyall:
|
||||
listeners = self._dispatcher_connections
|
||||
else:
|
||||
if getattr(msg, 'command', None) is None:
|
||||
eventname = '%s:%s' % (msg.module, msg.parameter if msg.parameter else 'value')
|
||||
else:
|
||||
eventname = '%s:%s()' % (msg.module, msg.command)
|
||||
listeners = self._dispatcher_subscriptions.get(eventname, [])
|
||||
listeners += list(self._dispatcher_active_connections)
|
||||
for conn in listeners:
|
||||
conn.queue_async_reply(msg)
|
||||
|
||||
def announce_update(self, moduleobj, pname, pobj):
|
||||
"""called by modules param setters to notify subscribers of new values
|
||||
"""
|
||||
msg = Value(moduleobj.name, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
||||
self.broadcast_event(msg)
|
||||
|
||||
def subscribe(self, conn, modulename, pname='value'):
|
||||
eventname = '%s:%s' % (modulename, pname)
|
||||
self._dispatcher_subscriptions.setdefault(eventname, set()).add(conn)
|
||||
|
||||
def unsubscribe(self, conn, modulename, pname='value'):
|
||||
eventname = '%s:%s' % (modulename, pname)
|
||||
if eventname in self._dispatcher_subscriptions:
|
||||
self._dispatcher_subscriptions.remove(conn)
|
||||
|
||||
def add_connection(self, conn):
|
||||
"""registers new connection"""
|
||||
self._dispatcher_connections.append(conn)
|
||||
|
||||
def remove_connection(self, conn):
|
||||
"""removes now longer functional connection"""
|
||||
if conn in self._dispatcher_connections:
|
||||
self._dispatcher_connections.remove(conn)
|
||||
for _evt, conns in self._dispatcher_subscriptions.items():
|
||||
conns.discard(conn)
|
||||
|
||||
def activate_connection(self, conn):
|
||||
self._dispatcher_active_connections.add(conn)
|
||||
|
||||
def deactivate_connection(self, conn):
|
||||
self._dispatcher_active_connections.discard(conn)
|
||||
|
||||
def register_module(self, moduleobj, modulename, export=True):
|
||||
self.log.debug('registering module %r as %s (export=%r)' %
|
||||
(moduleobj, modulename, export))
|
||||
self._dispatcher_modules[modulename] = moduleobj
|
||||
if export:
|
||||
self._dispatcher_export.append(modulename)
|
||||
|
||||
def get_module(self, modulename):
|
||||
module = self._dispatcher_modules.get(modulename, modulename)
|
||||
if module != modulename:
|
||||
self.log.debug('get_module(%r) -> %r' % (modulename, module))
|
||||
return module
|
||||
|
||||
def remove_module(self, modulename_or_obj):
|
||||
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
|
||||
modulename = moduleobj.name
|
||||
if modulename in self._dispatcher_export:
|
||||
self._dispatcher_export.remove(modulename)
|
||||
self._dispatcher_modules.pop(modulename)
|
||||
# XXX: also clean _dispatcher_subscriptions
|
||||
|
||||
def list_module_names(self):
|
||||
# return a copy of our list
|
||||
return self._dispatcher_export[:]
|
||||
|
||||
def list_modules(self):
|
||||
dn = []
|
||||
dd = {}
|
||||
for modulename in self._dispatcher_export:
|
||||
dn.append(modulename)
|
||||
module = self.get_module(modulename)
|
||||
descriptive_data = {
|
||||
'class': module.__class__.__name__,
|
||||
#'bases': module.__bases__,
|
||||
'parameters': module.PARAMS.keys(),
|
||||
'commands': module.CMDS.keys(),
|
||||
# XXX: what else?
|
||||
}
|
||||
dd[modulename] = descriptive_data
|
||||
return dn, dd
|
||||
|
||||
def list_module_params(self, modulename):
|
||||
self.log.debug('list_module_params(%r)' % modulename)
|
||||
if modulename in self._dispatcher_export:
|
||||
# XXX: omit export=False params!
|
||||
res = {}
|
||||
for paramname, param in self.get_module(modulename).PARAMS.items():
|
||||
if param.export == True:
|
||||
res[paramname] = param
|
||||
self.log.debug('list params for module %s -> %r' %
|
||||
(modulename, res))
|
||||
return res
|
||||
self.log.debug('-> module is not to be exported!')
|
||||
return {}
|
||||
|
||||
def _execute_command(self, modulename, command, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = []
|
||||
|
||||
moduleobj = self.get_module(modulename)
|
||||
if moduleobj is None:
|
||||
raise NoSuchmoduleError(module=modulename)
|
||||
|
||||
cmdspec = moduleobj.CMDS.get(command, None)
|
||||
if cmdspec is None:
|
||||
raise NoSuchCommandError(module=modulename, command=command)
|
||||
if len(cmdspec.arguments) != len(arguments):
|
||||
raise BadValueError(module=modulename, command=command, reason='Wrong number of arguments!')
|
||||
|
||||
# now call func and wrap result as value
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
func = getattr(moduleobj, 'do'+command)
|
||||
res = Value(modulename, command=command, value=func(*arguments), t=time.time())
|
||||
return res
|
||||
|
||||
def _setParamValue(self, modulename, pname, value):
|
||||
moduleobj = self.get_module(modulename)
|
||||
if moduleobj is None:
|
||||
raise NoSuchmoduleError(module=modulename)
|
||||
|
||||
pobj = moduleobj.PARAMS.get(pname, None)
|
||||
if pobj is None:
|
||||
raise NoSuchParamError(module=modulename, parameter=pname)
|
||||
if pobj.readonly:
|
||||
raise ReadonlyError(module=modulename, parameter=pname)
|
||||
|
||||
writefunc = getattr(moduleobj, 'write_%s' % pname, None)
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
if writefunc:
|
||||
value = writefunc(value)
|
||||
else:
|
||||
setattr(moduleobj, pname, value)
|
||||
if pobj.timestamp:
|
||||
return Value(modulename, pname, value=pobj.value, t=pobj.timestamp)
|
||||
return Value(modulename, pname, value=pobj.value)
|
||||
|
||||
def _getParamValue(self, modulename, pname):
|
||||
moduleobj = self.get_module(modulename)
|
||||
if moduleobj is None:
|
||||
raise NoSuchmoduleError(module=modulename)
|
||||
|
||||
pobj = moduleobj.PARAMS.get(pname, None)
|
||||
if pobj is None:
|
||||
raise NoSuchParamError(module=modulename, parameter=pname)
|
||||
|
||||
readfunc = getattr(moduleobj, 'read_%s' % pname, None)
|
||||
if readfunc:
|
||||
# should also update the pobj (via the setter from the metaclass)
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
readfunc()
|
||||
if pobj.timestamp:
|
||||
return Value(modulename, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
||||
return Value(modulename, parameter=pname, value=pobj.value)
|
||||
|
||||
|
||||
# now the (defined) handlers for the different requests
|
||||
def handle_Help(self, conn, msg):
|
||||
return HelpMessage()
|
||||
|
||||
def handle_Identify(self, conn, msg):
|
||||
return IdentifyReply(version_string='currently,is,ignored,here')
|
||||
|
||||
def handle_Describe(self, conn, msg):
|
||||
# XXX:collect descriptive data
|
||||
# XXX:how to get equipment_id?
|
||||
return DescribeReply(equipment_id = self.equipment_id, description = self.list_modules())
|
||||
|
||||
def handle_Poll(self, conn, msg):
|
||||
# XXX: trigger polling and force sending event
|
||||
res = self._getParamValue(msg.module, msg.parameter or 'value')
|
||||
#self.broadcast_event(res)
|
||||
if conn in self._dispatcher_active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
def handle_Write(self, conn, msg):
|
||||
# notify all by sending WriteReply
|
||||
#msg1 = WriteReply(**msg.as_dict())
|
||||
#self.broadcast_event(msg1)
|
||||
# try to actually write XXX: should this be done asyncron? we could just return the reply in that case
|
||||
if msg.parameter:
|
||||
res = self._setParamValue(msg.module, msg.parameter, msg.value)
|
||||
else:
|
||||
# first check if module has a target
|
||||
if 'target' not in self.get_module(msg.module).PARAMS:
|
||||
raise ReadonlyError(module=msg.module, parameter=None)
|
||||
res = self._setParamValue(msg.module, 'target', msg.value)
|
||||
res.parameter = 'target'
|
||||
#self.broadcast_event(res)
|
||||
if conn in self._dispatcher_active_connections:
|
||||
return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
def handle_Command(self, conn, msg):
|
||||
# notify all by sending CommandReply
|
||||
#msg1 = CommandReply(**msg.as_dict())
|
||||
#self.broadcast_event(msg1)
|
||||
# XXX: should this be done asyncron? we could just return the reply in that case
|
||||
|
||||
# try to actually execute command
|
||||
res = self._execute_command(msg.module, msg.command, msg.arguments)
|
||||
#self.broadcast_event(res)
|
||||
#if conn in self._dispatcher_active_connections:
|
||||
# return None # already send to myself
|
||||
return res # send reply to inactive conns
|
||||
|
||||
def handle_Heartbeat(self, conn, msg):
|
||||
return HeartbeatReply(**msg.as_dict())
|
||||
|
||||
def handle_Activate(self, conn, msg):
|
||||
self.activate_connection(conn)
|
||||
# easy approach: poll all values...
|
||||
for modulename, moduleobj in self._dispatcher_modules.items():
|
||||
for pname, pobj in moduleobj.PARAMS.items():
|
||||
# WARNING: THIS READS ALL PARAMS FROM HW!
|
||||
# XXX: should we send the cached values instead? (pbj.value)
|
||||
# also: ignore errors here.
|
||||
try:
|
||||
res = self._getParamValue(modulename, pname)
|
||||
except SECOPError as e:
|
||||
self.log.error('decide what to do here!')
|
||||
self.log.exception(e)
|
||||
res = Value(module=modulename, parameter=pname,
|
||||
value=pobj.value, t=pobj.timestamp,
|
||||
unit=pobj.unit)
|
||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||
self.broadcast_event(res)
|
||||
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
||||
return None
|
||||
|
||||
def handle_Deactivate(self, conn, msg):
|
||||
self.deactivate_connection(conn)
|
||||
conn.queue_async_reply(DeactivateReply(**msg.as_dict()))
|
||||
return None
|
||||
|
||||
def handle_Error(self, conn, msg):
|
||||
return msg
|
||||
|
||||
def unhandled(self, conn, msg):
|
||||
"""handler for unhandled Messages
|
||||
|
||||
(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)
|
||||
|
||||
|
59
secop/protocol/encoding/__init__.py
Normal file
59
secop/protocol/encoding/__init__.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/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)
|
||||
|
||||
# Base classes
|
||||
|
||||
|
||||
class MessageEncoder(object):
|
||||
"""en/decode a single Messageobject"""
|
||||
|
||||
def encode(self, messageobj):
|
||||
"""encodes the given message object into a frame"""
|
||||
raise NotImplemented
|
||||
|
||||
def decode(self, frame):
|
||||
"""decodes the given frame to a message object"""
|
||||
raise NotImplemented
|
||||
|
||||
from demo_v2 import DemoEncoder as DemoEncoderV2
|
||||
from demo_v3 import DemoEncoder as DemoEncoderV3
|
||||
from demo_v4 import DemoEncoder as DemoEncoderV4
|
||||
from text import TextEncoder
|
||||
from pickle import PickleEncoder
|
||||
from simplecomm import SCPEncoder
|
||||
|
||||
ENCODERS = {
|
||||
'pickle': PickleEncoder,
|
||||
'text': TextEncoder,
|
||||
'demo_v2': DemoEncoderV2,
|
||||
'demo_v3': DemoEncoderV3,
|
||||
'demo_v4': DemoEncoderV4,
|
||||
'demo': DemoEncoderV4,
|
||||
'scp': SCPEncoder,
|
||||
}
|
||||
|
||||
__ALL__ = ['ENCODERS']
|
102
secop/protocol/encoding/demo_v2.py
Normal file
102
secop/protocol/encoding/demo_v2.py
Normal file
@ -0,0 +1,102 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol import messages
|
||||
from secop.lib.parsing import *
|
||||
|
||||
import re
|
||||
|
||||
DEMO_RE = re.compile(
|
||||
r'^([!+-])?(\*|[a-z_][a-z_0-9]*)?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\=(.*))?')
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
|
||||
def decode(sef, encoded):
|
||||
# match [!][*|devicename][: *|paramname [: *|propname]] [=value]
|
||||
match = DEMO_RE.match(encoded)
|
||||
if match:
|
||||
novalue, devname, pname, propname, assign = match.groups()
|
||||
if assign:
|
||||
print "parsing", assign,
|
||||
assign = parse_args(assign)
|
||||
print "->", assign
|
||||
return messages.DemoRequest(
|
||||
novalue, devname, pname, propname, assign)
|
||||
return messages.HelpRequest()
|
||||
|
||||
def encode(self, msg):
|
||||
if isinstance(msg, messages.DemoReply):
|
||||
return msg.lines
|
||||
handler_name = '_encode_' + msg.__class__.__name__
|
||||
handler = getattr(self, handler_name, None)
|
||||
if handler is None:
|
||||
print "Handler %s not yet implemented!" % handler_name
|
||||
try:
|
||||
args = dict((k, msg.__dict__[k]) for k in msg.ARGS)
|
||||
result = handler(**args)
|
||||
except Exception as e:
|
||||
print "Error encoding %r with %r!" % (msg, handler)
|
||||
print e
|
||||
return '~InternalError~'
|
||||
return result
|
||||
|
||||
def _encode_AsyncDataUnit(self, devname, pname, value, timestamp,
|
||||
error=None, unit=''):
|
||||
return '#%s:%s=%s;t=%.3f' % (devname, pname, value, timestamp)
|
||||
|
||||
def _encode_Error(self, error):
|
||||
return '~Error~ %r' % error
|
||||
|
||||
def _encode_InternalError(self, error):
|
||||
return '~InternalError~ %r' % error
|
||||
|
||||
def _encode_ProtocollError(self, msgtype, msgname, msgargs):
|
||||
return '~ProtocolError~ %s.%s.%r' % (msgtype, msgname, msgargs)
|
||||
|
||||
def _encode_NoSuchDeviceError(self, device):
|
||||
return '~NoSuchDeviceError~ %s' % device
|
||||
|
||||
def _encode_NoSuchParamError(self, device, param):
|
||||
return '~NoSuchParameterError~ %s:%s' % (device, param)
|
||||
|
||||
def _encode_ParamReadonlyError(self, device, param):
|
||||
return '~ParamReadOnlyError~ %s:%s' % (device, param)
|
||||
|
||||
def _encode_NoSuchCommandError(self, device, command):
|
||||
return '~NoSuchCommandError~ %s.%s' % (device, command)
|
||||
|
||||
def _encode_CommandFailedError(self, device, command):
|
||||
return '~CommandFailedError~ %s.%s' % (device, command)
|
||||
|
||||
def _encode_InvalidParamValueError(self, device, param, value):
|
||||
return '~InvalidValueForParamError~ %s:%s=%r' % (device, param, value)
|
||||
|
||||
def _encode_HelpReply(self):
|
||||
return ['Help not yet implemented!',
|
||||
'ask Markus Zolliker about the protocol']
|
391
secop/protocol/encoding/demo_v3.py
Normal file
391
secop/protocol/encoding/demo_v3.py
Normal file
@ -0,0 +1,391 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol.messages import *
|
||||
from secop.protocol.errors import ProtocollError
|
||||
|
||||
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: "",
|
||||
ProtocollError: 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)
|
209
secop/protocol/encoding/demo_v4.py
Normal file
209
secop/protocol/encoding/demo_v4.py
Normal file
@ -0,0 +1,209 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol.messages import *
|
||||
from secop.protocol.errors import ProtocollError
|
||||
|
||||
import ast
|
||||
import re
|
||||
import json
|
||||
|
||||
# each message is like <messagetype> [ \space <messageargs> [ \space <json> ]] \lf
|
||||
|
||||
# note: the regex allow <> for spec for testing only!
|
||||
DEMO_RE = re.compile(
|
||||
r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""", re.X)
|
||||
|
||||
#"""
|
||||
# messagetypes:
|
||||
IDENTREQUEST = '*IDN?' # literal
|
||||
IDENTREPLY = 'Sine2020WP7.1&ISSE, SECoP, V2016-11-30, rc1' # literal
|
||||
DESCRIPTIONSREQUEST = 'describe' # literal
|
||||
DESCRIPTIONREPLY = 'describing' # +<id> +json
|
||||
ENABLEEVENTSREQUEST = 'activate' # literal
|
||||
ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer
|
||||
DISABLEEVENTSREQUEST = 'deactivate' # literal
|
||||
DISABLEEVENTSREPLY = 'inactive' # literal
|
||||
COMMANDREQUEST = 'do' # +module:command +json args (if needed)
|
||||
COMMANDREPLY = 'doing' # +module:command +json args (if needed)
|
||||
WRITEREQUEST = 'change' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
||||
WRITEREPLY = 'changing' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
||||
TRIGGERREQUEST = 'read' # +module[:parameter] -> NO direct reply, calls TRIGGER internally!
|
||||
HEARTBEATREQUEST = 'ping' # +nonce_without_space
|
||||
HEARTBEATREPLY = 'pong' # +nonce_without_space
|
||||
EVENTTRIGGERREPLY = 'update' # +module[:parameter] +json_result_value_with_qualifiers NO REQUEST (use WRITE/TRIGGER)
|
||||
EVENTCOMMANDREPLY = 'done' # +module:command +json result (if needed)
|
||||
#EVENTWRITEREPLY = 'changed' # +module[:parameter] +json_result_value_with_qualifiers NO REQUEST (use WRITE/TRIGGER)
|
||||
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
|
||||
HELPREQUEST = 'help' # literal
|
||||
HELPREPLY = 'helping' # +line number +json_text
|
||||
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
||||
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
||||
'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
|
||||
'CommandRunning', 'Disabled',]
|
||||
# note: above strings need to be unique in the sense, that none is/or starts with another
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
# map of msg to msgtype string as defined above.
|
||||
ENCODEMAP = {
|
||||
IdentifyRequest : (IDENTREQUEST,),
|
||||
IdentifyReply : (IDENTREPLY,),
|
||||
DescribeRequest : (DESCRIPTIONSREQUEST,),
|
||||
DescribeReply : (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
||||
ActivateRequest : (ENABLEEVENTSREQUEST,),
|
||||
ActivateReply : (ENABLEEVENTSREPLY,),
|
||||
DeactivateRequest: (DISABLEEVENTSREQUEST,),
|
||||
DeactivateReply : (DISABLEEVENTSREPLY,),
|
||||
CommandRequest : (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||
CommandReply : (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||
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',),
|
||||
PollRequest : (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
|
||||
HeartbeatRequest : (HEARTBEATREQUEST, 'nonce',),
|
||||
HeartbeatReply : (HEARTBEATREPLY, 'nonce',),
|
||||
HelpMessage: (HELPREQUEST, ),
|
||||
# EventMessage : (EVENTREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()'))
|
||||
# if msg.parameter or msg.command else msg.module, 'value',),
|
||||
ErrorMessage : (ERRORREPLY, 'errorclass', 'errorinfo',),
|
||||
Value: (EVENTTRIGGERREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()'))
|
||||
if msg.parameter or msg.command else msg.module,
|
||||
lambda msg: [msg.value, msg.qualifiers] if msg.qualifiers else [msg.value]),
|
||||
}
|
||||
DECODEMAP = {
|
||||
IDENTREQUEST : lambda spec, data: IdentifyRequest(),
|
||||
IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness
|
||||
DESCRIPTIONSREQUEST : lambda spec, data: DescribeRequest(),
|
||||
DESCRIPTIONREPLY : lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
|
||||
ENABLEEVENTSREQUEST : lambda spec, data: ActivateRequest(),
|
||||
ENABLEEVENTSREPLY: lambda spec, data:ActivateReply(),
|
||||
DISABLEEVENTSREQUEST: lambda spec, data:DeactivateRequest(),
|
||||
DISABLEEVENTSREPLY: lambda spec, data:DeactivateReply(),
|
||||
COMMANDREQUEST: lambda spec, data:CommandRequest(module=spec[0], command=spec[1], arguments=data),
|
||||
COMMANDREPLY: lambda spec, data: CommandReply(module=spec[0], command=spec[1], arguments=data),
|
||||
WRITEREQUEST: lambda spec, data: WriteRequest(module=spec[0], parameter=spec[1], value=data),
|
||||
WRITEREPLY:lambda spec, data:WriteReply(module=spec[0], parameter=spec[1], value=data),
|
||||
TRIGGERREQUEST:lambda spec, data:PollRequest(module=spec[0], parameter=spec[1]),
|
||||
HEARTBEATREQUEST:lambda spec, data:HeartbeatRequest(nonce=spec[0]),
|
||||
HEARTBEATREPLY:lambda spec, data:HeartbeatReply(nonce=spec[0]),
|
||||
HELPREQUEST: lambda spec, data:HelpMessage(),
|
||||
# HELPREPLY: lambda spec, data:None, # ignore this
|
||||
ERRORREPLY:lambda spec, data:ErrorMessage(errorclass=spec[0], errorinfo=data),
|
||||
EVENTTRIGGERREPLY:lambda spec, data:Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data)>1 else {}),
|
||||
EVENTCOMMANDREPLY: lambda spec, data:None, # ignore this
|
||||
# EVENTWRITEREPLY:lambda spec, data:Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data)>1 else {}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
MessageEncoder.__init__(self, *args, **kwds)
|
||||
self.tests()
|
||||
|
||||
def encode(self, msg):
|
||||
"""msg object -> transport layer message"""
|
||||
# fun for Humans
|
||||
if isinstance(msg, HelpMessage):
|
||||
text = """Try one of the following:
|
||||
'%s' to query protocol version
|
||||
'%s' to read the description
|
||||
'%s <module>[:<parameter>]' to request reading a value
|
||||
'%s <module>[:<parameter>] value' to request changing a value
|
||||
'%s <module>[:<command>()]' to execute a command
|
||||
'%s <nonce>' to request a heartbeat response
|
||||
'%s' to activate async updates
|
||||
'%s' to deactivate updates
|
||||
""" %(IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
|
||||
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
|
||||
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
||||
return '\n'.join('%s %d %s' %(HELPREPLY, i+1, l.strip()) for i,l in enumerate(text.split('\n')[:-1]))
|
||||
for msgcls, parts in self.ENCODEMAP.items():
|
||||
if isinstance(msg, msgcls):
|
||||
# resolve lambdas
|
||||
parts = [parts[0]] + [p(msg) if callable(p) else getattr(msg, p) for p in parts[1:]]
|
||||
if len(parts) > 1:
|
||||
parts[1] = str(parts[1])
|
||||
if len(parts) == 3:
|
||||
parts[2] = json.dumps(parts[2])
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
def decode(self, encoded):
|
||||
# first check beginning
|
||||
match = DEMO_RE.match(encoded)
|
||||
if not match:
|
||||
print repr(encoded), repr(IDENTREPLY)
|
||||
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
||||
return IdentifyReply(version_string=encoded)
|
||||
|
||||
return HelpMessage()
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
errorinfo='Regex did not match!',
|
||||
is_request=True)
|
||||
msgtype, msgspec, data = match.groups()
|
||||
if msgspec is None and data:
|
||||
return ErrorMessage(errorclass='InternalError',
|
||||
errorinfo='Regex matched json, but not spec!',
|
||||
is_request=True)
|
||||
|
||||
if msgtype in self.DECODEMAP:
|
||||
if msgspec and ':' in msgspec:
|
||||
msgspec = msgspec.split(':', 1)
|
||||
else:
|
||||
msgspec = (msgspec, None)
|
||||
if data:
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except ValueError as err:
|
||||
return ErrorMessage(errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(encoded)])
|
||||
return self.DECODEMAP[msgtype](msgspec, data)
|
||||
return ErrorMessage(errorclass='SyntaxError',
|
||||
errorinfo='%r: No Such Messagetype defined!' % encoded,
|
||||
is_request=True)
|
||||
|
||||
|
||||
def tests(self):
|
||||
print "---- Testing encoding -----"
|
||||
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
||||
print msgclass
|
||||
e=self.encode(msgclass(module='<module>',parameter='<paramname>',value=2.718,equipment_id='<id>',description='descriptive data',command='<cmd>',arguments='<arguments>',nonce='<nonce>',errorclass='InternalError',errorinfo='nix'))
|
||||
print e
|
||||
print self.decode(e)
|
||||
print
|
||||
print "---- Testing decoding -----"
|
||||
for msgtype, _ in sorted(self.DECODEMAP.items()):
|
||||
msg = '%s a:b 3' % msgtype
|
||||
if msgtype in [EVENTTRIGGERREPLY]:#, EVENTWRITEREPLY]:
|
||||
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
||||
print msg
|
||||
d=self.decode(msg)
|
||||
print d
|
||||
print self.encode(d)
|
||||
print
|
||||
print "---- Testing done -----"
|
||||
|
||||
|
||||
|
47
secop/protocol/encoding/pickle.py
Normal file
47
secop/protocol/encoding/pickle.py
Normal file
@ -0,0 +1,47 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol import messages
|
||||
from secop.lib.parsing import *
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
|
||||
class PickleEncoder(MessageEncoder):
|
||||
|
||||
def encode(self, messageobj):
|
||||
"""msg object -> transport layer message"""
|
||||
return pickle.dumps(messageobj)
|
||||
|
||||
def decode(self, encoded):
|
||||
"""transport layer message -> msg object"""
|
||||
return pickle.loads(encoded)
|
209
secop/protocol/encoding/simplecomm.py
Normal file
209
secop/protocol/encoding/simplecomm.py
Normal file
@ -0,0 +1,209 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol.messages import *
|
||||
from secop.lib.parsing import *
|
||||
|
||||
import re
|
||||
import ast
|
||||
|
||||
|
||||
SCPMESSAGE = re.compile(
|
||||
r'^(?:(?P<errorcode>[0-9@])\ )?(?P<device>[a-zA-Z0-9_\*]*)(?:/(?P<param>[a-zA-Z0-9_\*]*))+(?P<op>[-+=\?\ ])?(?P<value>.*)')
|
||||
|
||||
|
||||
class SCPEncoder(MessageEncoder):
|
||||
|
||||
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("'/version?' to query the current version")
|
||||
r.append("'/modules?' to query the list of modules")
|
||||
r.append(
|
||||
"'<module>/parameters?' to query the list of params of a module")
|
||||
r.append("'<module>/value?' to query the value of a module")
|
||||
r.append("'<module>/status?' to query the status of a module")
|
||||
r.append("'<module>/target=<new_value>' to move a module")
|
||||
r.append("replies copy the request and are prefixed with an errorcode:")
|
||||
r.append(
|
||||
"0=OK,3=NoSuchCommand,4=NosuchDevice,5=NoSuchParam,6=SyntaxError,7=BadValue,8=Readonly,9=Forbidden,@=Async")
|
||||
r.append("extensions: @-prefix as error-code,")
|
||||
r.append("'<module>/+' subscribe all params of module")
|
||||
r.append("'<module>/<param>+' subscribe a param of a module")
|
||||
r.append("use '-' instead of '+' to unsubscribe")
|
||||
r.append("'<module>/commands?' list of commands")
|
||||
r.append(
|
||||
"'<module>/<command>@[possible args] execute command (ex. 'stop@')")
|
||||
return '\n'.join(r)
|
||||
|
||||
return {
|
||||
ListDevicesRequest: lambda msg: "devices?",
|
||||
ListDevicesReply: lambda msg: "0 devices=" + repr(list(msg.list_of_devices)),
|
||||
GetVersionRequest: lambda msg: "version?",
|
||||
GetVersionReply: lambda msg: "0 version=%r" % msg.version,
|
||||
ListDeviceParamsRequest: lambda msg: "%s/parameters?" % msg.device,
|
||||
ListDeviceParamsReply: lambda msg: "0 %s/parameters=%r" % (msg.device, list(msg.params)),
|
||||
ReadValueRequest: lambda msg: "%s/value?" % msg.device,
|
||||
ReadValueReply: lambda msg: "0 %s/value?%r" % (msg.device, msg.value),
|
||||
WriteValueRequest: lambda msg: "%s/value=%r" % (msg.device, msg.value),
|
||||
WriteValueReply: lambda msg: "0 %s/value=%r" % (msg.device, msg.value),
|
||||
ReadParamRequest: lambda msg: "%s/%s?" % (msg.device, msg.param),
|
||||
ReadParamReply: lambda msg: "0 %s/%s?%r" % (msg.device, msg.param, msg.value),
|
||||
WriteParamRequest: lambda msg: "%s/%s=%r" % (msg.device, msg.param, msg.value),
|
||||
WriteParamReply: lambda msg: "0 %s/%s=%r" % (msg.device, msg.param, msg.readback_value),
|
||||
# extensions
|
||||
ReadAllDevicesRequest: lambda msg: "*/value?",
|
||||
ReadAllDevicesReply: lambda msg: ["0 %s/value=%s" % (m.device, m.value) for m in msg.readValueReplies],
|
||||
ListParamPropsRequest: lambda msg: "%s/%s/?" % (msg.device, msg.param),
|
||||
ListParamPropsReply: lambda msg: ["0 %s/%s/%s" % (msg.device, msg.param, p) for p in msg.props],
|
||||
AsyncDataUnit: lambda msg: "@ %s/%s=%r" % (msg.devname, msg.pname, msg.value),
|
||||
SubscribeRequest: lambda msg: "%s/%s+" % (msg.devname, msg.pname),
|
||||
# violates spec ! we would need the original request here....
|
||||
SubscribeReply: lambda msg: "0 / %r" % [repr(s) for s in msg.subscriptions],
|
||||
UnSubscribeRequest: lambda msg: "%s/%s+" % (msg.devname, msg.pname),
|
||||
# violates spec ! we would need the original request here....
|
||||
UnSubscribeReply: lambda msg: "0 / %r" % [repr(s) for s in msg.subscriptions],
|
||||
# errors
|
||||
# violates spec ! we would need the original request here....
|
||||
ErrorReply: lambda msg: "1 /%r" % msg.error,
|
||||
# violates spec ! we would need the original request here....
|
||||
InternalError: lambda msg: "1 /%r" % msg.error,
|
||||
# violates spec ! we would need the original request here....
|
||||
ProtocollError: lambda msg: "6 /%r" % msg.error,
|
||||
# violates spec ! we would need the original request here....
|
||||
CommandFailedError: lambda msg: "1 %s/%s" % (msg.device, msg.command),
|
||||
# violates spec ! we would need the original request here....
|
||||
NoSuchCommandError: lambda msg: "3 %s/%s" % (msg.device, msg.command),
|
||||
# violates spec ! we would need the original request here....
|
||||
NoSuchDeviceError: lambda msg: "4 %s/ %r" % (msg.device, msg.error),
|
||||
# violates spec ! we would need the original request here....
|
||||
NoSuchParamError: lambda msg: "5 %s/%s %r" % (msg.device, msg.param, msg.error),
|
||||
# violates spec ! we would need the original request here....
|
||||
ParamReadonlyError: lambda msg: "8 %s/%s %r" % (msg.device, msg.param, msg.error),
|
||||
# violates spec ! we would need the original request here....
|
||||
UnsupportedFeatureError: lambda msg: "3 / %r" % msg.feature,
|
||||
# violates spec ! we would need the original request here....
|
||||
InvalidParamValueError: lambda msg: "7 %s/%s=%r %r" % (msg.device, msg.param, msg.value, msg.error),
|
||||
}[msg.__class__](msg)
|
||||
|
||||
def decode(self, encoded):
|
||||
"""transport layer message -> msg object"""
|
||||
match = SCPMESSAGE.match(encoded)
|
||||
if not(match):
|
||||
return HelpRequest()
|
||||
err, dev, par, op, val = match.groups()
|
||||
if val is not None:
|
||||
try:
|
||||
val = ast.literal_eval(val)
|
||||
except Exception as e:
|
||||
return SyntaxError('while decoding %r: %s' % (encoded, e))
|
||||
if err == '@':
|
||||
# async
|
||||
if op == '=':
|
||||
return AsyncDataUnit(dev, par, val)
|
||||
return ProtocolError("Asyncupdates must have op = '='!")
|
||||
elif err is None:
|
||||
# request
|
||||
if op == '+':
|
||||
# subscribe
|
||||
if dev:
|
||||
if par:
|
||||
return SubscribeRequest(dev, par)
|
||||
return SubscribeRequest(dev, '*')
|
||||
if op == '-':
|
||||
# unsubscribe
|
||||
if dev:
|
||||
if par:
|
||||
return UnsubscribeRequest(dev, par)
|
||||
return UnsubscribeRequest(dev, '*')
|
||||
if op == '?':
|
||||
if dev is None:
|
||||
# 'server' commands
|
||||
if par == 'devices':
|
||||
return ListDevicesRequest()
|
||||
elif par == 'version':
|
||||
return GetVersionRequest()
|
||||
return ProtocolError()
|
||||
if par == 'parameters':
|
||||
return ListDeviceParamsRequest(dev)
|
||||
elif par == 'value':
|
||||
return ReadValueRequest(dev)
|
||||
elif dev == '*' and par == 'value':
|
||||
return ReadAllDevicesRequest()
|
||||
else:
|
||||
return ReadParamRequest(dev, par)
|
||||
elif op == '=':
|
||||
if dev and (par == 'value'):
|
||||
return WriteValueRequest(dev, val)
|
||||
if par.endswith('/') and op == '?':
|
||||
return ListParamPropsRequest(dev, par)
|
||||
return WriteParamRequest(dev, par, val)
|
||||
elif err == '0':
|
||||
# reply
|
||||
if dev == '':
|
||||
if par == 'devices':
|
||||
return ListDevicesReply(val)
|
||||
elif par == 'version':
|
||||
return GetVersionReply(val)
|
||||
return ProtocolError(encoded)
|
||||
if par == 'parameters':
|
||||
return ListDeviceParamsReply(dev, val)
|
||||
if par == 'value':
|
||||
if op == '?':
|
||||
return ReadValueReply(dev, val)
|
||||
elif op == '=':
|
||||
return WriteValueReply(dev, val)
|
||||
return ProtocolError(encoded)
|
||||
if op == '+':
|
||||
return SubscribeReply(ast.literal_eval(dev))
|
||||
if op == '-':
|
||||
return UnSubscribeReply(ast.literal_eval(dev))
|
||||
if op == '?':
|
||||
return ReadParamReply(dev, par, val)
|
||||
if op == '=':
|
||||
return WriteParamReply(dev, par, val)
|
||||
return ProtocolError(encoded)
|
||||
else:
|
||||
# error
|
||||
if err in ('1', '2'):
|
||||
return InternalError(encoded)
|
||||
elif err == '3':
|
||||
return NoSuchCommandError(dev, par)
|
||||
elif err == '4':
|
||||
return NoSuchDeviceError(dev, encoded)
|
||||
elif err == '5':
|
||||
return NoSuchParamError(dev, par, val)
|
||||
elif err == '7':
|
||||
return InvalidParamValueError(dev, par, val, encoded)
|
||||
elif err == '8':
|
||||
return ParamReadonlyError(dev, par, encoded)
|
||||
else: # err == 6 or other stuff
|
||||
return ProtocollError(encoded)
|
65
secop/protocol/encoding/text.py
Normal file
65
secop/protocol/encoding/text.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/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 secop.protocol.encoding import MessageEncoder
|
||||
from secop.protocol import messages
|
||||
from secop.lib.parsing import *
|
||||
|
||||
|
||||
class TextEncoder(MessageEncoder):
|
||||
|
||||
def __init__(self):
|
||||
# build safe namespace
|
||||
ns = dict()
|
||||
for n in dir(messages):
|
||||
if n.endswith(('Request', 'Reply')):
|
||||
ns[n] = getattr(messages, n)
|
||||
self.namespace = ns
|
||||
|
||||
def encode(self, messageobj):
|
||||
"""msg object -> transport layer message"""
|
||||
# fun for Humans
|
||||
if isinstance(messageobj, messages.HelpMessage):
|
||||
return "Error: try one of the following requests:\n" + \
|
||||
'\n'.join(['%s(%s)' % (getattr(messages, m).__name__,
|
||||
', '.join(getattr(messages, m).ARGS))
|
||||
for m in dir(messages)
|
||||
if m.endswith('Request') and len(m) > len("Request")])
|
||||
res = []
|
||||
for k in messageobj.ARGS:
|
||||
res.append('%s=%r' % (k, getattr(messageobj, k, None)))
|
||||
result = '%s(%s)' % (messageobj.__class__.__name__, ', '.join(res))
|
||||
return result
|
||||
|
||||
def decode(self, encoded):
|
||||
"""transport layer message -> msg object"""
|
||||
# WARNING: highly unsafe!
|
||||
# think message='import os\nos.unlink('\')\n'
|
||||
try:
|
||||
return eval(encoded, self.namespace, {})
|
||||
except SyntaxError:
|
||||
return messages.HelpMessage()
|
70
secop/protocol/errors.py
Normal file
70
secop/protocol/errors.py
Normal file
@ -0,0 +1,70 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define (internal) SECoP Errors"""
|
||||
|
||||
|
||||
class SECOPError(RuntimeError):
|
||||
def __init__(self, *args, **kwds):
|
||||
self.args = args
|
||||
for k,v in kwds.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
|
||||
class InternalError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class ProtocollError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
# XXX: unifiy NoSuch...Error ?
|
||||
class NoSuchModuleError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class NoSuchParamError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class NoSuchCommandError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class ReadonlyError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class CommandFailedError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidParamValueError(SECOPError):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Minimal testing of errors....")
|
||||
|
||||
print "OK"
|
||||
print
|
62
secop/protocol/framing/__init__.py
Normal file
62
secop/protocol/framing/__init__.py
Normal file
@ -0,0 +1,62 @@
|
||||
#!/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 Frames"""
|
||||
|
||||
|
||||
# Base class
|
||||
class Framer(object):
|
||||
"""Frames and unframes an encoded message
|
||||
|
||||
also transforms the encoded message to the 'wire-format' (and vise-versa)
|
||||
|
||||
note: not all MessageEncoders can use all Framers,
|
||||
but the intention is to have this for as many as possible.
|
||||
"""
|
||||
|
||||
def encode(self, *frames):
|
||||
"""return the wire-data for the given messageframes"""
|
||||
raise NotImplemented
|
||||
|
||||
def decode(self, data):
|
||||
"""return a list of messageframes found in data"""
|
||||
raise NotImplemented
|
||||
|
||||
def reset(self):
|
||||
"""resets the de/encoding stage (clears internal information)"""
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
# now some Implementations
|
||||
from null import NullFramer
|
||||
from eol import EOLFramer
|
||||
from rle import RLEFramer
|
||||
from demo import DemoFramer
|
||||
|
||||
FRAMERS = {
|
||||
'null': NullFramer,
|
||||
'eol': EOLFramer,
|
||||
'rle': RLEFramer,
|
||||
'demo': DemoFramer,
|
||||
}
|
||||
|
||||
__ALL__ = ['FRAMERS']
|
84
secop/protocol/framing/demo.py
Normal file
84
secop/protocol/framing/demo.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/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 Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
||||
|
||||
class DemoFramer(Framer):
|
||||
"""Text based message framer
|
||||
|
||||
frmes are delimited by '\n'
|
||||
messages are delimited by '\n\n'
|
||||
'\r' is ignored
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.data = b''
|
||||
self.decoded = []
|
||||
|
||||
def encode(self, frames):
|
||||
"""add transport layer encapsulation/framing of messages"""
|
||||
if isinstance(frames, (tuple, list)):
|
||||
return b'\n'.join(frames) + b'\n\n'
|
||||
return b'%s\n\n' % frames
|
||||
|
||||
def decode(self, data):
|
||||
"""remove transport layer encapsulation/framing of messages
|
||||
|
||||
returns a list of messageframes which got decoded from data!
|
||||
"""
|
||||
self.data += data
|
||||
res = []
|
||||
while b'\n' in self.data:
|
||||
frame, self.data = self.data.split(b'\n', 1)
|
||||
if frame.endswith('\r'):
|
||||
frame = frame[:-1]
|
||||
if self.data.startswith('\r'):
|
||||
self.data = self.data[1:]
|
||||
res.append(frame)
|
||||
return res
|
||||
|
||||
def decode2(self, data):
|
||||
"""remove transport layer encapsulation/framing of messages
|
||||
|
||||
returns a _list_ of messageframes which got decoded from data!
|
||||
"""
|
||||
self.data += data.replace(b'\r', '')
|
||||
while b'\n' in self.data:
|
||||
frame, self.data = self.data.split(b'\n', 1)
|
||||
if frame:
|
||||
# not an empty line -> belongs to this set of messages
|
||||
self.decoded.append(frame)
|
||||
else:
|
||||
# empty line -> our set of messages is finished decoding
|
||||
res = self.decoded
|
||||
self.decoded = []
|
||||
return res
|
||||
return None
|
||||
|
||||
def reset(self):
|
||||
self.data = b''
|
||||
self.decoded = []
|
||||
|
||||
|
57
secop/protocol/framing/eol.py
Normal file
57
secop/protocol/framing/eol.py
Normal file
@ -0,0 +1,57 @@
|
||||
#!/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 Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
||||
|
||||
class EOLFramer(Framer):
|
||||
"""Text based message framer
|
||||
|
||||
messages are delimited by '\r\n'
|
||||
upon reception the end of a message is detected by '\r\n','\n' or '\n\r'
|
||||
"""
|
||||
data = b''
|
||||
|
||||
def encode(self, *frames):
|
||||
"""add transport layer encapsulation/framing of messages"""
|
||||
return b'%s\r\n' % b'\r\n'.join(frames)
|
||||
|
||||
def decode(self, data):
|
||||
"""remove transport layer encapsulation/framing of messages
|
||||
|
||||
returns a list of messageframes which got decoded from data!
|
||||
"""
|
||||
self.data += data
|
||||
res = []
|
||||
while b'\n' in self.data:
|
||||
frame, self.data = self.data.split(b'\n', 1)
|
||||
if frame.endswith('\r'):
|
||||
frame = frame[:-1]
|
||||
if self.data.startswith('\r'):
|
||||
self.data = self.data[1:]
|
||||
res.append(frame)
|
||||
return res
|
||||
|
||||
def reset(self):
|
||||
self.data = b''
|
40
secop/protocol/framing/null.py
Normal file
40
secop/protocol/framing/null.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/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 Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
||||
|
||||
class NullFramer(Framer):
|
||||
"""do-nothing-framer
|
||||
|
||||
assumes messages are framed by themselfs or the interface does it already.
|
||||
"""
|
||||
|
||||
def encode(self, *frames):
|
||||
"""add transport layer encapsulation/framing of messages"""
|
||||
return ''.join(frames)
|
||||
|
||||
def decode(self, data):
|
||||
"""remove transport layer encapsulation/framing of messages"""
|
||||
return [data]
|
74
secop/protocol/framing/rle.py
Normal file
74
secop/protocol/framing/rle.py
Normal file
@ -0,0 +1,74 @@
|
||||
#!/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 Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
||||
|
||||
class RLEFramer(Framer):
|
||||
data = b''
|
||||
frames_to_go = 0
|
||||
|
||||
def encode(self, *frames):
|
||||
"""add transport layer encapsulation/framing of messages"""
|
||||
# format is 'number of frames:[framelengt:frme]*N'
|
||||
frdata = ['%d:%s' % (len(frame), frame) for frame in frames]
|
||||
return b'%d:' + b''.join(frdata)
|
||||
|
||||
def decode(self, data):
|
||||
"""remove transport layer encapsulation/framing of messages
|
||||
|
||||
returns a list of messageframes which got decoded from data!
|
||||
"""
|
||||
self.data += data
|
||||
res = []
|
||||
while self.data:
|
||||
if frames_to_go == 0:
|
||||
if ':' in self.data:
|
||||
# scan for and decode 'number of frames'
|
||||
frnum, self.data = self.data.split(':', 1)
|
||||
try:
|
||||
self.frames_to_go = int(frnum)
|
||||
except ValueError:
|
||||
# can not recover, complain!
|
||||
raise FramingError('invalid start of message found!')
|
||||
else:
|
||||
# not enough data to decode number of frames,
|
||||
# return what we have
|
||||
return res
|
||||
while self.frames_to_go:
|
||||
# there are still some (partial) frames stuck inside self.data
|
||||
frlen, self.data = self.data.split(':', 1)
|
||||
if len(self.data) >= frlen:
|
||||
res.append(self.data[:frlen])
|
||||
self.data = self.data[frlen:]
|
||||
self.frames_to_go -= 1
|
||||
else:
|
||||
# not enough data for this frame, return what we have
|
||||
return res
|
||||
|
||||
def reset(self):
|
||||
self.data = b''
|
||||
self.frames_to_go = 0
|
||||
|
||||
|
31
secop/protocol/interface/__init__.py
Normal file
31
secop/protocol/interface/__init__.py
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""provide server interfaces to be used by clients"""
|
||||
|
||||
from tcp import TCPServer
|
||||
|
||||
INTERFACES = {
|
||||
'tcp': TCPServer,
|
||||
}
|
||||
|
||||
# for 'from protocol.interface import *' to only import the dict
|
||||
__ALL__ = ['INTERFACES']
|
136
secop/protocol/interface/tcp.py
Normal file
136
secop/protocol/interface/tcp.py
Normal file
@ -0,0 +1,136 @@
|
||||
#!/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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""provides tcp interface to the SECoP Server"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import collections
|
||||
import SocketServer
|
||||
|
||||
DEF_PORT = 10767
|
||||
MAX_MESSAGE_SIZE = 1024
|
||||
|
||||
from secop.protocol.encoding import ENCODERS
|
||||
from secop.protocol.framing import FRAMERS
|
||||
from secop.protocol.messages import HelpMessage
|
||||
|
||||
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
|
||||
def setup(self):
|
||||
self.log = self.server.log
|
||||
self._queue = collections.deque(maxlen=100)
|
||||
self.framing = self.server.framingCLS()
|
||||
self.encoding = self.server.encodingCLS()
|
||||
|
||||
def handle(self):
|
||||
"""handle a new tcp-connection"""
|
||||
# copy state info
|
||||
mysocket = self.request
|
||||
clientaddr = self.client_address
|
||||
serverobj = self.server
|
||||
self.log.debug("handling new connection from %s" % repr(clientaddr))
|
||||
|
||||
# notify dispatcher of us
|
||||
serverobj.dispatcher.add_connection(self)
|
||||
|
||||
mysocket.settimeout(.3)
|
||||
# mysocket.setblocking(False)
|
||||
# start serving
|
||||
while True:
|
||||
# send replys fist, then listen for requests, timing out after 0.1s
|
||||
while self._queue:
|
||||
# put message into encoder to get frame(s)
|
||||
# put frame(s) into framer to get bytestring
|
||||
# send bytestring
|
||||
outmsg = self._queue.popleft()
|
||||
outframes = self.encoding.encode(outmsg)
|
||||
outdata = self.framing.encode(outframes)
|
||||
mysocket.sendall(outdata)
|
||||
|
||||
# XXX: improve: use polling/select here?
|
||||
try:
|
||||
data = mysocket.recv(MAX_MESSAGE_SIZE)
|
||||
except (socket.timeout, socket.error) as e:
|
||||
continue
|
||||
# XXX: should use select instead of busy polling
|
||||
if not data:
|
||||
continue
|
||||
# put data into (de-) framer,
|
||||
# put frames into (de-) coder and if a message appear,
|
||||
# call dispatcher.handle_request(self, message)
|
||||
# dispatcher will queue the reply before returning
|
||||
frames = self.framing.decode(data)
|
||||
if frames is not None:
|
||||
if not frames: # empty list
|
||||
self.queue_reply(HelpMessage(MSGTYPE=reply))
|
||||
for frame in frames:
|
||||
reply = None
|
||||
msg = self.encoding.decode(frame)
|
||||
if msg:
|
||||
serverobj.dispatcher.handle_request(self, msg)
|
||||
|
||||
def queue_async_reply(self, data):
|
||||
"""called by dispatcher for async data units"""
|
||||
self._queue.append(data)
|
||||
|
||||
def queue_reply(self, data):
|
||||
"""called by dispatcher to queue (sync) replies"""
|
||||
# sync replies go first!
|
||||
self._queue.appendleft(data)
|
||||
|
||||
def finish(self):
|
||||
"""called when handle() terminates, i.e. the socket closed"""
|
||||
# notify dispatcher
|
||||
self.server.dispatcher.remove_connection(self)
|
||||
# close socket
|
||||
try:
|
||||
self.request.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self.request.close()
|
||||
|
||||
|
||||
class TCPServer(SocketServer.ThreadingTCPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, logger, interfaceopts, dispatcher):
|
||||
self.dispatcher = dispatcher
|
||||
self.log = logger
|
||||
bindto = interfaceopts.pop('bindto', 'localhost')
|
||||
portnum = int(interfaceopts.pop('bindport', DEF_PORT))
|
||||
if ':' in bindto:
|
||||
bindto, _port = bindto.rsplit(':')
|
||||
portnum = int(_port)
|
||||
# tcp is a byte stream, so we need Framers (to get frames)
|
||||
# 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.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
|
||||
self.log.debug("TCPServer using encoding=%s" % self.encodingCLS.__name__)
|
||||
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
|
||||
TCPRequestHandler,
|
||||
bind_and_activate=True)
|
||||
self.log.info("TCPServer initiated")
|
27
secop/protocol/interface/zmq.py
Normal file
27
secop/protocol/interface/zmq.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""provide a zmq server"""
|
||||
|
||||
# tbd.
|
||||
|
||||
# use zmq frames??
|
||||
# handle async and sync with different zmq ports?
|
160
secop/protocol/messages.py
Normal file
160
secop/protocol/messages.py
Normal file
@ -0,0 +1,160 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Messages"""
|
||||
|
||||
|
||||
class Message(object):
|
||||
"""base class for messages"""
|
||||
is_request = False
|
||||
is_reply = False
|
||||
is_error = False
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.ARGS = set()
|
||||
for k, v in kwds.items():
|
||||
self.setvalue(k, v)
|
||||
|
||||
def setvalue(self, key, value):
|
||||
setattr(self, key, value)
|
||||
self.ARGS.add(key)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + '(' + \
|
||||
', '.join('%s=%s' % (k, repr(getattr(self, k)))
|
||||
for k in sorted(self.ARGS)) + ')'
|
||||
|
||||
def as_dict(self):
|
||||
"""returns set parameters as dict"""
|
||||
return dict(map(lambda k:(k, getattr(self,k)),self.ARGS))
|
||||
|
||||
|
||||
class Value(object):
|
||||
|
||||
def __init__(self, module, parameter=None, command=None, value=Ellipsis, **qualifiers):
|
||||
self.module = module
|
||||
self.parameter = parameter
|
||||
self.command = command
|
||||
self.value = value
|
||||
self.qualifiers = qualifiers
|
||||
self.msgtype = 'update' # 'changed' or 'done'
|
||||
|
||||
def __repr__(self):
|
||||
devspec = self.module
|
||||
if self.parameter:
|
||||
devspec = '%s:%s' % (devspec, self.parameter)
|
||||
elif self.command:
|
||||
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()]))
|
||||
|
||||
|
||||
class IdentifyRequest(Message):
|
||||
is_request = True
|
||||
|
||||
class IdentifyReply(Message):
|
||||
is_reply = True
|
||||
version_string = None
|
||||
|
||||
class DescribeRequest(Message):
|
||||
is_request = True
|
||||
|
||||
class DescribeReply(Message):
|
||||
is_reply = True
|
||||
equipment_id = None
|
||||
description = None
|
||||
|
||||
class ActivateRequest(Message):
|
||||
is_request = True
|
||||
|
||||
class ActivateReply(Message):
|
||||
is_reply = True
|
||||
|
||||
class DeactivateRequest(Message):
|
||||
is_request = True
|
||||
|
||||
class DeactivateReply(Message):
|
||||
is_reply = True
|
||||
|
||||
class CommandRequest(Message):
|
||||
is_request = True
|
||||
command = ''
|
||||
arguments = []
|
||||
|
||||
class CommandReply(Message):
|
||||
is_reply = True
|
||||
command = ''
|
||||
arguments = None
|
||||
|
||||
class WriteRequest(Message):
|
||||
is_request = True
|
||||
module = None
|
||||
parameter = None
|
||||
value = None
|
||||
|
||||
class WriteReply(Message):
|
||||
is_reply = True
|
||||
module = None
|
||||
parameter = None
|
||||
value = None
|
||||
|
||||
class PollRequest(Message):
|
||||
is_request = True
|
||||
module = None
|
||||
parameter = None
|
||||
|
||||
class HeartbeatRequest(Message):
|
||||
is_request = True
|
||||
nonce = 'alive'
|
||||
|
||||
class HeartbeatReply(Message):
|
||||
is_reply = True
|
||||
nonce = 'undefined'
|
||||
|
||||
class EventMessage(Message):
|
||||
# use Value directly for Replies !
|
||||
is_reply = True
|
||||
module = None
|
||||
parameter = None
|
||||
command = None
|
||||
value = None # Value object ! (includes qualifiers!)
|
||||
|
||||
class ErrorMessage(Message):
|
||||
is_error = True
|
||||
errorclass = 'InternalError'
|
||||
errorinfo = None
|
||||
|
||||
|
||||
class HelpMessage(Message):
|
||||
is_reply = True
|
||||
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
|
202
secop/protocol/messages_old.py
Normal file
202
secop/protocol/messages_old.py
Normal file
@ -0,0 +1,202 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Messages"""
|
||||
|
||||
# Request Types
|
||||
REQUEST = 'request'
|
||||
REPLY = 'reply'
|
||||
ERROR = 'error'
|
||||
|
||||
# Message types ('actions') hint: fetch is list+read
|
||||
LIST = 'list'
|
||||
READ = 'read'
|
||||
WRITE = 'write'
|
||||
COMMAND = 'command'
|
||||
POLL = 'poll'
|
||||
SUBSCRIBE = 'subscribe'
|
||||
UNSUBSCRIBE = 'unsubscribe'
|
||||
TRIGGER = 'trigger'
|
||||
EVENT = 'event'
|
||||
ERROR = 'error'
|
||||
HELP = 'help'
|
||||
|
||||
# base class for messages
|
||||
|
||||
|
||||
class Message(object):
|
||||
|
||||
MSGTYPE = 'Undefined'
|
||||
devs = None
|
||||
pars = None
|
||||
props = None
|
||||
result = None
|
||||
error = None
|
||||
ARGS = None
|
||||
errortype = None
|
||||
|
||||
def __init__(self, **kwds):
|
||||
self.devs = []
|
||||
self.pars = []
|
||||
self.props = []
|
||||
self.result = []
|
||||
self.ARGS = set()
|
||||
for k, v in kwds.items():
|
||||
self.setvalue(k, v)
|
||||
|
||||
def setvalue(self, key, value):
|
||||
setattr(self, key, value)
|
||||
self.ARGS.add(key)
|
||||
|
||||
@property
|
||||
def NAME(self):
|
||||
# generate sensible name
|
||||
r = 'Message'
|
||||
if self.props:
|
||||
r = 'Property' if self.props != ['*'] else 'Properties'
|
||||
elif self.pars:
|
||||
r = 'Parameter' if self.pars != ['*'] else 'Parameters'
|
||||
elif self.devs:
|
||||
r = 'Device' if self.devs != ['*'] else 'Devices'
|
||||
|
||||
t = ''
|
||||
if self.MSGTYPE in [LIST, READ, WRITE, COMMAND,
|
||||
POLL, SUBSCRIBE, UNSUBSCRIBE, HELP]:
|
||||
t = 'Request' if not self.result else 'Reply'
|
||||
|
||||
if self.errortype is None:
|
||||
return self.MSGTYPE.title() + r + t
|
||||
else:
|
||||
return self.errortype + 'Error'
|
||||
|
||||
def __repr__(self):
|
||||
return self.NAME + '(' + \
|
||||
', '.join('%s=%r' % (k, getattr(self, k))
|
||||
for k in self.ARGS if getattr(self, k) is not None) + ')'
|
||||
|
||||
|
||||
class Value(object):
|
||||
|
||||
def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
|
||||
self.dev = ''
|
||||
self.param = ''
|
||||
self.prop = ''
|
||||
self.value = value
|
||||
self.qualifiers = qualifiers or dict()
|
||||
self.__dict__.update(kwds)
|
||||
|
||||
def __repr__(self):
|
||||
devspec = self.dev
|
||||
if self.param:
|
||||
devspec = '%s:%s' % (devspec, self.param)
|
||||
if self.prop:
|
||||
devspec = '%s:%s' % (devspec, self.prop)
|
||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
||||
[repr(self.value)] + ['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
|
||||
|
||||
|
||||
class ListMessage(Message):
|
||||
MSGTYPE = LIST
|
||||
|
||||
|
||||
class ReadMessage(Message):
|
||||
MSGTYPE = READ # return cached value
|
||||
|
||||
|
||||
class WriteMessage(Message):
|
||||
MSGTYPE = WRITE # write value to some spec
|
||||
target = None # actually float or string
|
||||
|
||||
|
||||
class CommandMessage(Message):
|
||||
MSGTYPE = COMMAND
|
||||
cmd = '' # always string
|
||||
args = []
|
||||
result = []
|
||||
|
||||
|
||||
class PollMessage(Message):
|
||||
MSGTYPE = POLL # read HW and return hw_value
|
||||
|
||||
|
||||
class SubscribeMessage(Message):
|
||||
MSGTYPE = SUBSCRIBE
|
||||
|
||||
|
||||
class UnsubscribeMessage(Message):
|
||||
MSGTYPE = UNSUBSCRIBE
|
||||
|
||||
|
||||
class TriggerMessage(Message):
|
||||
MSGTYPE = TRIGGER
|
||||
|
||||
|
||||
class EventMessage(Message):
|
||||
MSGTYPE = EVENT
|
||||
|
||||
|
||||
class ErrorMessage(Message):
|
||||
MSGTYPE = ERROR
|
||||
errorstring = 'an unhandled error occured'
|
||||
errortype = 'UnknownError'
|
||||
|
||||
|
||||
class HelpMessage(Message):
|
||||
MSGTYPE = HELP
|
||||
|
||||
|
||||
class NoSuchDeviceError(ErrorMessage):
|
||||
def __init__(self, *devs):
|
||||
ErrorMessage.__init__(self, devs=devs, errorstring="Device %r does not exist" % devs[0], errortype='NoSuchDevice')
|
||||
|
||||
|
||||
class NoSuchParamError(ErrorMessage):
|
||||
def __init__(self, dev, *params):
|
||||
ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r has no parameter %r" % (dev, params[0]), errortype='NoSuchParam')
|
||||
|
||||
|
||||
class ParamReadonlyError(ErrorMessage):
|
||||
def __init__(self, dev, *params):
|
||||
ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r, parameter %r is not writeable!" % (dev, params[0]), errortype='ParamReadOnly')
|
||||
|
||||
|
||||
class InvalidParamValueError(ErrorMessage):
|
||||
def __init__(self, dev, param, value, e):
|
||||
ErrorMessage.__init__(self, devs=(dev,), params=params, values=(value), errorstring=str(e), errortype='InvalidParamValueError')
|
||||
|
||||
|
||||
class InternalError(ErrorMessage):
|
||||
def __init__(self, err, **kwds):
|
||||
ErrorMessage.__init__(self, errorstring=str(err), errortype='InternalError', **kwds)
|
||||
|
||||
|
||||
MESSAGE = dict((cls.MSGTYPE, cls) for cls in [HelpMessage, ErrorMessage, EventMessage, TriggerMessage, UnsubscribeMessage, SubscribeMessage,
|
||||
PollMessage, CommandMessage, WriteMessage, ReadMessage, ListMessage])
|
||||
|
||||
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
|
37
secop/protocol/status.py
Normal file
37
secop/protocol/status.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""Define Status constants"""
|
||||
|
||||
# could also be some objects
|
||||
OK = 100
|
||||
BUSY = 200
|
||||
WARN = 300
|
||||
UNSTABLE = 350
|
||||
ERROR = 400
|
||||
UNKNOWN = -1
|
||||
|
||||
#OK = 'idle'
|
||||
#BUSY = 'busy'
|
||||
#WARN = 'alarm'
|
||||
#UNSTABLE = 'unstable'
|
||||
#ERROR = 'ERROR'
|
||||
#UNKNOWN = 'unknown'
|
Reference in New Issue
Block a user