improve Py2/3 compat

Change-Id: I1dfdcb88a492401851d5157c734cd708496bf004
Reviewed-on: https://forge.frm2.tum.de/review/17734
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
Enrico Faulhaber 2018-04-16 14:08:12 +02:00
parent 0d25dc35e0
commit 3b802e67c8
39 changed files with 917 additions and 1618 deletions

View File

@ -28,12 +28,12 @@ ramp.default=60
ramp.unit=K/min
abslimits.datatype=["tuple",[["double"],["double"]]]
abslimits.default=[0,2000]
abslimits.description=currently active absolute limits for the setpoint. depend on the regulationmode parameter (both/stick->0..600, tube->0..300K).
abslimits.description=currently active absolute limits for the setpoint.
abslimits.unit='degC'
abslimits.readonly=True
userlimits.datatype=["tuple",[["double"],["double"]]]
userlimits.default=[0,300]
userlimits.description=current user set limits for the setpoint. must be inside abslimits.
userlimits.description=current user set limits for the setpoint. must be inside or coincide with abslimits.
userlimits.unit='degC'
[module T_sample]

View File

@ -18,6 +18,7 @@
#
# Module authors:
# Alexander Lenz <alexander.lenz@frm2.tum.de>
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
@ -26,5 +27,4 @@ try:
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
except ImportError:
print('can not import sip, the gui may not work as expected')
pass

View File

@ -26,6 +26,19 @@
from __future__ import print_function
import code
from os import path
import socket
import threading
from collections import deque
try:
import configparser
except ImportError:
import ConfigParser as configparser
import mlzlog
from secop.protocol.interface.tcp import decode_msg, get_msg, encode_msg_frame
from secop.protocol.messages import EVENTREPLY, DESCRIPTIONREQUEST, Message
class NameSpace(dict):
@ -49,14 +62,9 @@ class NameSpace(dict):
dict.__delitem__(self, name)
try:
import ConfigParser
except ImportError:
import configparser as ConfigParser
def getClientOpts(cfgfile):
parser = ConfigParser.SafeConfigParser()
parser = configparser.SafeConfigParser()
if not parser.read([cfgfile + '.cfg']):
print("Error reading cfg file %r" % cfgfile)
return {}
@ -65,9 +73,6 @@ def getClientOpts(cfgfile):
return dict(item for item in parser.items('client'))
from os import path
class ClientConsole(object):
def __init__(self, cfgname, basepath):
@ -93,23 +98,10 @@ class ClientConsole(object):
help(arg)
import socket
import threading
from collections import deque
import mlzlog
from secop.protocol.encoding import ENCODERS
from secop.protocol.framing import FRAMERS
from secop.protocol.messages import EventMessage, DescribeRequest
class TCPConnection(object):
def __init__(self, connect, port, encoding, framing, **kwds):
def __init__(self, connect, port, **kwds):
self.log = mlzlog.log.getChild('connection', False)
self.encoder = ENCODERS[encoding]()
self.framer = FRAMERS[framing]()
self.connection = socket.create_connection((connect, port), 3)
self.queue = deque()
self._rcvdata = ''
@ -120,8 +112,7 @@ class TCPConnection(object):
def send(self, msg):
self.log.debug("Sending msg %r" % msg)
frame = self.encoder.encode(msg)
data = self.framer.encode(frame)
data = encode_msg_frame(*msg.serialize())
self.log.debug("raw data: %r" % data)
self.connection.sendall(data)
@ -133,20 +124,29 @@ class TCPConnection(object):
self.log.exception("Exception in RCV thread: %r" % e)
def thread_step(self):
data = b''
while True:
data = self.connection.recv(1024)
self.log.debug("RCV: got raw data %r" % data)
if data:
frames = self.framer.decode(data)
self.log.debug("RCV: frames %r" % frames)
for frame in frames:
msgs = self.encoder.decode(frame)
self.log.debug("RCV: msgs %r" % msgs)
for msg in msgs:
self.handle(msg)
newdata = self.connection.recv(1024)
self.log.debug("RCV: got raw data %r" % newdata)
data = data + newdata
while True:
origin, data = get_msg(data)
if origin is None:
break # no more messages to process
if not origin: # empty string
continue # ???
msg = decode_msg(origin)
# construct msgObj from msg
try:
msgObj = Message(*msg)
msgObj.origin = origin.decode('latin-1')
self.handle(msgObj)
except Exception:
# ??? what to do here?
pass
def handle(self, msg):
if isinstance(msg, EventMessage):
if msg.action == EVENTREPLY:
self.log.info("got Async: %r" % msg)
for cb in self.callbacks:
try:
@ -188,7 +188,7 @@ class Client(object):
# XXX: further notification-callbacks needed ???
def populateNamespace(self, namespace):
self.connection.send(DescribeRequest())
self.connection.send(Message(DESCRIPTIONREQUEST))
# reply = self.connection.read()
# self.log.info("found modules %r" % reply)
# create proxies, populate cache....

View File

@ -32,11 +32,12 @@ from collections import OrderedDict
import time
import serial
# Py2/3
try:
import Queue
# py3
import queue
except ImportError:
import queue as Queue
# py2
import Queue as queue
import mlzlog
@ -61,7 +62,7 @@ class TCPConnection(object):
self.connect()
def connect(self):
self._readbuffer = Queue.Queue(100)
self._readbuffer = queue.Queue(100)
io = socket.create_connection((self._host, self._port))
io.setblocking(False)
io.settimeout(0.3)
@ -75,7 +76,7 @@ class TCPConnection(object):
data = u''
while True:
try:
newdata = u''
newdata = b''
dlist = [self._io.fileno()]
rlist, wlist, xlist = select(dlist, dlist, dlist, 1)
if dlist[0] in rlist + wlist:
@ -92,14 +93,14 @@ class TCPConnection(object):
for cb, arg in self.callbacks:
cb(arg)
return
data += newdata
data += newdata.decode('latin-1')
while '\n' in data:
line, data = data.split('\n', 1)
try:
self._readbuffer.put(line.strip('\r'),
block=True,
timeout=1)
except Queue.Full:
except queue.Full:
self.log.debug('rcv queue full! dropping line: %r' %
line)
finally:
@ -111,7 +112,7 @@ class TCPConnection(object):
while i:
try:
return self._readbuffer.get(block=True, timeout=1)
except Queue.Empty:
except queue.Empty:
continue
if not block:
i -= 1
@ -120,7 +121,7 @@ class TCPConnection(object):
return not self._readbuffer.empty()
def write(self, data):
self._io.sendall(data)
self._io.sendall(data.encode('latin-1'))
def writeline(self, line):
self.write(line + '\n')
@ -370,7 +371,7 @@ class Client(object):
try:
describing_data = self._decode_substruct(
['modules'], describing_data)
for modname, module in describing_data['modules'].items():
for modname, module in list(describing_data['modules'].items()):
describing_data['modules'][modname] = self._decode_substruct(
['parameters', 'commands'], module)
@ -383,14 +384,12 @@ class Client(object):
# pprint.pprint(r(describing_data))
for module, moduleData in self.describing_data['modules'].items():
for parameter, parameterData in moduleData[
'parameters'].items():
for parameter, parameterData in moduleData['parameters'].items():
datatype = get_datatype(parameterData['datatype'])
self.describing_data['modules'][module]['parameters'] \
[parameter]['datatype'] = datatype
for _cmdname, cmdData in moduleData[
'commands'].items():
cmdData['arguments'] = map(get_datatype, cmdData['arguments'])
for _cmdname, cmdData in moduleData['commands'].items():
cmdData['arguments'] = list(map(get_datatype, cmdData['arguments']))
cmdData['resulttype'] = get_datatype(cmdData['resulttype'])
except Exception as _exc:
print(formatException(verbose=True))
@ -544,10 +543,10 @@ class Client(object):
@property
def modules(self):
return self.describing_data['modules'].keys()
return list(self.describing_data['modules'].keys())
def getParameters(self, module):
return self.describing_data['modules'][module]['parameters'].keys()
return list(self.describing_data['modules'][module]['parameters'].keys())
def getModuleProperties(self, module):
return self.describing_data['modules'][module]['properties']

View File

