First checkin, base point for development
+ docu-converter + some python source files + Makefile Change-Id: I055522c2feff9b72fc4bc88d6663642c78693fe8
This commit is contained in:
parent
8152eab7c9
commit
b52c2d7a60
2
.description
Normal file
2
.description
Normal file
@ -0,0 +1,2 @@
|
||||
SECoP playground for creating specification and testing one implementation.
|
||||
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.pdf binary
|
||||
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
html/*
|
||||
*.pyc
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
.PHONY: all doc clean
|
||||
|
||||
all: clean doc
|
||||
|
||||
clean:
|
||||
@echo "cleaning pyc files"
|
||||
@find . -name '*.pyc' -delete
|
||||
@echo "cleaning html tree"
|
||||
@rm -rf html
|
||||
@mkdir html
|
||||
|
||||
doc: doc/*.md
|
||||
@echo "Generating html tree"
|
||||
@bin/make_doc.py
|
||||
|
64
bin/make_doc.py
Executable file
64
bin/make_doc.py
Executable file
@ -0,0 +1,64 @@
|
||||
#!/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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import os
|
||||
from os import path
|
||||
import markdown
|
||||
import codecs
|
||||
|
||||
BASE_PATH = path.abspath(path.join(path.dirname(__file__), '..'))
|
||||
DOC_SRC = path.join(BASE_PATH, 'doc')
|
||||
DOC_DST = path.join(BASE_PATH, 'html')
|
||||
|
||||
conv = markdown.Markdown()
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(DOC_SRC):
|
||||
# re-create the dir-structure of DOC_SRC into DOC_DST
|
||||
dst_path = path.join(DOC_DST, path.relpath(dirpath, DOC_SRC))
|
||||
try:
|
||||
os.mkdir(dst_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
for fn in filenames:
|
||||
full_name = path.join(dirpath, fn)
|
||||
sub_name = path.relpath(full_name, DOC_SRC)
|
||||
final_name = path.join(DOC_DST, sub_name)
|
||||
|
||||
if not fn.endswith('md'):
|
||||
# just copy everything else
|
||||
with open(full_name, 'rb') as fi:
|
||||
with open(final_name, 'wb') as fo:
|
||||
# WARNING: possible Memory hog!
|
||||
fo.write(fi.read())
|
||||
continue
|
||||
# treat .md files special
|
||||
final_sub_name = path.splitext(sub_name)[0] + '.html'
|
||||
final_name = path.join(DOC_DST, final_sub_name)
|
||||
print "Converting", sub_name, "to", final_sub_name
|
||||
# transform one file
|
||||
conv.reset()
|
||||
conv.convertFile(input=full_name,
|
||||
output=final_name,
|
||||
encoding="utf-8")
|
32
bin/server.py
Executable file
32
bin/server.py
Executable file
@ -0,0 +1,32 @@
|
||||
#!/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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import os
|
||||
import sys
|
||||
from os import path
|
||||
|
||||
sys.path[0]=path.abspath(path.join(sys.path[0],'../src'))
|
||||
|
||||
import transport
|
||||
|
||||
transport.startup_server()
|
2
doc/index.md
Normal file
2
doc/index.md
Normal file
@ -0,0 +1,2 @@
|
||||
Markdown docu to be generated
|
||||
=============================
|
16
etc/config.ini
Normal file
16
etc/config.ini
Normal file
@ -0,0 +1,16 @@
|
||||
[server]
|
||||
bindto=localhost
|
||||
bindport=10767
|
||||
protocol=pickle
|
||||
|
||||
[device "LN2"]
|
||||
class=devices.test.LN2
|
||||
|
||||
[device "heater"]
|
||||
class=devices.test.Heater
|
||||
maxheaterpower=10
|
||||
|
||||
[device "T1"]
|
||||
class=devices.test.temp
|
||||
sensor="X34598T7"
|
||||
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
# for generating docu
|
||||
markdown>=2.6
|
||||
# for zmq
|
||||
#pyzmq>=13.1.0
|
||||
|
65
src/device.py
Normal file
65
src/device.py
Normal file
@ -0,0 +1,65 @@
|
||||
from lib import attrdict
|
||||
|
||||
class Status(object):
|
||||
OK = 200
|
||||
MOVING = 210
|
||||
WARN = 220
|
||||
UNSTABLE = 230
|
||||
ERROR = 240
|
||||
UNKNOWN = 999
|
||||
|
||||
status = Status()
|
||||
|
||||
|
||||
# XXX: deriving PARS/CMDS should be done in a suitable metaclass....
|
||||
class Device(object):
|
||||
name = None
|
||||
def read_status(self):
|
||||
raise NotImplemented
|
||||
def read_name(self):
|
||||
return self.name
|
||||
|
||||
class Readable(Device):
|
||||
unit = ''
|
||||
def read_value(self):
|
||||
raise NotImplemented
|
||||
def read_unit(self):
|
||||
return self.unit
|
||||
|
||||
class Writeable(Readable):
|
||||
def read_target(self):
|
||||
return self.target
|
||||
def write_target(self, target):
|
||||
self.target = target
|
||||
|
||||
class Driveable(Writeable):
|
||||
def do_wait(self):
|
||||
raise NotImplemented
|
||||
def do_stop(self):
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
def get_device_pars(dev):
|
||||
# returns a mapping of the devices parameter names to some 'description'
|
||||
res = {}
|
||||
for n in dir(dev):
|
||||
if n.startswith('read_'):
|
||||
pname = n[5:]
|
||||
entry = attrdict(readonly=True, description=getattr(dev,n).__doc__)
|
||||
if hasattr(dev, 'write_%s' % pname):
|
||||
entry['readonly'] = False
|
||||
res[pname] = entry
|
||||
return res
|
||||
|
||||
def get_device_cmds(dev):
|
||||
# returns a mapping of the devices commands names to some 'description'
|
||||
res = {}
|
||||
for n in dir(dev):
|
||||
if n.startswith('do_'):
|
||||
cname = n[5:]
|
||||
func = getattr(dev,n)
|
||||
entry = attrdict(description=func.__doc__, args='unknown') # XXX: use inspect!
|
||||
res[cname] = entry
|
||||
return res
|
||||
|
||||
|
50
src/lib.py
Normal file
50
src/lib.py
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
class attrdict(dict):
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "minimal testing: transport"
|
||||
testcases = dict(
|
||||
error=[ErrorReply(),
|
||||
NoSuchDeviceErrorReply('device3'),
|
||||
NoSuchParamErrorReply('device2', 'param3'),
|
||||
ParamReadonlyErrorReply('device1', 'param1'),
|
||||
UnsupportedFeatureErrorReply('feature5'),
|
||||
NoSuchCommandErrorReply('device1','fance_command'),
|
||||
CommandFailedErrorReply('device1','stop'),
|
||||
InvalidParamValueErrorReply('device1','param2','STRING_Value'),
|
||||
],
|
||||
reply=[Reply(),
|
||||
ListDevicesReply('device1', 'device2'),
|
||||
ListDeviceParamsReply('device', ['param1', 'param2']),
|
||||
ReadValueReply('device2', 3.1415),
|
||||
ReadParamReply('device1', 'param2', 2.718),
|
||||
WriteParamReply('device1', 'param2', 2.718),
|
||||
RequestAsyncDataReply('device1', 'XXX: what to put here?'),
|
||||
AsyncDataUnit('device1', 'param2', 2.718),
|
||||
ListOfFeaturesReply('feature1', 'feature2'),
|
||||
ActivateFeatureReply(),
|
||||
],
|
||||
request=[Request(),
|
||||
ListDevicesRequest(),
|
||||
ListDeviceParamsRequest('device1'),
|
||||
ReadValueRequest('device2'),
|
||||
ReadParamRequest('device1', 'param2'),
|
||||
WriteParamRequest('device1', 'param2', 2.718),
|
||||
RequestAsyncDataRequest('device1', ['param1', 'param2']),
|
||||
ListOfFeaturesRequest(),
|
||||
ActivateFeatureRequest('feature1'),
|
||||
],
|
||||
)
|
||||
for msgtype, msgs in testcases.items():
|
||||
print "___ testing %ss ___" % msgtype
|
||||
for msg in msgs:
|
||||
print msg.__class__.__name__, 'is', msgtype,
|
||||
decoded = parse(msg)
|
||||
if decoded[0] != msgtype:
|
||||
print "\tFAIL, got %r but expected %r" %(decoded[0], msgtype)
|
||||
else:
|
||||
print "\tOk"
|
||||
print
|
||||
|
246
src/messages.py
Normal file
246
src/messages.py
Normal file
@ -0,0 +1,246 @@
|
||||
|
||||
from lib import attrdict
|
||||
|
||||
class Request(object):
|
||||
pars = []
|
||||
def __repr__(self):
|
||||
pars = ', '.join('%s=%r' % (k, self.__dict__[k]) for k in self.pars)
|
||||
s = '%s(%s)' % (self.__class__.__name__, pars)
|
||||
return s
|
||||
|
||||
class Reply(object):
|
||||
pars = []
|
||||
def __repr__(self):
|
||||
pars = ', '.join('%s=%r' % (k, self.__dict__[k]) for k in self.pars)
|
||||
s = '%s(%s)' % (self.__class__.__name__, pars)
|
||||
return s
|
||||
|
||||
|
||||
class ListDevicesRequest(Request):
|
||||
pass
|
||||
|
||||
class ListDevicesReply(Reply):
|
||||
pars = ['list_of_devices']
|
||||
def __init__(self, args):
|
||||
self.list_of_devices = args
|
||||
|
||||
|
||||
class ListDeviceParamsRequest(Request):
|
||||
pars = ['device']
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
|
||||
class ListDeviceParamsReply(Reply):
|
||||
pars = ['device', 'params']
|
||||
def __init__(self, device, params):
|
||||
self.device = device
|
||||
self.params = params
|
||||
|
||||
class ReadValueRequest(Request):
|
||||
pars = ['device']
|
||||
def __init__(self, device, maxage=0):
|
||||
self.device = device
|
||||
|
||||
class ReadValueReply(Reply):
|
||||
pars = ['device', 'value', 'timestamp', 'error', 'unit']
|
||||
def __init__(self, device, value, timestamp=0, error=0, unit=None):
|
||||
self.device = device
|
||||
self.value = value
|
||||
self.timestamp = timestamp
|
||||
self.error = error
|
||||
self.unit = unit
|
||||
|
||||
|
||||
class ReadParamRequest(Request):
|
||||
pars = ['device', 'param']
|
||||
def __init__(self, device, param, maxage=0):
|
||||
self.device = device
|
||||
self.param = param
|
||||
|
||||
class ReadParamReply(Reply):
|
||||
pars = ['device', 'param', 'value', 'timestamp', 'error', 'unit']
|
||||
def __init__(self, device, param, value, timestamp=0, error=0, unit=None):
|
||||
self.device = device
|
||||
self.param = param
|
||||
self.value = value
|
||||
self.timestamp = timestamp
|
||||
self.error = error
|
||||
self.unit = unit
|
||||
|
||||
|
||||
class WriteParamRequest(Request):
|
||||
pars = ['device', 'param', 'value']
|
||||
def __init__(self, device, param, value):
|
||||
self.device = device
|
||||
self.param = param
|
||||
self.value = value
|
||||
|
||||
class WriteParamReply(Reply):
|
||||
pars = ['device', 'param', 'readback_value', 'timestamp', 'error', 'unit']
|
||||
def __init__(self, device, param, readback_value, timestamp=0, error=0, unit=None):
|
||||
self.device = device
|
||||
self.param = param
|
||||
self.readback_value = readback_value
|
||||
self.timestamp = timestamp
|
||||
self.error = error
|
||||
self.unit = unit
|
||||
|
||||
|
||||
class RequestAsyncDataRequest(Request):
|
||||
pars = ['device', 'params']
|
||||
def __init__(self, device, *args):
|
||||
self.device = device
|
||||
self.params = args
|
||||
|
||||
class RequestAsyncDataReply(Reply):
|
||||
pars = ['device', 'paramvalue_list']
|
||||
def __init__(self, device, *args):
|
||||
self.device = device
|
||||
self.paramvalue_list = args
|
||||
|
||||
class AsyncDataUnit(ReadParamReply):
|
||||
pass
|
||||
|
||||
|
||||
class ListOfFeaturesRequest(Request):
|
||||
pass
|
||||
|
||||
class ListOfFeaturesReply(Reply):
|
||||
pars = ['features']
|
||||
def __init__(self, *args):
|
||||
self.features = args
|
||||
|
||||
class ActivateFeatureRequest(Request):
|
||||
pars = ['feature']
|
||||
def __init__(self, feature):
|
||||
self.feature = feature
|
||||
|
||||
class ActivateFeatureReply(Reply):
|
||||
# Ack style or Error
|
||||
# may be should reply with active features?
|
||||
pass
|
||||
|
||||
|
||||
Features = [
|
||||
'Feature1',
|
||||
'Feature2',
|
||||
'Feature3',
|
||||
]
|
||||
|
||||
|
||||
# Error replies:
|
||||
|
||||
class ErrorReply(Reply):
|
||||
pars = ['error']
|
||||
def __init__(self, error):
|
||||
self.error = error
|
||||
|
||||
class NoSuchDeviceErrorReply(ErrorReply):
|
||||
pars = ['device']
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
|
||||
class NoSuchParamErrorReply(ErrorReply):
|
||||
pars = ['device', 'param']
|
||||
def __init__(self, device, param):
|
||||
self.device = device
|
||||
self.param = param
|
||||
|
||||
class ParamReadonlyErrorReply(ErrorReply):
|
||||
pars = ['device', 'param']
|
||||
def __init__(self, device, param):
|
||||
self.device = device
|
||||
self.param = param
|
||||
|
||||
class UnsupportedFeatureErrorReply(ErrorReply):
|
||||
pars = ['feature']
|
||||
def __init__(self, feature):
|
||||
self.feature = feature
|
||||
|
||||
class NoSuchCommandErrorReply(ErrorReply):
|
||||
pars = ['device', 'command']
|
||||
def __init__(self, device, command):
|
||||
self.device = device
|
||||
self.command = command
|
||||
|
||||
class CommandFailedErrorReply(ErrorReply):
|
||||
pars = ['device', 'command']
|
||||
def __init__(self, device, command):
|
||||
self.device = device
|
||||
self.command = command
|
||||
|
||||
class InvalidParamValueErrorReply(ErrorReply):
|
||||
pars = ['device', 'param', 'value']
|
||||
def __init__(self, device, param, value):
|
||||
self.device = device
|
||||
self.param = param
|
||||
self.value = value
|
||||
|
||||
class attrdict(dict):
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
def parse(message):
|
||||
# parses a message and returns
|
||||
# msgtype, msgname and parameters of message (as dict)
|
||||
msgtype = 'unknown'
|
||||
msgname = 'unknown'
|
||||
if isinstance(message, ErrorReply):
|
||||
msgtype = 'error'
|
||||
msgname = message.__class__.__name__[:-len('Reply')]
|
||||
elif isinstance(message, Request):
|
||||
msgtype = 'request'
|
||||
msgname = message.__class__.__name__[:-len('Request')]
|
||||
elif isinstance(message, Reply):
|
||||
msgtype = 'reply'
|
||||
msgname = message.__class__.__name__[:-len('Reply')]
|
||||
return msgtype, msgname, \
|
||||
attrdict([(k, getattr(message, k)) for k in message.pars])
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "minimal testing: transport"
|
||||
testcases = dict(
|
||||
error=[ErrorReply(),
|
||||
NoSuchDeviceErrorReply('device3'),
|
||||
NoSuchParamErrorReply('device2', 'param3'),
|
||||
ParamReadonlyErrorReply('device1', 'param1'),
|
||||
UnsupportedFeatureErrorReply('feature5'),
|
||||
NoSuchCommandErrorReply('device1','fance_command'),
|
||||
CommandFailedErrorReply('device1','stop'),
|
||||
InvalidParamValueErrorReply('device1','param2','STRING_Value'),
|
||||
],
|
||||
reply=[Reply(),
|
||||
ListDevicesReply('device1', 'device2'),
|
||||
ListDeviceParamsReply('device', ['param1', 'param2']),
|
||||
ReadValueReply('device2', 3.1415),
|
||||
ReadParamReply('device1', 'param2', 2.718),
|
||||
WriteParamReply('device1', 'param2', 2.718),
|
||||
RequestAsyncDataReply('device1', 'XXX: what to put here?'),
|
||||
AsyncDataUnit('device1', 'param2', 2.718),
|
||||
ListOfFeaturesReply('feature1', 'feature2'),
|
||||
ActivateFeatureReply(),
|
||||
],
|
||||
request=[Request(),
|
||||
ListDevicesRequest(),
|
||||
ListDeviceParamsRequest('device1'),
|
||||
ReadValueRequest('device2'),
|
||||
ReadParamRequest('device1', 'param2'),
|
||||
WriteParamRequest('device1', 'param2', 2.718),
|
||||
RequestAsyncDataRequest('device1', ['param1', 'param2']),
|
||||
ListOfFeaturesRequest(),
|
||||
ActivateFeatureRequest('feature1'),
|
||||
],
|
||||
)
|
||||
for msgtype, msgs in testcases.items():
|
||||
print "___ testing %ss ___" % msgtype
|
||||
for msg in msgs:
|
||||
print msg.__class__.__name__, 'is', msgtype,
|
||||
decoded = parse(msg)
|
||||
if decoded[0] != msgtype:
|
||||
print "\tFAIL, got %r but expected %r" %(decoded[0], msgtype)
|
||||
else:
|
||||
print "\tOk"
|
||||
print
|
||||
|
158
src/server.py
Normal file
158
src/server.py
Normal file
@ -0,0 +1,158 @@
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from messages import *
|
||||
from device import *
|
||||
|
||||
|
||||
class DeviceServer(object):
|
||||
def __init__(self):
|
||||
self._devices = {}
|
||||
self.log = logging
|
||||
|
||||
self.log.basicConfig(level=logging.WARNING,
|
||||
format='%(asctime)s %(levelname)s %(message)s')
|
||||
|
||||
def registerDevice(self, deviceobj, devicename):
|
||||
# make the server export a deviceobj under a given name.
|
||||
# all exportet properties are taken from the device
|
||||
if devicename in self._devices:
|
||||
self.log.error('IGN: Device %r already registered' % devicename)
|
||||
else:
|
||||
self._devices[devicename] = deviceobj
|
||||
deviceobj.name = devicename
|
||||
|
||||
def unRegisterDevice(self, device_obj_or_name):
|
||||
if not device_obj_or_name in self._devices:
|
||||
self.log.error('IGN: Device %r not registered!' % device_obj_or_name)
|
||||
else:
|
||||
del self._devices[device_obj_or_name]
|
||||
# may need to do more
|
||||
|
||||
def handle(self, msg):
|
||||
# server got a message, handle it
|
||||
msgtype, msgname, msgargs = parse(msg)
|
||||
if msgtype != 'request':
|
||||
self.log.error('IGN: Server only handles request, but got %s/%s!' % (msgtype, msgname))
|
||||
return
|
||||
try:
|
||||
self.log.info('handling message %s with %r' % (msgname, msgargs))
|
||||
res = self._handle(msgname, msgargs)
|
||||
self.log.info('replying with %r' % res)
|
||||
return res
|
||||
except Exception as err:
|
||||
res = ErrorReply('Exception:\n%r' % err)
|
||||
self.log.info('replying with %r' % res)
|
||||
return res
|
||||
|
||||
def _handle(self, msgname, msgargs):
|
||||
# check all supported Requests, act and return reply
|
||||
self.log.debug('handling request %r' % msgname)
|
||||
if msgname == 'ListDevices':
|
||||
return ListDevicesReply(list(self._devices.keys()))
|
||||
elif msgname == 'ListDeviceParams':
|
||||
devobj = self._devices.get(msgargs.device, None)
|
||||
if devobj:
|
||||
return ListDeviceParamsReply(msgargs.device, get_device_pars(devobj))
|
||||
else:
|
||||
return NoSuchDeviceErrorReply(msgargs.device)
|
||||
elif msgname == 'ReadValue':
|
||||
devobj = self._devices.get(msgargs.device, None)
|
||||
if devobj:
|
||||
return ReadValueReply(msgargs.device, devobj.read_value(), timestamp=time.time())
|
||||
else:
|
||||
return NoSuchDeviceErrorReply(msgargs.device)
|
||||
elif msgname == 'ReadParam':
|
||||
devobj = self._devices.get(msgargs.device, None)
|
||||
if devobj:
|
||||
readfunc = getattr(devobj, 'read_%s' % msgargs.param, None)
|
||||
if readfunc:
|
||||
return ReadParamReply(msgargs.device, msgargs.param, readfunc(), timestamp=time.time())
|
||||
else:
|
||||
return NoSuchParamErrorReply(msgargs.device, msgargs.param)
|
||||
else:
|
||||
return NoSuchDeviceErrorReply(msgargs.device)
|
||||
elif msgname == 'WriteParam':
|
||||
devobj = self._devices.get(msgargs.device, None)
|
||||
if devobj:
|
||||
writefunc = getattr(devobj, 'write_%s' % msgargs.param, None)
|
||||
if writefunc:
|
||||
return WriteParamReply(msgargs.device, msgargs.param, writefunc(msgargs.value) or msgargs.value, timestamp=time.time())
|
||||
else:
|
||||
if getattr(devobj, 'read_%s' % msgargs.param, None):
|
||||
return ParamReadonlyErrorReply(msgargs.device, msgargs.param)
|
||||
else:
|
||||
return NoSuchParamErrorReply(msgargs.device, msgargs.param)
|
||||
else:
|
||||
|
||||
return NoSuchDeviceErrorReply(msgargs.device)
|
||||
elif msgname == 'RequestAsyncData':
|
||||
return ErrorReply('AsyncData is not (yet) supported')
|
||||
elif msgname == 'ListOfFeatures':
|
||||
return ListOfFeaturesReply([])
|
||||
elif msgname == 'ActivateFeature':
|
||||
return ErrorReply('Features are not (yet) supported')
|
||||
else:
|
||||
self.log.error('IGN: got unhandled request %s' % msgname)
|
||||
return ErrorReply('Got Unhandled Request')
|
||||
|
||||
|
||||
class TestDevice(Driveable):
|
||||
name = 'Unset'
|
||||
unit = 'Oinks'
|
||||
def read_status(self):
|
||||
return status.OK
|
||||
def read_value(self):
|
||||
"""The devices main value"""
|
||||
return 3.1415
|
||||
def read_testpar1(self):
|
||||
return 2.718
|
||||
def read_fail(self):
|
||||
raise KeyError()
|
||||
def read_none(self):
|
||||
pass
|
||||
# def read_NotImplemented(self):
|
||||
# raise NotImplemented()
|
||||
def do_wait(self):
|
||||
time.sleep(3)
|
||||
def do_stop(self):
|
||||
pass
|
||||
def do_count(self):
|
||||
print "counting:"
|
||||
for d in range(10-1,-1,-1):
|
||||
print '%d',
|
||||
time.sleep(1)
|
||||
print
|
||||
def do_add_args(self, arg1, arg2):
|
||||
return arg1+arg2
|
||||
def do_return_stuff(self):
|
||||
return [{a:1},(2,3)]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "minimal testing: server"
|
||||
srv = DeviceServer()
|
||||
srv.registerDevice(TestDevice(), 'dev1')
|
||||
srv.registerDevice(TestDevice(), 'dev2')
|
||||
devices = parse(srv.handle(ListDevicesRequest()))[2]['list_of_devices']
|
||||
print 'Srv exports these devices:', devices
|
||||
for dev in sorted(devices):
|
||||
print '___ testing device %s ___' % dev
|
||||
params = parse(srv.handle(ListDeviceParamsRequest(dev)))[2]['params']
|
||||
print '-has params: ', sorted(params.keys())
|
||||
for p in sorted(params.keys()):
|
||||
pinfo = params[p]
|
||||
if pinfo.readonly:
|
||||
print ' - param %r is readonly' % p
|
||||
if pinfo.description:
|
||||
print ' - param %r\'s description is: %r' % (p, pinfo.description)
|
||||
else:
|
||||
print ' - param %r has no description' % p
|
||||
replytype, replyname, rv = parse(srv.handle(ReadParamRequest(dev, p)))
|
||||
if replytype == 'error':
|
||||
print ' - reading param %r resulted in error/%s' %(p, replyname)
|
||||
else:
|
||||
print ' - param %r current value is %r' % (p, rv.value)
|
||||
print ' - param %r current unit is %r' % (p, rv.unit)
|
||||
|
143
src/transport.py
Normal file
143
src/transport.py
Normal file
@ -0,0 +1,143 @@
|
||||
#!/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 transport layer of SECoP"""
|
||||
|
||||
# currently implements pickling Python-objects over plain TCP
|
||||
# WARNING: This is not (really) portable to other languages!
|
||||
|
||||
import time
|
||||
import socket
|
||||
import threading
|
||||
import SocketServer
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
|
||||
from server import DeviceServer
|
||||
from messages import ListOfFeaturesRequest
|
||||
|
||||
DEF_PORT = 10767
|
||||
MAX_MESSAGE_SIZE = 1024
|
||||
|
||||
def decodeMessage(msg):
|
||||
"""transport layer message -> msg object"""
|
||||
return pickle.loads(msg)
|
||||
|
||||
def encodeMessage(msgobj):
|
||||
"""msg object -> transport layer message"""
|
||||
return pickle.dumps(msgobj)
|
||||
|
||||
def encodeMessageFrame(msg):
|
||||
"""add transport layer encapsulation/framing of messages"""
|
||||
return '%s\n' % msg
|
||||
|
||||
def decodeMessageFrame(frame):
|
||||
"""remove transport layer encapsulation/framing of messages"""
|
||||
if '\n' in frame:
|
||||
# WARNING: ignores everything after first '\n'
|
||||
return frame.split('\n', 1)[0]
|
||||
# invalid/incomplete frames return nothing here atm.
|
||||
return None
|
||||
|
||||
|
||||
class SECoPClient(object):
|
||||
"""connects to a SECoPServer and provides communication"""
|
||||
_socket = None
|
||||
def connect(self, server='localhost'):
|
||||
if self._socket:
|
||||
raise Exception('%r is already connected!' % self)
|
||||
if ':' not in server:
|
||||
server = '%s:%d' % (server, DEF_PORT)
|
||||
host, port = server.split(':')
|
||||
port = int(port)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.connect((host, port))
|
||||
self._negotiateServerSettings()
|
||||
|
||||
def close(self):
|
||||
if not self._socket:
|
||||
raise Exception('%r is not connected!' % self)
|
||||
self._socket.close(socket.SH_RDONLY)
|
||||
self._socket.close(socket.SH_RDWR)
|
||||
self._socket = None
|
||||
|
||||
def _sendRequest(self, request):
|
||||
if not self._socket:
|
||||
raise Exception('%r is not connected!' % self)
|
||||
self._socket.send(encodeMessageFrame(encodeMessage(request)))
|
||||
|
||||
def _recvReply(self):
|
||||
if not self._socket:
|
||||
raise Exception('%r is not connected!' % self)
|
||||
rawdata = ''
|
||||
while True:
|
||||
data = self._socket.recv(MAX_MESSAGE_SIZE)
|
||||
if not(data):
|
||||
time.sleep(0.1)
|
||||
# XXX: needs timeout mechanism!
|
||||
continue
|
||||
rawdata = rawdata + data
|
||||
msg = decodeMessageFrame(rawdata)
|
||||
if msg:
|
||||
return decodeMessage(msg)
|
||||
|
||||
def _negotiateServerSettings(self):
|
||||
self._sendRequest(ListOfFeaturesRequest())
|
||||
print self._recvReply()
|
||||
# XXX: fill with life!
|
||||
|
||||
|
||||
class SECoPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
def handle(self):
|
||||
"""handle a new tcp-connection"""
|
||||
# self.client_address
|
||||
socket = self.request
|
||||
frame = ''
|
||||
# start serving
|
||||
while True:
|
||||
_frame = socket.recv(MAX_MESSAGE_SIZE)
|
||||
if not _frame:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
frame = frame + _frame
|
||||
msg = decodeMessageFrame(frame)
|
||||
if msg:
|
||||
requestObj = decodeMessage(msg)
|
||||
replyObj = self.handle_request(requestObj)
|
||||
self.send(encodeMessageFrame(encodeMessage(replyObj)))
|
||||
frame = ''
|
||||
|
||||
def handle_request(self, requestObj):
|
||||
# XXX: handle connection/Server specific Requests
|
||||
# pass other (Device) requests to the DeviceServer
|
||||
return self.server.handle(requestObj)
|
||||
|
||||
|
||||
class SECoPServer(SocketServer.ThreadingTCPServer, DeviceServer):
|
||||
daemon_threads = False
|
||||
|
||||
def startup_server():
|
||||
srv = SECoPServer(('localhost', DEF_PORT), SECoPRequestHandler, bind_and_activate=True)
|
||||
srv.serve_forever()
|
||||
srv.server_close()
|
Loading…
x
Reference in New Issue
Block a user