@ -21,6 +21,13 @@
# *****************************************************************************
"""Define validated data types."""
try:
# py2
unicode(u'')
except NameError:
# py3
unicode = str # pylint: disable=redefined-builtin
from base64 import b64encode, b64decode
from .errors import ProgrammingError, ParsingError
@ -30,19 +37,19 @@ Parser = Parser()
# Only export these classes for 'from secop.datatypes import *'
__all__ = [
"DataType",
"FloatRange", "IntRange",
"BoolType", "EnumType",
"BLOBType", "StringType",
"TupleOf", "ArrayOf", "StructOf",
"Command",
u'DataType',
u'FloatRange', u'IntRange',
u'BoolType', u'EnumType',
u'BLOBType', u'StringType',
u'TupleOf', u'ArrayOf', u'StructOf',
u'Command',
]
# base class for all DataTypes
class DataType(object):
as_json = ['undefined']
as_json = [u'undefined']
IS_COMMAND = False
def validate(self, value):
@ -80,39 +87,39 @@ class FloatRange(DataType):
self.min = None if minval is None else float(minval)
self.max = None if maxval is None else float(maxval)
# note: as we may compare to Inf all comparisons would be false
if (self.min or float('-inf')) <= (self.max or float('+inf')):
if (self.min or float(u'-inf')) <= (self.max or float(u'+inf')):
if minval is None and maxval is None:
self.as_json = ['double']
self.as_json = [u'double']
else:
self.as_json = ['double', minval, maxval]
self.as_json = [u'double', minval, maxval]
else:
raise ValueError('Max must be larger then min!')
raise ValueError(u'Max must be larger then min!')
def validate(self, value):
try:
value = float(value)
except:
raise ValueError('Can not validate %r to float' % value)
raise ValueError(u'Can not validate %r to float' % value)
if self.min is not None and value < self.min:
raise ValueError('%r should not be less then %s' %
raise ValueError(u'%r should not be less then %s' %
(value, self.min))
if self.max is not None and value > self.max:
raise ValueError('%r should not be greater than %s' %
raise ValueError(u'%r should not be greater than %s' %
(value, self.max))
if None in (self.min, self.max):
return value
if self.min <= value <= self.max:
return value
raise ValueError('%r should be an float between %.3f and %.3f' %
raise ValueError(u'%r should be an float between %.3f and %.3f' %
(value, self.min, self.max))
def __repr__(self):
if self.max is not None:
return "FloatRange(%r, %r)" % (
float('-inf') if self.min is None else self.min, self.max)
return u'FloatRange(%r, %r)' % (
float(u'-inf') if self.min is None else self.min, self.max)
if self.min is not None:
return "FloatRange(%r)" % self.min
return "FloatRange()"
return u'FloatRange(%r)' % self.min
return u'FloatRange()'
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -134,31 +141,31 @@ class IntRange(DataType):
self.min = int(minval) if minval is not None else minval
self.max = int(maxval) if maxval is not None else maxval
if self.min is not None and self.max is not None and self.min > self.max:
raise ValueError('Max must be larger then min!')
raise ValueError(u'Max must be larger then min!')
if self.min is None and self.max is None:
self.as_json = ['int']
self.as_json = [u'int']
else:
self.as_json = ['int', self.min, self.max]
self.as_json = [u'int', self.min, self.max]
def validate(self, value):
try:
value = int(value)
if self.min is not None and value < self.min:
raise ValueError('%r should be an int between %d and %d' %
raise ValueError(u'%r should be an int between %d and %d' %
(value, self.min, self.max or 0))
if self.max is not None and value > self.max:
raise ValueError('%r should be an int between %d and %d' %
raise ValueError(u'%r should be an int between %d and %d' %
(value, self.min or 0, self.max))
return value
except:
raise ValueError('Can not validate %r to int' % value)
raise ValueError(u'Can not validate %r to int' % value)
def __repr__(self):
if self.max is not None:
return "IntRange(%d, %d)" % (self.min, self.max)
return u'IntRange(%d, %d)' % (self.min, self.max)
if self.min is not None:
return "IntRange(%d)" % self.min
return "IntRange()"
return u'IntRange(%d)' % self.min
return u'IntRange()'
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -174,37 +181,37 @@ class IntRange(DataType):
class EnumType(DataType):
as_json = ['enum']
as_json = [u'enum']
def __init__(self, *args, **kwds):
# enum keys are ints! remember mapping from intvalue to 'name'
self.entries = {}
self.entries = {} # maps ints to strings
num = 0
for arg in args:
if not isinstance(arg, str):
raise ValueError('EnumType entries MUST be strings!')
if not isinstance(arg, (str, unicode)):
raise ValueError(u'EnumType entries MUST be strings!')
self.entries[num] = arg
num += 1
for k, v in kwds.items():
for k, v in list(kwds.items()):
v = int(v)
if v in self.entries:
raise ValueError(
'keyword argument %r=%d is already assigned %r' %
u'keyword argument %r=%d is already assigned %r' %
(k, v, self.entries[v]))
self.entries[v] = k
self.entries[v] = unicode(k)
# if len(self.entries) == 0:
# raise ValueError('Empty enums ae not allowed!')
# also keep a mapping from name strings to numbers
self.reversed = {}
self.reversed = {} # maps Strings to ints
for k, v in self.entries.items():
if v in self.reversed:
raise ValueError('Mapping for %r=%r is not Unique!' % (v, k))
raise ValueError(u'Mapping for %r=%r is not Unique!' % (v, k))
self.reversed[v] = k
self.as_json = ['enum', self.reversed.copy()]
self.as_json = [u'enum', self.reversed.copy()]
def __repr__(self):
return "EnumType(%s)" % ', '.join(
['%s=%d' % (v, k) for k, v in self.entries.items()])
return u'EnumType(%s)' % u', '.join(
[u'%s=%d' % (v, k) for k, v in list(self.entries.items())])
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -212,8 +219,8 @@ class EnumType(DataType):
return self.reversed[value]
if int(value) in self.entries:
return int(value)
raise ValueError('%r is not one of %s' %
(str(value), ', '.join(self.reversed.keys())))
raise ValueError(u'%r is not one of %s' %
(unicode(value), u', '.join(list(self.reversed.keys()))))
def import_value(self, value):
"""returns a python object from serialisation"""
@ -223,11 +230,11 @@ class EnumType(DataType):
def validate(self, value):
"""return the validated (internal) value or raise"""
if value in self.reversed:
return value
return self.reversed[value]
if int(value) in self.entries:
return self.entries[int(value)]
raise ValueError('%r is not one of %s' %
(str(value), ', '.join(map(str, self.entries.keys()))))
return int(value)
raise ValueError(u'%r is not one of %s' %
(unicode(value), u', '.join(map(unicode, self.entries))))
def from_string(self, text):
value = text
@ -240,36 +247,36 @@ class BLOBType(DataType):
def __init__(self, maxsize=None, minsize=0):
if maxsize is None:
raise ValueError('BLOBType needs a maximum number of Bytes count!')
raise ValueError(u'BLOBType needs a maximum number of Bytes count!')
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
self.minsize = minsize
self.maxsize = maxsize
if minsize < 0:
raise ValueError('sizes must be bigger than or equal to 0!')
raise ValueError(u'sizes must be bigger than or equal to 0!')
if minsize:
self.as_json = ['blob', maxsize, minsize]
self.as_json = [u'blob', maxsize, minsize]
else:
self.as_json = ['blob', maxsize]
self.as_json = [u'blob', maxsize]
def __repr__(self):
if self.minsize:
return 'BLOB(%s, %s)' % (
str(self.minsize) if self.minsize else 'unspecified',
str(self.maxsize) if self.maxsize else 'unspecified')
return 'BLOB(%s)' % (str(self.minsize) if self.minsize else 'unspecified')
return u'BLOB(%s, %s)' % (
unicode(self.minsize) if self.minsize else u'unspecified',
unicode(self.maxsize) if self.maxsize else u'unspecified')
return u'BLOB(%s)' % (unicode(self.minsize) if self.minsize else u'unspecified')
def validate(self, value):
"""return the validated (internal) value or raise"""
if type(value) not in [str, unicode]:
raise ValueError('%r has the wrong type!' % value)
if type(value) not in [unicode, str]:
raise ValueError(u'%r has the wrong type!' % value)
size = len(value)
if size < self.minsize:
raise ValueError(
'%r must be at least %d bytes long!' % (value, self.minsize))
u'%r must be at least %d bytes long!' % (value, self.minsize))
if self.maxsize is not None:
if size > self.maxsize:
raise ValueError(
'%r must be at most %d bytes long!' % (value, self.maxsize))
u'%r must be at most %d bytes long!' % (value, self.maxsize))
return value
def export_value(self, value):
@ -287,76 +294,76 @@ class BLOBType(DataType):
class StringType(DataType):
as_json = ['string']
as_json = [u'string']
minsize = None
maxsize = None
def __init__(self, maxsize=255, minsize=0):
if maxsize is None:
raise ValueError('StringType needs a maximum bytes count!')
raise ValueError(u'StringType needs a maximum bytes count!')
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
if minsize < 0:
raise ValueError('sizes must be >= 0')
raise ValueError(u'sizes must be >= 0')
if minsize:
self.as_json = ['string', maxsize, minsize]
self.as_json = [u'string', maxsize, minsize]
else:
self.as_json = ['string', maxsize]
self.as_json = [u'string', maxsize]
self.minsize = minsize
self.maxsize = maxsize
def __repr__(self):
if self.minsize:
return 'StringType(%s, %s)' % (
str(self.minsize) or 'unspecified', str(self.maxsize) or 'unspecified')
return 'StringType(%s)' % str(self.maxsize)
return u'StringType(%s, %s)' % (
unicode(self.minsize) or u'unspecified', unicode(self.maxsize) or u'unspecified')
return u'StringType(%s)' % unicode(self.maxsize)
def validate(self, value):
"""return the validated (internal) value or raise"""
if type(value) not in [str, unicode]:
raise ValueError('%r has the wrong type!' % value)
if type(value) not in (unicode, str):
raise ValueError(u'%r has the wrong type!' % value)
size = len(value)
if size < self.minsize:
raise ValueError(
'%r must be at least %d bytes long!' % (value, self.minsize))
u'%r must be at least %d bytes long!' % (value, self.minsize))
if self.maxsize is not None:
if size > self.maxsize:
raise ValueError(
'%r must be at most %d bytes long!' % (value, self.maxsize))
if '\0' in value:
u'%r must be at most %d bytes long!' % (value, self.maxsize))
if u'\0' in value:
raise ValueError(
'Strings are not allowed to embed a \\0! Use a Blob instead!')
u'Strings are not allowed to embed a \\0! Use a Blob instead!')
return value
def export_value(self, value):
"""returns a python object fit for serialisation"""
return '%s' % value
return u'%s' % value
def import_value(self, value):
"""returns a python object from serialisation"""
# XXX: do we keep it as unicode str, or convert it to something else? (UTF-8 maybe?)
return str(value)
return unicode(value)
def from_string(self, text):
value = str(text)
value = unicode(text)
return self.validate(value)
# Bool is a special enum
class BoolType(DataType):
as_json = ['bool']
as_json = [u'bool']
def __repr__(self):
return 'BoolType()'
return u'BoolType()'
def validate(self, value):
"""return the validated (internal) value or raise"""
if value in [0, '0', 'False', 'false', 'no', 'off', False]:
if value in [0, u'0', u'False', u'false', u'no', u'off', False]:
return False
if value in [1, '1', 'True', 'true', 'yes', 'on', True]:
if value in [1, u'1', u'True', u'true', u'yes', u'on', True]:
return True
raise ValueError('%r is not a boolean value!' % value)
raise ValueError(u'%r is not a boolean value!' % value)
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -382,26 +389,26 @@ class ArrayOf(DataType):
self.subtype = subtype
if not isinstance(subtype, DataType):
raise ValueError(
'ArrayOf only works with DataType objs as first argument!')
u'ArrayOf only works with DataType objs as first argument!')
if maxsize is None:
raise ValueError('ArrayOf needs a maximum size')
raise ValueError(u'ArrayOf needs a maximum size')
minsize, maxsize = min(minsize, maxsize), max(minsize, maxsize)
if minsize < 0:
raise ValueError('sizes must be > 0')
raise ValueError(u'sizes must be > 0')
if maxsize < 1:
raise ValueError('Maximum size must be >= 1!')
raise ValueError(u'Maximum size must be >= 1!')
# if only one arg is given, it is maxsize!
if minsize:
self.as_json = ['array', subtype.as_json, maxsize, minsize]
self.as_json = [u'array', subtype.as_json, maxsize, minsize]
else:
self.as_json = ['array', subtype.as_json, maxsize]
self.as_json = [u'array', subtype.as_json, maxsize]
self.minsize = minsize
self.maxsize = maxsize
def __repr__(self):
return 'ArrayOf(%s, %s, %s)' % (
repr(self.subtype), self.minsize or 'unspecified', self.maxsize or 'unspecified')
return u'ArrayOf(%s, %s, %s)' % (
repr(self.subtype), self.minsize or u'unspecified', self.maxsize or u'unspecified')
def validate(self, value):
"""validate a external representation to an internal one"""
@ -409,15 +416,15 @@ class ArrayOf(DataType):
# check number of elements
if self.minsize is not None and len(value) < self.minsize:
raise ValueError(
'Array too small, needs at least %d elements!' %
u'Array too small, needs at least %d elements!' %
self.minsize)
if self.maxsize is not None and len(value) > self.maxsize:
raise ValueError(
'Array too big, holds at most %d elements!' % self.minsize)
u'Array too big, holds at most %d elements!' % self.minsize)
# apply subtype valiation to all elements and return as list
return [self.subtype.validate(elem) for elem in value]
raise ValueError(
'Can not convert %s to ArrayOf DataType!' % repr(value))
u'Can not convert %s to ArrayOf DataType!' % repr(value))
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -430,7 +437,7 @@ class ArrayOf(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError('trailing garbage: %r' % rem)
raise ParsingError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -438,16 +445,16 @@ class TupleOf(DataType):
def __init__(self, *subtypes):
if not subtypes:
raise ValueError('Empty tuples are not allowed!')
raise ValueError(u'Empty tuples are not allowed!')
for subtype in subtypes:
if not isinstance(subtype, DataType):
raise ValueError(
'TupleOf only works with DataType objs as arguments!')
u'TupleOf only works with DataType objs as arguments!')
self.subtypes = subtypes
self.as_json = ['tuple', [subtype.as_json for subtype in subtypes]]
self.as_json = [u'tuple', [subtype.as_json for subtype in subtypes]]
def __repr__(self):
return 'TupleOf(%s)' % ', '.join([repr(st) for st in self.subtypes])
return u'TupleOf(%s)' % u', '.join([repr(st) for st in self.subtypes])
def validate(self, value):
"""return the validated value or raise"""
@ -455,13 +462,13 @@ class TupleOf(DataType):
try:
if len(value) != len(self.subtypes):
raise ValueError(
'Illegal number of Arguments! Need %d arguments.' %
u'Illegal number of Arguments! Need %d arguments.' %
(len(self.subtypes)))
# validate elements and return as list
return [sub.validate(elem)
for sub, elem in zip(self.subtypes, value)]
except Exception as exc:
raise ValueError('Can not validate:', str(exc))
raise ValueError(u'Can not validate:', unicode(exc))
def export_value(self, value):
"""returns a python object fit for serialisation"""
@ -474,7 +481,7 @@ class TupleOf(DataType):
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError('trailing garbage: %r' % rem)
raise ParsingError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -482,57 +489,57 @@ class StructOf(DataType):
def __init__(self, **named_subtypes):
if not named_subtypes:
raise ValueError('Empty structs are not allowed!')
for name, subtype in named_subtypes.items():
raise ValueError(u'Empty structs are not allowed!')
for name, subtype in list(named_subtypes.items()):
if not isinstance(subtype, DataType):
raise ProgrammingError(
'StructOf only works with named DataType objs as keyworded arguments!')
if not isinstance(name, (str, unicode)):
u'StructOf only works with named DataType objs as keyworded arguments!')
if not isinstance(name, (unicode, str)):
raise ProgrammingError(
'StructOf only works with named DataType objs as keyworded arguments!')
u'StructOf only works with named DataType objs as keyworded arguments!')
self.named_subtypes = named_subtypes
self.as_json = ['struct', dict((n, s.as_json)
for n, s in named_subtypes.items())]
self.as_json = [u'struct', dict((n, s.as_json)
for n, s in list(named_subtypes.items()))]
def __repr__(self):
return 'StructOf(%s)' % ', '.join(
['%s=%s' % (n, repr(st)) for n, st in self.named_subtypes.iteritems()])
return u'StructOf(%s)' % u', '.join(
[u'%s=%s' % (n, repr(st)) for n, st in list(self.named_subtypes.items())])
def validate(self, value):
"""return the validated value or raise"""
try:
if len(value.keys()) != len(self.named_subtypes.keys()):
if len(list(value.keys())) != len(list(self.named_subtypes.keys())):
raise ValueError(
'Illegal number of Arguments! Need %d arguments.' %
len(self.named_subtypes.keys()))
u'Illegal number of Arguments! Need %d arguments.' %
len(list(self.named_subtypes.keys())))
# validate elements and return as dict
return dict((str(k), self.named_subtypes[k].validate(v))
for k, v in value.items())
return dict((unicode(k), self.named_subtypes[k].validate(v))
for k, v in list(value.items()))
except Exception as exc:
raise ValueError('Can not validate %s: %s' % (repr(value), str(exc)))
raise ValueError(u'Can not validate %s: %s' % (repr(value), unicode(exc)))
def export_value(self, value):
"""returns a python object fit for serialisation"""
if len(value.keys()) != len(self.named_subtypes.keys()):
if len(list(value.keys())) != len(list(self.named_subtypes.keys())):
raise ValueError(
'Illegal number of Arguments! Need %d arguments.' % len(
self.namd_subtypes.keys()))
return dict((str(k), self.named_subtypes[k].export_value(v))
for k, v in value.items())
u'Illegal number of Arguments! Need %d arguments.' % len(
list(self.namd_subtypes.keys())))
return dict((unicode(k), self.named_subtypes[k].export_value(v))
for k, v in list(value.items()))
def import_value(self, value):
"""returns a python object from serialisation"""
if len(value.keys()) != len(self.named_subtypes.keys()):
if len(list(value.keys())) != len(list(self.named_subtypes.keys())):
raise ValueError(
'Illegal number of Arguments! Need %d arguments.' % len(
self.namd_subtypes.keys()))
return dict((str(k), self.named_subtypes[k].import_value(v))
for k, v in value.items())
u'Illegal number of Arguments! Need %d arguments.' % len(
list(self.namd_subtypes.keys())))
return dict((unicode(k), self.named_subtypes[k].import_value(v))
for k, v in list(value.items()))
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError('trailing garbage: %r' % rem)
raise ParsingError(u'trailing garbage: %r' % rem)
return self.validate(dict(value))
@ -543,50 +550,50 @@ class Command(DataType):
def __init__(self, argtypes=tuple(), resulttype=None):
for arg in argtypes:
if not isinstance(arg, DataType):
raise ValueError('Command: Argument types must be DataTypes!')
raise ValueError(u'Command: Argument types must be DataTypes!')
if resulttype is not None:
if not isinstance(resulttype, DataType):
raise ValueError('Command: result type must be DataTypes!')
raise ValueError(u'Command: result type must be DataTypes!')
self.argtypes = argtypes
self.resulttype = resulttype
if resulttype is not None:
self.as_json = ['command',
self.as_json = [u'command',
[t.as_json for t in argtypes],
resulttype.as_json]
else:
self.as_json = ['command',
self.as_json = [u'command',
[t.as_json for t in argtypes],
None] # XXX: or NoneType ???
def __repr__(self):
argstr = ', '.join(repr(arg) for arg in self.argtypes)
argstr = u', '.join(repr(arg) for arg in self.argtypes)
if self.resulttype is None:
return 'Command(%s)' % argstr
return 'Command(%s)->%s' % (argstr, repr(self.resulttype))
return u'Command(%s)' % argstr
return u'Command(%s)->%s' % (argstr, repr(self.resulttype))
def validate(self, value):
"""return the validated arguments value or raise"""
try:
if len(value) != len(self.argtypes):
raise ValueError(
'Illegal number of Arguments! Need %d arguments.' %
u'Illegal number of Arguments! Need %d arguments.' %
len(self.argtypes))
# validate elements and return
return [t.validate(v) for t, v in zip(self.argtypes, value)]
except Exception as exc:
raise ValueError('Can not validate %s: %s' % (repr(value), str(exc)))
raise ValueError(u'Can not validate %s: %s' % (repr(value), unicode(exc)))
def export_value(self, value):
raise ProgrammingError('values of type command can not be transported!')
raise ProgrammingError(u'values of type command can not be transported!')
def import_value(self, value):
raise ProgrammingError('values of type command can not be transported!')
raise ProgrammingError(u'values of type command can not be transported!')
def from_string(self, text):
value, rem = Parser.parse(text)
if rem:
raise ParsingError('trailing garbage: %r' % rem)
raise ParsingError(u'trailing garbage: %r' % rem)
return self.validate(value)
@ -601,7 +608,7 @@ DATATYPES = dict(
tuple=lambda subtypes: TupleOf(*map(get_datatype, subtypes)),
enum=lambda kwds: EnumType(**kwds),
struct=lambda named_subtypes: StructOf(
**dict((n, get_datatype(t)) for n, t in named_subtypes.items())),
**dict((n, get_datatype(t)) for n, t in list(named_subtypes.items()))),
command=Command,
)
@ -616,12 +623,12 @@ def get_datatype(json):
return json
if not isinstance(json, list):
raise ValueError(
'Can not interpret datatype %r, it should be a list!' % json)
u'Can not interpret datatype %r, it should be a list!' % json)
if len(json) < 1:
raise ValueError('can not validate %r' % json)
raise ValueError(u'can not validate %r' % json)
base = json[0]
if base in DATATYPES:
if base in ('enum', 'struct'):
if base in (u'enum', u'struct'):
if len(json) > 1:
args = json[1:]
else:
@ -631,5 +638,5 @@ def get_datatype(json):
try:
return DATATYPES[base](*args)
except (TypeError, AttributeError):
raise ValueError('Invalid datatype descriptor in %r' % json)
raise ValueError('can not convert %r to datatype' % json)
raise ValueError(u'Invalid datatype descriptor in %r' % json)
raise ValueError(u'can not convert %r to datatype' % json)

View File

@ -1,7 +1,5 @@
#!/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

View File

@ -24,10 +24,17 @@
from __future__ import print_function
try:
# py2
unicode(u'')
except NameError:
# py3
unicode = str # pylint: disable=redefined-builtin
from secop.gui.util import loadUi
from secop.gui.params import ParameterView
from secop.datatypes import * # pylint: disable=unused-wildcard-import,wildcard-import
#from secop.datatypes import ...
from secop.gui.qt import QDialog, QLabel, QCheckBox, QWidget, QMessageBox, \
QPushButton, QSizePolicy
@ -93,7 +100,7 @@ class ParameterGroup(QWidget):
self._row = 0
self._widgets = []
self.paramGroupBox.setTitle('Group: ' + str(groupname))
self.paramGroupBox.setTitle('Group: ' + unicode(groupname))
self.paramGroupBox.toggled.connect(self.on_toggle_clicked)
self.paramGroupBox.setChecked(False)
@ -275,7 +282,7 @@ class ModuleCtrl(QWidget):
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
# make 'display' label
view = QLabel(str(props[prop]))
view = QLabel(unicode(props[prop]))
view.setFont(self.font())
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
view.setWordWrap(True)
@ -351,9 +358,9 @@ class ModuleCtrl(QWidget):
try:
self._node.setParameter(module, parameter, target)
except Exception as e:
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
QMessageBox.warning(self.parent(), 'Operation failed', unicode(e))
def _updateValue(self, module, parameter, value):
if module != self._module:
return
self._paramWidgets[parameter][1].updateValue(str(value[0]))
self._paramWidgets[parameter][1].updateValue(unicode(value[0]))

View File

@ -21,6 +21,14 @@
#
# *****************************************************************************
from __future__ import print_function, division
try:
# py2
unicode(u'')
except NameError:
unicode = str # pylint: disable=redefined-builtin
import pprint
import json
from time import sleep
@ -121,9 +129,9 @@ class NodeCtrl(QWidget):
def _getLogWidth(self):
fontMetrics = QFontMetrics(QFont('Monospace'))
# calculate max avail characters by using an a (which is possible
# calculate max avail characters by using an m (which is possible
# due to monospace)
result = self.logTextBrowser.width() / fontMetrics.width('a')
result = self.logTextBrowser.width() / fontMetrics.width('m')
return result
def _init_modules_tab(self):
@ -277,6 +285,7 @@ class DrivableWidget(ReadableWidget):
if self._is_enum:
# EnumType: disable Linedit
self.targetLineEdit.setHidden(True)
self.cmdPushButton.setHidden(True)
else:
# normal types: disable Combobox
self.targetComboBox.setHidden(True)

View File

@ -21,11 +21,17 @@
#
# *****************************************************************************
try:
# py2
unicode(u'')
except NameError:
unicode = str # pylint: disable=redefined-builtin
from secop.gui.qt import QWidget, QLabel, QPushButton as QButton, QLineEdit, \
QMessageBox, QCheckBox, QSizePolicy, Qt, pyqtSignal, pyqtSlot
from secop.gui.util import loadUi
from secop.datatypes import * # pylint: disable=wildcard-import
from secop.datatypes import EnumType
class ParameterWidget(QWidget):
@ -107,9 +113,8 @@ class EnumParameterWidget(GenericParameterWidget):
@pyqtSlot()
def on_setPushButton_clicked(self):
enumval, enumname = self._map[self.setComboBox.currentIndex()]
_enumval, enumname = self._map[self.setComboBox.currentIndex()]
self.setRequested.emit(self._module, self._paramcmd, enumname)
self.setRequested.emit(self._module, self._paramcmd, str(enumval))
def updateValue(self, valuestr):
try:

View File

@ -21,10 +21,16 @@
#
# *****************************************************************************
try:
# py2
unicode(u'')
except NameError:
# py3
unicode = str # pylint: disable=redefined-builtin
from secop.gui.qt import QWidget, QLabel, QSizePolicy
from secop.gui.util import loadUi
#from secop.datatypes import get_datatype
class ParameterView(QWidget):

View File

@ -1,3 +1,26 @@
# -*- 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 needed stuff from PyQt4/PyQt5"""
# pylint: disable=unused-import
from __future__ import print_function

View File

@ -23,7 +23,8 @@
from __future__ import print_function
from secop.datatypes import * # pylint: disable=unused-wildcard-import,wildcard-import
from secop.datatypes import FloatRange, IntRange, StringType, BLOBType, \
EnumType, BoolType, TupleOf, StructOf, ArrayOf
from secop.gui.qt import QDialog, QLabel, QLineEdit,\
QGroupBox, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox, \
@ -112,8 +113,8 @@ class FloatWidget(QDoubleSpinBox):
self.datatype = datatype
if readonly:
self.setEnabled(False)
self.setMaximum(datatype.max)
self.setMinimum(datatype.min)
self.setMaximum(datatype.max or 1e6) # XXX!
self.setMinimum(datatype.min or 0) # XXX!
self.setDecimals(12)
def get_value(self):
@ -141,7 +142,7 @@ class TupleWidget(QFrame):
self.update()
def get_value(self):
return [v.validate(w.get_value()) for w,v in zip(self.subwidgets, self.datatypes)]
return [v.validate(w.get_value()) for w, v in zip(self.subwidgets, self.datatypes)]
def set_value(self, value):
for w, _ in zip(self.subwidgets, value):
@ -225,7 +226,7 @@ class msg(QDialog):
row = 0
self.gridLayout.addWidget(QLabel('struct'), row, 0)
dt = StructOf(i=IntRange(0,10), f=FloatRange(), b=BoolType())
dt = StructOf(i=IntRange(0, 10), f=FloatRange(), b=BoolType())
w = StructWidget(dt)
self.gridLayout.addWidget(w, row, 1)
row+=1

View File

@ -109,7 +109,7 @@ def mkthread(func, *args, **kwds):
def formatExtendedFrame(frame):
ret = []
for key, value in frame.f_locals.iteritems():
for key, value in frame.f_locals.items():
try:
valstr = repr(value)[:256]
except Exception:
@ -217,7 +217,8 @@ def tcpSocket(host, defaultport, timeout=None):
return s
def closeSocket(sock, socket=socket):
# keep a reference to socket to avoid (interpreter) shut-down problems
def closeSocket(sock, socket=socket): # pylint: disable=redefined-outer-name
"""Do our best to close a socket."""
if sock is None:
return

View File

@ -44,14 +44,12 @@ class LocalTimezone(tzinfo):
def utcoffset(self, dt):
if self._isdst(dt):
return self.DSTOFFSET
else:
return self.STDOFFSET
return self.STDOFFSET
def dst(self, dt):
if self._isdst(dt):
return self.DSTDIFF
else:
return self.ZERO
return self.ZERO
def tzname(self, dt):
return time.tzname[self._isdst(dt)]
@ -81,7 +79,7 @@ def format_time(timestamp=None):
class Timezone(tzinfo):
def __init__(self, offset, name='unknown timezone'):
def __init__(self, offset, name='unknown timezone'): # pylint: disable=W0231
self.offset = offset
self.name = name
@ -114,7 +112,7 @@ def _parse_isostring(isostring):
kw['microsecond'] = kw['microsecond'].ljust(6, '0')
_tzinfo = kw.pop('tzinfo')
if _tzinfo == 'Z':
_tzinfo = timezone.utc
_tzinfo = timezone.utc # pylint: disable=E0602
elif _tzinfo is not None:
offset_mins = int(_tzinfo[-2:]) if len(_tzinfo) > 3 else 0
offset_hours = int(_tzinfo[1:3])
@ -304,9 +302,9 @@ class ArgsParser(object):
if self.peek() == '-':
self.get()
number = self.parse_pos_int()
if number is None:
return number
return -number
if number is not None:
return -number # pylint: disable=invalid-unary-operand-type
return None
return self.parse_pos_int()
def parse_pos_int(self):
@ -314,7 +312,7 @@ class ArgsParser(object):
number = 0
if self.peek() not in self.DIGITS_CHARS:
return None
while (self.peek() in self.DIGITS_CHARS):
while self.peek() in self.DIGITS_CHARS:
number = number * 10 + int(self.get())
self.skip()
return number

View File

@ -73,7 +73,7 @@ class SequencerMixin(object):
self._seq_fault_on_error = fault_on_error
self._seq_fault_on_stop = fault_on_stop
self._seq_stopflag = False
self._seq_phase = ''
self._seq_phase = u''
self._seq_error = None
self._seq_stopped = None
@ -115,7 +115,7 @@ class SequencerMixin(object):
the default is to only go into ALARM.
"""
if self.seq_is_alive():
raise IsBusyError('move sequence already in progress')
raise IsBusyError(u'move sequence already in progress')
self._seq_stopflag = False
self._seq_error = self._seq_stopped = None
@ -128,7 +128,7 @@ class SequencerMixin(object):
def read_status(self, maxage=0):
if self.seq_is_alive():
return status.BUSY, 'moving: ' + self._seq_phase
return status.BUSY, u'moving: ' + self._seq_phase
elif self._seq_error:
if self._seq_fault_on_error:
return status.ERROR, self._seq_error
@ -137,9 +137,9 @@ class SequencerMixin(object):
if self._seq_fault_on_stop:
return status.ERROR, self._seq_stopped
return status.WARN, self._seq_stopped
if hasattr(self, 'read_hw_status'):
if hasattr(self, u'read_hw_status'):
return self.read_hw_status(maxage)
return status.OK, ''
return status.OK, u''
def do_stop(self):
if self.seq_is_alive():
@ -149,7 +149,7 @@ class SequencerMixin(object):
try:
self._seq_thread_inner(seq, store_init)
except Exception as e:
self.log.exception('unhandled error in sequence thread: %s', e)
self.log.exception(u'unhandled error in sequence thread: %s', e)
self._seq_error = str(e)
finally:
self._seq_thread = None
@ -158,11 +158,11 @@ class SequencerMixin(object):
def _seq_thread_inner(self, seq, store_init):
store = Namespace()
store.__dict__.update(store_init)
self.log.debug('sequence: starting, values %s', store_init)
self.log.debug(u'sequence: starting, values %s', store_init)
for step in seq:
self._seq_phase = step.desc
self.log.debug('sequence: entering phase: %s', step.desc)
self.log.debug(u'sequence: entering phase: %s', step.desc)
try:
i = 0
while True:
@ -170,10 +170,10 @@ class SequencerMixin(object):
result = step.func(store, *step.args)
if self._seq_stopflag:
if result:
self._seq_stopped = 'stopped while %s' % step.desc
self._seq_stopped = u'stopped while %s' % step.desc
else:
self._seq_stopped = 'stopped after %s' % step.desc
cleanup_func = step.kwds.get('cleanup', None)
self._seq_stopped = u'stopped after %s' % step.desc
cleanup_func = step.kwds.get(u'cleanup', None)
if callable(cleanup_func):
try:
cleanup_func(store, result, *step.args)
@ -187,6 +187,6 @@ class SequencerMixin(object):
i += 1
except Exception as e:
self.log.exception(
'error in sequence step %r: %s', step.desc, e)
self._seq_error = 'during %s: %s' % (step.desc, e)
u'error in sequence step %r: %s', step.desc, e)
self._seq_error = u'during %s: %s' % (step.desc, e)
break

View File

@ -30,6 +30,8 @@ import time
import types
import inspect
import six # for py2/3 compat
from secop.lib import formatExtendedStack, mkthread
from secop.lib.parsing import format_time
from secop.errors import ConfigError, ProgrammingError
@ -123,7 +125,7 @@ class Override(object):
def apply(self, paramobj):
if isinstance(paramobj, Param):
for k, v in self.kwds.iteritems():
for k, v in self.kwds.items():
if hasattr(paramobj, k):
setattr(paramobj, k, v)
return paramobj
@ -184,9 +186,9 @@ class ModuleMeta(type):
newparams = getattr(newtype, 'parameters')
for base in reversed(bases):
overrides = getattr(base, 'overrides', {})
for n, o in overrides.iteritems():
for n, o in overrides.items():
newparams[n] = o.apply(newparams[n].copy())
for n, o in attrs.get('overrides', {}).iteritems():
for n, o in attrs.get('overrides', {}).items():
newparams[n] = o.apply(newparams[n].copy())
# check validity of Param entries
@ -291,9 +293,9 @@ class ModuleMeta(type):
# if you want to 'update from the hardware', call self.read_<pname>
# the return value of this method will be used as the new cached value and
# be returned.
@six.add_metaclass(ModuleMeta)
class Module(object):
"""Basic Module, doesn't do much"""
__metaclass__ = ModuleMeta
# static properties, definitions in derived classes should overwrite earlier ones.
# how to configure some stuff which makes sense to take from configfile???
properties = {
@ -318,13 +320,13 @@ class Module(object):
self.name = devname
# make local copies of parameter
params = {}
for k, v in self.parameters.items()[:]:
for k, v in list(self.parameters.items()):
params[k] = v.copy()
self.parameters = params
# make local copies of properties
props = {}
for k, v in self.properties.items()[:]:
for k, v in list(self.properties.items()):
props[k] = v
self.properties = props
@ -332,7 +334,7 @@ class Module(object):
# check and apply properties specified in cfgdict
# moduleproperties are to be specified as
# '.<propertyname>=<propertyvalue>'
for k, v in cfgdict.items():
for k, v in list(cfgdict.items()): # keep list() as dict may change during iter
if k[0] == '.':
if k[1:] in self.properties:
self.properties[k[1:]] = v
@ -347,13 +349,13 @@ class Module(object):
#self.properties['interface'] = self.properties['interfaces'][0]
# remove unset (default) module properties
for k, v in self.properties.items():
for k, v in list(self.properties.items()): # keep list() as dict may change during iter
if v is None:
del self.properties[k]
# check and apply parameter_properties
# specified as '<paramname>.<propertyname> = <propertyvalue>'
for k, v in cfgdict.items()[:]:
for k, v in list(cfgdict.items()): # keep list() as dict may change during iter
if '.' in k[1:]:
paramname, propname = k.split('.', 1)
if paramname in self.parameters:
@ -370,8 +372,8 @@ class Module(object):
if k not in self.parameters:
raise ConfigError(
'Module %s:config Parameter %r '
'not unterstood! (use on of %r)' %
(self.name, k, self.parameters.keys()))
'not unterstood! (use one of %s)' %
(self.name, k, ', '.join(self.parameters)))
# complain if a Param entry has no default value and
# is not specified in cfgdict
@ -464,7 +466,7 @@ class Readable(Module):
fastpoll = stat[0] == status.BUSY
# if fastpoll:
# self.log.info('fastpoll!')
for pname, pobj in self.parameters.iteritems():
for pname, pobj in self.parameters.items():
if not pobj.poll:
continue
if pname == 'status':
@ -520,7 +522,7 @@ class Communicator(Module):
"""
commands = {
"communicate" : Command("provides the simplest mean to communication",
"communicate": Command("provides the simplest mean to communication",
arguments=[StringType()],
result=StringType()
),

View File

@ -36,6 +36,7 @@ text -> string
further convertions are done by the validator of the datatype....
"""
from __future__ import print_function
from collections import OrderedDict
@ -160,7 +161,7 @@ class Parser(object):
return self.parse_string(orgtext)
def parse(self, orgtext):
print "parsing %r" % orgtext
print("parsing %r" % orgtext)
res, rem = self.parse_sub(orgtext)
if rem and rem[0] in u',;':
return self.parse_sub(u'[%s]' % orgtext)

View File

@ -36,24 +36,28 @@ Interface to the modules:
- remove_module(modulename_or_obj): removes the module (during shutdown)
"""
from __future__ import print_function
import time
from time import time as currenttime
import threading
from secop.protocol.messages import Value, CommandReply, HelpMessage, \
DeactivateReply, IdentifyReply, DescribeReply, HeartbeatReply, \
ActivateReply, WriteReply
from secop.protocol.messages import Message, EVENTREPLY, IDENTREQUEST
from secop.protocol.errors import SECOPError, NoSuchModuleError, \
NoSuchCommandError, NoSuchParamError, BadValueError, ReadonlyError
from secop.lib.parsing import format_time
from secop.lib import formatExtendedStack, formatException
try:
unicode('a')
except NameError:
# no unicode on py3
unicode = str # pylint: disable=redefined-builtin
class Dispatcher(object):
def __init__(self, logger, options):
# to avoid errors, we want to eat all options here
self.equipment_id = options['equipment_id']
self.equipment_id = options[u'equipment_id']
self.nodeopts = {}
for k in list(options):
self.nodeopts[k] = options.pop(k)
@ -71,80 +75,43 @@ class Dispatcher(object):
self._subscriptions = {}
self._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._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 = msg.get_error(
errorclass=err.__class__.__name__,
errorinfo=[repr(err), str(msg)])
except (ValueError, TypeError) as err:
self.log.exception(err)
reply = msg.get_error(
errorclass='BadValue',
errorinfo=[repr(err), str(msg)])
except Exception as err:
self.log.exception(err)
reply = msg.get_error(
errorclass='InternalError', errorinfo=[
formatException(), str(msg), formatExtendedStack()])
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"""
"""broadcasts a msg to all active connections
used from the dispatcher"""
if reallyall:
listeners = self._connections
else:
if getattr(msg, 'command', None) is None:
eventname = '%s:%s' % (msg.module, msg.parameter
if msg.parameter else 'value')
if getattr(msg, u'command', None) is None:
eventname = u'%s:%s' % (msg.module, msg.parameter
if msg.parameter else u'value')
else:
eventname = '%s:%s()' % (msg.module, msg.command)
listeners = self._subscriptions.get(eventname, [])
listeners += list(self._active_connections)
eventname = u'%s:%s()' % (msg.module, msg.command)
listeners = self._subscriptions.get(eventname, set()).copy()
listeners.update(self._subscriptions.get(msg.module, set()))
listeners.update(self._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.export_value(),
t=pobj.timestamp)
msg = Message(EVENTREPLY, module=moduleobj.name, parameter=pname)
msg.set_result(pobj.export_value(), dict(t=pobj.timestamp))
self.broadcast_event(msg)
def subscribe(self, conn, modulename, pname='value'):
eventname = '%s:%s' % (modulename, pname)
def subscribe(self, conn, modulename, pname=u'value'):
eventname = modulename
if pname:
eventname = u'%s:%s' % (modulename, pname)
self._subscriptions.setdefault(eventname, set()).add(conn)
def unsubscribe(self, conn, modulename, pname='value'):
eventname = '%s:%s' % (modulename, pname)
def unsubscribe(self, conn, modulename, pname=u'value'):
eventname = modulename
if pname:
eventname = u'%s:%s' % (modulename, pname)
if eventname in self._subscriptions:
self._subscriptions.remove(conn)
self._subscriptions.setdefault(eventname, set()).discard(conn)
def add_connection(self, conn):
"""registers new connection"""
@ -154,17 +121,11 @@ class Dispatcher(object):
"""removes now longer functional connection"""
if conn in self._connections:
self._connections.remove(conn)
for _evt, conns in self._subscriptions.items():
for _evt, conns in list(self._subscriptions.items()):
conns.discard(conn)
def activate_connection(self, conn):
self._active_connections.add(conn)
def deactivate_connection(self, conn):
self._active_connections.discard(conn)
def register_module(self, moduleobj, modulename, export=True):
self.log.debug('registering module %r as %s (export=%r)' %
self.log.debug(u'registering module %r as %s (export=%r)' %
(moduleobj, modulename, export))
self._modules[modulename] = moduleobj
if export:
@ -173,9 +134,9 @@ class Dispatcher(object):
def get_module(self, modulename):
if modulename in self._modules:
return self._modules[modulename]
elif modulename in self._modules.values():
elif modulename in list(self._modules.values()):
return modulename
raise NoSuchModuleError(module=str(modulename))
raise NoSuchModuleError(module=unicode(modulename))
def remove_module(self, modulename_or_obj):
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
@ -190,75 +151,55 @@ class Dispatcher(object):
return self._export[:]
def list_module_params(self, modulename, only_static=False):
self.log.debug('list_module_params(%r)' % modulename)
self.log.debug(u'list_module_params(%r)' % modulename)
if modulename in self._export:
# omit export=False params!
res = {}
for paramname, param in self.get_module(modulename).parameters.items():
for paramname, param in list(self.get_module(modulename).parameters.items()):
if param.export:
res[paramname] = param.as_dict(only_static)
self.log.debug('list params for module %s -> %r' %
self.log.debug(u'list params for module %s -> %r' %
(modulename, res))
return res
self.log.debug('-> module is not to be exported!')
self.log.debug(u'-> module is not to be exported!')
return {}
def list_module_cmds(self, modulename):
self.log.debug('list_module_cmds(%r)' % modulename)
self.log.debug(u'list_module_cmds(%r)' % modulename)
if modulename in self._export:
# omit export=False params!
res = {}
for cmdname, cmdobj in self.get_module(modulename).commands.items():
for cmdname, cmdobj in list(self.get_module(modulename).commands.items()):
res[cmdname] = cmdobj.as_dict()
self.log.debug('list cmds for module %s -> %r' % (modulename, res))
self.log.debug(u'list cmds for module %s -> %r' % (modulename, res))
return res
self.log.debug('-> module is not to be exported!')
self.log.debug(u'-> module is not to be exported!')
return {}
def get_descriptive_data(self):
"""returns a python object which upon serialisation results in the descriptive data"""
# XXX: be lazy and cache this?
# format: {[{[{[, specific entries first
result = {'modules': []}
result = {u'modules': []}
for modulename in self._export:
module = self.get_module(modulename)
# some of these need rework !
mod_desc = {'parameters': [], 'commands': []}
for pname, param in self.list_module_params(
modulename, only_static=True).items():
mod_desc['parameters'].extend([pname, param])
for cname, cmd in self.list_module_cmds(modulename).items():
mod_desc['commands'].extend([cname, cmd])
for propname, prop in module.properties.items():
mod_desc = {u'parameters': [], u'commands': []}
for pname, param in list(self.list_module_params(
modulename, only_static=True).items()):
mod_desc[u'parameters'].extend([pname, param])
for cname, cmd in list(self.list_module_cmds(modulename).items()):
mod_desc[u'commands'].extend([cname, cmd])
for propname, prop in list(module.properties.items()):
mod_desc[propname] = prop
result['modules'].extend([modulename, mod_desc])
result['equipment_id'] = self.equipment_id
result['firmware'] = 'The SECoP playground'
result['version'] = "2017.07"
result[u'modules'].extend([modulename, mod_desc])
result[u'equipment_id'] = self.equipment_id
result[u'firmware'] = u'The SECoP playground'
result[u'version'] = u'2017.07'
result.update(self.nodeopts)
# XXX: what else?
return result
def get_descriptive_data_old(self):
# XXX: be lazy and cache this?
result = {'modules': {}}
for modulename in self._export:
module = self.get_module(modulename)
# some of these need rework !
dd = {
'parameters': self.list_module_params(
modulename,
only_static=True),
'commands': self.list_module_cmds(modulename),
'properties': module.properties,
}
result['modules'][modulename] = dd
result['equipment_id'] = self.equipment_id
result['firmware'] = 'The SECoP playground'
result['version'] = "2017.01"
# XXX: what else?
return result
def _execute_command(self, modulename, command, arguments=None):
if arguments is None:
arguments = []
@ -274,19 +215,13 @@ class Dispatcher(object):
raise BadValueError(
module=modulename,
command=command,
reason='Wrong number of arguments!')
reason=u'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)
func = getattr(moduleobj, u'do_' + command)
res = func(*arguments)
res = CommandReply(
module=modulename,
command=command,
result=res,
qualifiers=dict(t=time.time()))
# res = Value(modulename, command=command, value=func(*arguments), t=time.time())
return res
return res, dict(t=currenttime())
def _setParamValue(self, modulename, pname, value):
moduleobj = self.get_module(modulename)
@ -299,19 +234,15 @@ class Dispatcher(object):
if pobj.readonly:
raise ReadonlyError(module=modulename, parameter=pname)
writefunc = getattr(moduleobj, 'write_%s' % pname, None)
writefunc = getattr(moduleobj, u'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 WriteReply(
module=modulename,
parameter=pname,
value=[pobj.value, dict(t=format_time(pobj.timestamp))])
return WriteReply(
module=modulename, parameter=pname, value=[pobj.value, {}])
return pobj.export_value(), dict(t=pobj.timestamp)
return pobj.export_value(), {}
def _getParamValue(self, modulename, pname):
moduleobj = self.get_module(modulename)
@ -322,121 +253,181 @@ class Dispatcher(object):
if pobj is None:
raise NoSuchParamError(module=modulename, parameter=pname)
readfunc = getattr(moduleobj, 'read_%s' % pname, None)
readfunc = getattr(moduleobj, u'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:
res = Value(
modulename,
parameter=pname,
value=pobj.export_value(),
t=pobj.timestamp)
else:
res = Value(modulename, parameter=pname, value=pobj.export_value())
return res
return pobj.export_value(), dict(t=pobj.timestamp)
return pobj.export_value(), {}
#
# api to be called from the 'interface'
# any method above has no idea about 'messages', this is handled here
#
def handle_request(self, conn, msg):
"""handles incoming request
will call 'queue.request(data)' on conn to send reply before returning
"""
self.log.debug(u'Dispatcher: handling msg: %r' % msg)
# if there was an error in the frontend, bounce the resulting
# error msgObj directly back to the client
if msg.errorclass:
return msg
# play thread safe !
with self._lock:
if msg.action == IDENTREQUEST:
self.log.debug(u'Looking for handle_ident')
handler = self.handle_ident
else:
self.log.debug(u'Looking for handle_%s' % msg.action)
handler = getattr(self, u'handle_%s' % msg.action, None)
if handler:
try:
reply = handler(conn, msg)
if reply:
conn.queue_reply(reply)
return None
except SECOPError as err:
self.log.exception(err)
msg.set_error(err.name, unicode(err), {})#u'traceback': formatException(),
#u'extended_stack':formatExtendedStack()})
return msg
except (ValueError, TypeError) as err:
self.log.exception(err)
msg.set_error(u'BadValue', unicode(err), {u'traceback': formatException()})
print(u'--------------------')
print(formatExtendedStack())
print(u'====================')
return msg
except Exception as err:
self.log.exception(err)
msg.set_error(u'InternalError', unicode(err), {u'traceback': formatException()})
print(u'--------------------')
print(formatExtendedStack())
print(u'====================')
return msg
else:
self.log.error(u'Can not handle msg %r' % msg)
msg.set_error(u'Protocol', u'unhandled msg', {})
return msg
# now the (defined) handlers for the different requests
def handle_Help(self, conn, msg):
return HelpMessage()
def handle_help(self, conn, msg):
msg.mkreply()
return msg
def handle_Identify(self, conn, msg):
return IdentifyReply(version_string='currently,is,ignored,here')
def handle_ident(self, conn, msg):
msg.mkreply()
return msg
def handle_Describe(self, conn, msg):
def handle_describe(self, conn, msg):
# XXX:collect descriptive data
return DescribeReply(
equipment_id=self.equipment_id,
description=self.get_descriptive_data())
msg.setvalue(u'specifier', u'.')
msg.setvalue(u'data', self.get_descriptive_data())
msg.mkreply()
return msg
def handle_Poll(self, conn, msg):
def handle_read(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._active_connections:
return None # already send to myself
return res # send reply to inactive conns
if not msg.parameter:
msg.parameter = u'value'
msg.set_result(*self._getParamValue(msg.module, msg.parameter))
def handle_Write(self, conn, msg):
# notify all by sending WriteReply
#msg1 = WriteReply(**msg.as_dict())
# self.broadcast_event(msg1)
#if conn in self._active_connections:
# return None # already send to myself
#if conn in self._subscriptions.get(msg.module, set()):
# return None # already send to myself
msg.mkreply()
return msg # send reply to inactive conns
def handle_change(self, conn, msg):
# 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).parameters:
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._active_connections:
# return None # already send to myself
return res
if not msg.parameter:
msg.parameter = u'target'
msg.set_result(*self._setParamValue(msg.module, msg.parameter, msg.data))
def handle_Command(self, conn, msg):
# notify all by sending CommandReply
#msg1 = CommandReply(**msg.as_dict())
# self.broadcast_event(msg1)
#if conn in self._active_connections:
# return None # already send to myself
#if conn in self._subscriptions.get(msg.module, set()):
# return None # already send to myself
msg.mkreply()
return msg # send reply to inactive conns
def handle_do(self, conn, msg):
# XXX: should this be done asyncron? we could just return the reply in
# that case
if not msg.args:
msg.args = []
# try to actually execute command
res = self._execute_command(msg.module, msg.command, msg.arguments)
# self.broadcast_event(res)
# if conn in self._active_connections:
msg.set_result(*self._execute_command(msg.module, msg.command, msg.args))
#if conn in self._active_connections:
# return None # already send to myself
return res # send reply to inactive conns
#if conn in self._subscriptions.get(msg.module, set()):
# return None # already send to myself
msg.mkreply()
return msg # send reply to inactive conns
def handle_Heartbeat(self, conn, msg):
return HeartbeatReply(**msg.as_dict())
def handle_ping(self, conn, msg):
msg.setvalue(u'data', {u't':currenttime()})
msg.mkreply()
return msg
def handle_Activate(self, conn, msg):
self.activate_connection(conn)
# easy approach: poll all values...
for modulename, moduleobj in self._modules.items():
def handle_activate(self, conn, msg):
if msg.module:
if msg.module not in self._modules:
raise NoSuchModuleError()
# activate only ONE module
self.subscribe(conn, msg.specifier, u'')
modules = [msg.specifier]
else:
# activate all modules
self._active_connections.add(conn)
modules = self._modules
# for initial update poll all values...
for modulename in modules:
moduleobj = self._modules.get(modulename, None)
if moduleobj is None:
self.log.error(u'activate: can not lookup module %r, skipping it' % modulename)
continue
for pname, pobj in moduleobj.parameters.items():
if not pobj.export:
if not pobj.export: # XXX: handle export_as cases!
continue
# WARNING: THIS READS ALL parameters FROM HW!
# XXX: should we send the cached values instead? (pbj.value)
# also: ignore errors here.
try:
res = self._getParamValue(modulename, pname)
if res[0] == Ellipsis: # means we do not have a value at all so skip this
self.log.error(
u'activate: got no value for %s:%s!' %
(modulename, pname))
#else:
#rm = Message(EVENTREPLY, u'%s:%s' % (modulename, pname))
#rm.set_result(*res)
#self.broadcast_event(rm)
except SECOPError as e:
self.log.error('decide what to do here!')
self.log.error(u'decide what to do here! (ignore error and skip update)')
self.log.exception(e)
res = Value(
module=modulename,
parameter=pname,
value=pobj.export_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)
else:
self.log.error(
'activate: got no value for %s:%s!' %
modulename, pname)
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
msg.mkreply()
conn.queue_async_reply(msg) # should be sent AFTER all the ^^initial updates
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):
def handle_deactivate(self, conn, msg):
if msg.specifier:
self.unsubscribe(conn, msg.specifier, u'')
else:
self._active_connections.discard(conn)
# XXX: also check all entries in self._subscriptions?
msg.mkreply()
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 msg.get_error(
errorclass="InternalError", errorinfo="Unhandled Request")
def handle_error(self, conn, msg):
# is already an error-reply (came from interface frontend) -> just send it back
return msg

View File

@ -1,332 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on
# (think compressors)
from __future__ import print_function
# Base class
class MessageEncoder(object):
"""en/decode a single Messageobject"""
def encode(self, msg):
"""encodes the given message object into a frame"""
raise NotImplementedError
def decode(self, encoded):
"""decodes the given frame to a message object"""
raise NotImplementedError
import re
import json
#from secop.lib.parsing import format_time
from secop.protocol.messages import Value, IdentifyRequest, IdentifyReply, \
DescribeRequest, DescribeReply, ActivateRequest, ActivateReply, \
DeactivateRequest, DeactivateReply, CommandRequest, CommandReply, \
WriteRequest, WriteReply, PollRequest, HeartbeatRequest, HeartbeatReply, \
ErrorMessage, HelpMessage
#from secop.protocol.errors import ProtocolError
# each message is like <messagetype> [ \space <messageargs> [ \space
# <json> ]] \lf
# note: the regex allow <> for spec for testing only!
SECOP_RE = re.compile(
r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""",
re.X)
#"""
# messagetypes:
IDENTREQUEST = '*IDN?' # literal
# literal! first part is fixed!
#IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
#IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2017-01-25,rc1'
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)
# +module:command +json args (if needed) # send after the command finished !
COMMANDREPLY = 'done'
# +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
WRITEREQUEST = 'change'
# +module[:parameter] +json_value # send with the read back value
WRITEREPLY = 'changed'
# +module[:parameter] -> NO direct reply, calls TRIGGER internally!
TRIGGERREQUEST = 'read'
EVENT = 'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = 'ping' # +nonce_without_space
HEARTBEATREPLY = 'pong' # +nonce_without_space
ERRORREPLY = 'error' # +errorclass +json_extended_info
HELPREQUEST = 'help' # literal
HELPREPLY = 'helping' # +line number +json_text
ERRORCLASSES = [
'NoSuchModule',
'NoSuchParameter',
'NoSuchCommand',
'CommandFailed',
'ReadOnly',
'BadValue',
'CommunicationFailed',
'IsBusy',
'IsError',
'ProtocolError',
'InternalError',
'CommandRunning',
'Disabled',
]
# note: above strings need to be unique in the sense, that none is/or
# starts with another
def encode_cmd_result(msgobj):
q = msgobj.qualifiers.copy()
if 't' in q:
q['t'] = str(q['t'])
return msgobj.result, q
def encode_value_data(vobj):
q = vobj.qualifiers.copy()
if 't' in q:
q['t'] = str(q['t'])
return vobj.value, q
def encode_error_msg(emsg):
# note: result is JSON-ified....
return [
emsg.origin, dict((k, getattr(emsg, k)) for k in emsg.ARGS
if k != 'origin')
]
class SECoPEncoder(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),
encode_cmd_result, ),
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, ),
ErrorMessage: (
ERRORREPLY,
"errorclass",
encode_error_msg, ),
Value: (
EVENT,
lambda msg: "%s:%s" % (msg.module, msg.parameter or (
msg.command + '()')) if msg.parameter or msg.command else msg.module,
encode_value_data, ),
}
DECODEMAP = {
IDENTREQUEST: lambda spec, data: IdentifyRequest(),
# handled specially, listed here for completeness
# IDENTREPLY: lambda spec, data: IdentifyReply(encoded),
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], result=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),
EVENT: 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]))
if isinstance(msg, HeartbeatRequest):
if msg.nonce:
return 'ping %s' % msg.nonce
return 'ping'
if isinstance(msg, HeartbeatReply):
if msg.nonce:
return 'pong %s' % msg.nonce
return 'pong'
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 = SECOP_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='Protocol',
# errorinfo='Regex did not match!',
# is_request=True)
msgtype, msgspec, data = match.groups()
if msgspec is None and data:
return ErrorMessage(
errorclass='Internal',
errorinfo='Regex matched json, but not spec!',
is_request=True,
origin=encoded)
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)],
origin=encoded)
msg = self.DECODEMAP[msgtype](msgspec, data)
msg.setvalue("origin", encoded)
return msg
return ErrorMessage(
errorclass='Protocol',
errorinfo='%r: No Such Messagetype defined!' % encoded,
is_request=True,
origin=encoded)
def tests(self):
print("---- Testing encoding -----")
for msgclass in sorted(self.ENCODEMAP):
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 == EVENT:
msg = '%s a:b [3,{"t":193868}]' % msgtype
print(msg)
d = self.decode(msg)
print(d)
print(self.encode(d))
print()
print("---- Testing done -----")
ENCODERS = {
'secop': SECoPEncoder,
}
__ALL__ = ['ENCODERS']

View File

@ -25,13 +25,14 @@
class SECOPError(RuntimeError):
def __init__(self, *args, **kwds):
RuntimeError.__init__(self)
self.args = args
for k, v in kwds.items():
for k, v in list(kwds.items()):
setattr(self, k, v)
def __repr__(self):
args = ', '.join(map(repr, self.args))
kwds = ', '.join(['%s=%r' % i for i in self.__dict__.items()])
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())])
res = []
if args:
res.append(args)
@ -45,14 +46,13 @@ class SECOPError(RuntimeError):
class InternalError(SECOPError):
pass
name = 'InternalError'
class ProtocolError(SECOPError):
pass
name = 'SyntaxError'
# XXX: unifiy NoSuch...Error ?
class NoSuchModuleError(SECOPError):
pass
@ -77,17 +77,42 @@ class CommandFailedError(SECOPError):
pass
class InvalidParamValueError(SECOPError):
class CommandRunningError(SECOPError):
pass
class CommunicationFailedError(SECOPError):
pass
class IsBusyError(SECOPError):
pass
class IsErrorError(SECOPError):
pass
class DisabledError(SECOPError):
pass
EXCEPTIONS = dict(
Internal=InternalError,
Protocol=ProtocolError,
NoSuchModule=NoSuchModuleError,
NoSuchParam=NoSuchParamError,
NoSuchCommand=NoSuchCommandError,
BadValue=BadValueError,
Readonly=ReadonlyError,
CommandFailed=CommandFailedError,
InvalidParam=InvalidParamValueError, )
CommandRunning=CommandRunningError,
Readonly=ReadonlyError,
BadValue=BadValueError,
CommunicationFailed=CommunicationFailedError,
IsBusy=IsBusyError,
IsError=IsErrorError,
Disabled=DisabledError,
SyntaxError=ProtocolError,
InternalError=InternalError,
# internal short versions (candidates for spec)
Protocol=ProtocolError,
Internal=InternalError,
)

View File

@ -1,60 +0,0 @@
#!/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']

View File

@ -1,81 +0,0 @@
#!/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 = []

View File

@ -1,56 +0,0 @@
#!/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''

View File

@ -1,39 +0,0 @@
#!/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]

View File

@ -1,71 +0,0 @@
#!/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

View File

@ -20,8 +20,9 @@
#
# *****************************************************************************
"""provide server interfaces to be used by clients"""
from __future__ import absolute_import
from tcp import TCPServer
from .tcp import TCPServer
INTERFACES = {'tcp': TCPServer, }

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
@ -20,27 +19,81 @@
#
# *****************************************************************************
"""provides tcp interface to the SECoP Server"""
from __future__ import print_function
import os
import socket
import collections
import SocketServer
try:
import socketserver # py3
except ImportError:
import SocketServer as socketserver # py2
from secop.lib import formatExtendedStack, formatException
from secop.protocol.messages import HELPREPLY, Message, HelpMessage
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
EOL = b'\n'
CR = b'\r'
SPACE = b' '
class TCPRequestHandler(SocketServer.BaseRequestHandler):
def encode_msg_frame(action, specifier=None, data=None):
""" encode a msg_tripel into an msg_frame, ready to be sent
action (and optional specifier) are unicode strings,
data may be an json-yfied python object"""
action = action.encode('utf-8')
if specifier is None:
# implicit: data is None
return b''.join((action, EOL))
specifier = specifier.encode('utf-8')
if data:
data = data.encode('utf-8')
return b''.join((action, SPACE, specifier, SPACE, data, EOL))
return b''.join((action, SPACE, specifier, EOL))
def get_msg(_bytes):
"""try to deframe the next msg in (binary) input
always return a tupel (msg, remaining_input)
msg may also be None
"""
if EOL not in _bytes:
return None, _bytes
return _bytes.split(EOL, 1)
def decode_msg(msg):
"""decode the (binary) msg into a (unicode) msg_tripel"""
# check for leading/trailing CR and remove it
if msg and msg[0] == CR:
msg = msg[1:]
if msg and msg[-1] == CR:
msg = msg[:-1]
res = msg.split(b' ', 2)
action = res[0].decode('utf-8')
if len(res) == 1:
return action, None, None
specifier = res[1].decode('utf-8')
if len(res) == 2:
return action, specifier, None
data = res[2].decode('utf-8')
return action, specifier, data
class TCPRequestHandler(socketserver.BaseRequestHandler):
def setup(self):
self.log = self.server.log
# Queue of msgObjects to send
self._queue = collections.deque(maxlen=100)
self.framing = self.server.framingCLS()
self.encoding = self.server.encodingCLS()
# self.framing = self.server.framingCLS()
# self.encoding = self.server.encodingCLS()
def handle(self):
"""handle a new tcp-connection"""
@ -49,6 +102,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
clientaddr = self.client_address
serverobj = self.server
self.log.info("handling new connection from %s:%d" % clientaddr)
data = b''
# notify dispatcher of us
serverobj.dispatcher.add_connection(self)
@ -57,14 +111,16 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
# mysocket.setblocking(False)
# start serving
while True:
# send replys fist, then listen for requests, timing out after 0.1s
# send replys first, 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)
#outmsg.mkreply()
outdata = encode_msg_frame(*outmsg.serialize())
# outframes = self.encoding.encode(outmsg)
# outdata = self.framing.encode(outframes)
try:
mysocket.sendall(outdata)
except Exception:
@ -72,9 +128,12 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
# XXX: improve: use polling/select here?
try:
data = mysocket.recv(MAX_MESSAGE_SIZE)
except (socket.timeout, socket.error) as e:
data = data + mysocket.recv(MAX_MESSAGE_SIZE)
except socket.timeout as e:
continue
except socket.error as e:
self.log.exception(e)
return
# XXX: should use select instead of busy polling
if not data:
continue
@ -82,24 +141,49 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
# 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)
while True:
origin, data = get_msg(data)
if origin is None:
break # no more messages to process
if not origin: # empty string -> send help message
for idx, line in enumerate(HelpMessage.splitlines()):
msg = Message(HELPREPLY, specifier='%d' % idx)
msg.data = line
self.queue_async_reply(msg)
continue
msg = decode_msg(origin)
# construct msgObj from msg
try:
msgObj = Message(*msg)
msgObj.origin = origin.decode('latin-1')
msgObj = serverobj.dispatcher.handle_request(self, msgObj)
except Exception as err:
# create Error Obj instead
msgObj.set_error(u'Internal', str(err), {'exception': formatException(),
'traceback':formatExtendedStack()})
print('--------------------')
print(formatException())
print('--------------------')
print(formatExtendedStack())
print('====================')
if msgObj:
self.queue_reply(msgObj)
def queue_async_reply(self, data):
"""called by dispatcher for async data units"""
self._queue.append(data)
if data:
self._queue.append(data)
else:
self.log.error('should asynq_queue %s' % data)
def queue_reply(self, data):
"""called by dispatcher to queue (sync) replies"""
# sync replies go first!
self._queue.appendleft(data)
if data:
self._queue.appendleft(data)
else:
self.log.error('should queue %s' % data)
def finish(self):
"""called when handle() terminates, i.e. the socket closed"""
@ -115,7 +199,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
self.request.close()
class TCPServer(SocketServer.ThreadingTCPServer):
class TCPServer(socketserver.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
@ -129,12 +213,14 @@ class TCPServer(SocketServer.ThreadingTCPServer):
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')]
interfaceopts.pop('framing') # HACK
interfaceopts.pop('encoding') # HACK
# self.framingCLS = FRAMERS[interfaceopts.pop('framing', 'none')]
# self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
self.log.info("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.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")

View File

@ -20,178 +20,185 @@
#
# *****************************************************************************
"""Define SECoP Messages"""
from __future__ import print_function
import json
from secop.protocol.errors import EXCEPTIONS
# allowed actions:
IDENTREQUEST = u'*IDN?' # literal
# literal! first part is fixed!
IDENTREPLY = u'SINE2020&ISSE,SECoP,V2018-02-13,rc2'
DESCRIPTIONREQUEST = u'describe' # literal
DESCRIPTIONREPLY = u'describing' # +<id> +json
ENABLEEVENTSREQUEST = u'activate' # literal + optional spec
ENABLEEVENTSREPLY = u'active' # literal + optional spec, is end-of-initial-data-transfer
DISABLEEVENTSREQUEST = u'deactivate' # literal + optional spec
DISABLEEVENTSREPLY = u'inactive' # literal + optional spec
COMMANDREQUEST = u'do' # +module:command +json args (if needed)
# +module:command +json args (if needed) # send after the command finished !
COMMANDREPLY = u'done'
# +module[:parameter] +json_value -> NO direct reply, calls POLL internally
WRITEREQUEST = u'change'
# +module[:parameter] +json_value # send with the read back value
WRITEREPLY = u'changed'
# +module[:parameter] -> NO direct reply, calls POLL internally!
POLLREQUEST = u'read'
EVENTREPLY = u'update' # +module[:parameter] +json_value (value, qualifiers_as_dict)
HEARTBEATREQUEST = u'ping' # +nonce_without_space
HEARTBEATREPLY = u'pong' # +nonce_without_space
ERRORREPLY = u'error' # +errorclass +json_extended_info
HELPREQUEST = u'help' # literal
HELPREPLY = u'helping' # +line number +json_text
# helper mapping to find the REPLY for a REQUEST
REQUEST2REPLY = {
IDENTREQUEST: IDENTREPLY,
DESCRIPTIONREQUEST: DESCRIPTIONREPLY,
ENABLEEVENTSREQUEST: ENABLEEVENTSREPLY,
DISABLEEVENTSREQUEST: DISABLEEVENTSREPLY,
COMMANDREQUEST: COMMANDREPLY,
WRITEREQUEST: WRITEREPLY,
POLLREQUEST: EVENTREPLY,
HEARTBEATREQUEST: HEARTBEATREPLY,
HELPREQUEST: HELPREPLY,
}
class Message(object):
"""base class for messages"""
is_request = False
is_reply = False
is_error = False
qualifiers = {}
origin = "<unknown source>"
origin = u'<unknown source>'
action = u'<unknown message type>'
specifier = None
data = None
def __init__(self, **kwds):
self.ARGS = set()
# cooked versions
module = None
parameter = None
command = None
args = None
# if set, these are used for generating the reply
qualifiers = None # will be rectified to dict() in __init__
value = None # also the result of a command
# if set, these are used for generating the error msg
errorclass = '' # -> specifier
errordescription = '' # -> data[1] (data[0] is origin)
errorinfo = {} # -> data[2]
def __init__(self, action, specifier=None, data=None, **kwds):
self.qualifiers = {}
self.action = action
if data:
data = json.loads(data)
if specifier:
self.module = specifier
self.specifier = specifier
if ':' in specifier:
self.module, p = specifier.split(':',1)
if action in (COMMANDREQUEST, COMMANDREPLY):
self.command = p
# XXX: extract args?
self.args = data
else:
self.parameter = p
if data is not None:
self.data = data
elif data is not None:
self.data = data
# record extra values
self.__arguments = set()
for k, v in kwds.items():
self.setvalue(k, v)
def setvalue(self, key, value):
setattr(self, key, value)
self.ARGS.add(key)
self.__arguments.add(key)
def setqualifier(self, key, value):
self.qualifiers[key] = value
def __repr__(self):
return self.__class__.__name__ + '(' + \
', '.join('%s=%s' % (k, repr(getattr(self, k)))
for k in sorted(self.ARGS)) + ')'
return u'Message(%r' % self.action + \
u', '.join('%s=%s' % (k, repr(getattr(self, k)))
for k in sorted(self.__arguments)) + u')'
def as_dict(self):
"""returns set parameters as dict"""
return dict(map(lambda k: (k, getattr(self, k)), self.ARGS))
def serialize(self):
"""return <action>,<specifier>,<jsonyfied_data> triple"""
if self.errorclass:
for k in self.__arguments:
if k in (u'origin', u'errorclass', u'errorinfo', u'errordescription'):
if k in self.errorinfo:
del self.errorinfo[k]
continue
self.errorinfo[k] = getattr(self, k)
data = [self.origin, self.errordescription, self.errorinfo]
print(repr(data))
return ERRORREPLY, self.errorclass, json.dumps(data)
elif self.value or self.qualifiers:
data = [self.value, self.qualifiers]
else:
data = self.data
try:
data = json.dumps(data) if data else u''
except TypeError:
print('Can not serialze: %s' % repr(data))
data = u'none'
if self.specifier:
specifier = self.specifier
else:
specifier = self.module
if self.parameter:
specifier = u'%s:%s' %(self.module, self.parameter)
if self.command:
specifier = u'%s:%s' %(self.module, self.command)
return self.action, specifier, data
def mkreply(self):
self.action = REQUEST2REPLY.get(self.action, self.action)
def set_error(self, errorclass, errordescription, errorinfo):
if errorclass not in EXCEPTIONS:
errordescription = '%s is not an official errorclass!\n%s' % (errorclass, errordescription)
errorclass = u'Internal'
# used to mark thes as an error message
# XXX: check errorclass for allowed values !
self.setvalue(u'errorclass', errorclass) # a str
self.setvalue(u'errordescription', errordescription) # a str
self.setvalue(u'errorinfo', errorinfo) # a dict
self.action = ERRORREPLY
def set_result(self, value, qualifiers):
# used to mark thes as an result reply message
self.setvalue(u'value', value)
self.qualifiers.update(qualifiers)
self.__arguments.add(u'qualifier')
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, format_time(v) if k == "timestamp" else repr(v))
for k, v in self.qualifiers.items()
]))
class Request(Message):
is_request = True
def get_reply(self):
"""returns a Reply object prefilled with the attributes from this request."""
m = Message()
m.is_request = False
m.is_reply = True
m.is_error = False
m.qualifiers = self.qualifiers
m.origin = self.origin
for k in self.ARGS:
m.setvalue(k, self.__dict__[k])
return m
def get_error(self, errorclass, errorinfo):
"""returns a Reply object prefilled with the attributes from this request."""
m = ErrorMessage()
m.qualifiers = self.qualifiers
m.origin = self.origin
for k in self.ARGS:
m.setvalue(k, self.__dict__[k])
m.setvalue("errorclass", errorclass[:-5]
if errorclass.endswith('rror') else errorclass)
m.setvalue("errorinfo", errorinfo)
return m
class IdentifyRequest(Request):
pass
class IdentifyReply(Message):
is_reply = True
version_string = None
class DescribeRequest(Request):
pass
class DescribeReply(Message):
is_reply = True
equipment_id = None
description = None
class ActivateRequest(Request):
pass
class ActivateReply(Message):
is_reply = True
class DeactivateRequest(Request):
pass
class DeactivateReply(Message):
is_reply = True
class CommandRequest(Request):
command = ''
arguments = []
class CommandReply(Message):
is_reply = True
command = ''
result = None
class WriteRequest(Request):
module = None
parameter = None
value = None
class WriteReply(Message):
is_reply = True
module = None
parameter = None
value = None
class PollRequest(Request):
is_request = True
module = None
parameter = None
class HeartbeatRequest(Request):
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(Request):
is_reply = True # !sic!
HelpMessage = u"""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, DESCRIPTIONREQUEST, POLLREQUEST,
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)

View File

@ -1,235 +0,0 @@
# -*- 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 = 'Module' if self.devs != ['*'] else 'Modules'
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 NoSuchModuleError(ErrorMessage):
def __init__(self, *devs):
ErrorMessage.__init__(
self,
devs=devs,
errorstring="Module %r does not exist" % devs[0],
errortype='NoSuchModule')
class NoSuchParamError(ErrorMessage):
def __init__(self, dev, *params):
ErrorMessage.__init__(
self,
devs=(dev, ),
params=params,
errorstring="Module %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="Module %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

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under

View File

@ -21,11 +21,17 @@
#
# *****************************************************************************
"""Define helpers"""
from __future__ import print_function
import os
import ast
import time
import threading
import ConfigParser
try:
import configparser # py3
except ImportError:
import ConfigParser as configparser # py2
from daemon import DaemonContext
@ -42,14 +48,14 @@ from secop.errors import ConfigError
class Server(object):
def __init__(self, name, parentLogger=None):
def __init__(self, name, parent_logger=None):
self._name = name
self.log = parentLogger.getChild(name, True)
self.log = parent_logger.getChild(name, True)
cfg = getGeneralConfig()
self._pidfile = os.path.join(cfg['piddir'], name + '.pid')
self._cfgfile = os.path.join(cfg['confdir'], name + '.cfg')
self._pidfile = os.path.join(cfg[u'piddir'], name + u'.pid')
self._cfgfile = os.path.join(cfg[u'confdir'], name + u'.cfg')
self._dispatcher = None
self._interface = None
@ -61,7 +67,7 @@ class Server(object):
pidfile = pidlockfile.TimeoutPIDLockFile(self._pidfile)
if pidfile.is_locked():
self.log.error('Pidfile already exists. Exiting')
self.log.error(u'Pidfile already exists. Exiting')
with DaemonContext(
pidfile=pidfile,
@ -72,13 +78,13 @@ class Server(object):
try:
self._processCfg()
except Exception:
print formatException(verbose=True)
print(formatException(verbose=True))
raise
self.log.info('startup done, handling transport messages')
self.log.info(u'startup done, handling transport messages')
self._threads = set()
for _if in self._interfaces:
self.log.debug('starting thread for interface %r' % _if)
self.log.debug(u'starting thread for interface %r' % _if)
t = threading.Thread(target=_if.serve_forever)
t.daemon = True
t.start()
@ -87,20 +93,20 @@ class Server(object):
time.sleep(1)
for t in self._threads:
if not t.is_alive():
self.log.debug('thread %r died (%d still running)' %
self.log.debug(u'thread %r died (%d still running)' %
(t, len(self._threads)))
t.join()
self._threads.discard(t)
def _processCfg(self):
self.log.debug('Parse config file %s ...' % self._cfgfile)
self.log.debug(u'Parse config file %s ...' % self._cfgfile)
parser = ConfigParser.SafeConfigParser()
parser = configparser.SafeConfigParser()
parser.optionxform = str
if not parser.read([self._cfgfile]):
self.log.error("Couldn't read cfg file !")
raise ConfigError("Couldn't read cfg file %r" % self._cfgfile)
self.log.error(u'Couldn\'t read cfg file !')
raise ConfigError(u'Couldn\'t read cfg file %r' % self._cfgfile)
self._interfaces = []
@ -109,63 +115,63 @@ class Server(object):
equipment_id = None
nodeopts = []
for section in parser.sections():
if section.lower().startswith('module '):
if section.lower().startswith(u'module '):
# module section
# omit leading 'module ' string
devname = section[len('module '):]
devname = section[len(u'module '):]
devopts = dict(item for item in parser.items(section))
if 'class' not in devopts:
self.log.error('Module %s needs a class option!')
if u'class' not in devopts:
self.log.error(u'Module %s needs a class option!')
raise ConfigError(
'cfgfile %r: Module %s needs a class option!' %
u'cfgfile %r: Module %s needs a class option!' %
(self._cfgfile, devname))
# MAGIC: transform \n.\n into \n\n which are normally stripped
# by the ini parser
for k in devopts:
v = devopts[k]
while '\n.\n' in v:
v = v.replace('\n.\n', '\n\n')
while u'\n.\n' in v:
v = v.replace(u'\n.\n', u'\n\n')
devopts[k] = v
# try to import the class, raise if this fails
devopts['class'] = get_class(devopts['class'])
devopts[u'class'] = get_class(devopts[u'class'])
# all went well so far
moduleopts.append([devname, devopts])
if section.lower().startswith('interface '):
if section.lower().startswith(u'interface '):
# interface section
# omit leading 'interface ' string
ifname = section[len('interface '):]
ifname = section[len(u'interface '):]
ifopts = dict(item for item in parser.items(section))
if 'interface' not in ifopts:
self.log.error('Interface %s needs an interface option!')
if u'interface' not in ifopts:
self.log.error(u'Interface %s needs an interface option!')
raise ConfigError(
'cfgfile %r: Interface %s needs an interface option!' %
u'cfgfile %r: Interface %s needs an interface option!' %
(self._cfgfile, ifname))
# all went well so far
interfaceopts.append([ifname, ifopts])
if section.lower().startswith('equipment ') or section.lower().startswith('node '):
if section.lower().startswith(u'equipment ') or section.lower().startswith(u'node '):
if equipment_id is not None:
raise ConfigError('cfgfile %r: only one [node <id>] section allowed, found another [%s]!' % (
raise ConfigError(u'cfgfile %r: only one [node <id>] section allowed, found another [%s]!' % (
self._cfgfile, section))
# equipment/node settings
equipment_id = section.split(' ', 1)[1].replace(' ', '_')
equipment_id = section.split(u' ', 1)[1].replace(u' ', u'_')
nodeopts = dict(item for item in parser.items(section))
nodeopts['equipment_id'] = equipment_id
nodeopts['id'] = equipment_id
nodeopts[u'equipment_id'] = equipment_id
nodeopts[u'id'] = equipment_id
# MAGIC: transform \n.\n into \n\n which are normally stripped
# by the ini parser
for k in nodeopts:
v = nodeopts[k]
while '\n.\n' in v:
v = v.replace('\n.\n', '\n\n')
while u'\n.\n' in v:
v = v.replace(u'\n.\n', u'\n\n')
nodeopts[k] = v
if equipment_id is None:
self.log.error('Need a [node <id>] section, none found!')
self.log.error(u'Need a [node <id>] section, none found!')
raise ConfigError(
'cfgfile %r: need an [node <id>] option!' % (self._cfgfile))
u'cfgfile %r: need an [node <id>] option!' % (self._cfgfile))
self._dispatcher = self._buildObject(
'Dispatcher', Dispatcher, nodeopts)
u'Dispatcher', Dispatcher, nodeopts)
self._processInterfaceOptions(interfaceopts)
self._processModuleOptions(moduleopts)
@ -173,13 +179,13 @@ class Server(object):
# check modules opts by creating them
devs = []
for devname, devopts in moduleopts:
devclass = devopts.pop('class')
devclass = devopts.pop(u'class')
# create module
self.log.debug('Creating Module %r' % devname)
export = devopts.pop('export', '1')
export = export.lower() in ('1', 'on', 'true', 'yes')
if 'default' in devopts:
devopts['value'] = devopts.pop('default')
self.log.debug(u'Creating Module %r' % devname)
export = devopts.pop(u'export', u'1')
export = export.lower() in (u'1', u'on', u'true', u'yes')
if u'default' in devopts:
devopts[u'value'] = devopts.pop(u'default')
# strip '"
for k, v in devopts.items():
try:
@ -192,13 +198,13 @@ class Server(object):
# connect modules with dispatcher
for devname, devobj, export in devs:
self.log.info('registering module %r' % devname)
self.log.info(u'registering module %r' % devname)
self._dispatcher.register_module(devobj, devname, export)
# also call init on the modules
devobj.init()
# call a possibly empty postinit on each module after registering all
for _devname, devobj, _export in devs:
postinit = getattr(devobj, 'postinit', None)
postinit = getattr(devobj, u'postinit', None)
if postinit:
postinit()
@ -206,17 +212,17 @@ class Server(object):
# eval interfaces
self._interfaces = []
for ifname, ifopts in interfaceopts:
ifclass = ifopts.pop('interface')
ifclass = ifopts.pop(u'interface')
ifclass = INTERFACES[ifclass]
interface = self._buildObject(ifname, ifclass, ifopts,
self._dispatcher)
self._interfaces.append(interface)
def _buildObject(self, name, cls, options, *args):
self.log.debug('Creating %s ...' % name)
self.log.debug(u'Creating %s ...' % name)
# cls.__init__ should pop all used args from options!
obj = cls(self.log.getChild(name.lower()), options, *args)
if options:
raise ConfigError('%s: don\'t know how to handle option(s): %s' %
(cls.__name__, ', '.join(options.keys())))
raise ConfigError(u'%s: don\'t know how to handle option(s): %s' %
(cls.__name__, u', '.join(options)))
return obj

View File

@ -40,7 +40,7 @@ class SimBase(object):
if '.extra_params' in cfgdict:
extra_params = cfgdict.pop('.extra_params')
# make a copy of self.parameter
self.parameters = dict((k,v.copy()) for k,v in self.parameters.items())
self.parameters = dict((k, v.copy()) for k, v in self.parameters.items())
for k in extra_params.split(','):
k = k.strip()
self.parameters[k] = Param('extra_param: %s' % k.strip(),

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
@ -17,6 +16,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""playing implementation of a (simple) simulated cryostat"""
@ -277,11 +277,11 @@ class Cryostat(CryoBase):
error = self.setpoint - newregulation
# use a simple filter to smooth delta a little
delta = (delta + regulation - newregulation) / 2.
delta = (delta + regulation - newregulation) * 0.5
kp = self.p / 10. # LakeShore P = 10*k_p
kp = self.p * 0.1 # LakeShore P = 10*k_p
ki = kp * abs(self.i) / 500. # LakeShore I = 500/T_i
kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d
kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d
_P = kp * error
_I += ki * error * h
_D = kd * delta / h
@ -301,7 +301,7 @@ class Cryostat(CryoBase):
# damp oscillations due to D switching signs
if _D * lastD < -0.2:
v = (v + heater) / 2.
v = (v + heater) * 0.5
# clamp new heater power to 0..100%
heater = clamp(v, 0., 100.)
lastD = _D
@ -357,10 +357,10 @@ class Cryostat(CryoBase):
self.status = status.BUSY, 'unstable'
elif self.setpoint == self.target:
self.status = status.OK, 'at target'
damper -= (damper - 1) / 10. # max value for damper is 11
damper -= (damper - 1) * 0.1 # max value for damper is 11
else:
self.status = status.BUSY, 'ramping setpoint'
damper -= (damper - 1) / 20.
damper -= (damper - 1) * 0.05
self.regulationtemp = round(regulation, 3)
self.sampletemp = round(sample, 3)
self.heaterpower = round(heater * self.maxpower * 0.01, 3)

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
@ -17,6 +16,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""testing devices"""
@ -312,6 +312,6 @@ class DatatypesTest(Readable):
class ArrayTest(Readable):
parameters = {
"x" : Param('value', datatype=ArrayOf(FloatRange(),100000,100000),
"x": Param('value', datatype=ArrayOf(FloatRange(), 100000, 100000),
default = 100000 * [0]),
}

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
@ -17,6 +16,7 @@
#
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
"""testing devices"""

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under
@ -18,8 +17,11 @@
# Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Erik Dahlbäck <erik.dahlback@esss.se>
#
# *****************************************************************************
from __future__ import absolute_import
from secop.datatypes import EnumType, FloatRange, StringType
from secop.modules import Readable, Drivable, Param
from secop.protocol import status

View File

@ -20,8 +20,7 @@
#
# *****************************************************************************
"""
Supporting classes for FRM2 magnets, currently only Garfield (amagnet).
"""Supporting classes for FRM2 magnets, currently only Garfield (amagnet).
"""
# partially borrowed from nicos
@ -127,7 +126,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
maxfield = tryfield
# if interval is so small, that any error within is acceptable:
if maxfield - minfield < 1e-4:
ratio = (field - minfield) / (maxfield - minfield)
ratio = (field - minfield) / float(maxfield - minfield)
trycurr = (maxcurr - mincurr) * ratio + mincurr
self.log.debug('current for %g T is %g A', field, trycurr)
return trycurr # interpolated
@ -184,7 +183,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
def write_ramp(self, newramp):
# This is an approximation!
self._currentsource.ramp = newramp / self.calibration[0]
self._currentsource.ramp = float(newramp) / self.calibration[0]
def _get_field_polarity(self):
sign = int(self._polswitch.read_value())

View File

@ -37,7 +37,7 @@ import PyTango
from secop.lib import lazy_property
from secop.protocol import status
#from secop.parse import Parser
from secop.datatypes import IntRange, FloatRange, StringType, TupleOf, \
ArrayOf, EnumType
from secop.errors import ConfigError, ProgrammingError, CommunicationError, \

View File

@ -107,9 +107,9 @@ def test_EnumType():
with pytest.raises(TypeError):
dt.validate([19, 'X'])
assert dt.validate('a') == 'a'
assert dt.validate('stuff') == 'stuff'
assert dt.validate(1) == 'stuff'
assert dt.validate('a') == 3
assert dt.validate('stuff') == 1
assert dt.validate(1) == 1
with pytest.raises(ValueError):
dt.validate(2)