polishing for a demo

+ adopting additional requests

Change-Id: If5ca29b5d247f1bc429ca101b0081b1d14f6e6f1
This commit is contained in:
Enrico Faulhaber
2017-01-25 11:47:19 +01:00
parent d5e935788f
commit 6ec30e38e8
43 changed files with 828 additions and 578 deletions

View File

@ -13,3 +13,9 @@ doc: doc/*.md
@echo "Generating html tree" @echo "Generating html tree"
@bin/make_doc.py @bin/make_doc.py
demo:
@bin/secop-server -q demo &
@bin/secop-server -q test &
@bin/secop-server -q cryo &
@bin/secop-gui localhost:10767 localhost:10768 localhost:10769
@ps aux|grep [s]ecop-server|awk '{print $$2}'|xargs kill

29
etc/cryo.cfg Normal file
View File

@ -0,0 +1,29 @@
[equipment]
id=cryo_7
[interface tcp]
interface=tcp
bindto=0.0.0.0
bindport=10769
# protocol to use for this interface
framing=eol
encoding=demo
[device cryo]
class=devices.cryo.Cryostat
jitter=0.1
T_start=10.0
target=10.0
looptime=1
ramp=6
maxpower=20.0
heater=4.1
p=40
i=10
d=2
mode=pid
tolerance=0.1
window=30
timeout=900

View File

@ -1,7 +1,7 @@
[equipment] [equipment]
id=demonstration id=Equipment_ID_for_demonstration
[interface testing] [interface tcp]
interface=tcp interface=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10767
@ -11,8 +11,8 @@ encoding=demo
[device heatswitch] [device heatswitch]
class=devices.demo.Switch class=devices.demo.Switch
switch_on_time=3 switch_on_time=5
switch_off_time=5 switch_off_time=10
[device mf] [device mf]
class=devices.demo.MagneticField class=devices.demo.MagneticField
@ -39,5 +39,5 @@ system=Cryomagnet MX15
subdev_mf=mf subdev_mf=mf
subdev_ts=ts subdev_ts=ts
[device vt] #[device vt]
class=devices.demo.ValidatorTest #class=devices.demo.ValidatorTest

View File

@ -1,17 +1,10 @@
[equipment] [equipment]
id=Fancy_ID_without_spaces-like:MLZ_furnace7 id=test config
[client] [interface tcp]
connectto=0.0.0.0
port=10767
interface = tcp
framing=eol
encoding=text
[interface testing]
interface=tcp interface=tcp
bindto=0.0.0.0 bindto=0.0.0.0
bindport=10767 bindport=10768
# protocol to use for this interface # protocol to use for this interface
framing=eol framing=eol
encoding=demo encoding=demo

View File

@ -19,17 +19,14 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Client side proxies""" """Define Client side proxies"""
# nothing here yet. # nothing here yet.
import code import code
class NameSpace(dict): class NameSpace(dict):
def __init__(self): def __init__(self):
dict.__init__(self) dict.__init__(self)
self.__const = set() self.__const = set()
@ -66,7 +63,6 @@ from os import path
class ClientConsole(object): class ClientConsole(object):
def __init__(self, cfgname, basepath): def __init__(self, cfgname, basepath):
self.namespace = NameSpace() self.namespace = NameSpace()
self.namespace.setconst('help', self.helpCmd) self.namespace.setconst('help', self.helpCmd)
@ -89,6 +85,7 @@ class ClientConsole(object):
else: else:
help(arg) help(arg)
import socket import socket
import threading import threading
from collections import deque from collections import deque
@ -99,7 +96,6 @@ from secop.protocol.messages import *
class TCPConnection(object): class TCPConnection(object):
def __init__(self, connect, port, encoding, framing, **kwds): def __init__(self, connect, port, encoding, framing, **kwds):
self.log = loggers.log.getChild('connection', False) self.log = loggers.log.getChild('connection', False)
self.encoder = ENCODERS[encoding]() self.encoder = ENCODERS[encoding]()
@ -147,8 +143,7 @@ class TCPConnection(object):
cb(msg) cb(msg)
except Exception as e: except Exception as e:
self.log.debug( self.log.debug(
"handle_async: got exception %r" % "handle_async: got exception %r" % e, exception=true)
e, exception=true)
else: else:
self.queue.append(msg) self.queue.append(msg)
@ -167,7 +162,6 @@ class TCPConnection(object):
class Client(object): class Client(object):
def __init__(self, opts): def __init__(self, opts):
self.log = loggers.log.getChild('client', True) self.log = loggers.log.getChild('client', True)
self._cache = dict() self._cache = dict()
@ -184,7 +178,7 @@ class Client(object):
def populateNamespace(self, namespace): def populateNamespace(self, namespace):
self.connection.send(ListDevicesRequest()) self.connection.send(ListDevicesRequest())
# reply = self.connection.read() # reply = self.connection.read()
# self.log.info("found devices %r" % reply) # self.log.info("found devices %r" % reply)
# create proxies, populate cache.... # create proxies, populate cache....
namespace.setconst('connection', self.connection) namespace.setconst('connection', self.connection)

View File

@ -19,12 +19,12 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Client side proxies""" """Define Client side proxies"""
import json import json
import socket import socket
import serial import serial
from select import select
import threading import threading
import Queue import Queue
@ -45,6 +45,7 @@ class TCPConnection(object):
self._host = host self._host = host
self._port = int(port) self._port = int(port)
self._thread = None self._thread = None
self.callbacks = [] # called if SEC-node shuts down
self.connect() self.connect()
def connect(self): def connect(self):
@ -62,24 +63,33 @@ class TCPConnection(object):
data = u'' data = u''
while True: while True:
try: try:
newdata = self._io.recv(1024)
except socket.timeout:
newdata = u'' newdata = u''
dlist = [self._io.fileno()]
rlist, wlist, xlist = select(dlist, dlist, dlist, 1)
if dlist[0] in rlist + wlist:
newdata = self._io.recv(1024)
if dlist[0] in xlist:
print "Problem: exception on socket, reconnecting!"
for cb, arg in self.callbacks:
cb(arg)
return
except socket.timeout:
pass pass
except Exception as err: except Exception as err:
print err, "reconnecting" print err, "reconnecting"
self.connect() for cb, arg in self.callbacks:
data = u'' cb(arg)
continue return
data += newdata data += newdata
while '\n' in data: while '\n' in data:
line, data = data.split('\n', 1) line, data = data.split('\n', 1)
try: try:
self._readbuffer.put( self._readbuffer.put(line.strip('\r'),
line.strip('\r'), block=True, timeout=1) block=True,
timeout=1)
except Queue.Full: except Queue.Full:
self.log.debug( self.log.debug('rcv queue full! dropping line: %r' %
'rcv queue full! dropping line: %r' % line) line)
finally: finally:
self._thread = None self._thread = None
@ -147,8 +157,9 @@ class Client(object):
devport = opts.pop('device') devport = opts.pop('device')
baudrate = int(opts.pop('baudrate', 115200)) baudrate = int(opts.pop('baudrate', 115200))
self.contactPoint = "serial://%s:%s" % (devport, baudrate) self.contactPoint = "serial://%s:%s" % (devport, baudrate)
self.connection = serial.Serial(devport, baudrate=baudrate, self.connection = serial.Serial(
timeout=1) devport, baudrate=baudrate, timeout=1)
self.connection.callbacks = []
else: else:
host = opts.pop('connectto', 'localhost') host = opts.pop('connectto', 'localhost')
port = int(opts.pop('port', 10767)) port = int(opts.pop('port', 10767))
@ -198,7 +209,7 @@ class Client(object):
self.secop_id = line self.secop_id = line
continue continue
msgtype, spec, data = self.decode_message(line) msgtype, spec, data = self.decode_message(line)
if msgtype in ('update', 'changed'): if msgtype in ('event', 'update', 'changed'):
# handle async stuff # handle async stuff
self._handle_event(spec, data) self._handle_event(spec, data)
# handle sync stuff # handle sync stuff
@ -217,21 +228,20 @@ class Client(object):
if entry: if entry:
self.log.error("request %r resulted in Error %r" % self.log.error("request %r resulted in Error %r" %
(data[0], spec)) (data[0], spec))
entry.extend([True, EXCEPTIONS[spec](data)]) entry.extend([True, EXCEPTIONS[spec](*data)])
entry[0].set() entry[0].set()
return return
self.log.error("got an unexpected error %s %r" % self.log.error("got an unexpected error %s %r" % (spec, data[0]))
(spec, data[0]))
return return
if msgtype == "describing": if msgtype == "describing":
data = [spec, data] entry = self.expected_replies.get((msgtype, ''), None)
spec = '' else:
entry = self.expected_replies.get((msgtype, spec), None) entry = self.expected_replies.get((msgtype, spec), None)
if entry: if entry:
self.log.debug("got expected reply '%s %s'" % self.log.debug("got expected reply '%s %s'" % (msgtype, spec)
(msgtype, spec) if spec else if spec else "got expected reply '%s'" % msgtype)
"got expected reply '%s'" % msgtype) entry.extend([False, msgtype, spec, data])
entry.extend([False, data])
entry[0].set() entry[0].set()
return return
@ -282,8 +292,8 @@ class Client(object):
try: try:
mkthread(func, data) mkthread(func, data)
except Exception as err: except Exception as err:
self.log.exception( self.log.exception('Exception in Single-shot Callback!',
'Exception in Single-shot Callback!', err) err)
run.add(func) run.add(func)
self.single_shots[spec].difference_update(run) self.single_shots[spec].difference_update(run)
@ -294,7 +304,8 @@ class Client(object):
return self._getDescribingModuleData(module)['parameters'][parameter] return self._getDescribingModuleData(module)['parameters'][parameter]
def _issueDescribe(self): def _issueDescribe(self):
self.equipment_id, self.describing_data = self.communicate('describe') _, self.equipment_id, self.describing_data = self._communicate(
'describe')
for module, moduleData in self.describing_data['modules'].items(): for module, moduleData in self.describing_data['modules'].items():
for parameter, parameterData in moduleData['parameters'].items(): for parameter, parameterData in moduleData['parameters'].items():
@ -303,17 +314,18 @@ class Client(object):
[parameter]['validator'] = validator [parameter]['validator'] = validator
def register_callback(self, module, parameter, cb): def register_callback(self, module, parameter, cb):
self.log.debug( self.log.debug('registering callback %r for %s:%s' %
'registering callback %r for %s:%s' % (cb, module, parameter))
(cb, module, parameter))
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb) self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
def unregister_callback(self, module, parameter, cb): def unregister_callback(self, module, parameter, cb):
self.log.debug( self.log.debug('unregistering callback %r for %s:%s' %
'unregistering callback %r for %s:%s' % (cb, module, parameter))
(cb, module, parameter)) self.callbacks.setdefault('%s:%s' % (module, parameter),
self.callbacks.setdefault('%s:%s' % set()).discard(cb)
(module, parameter), set()).discard(cb)
def register_shutdown_callback(self, func, arg):
self.connection.callbacks.append((func, arg))
def _get_reply_from_request(self, requesttype): def _get_reply_from_request(self, requesttype):
# maps each (sync) request to the corresponding reply # maps each (sync) request to the corresponding reply
@ -331,18 +343,24 @@ class Client(object):
return REPLYMAP.get(requesttype, requesttype) return REPLYMAP.get(requesttype, requesttype)
def communicate(self, msgtype, spec='', data=None): def communicate(self, msgtype, spec='', data=None):
# only return the data portion....
return self._communicate(msgtype, spec, data)[2]
def _communicate(self, msgtype, spec='', data=None):
self.log.debug('communicate: %r %r %r' % (msgtype, spec, data)) self.log.debug('communicate: %r %r %r' % (msgtype, spec, data))
if self.stopflag: if self.stopflag:
raise RuntimeError('alreading stopping!') raise RuntimeError('alreading stopping!')
if msgtype == "*IDN?": if msgtype == "*IDN?":
return self.secop_id return self.secop_id
if msgtype not in ('*IDN?', 'describe', 'activate', if msgtype not in ('*IDN?', 'describe', 'activate', 'deactivate', 'do',
'deactivate', 'do', 'change', 'read', 'ping', 'help'): 'change', 'read', 'ping', 'help'):
raise EXCEPTIONS['Protocol'](errorclass='Protocol', raise EXCEPTIONS['Protocol'](args=[
errorinfo='%r: No Such Messagetype defined!' % self.encode_message(msgtype, spec, data),
msgtype, dict(
origin=self.encode_message(msgtype, spec, data)) errorclass='Protocol',
errorinfo='%r: No Such Messagetype defined!' % msgtype, ),
])
# sanitize input + handle syntactic sugar # sanitize input + handle syntactic sugar
msgtype = str(msgtype) msgtype = str(msgtype)
@ -356,7 +374,8 @@ class Client(object):
rply = self._get_reply_from_request(msgtype) rply = self._get_reply_from_request(msgtype)
if (rply, spec) in self.expected_replies: if (rply, spec) in self.expected_replies:
raise RuntimeError( raise RuntimeError(
"can not have more than one requests of the same type at the same time!") "can not have more than one requests of the same type at the same time!"
)
# prepare sending request # prepare sending request
event = threading.Event() event = threading.Event()
@ -371,11 +390,14 @@ class Client(object):
# wait for reply. timeout after 10s # wait for reply. timeout after 10s
if event.wait(10): if event.wait(10):
self.log.debug('checking reply') self.log.debug('checking reply')
event, is_error, result = self.expected_replies.pop((rply, spec)) entry = self.expected_replies.pop((rply, spec))
# entry is: event, is_error, exc_or_msgtype [,spec, date]<- if !err
is_error = entry[1]
if is_error: if is_error:
# if error, result contains the rigth Exception to raise # if error, entry[2] contains the rigth Exception to raise
raise result raise entry[2]
return result # valid reply: entry[2:5] contain msgtype, spec, data
return tuple(entry[2:5])
# timed out # timed out
del self.expected_replies[(rply, spec)] del self.expected_replies[(rply, spec)]
@ -447,17 +469,21 @@ class Client(object):
return self.describing_data['modules'][module]['parameters'].keys() return self.describing_data['modules'][module]['parameters'].keys()
def getModuleBaseClass(self, module): def getModuleBaseClass(self, module):
return self.describing_data['modules'][module]['baseclass'] return self.describing_data['modules'][module]['interfaceclass']
def getCommands(self, module): def getCommands(self, module):
return self.describing_data['modules'][module]['commands'].keys() return self.describing_data['modules'][module]['commands'].keys()
def getProperties(self, module, parameter): def getProperties(self, module, parameter):
return self.describing_data['modules'][ return self.describing_data['modules'][module]['parameters'][parameter]
module]['parameters'][parameter]
def syncCommunicate(self, *msg): def syncCommunicate(self, *msg):
return self.communicate(*msg) res = self._communicate(*msg)
try:
res = self.encode_message(*res)
except Exception:
res = str(res)
return res
def ping(self, pingctr=[0]): def ping(self, pingctr=[0]):
pingctr[0] = pingctr[0] + 1 pingctr[0] = pingctr[0] + 1

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Baseclasses for real devices implemented in the server""" """Define Baseclasses for real devices implemented in the server"""
# XXX: connect with 'protocol'-Devices. # XXX: connect with 'protocol'-Devices.
@ -47,9 +46,13 @@ EVENT_ONLY_ON_CHANGED_VALUES = False
class PARAM(object): class PARAM(object):
def __init__(self,
def __init__(self, description, validator=float, default=Ellipsis, description,
unit=None, readonly=True, export=True): validator=float,
default=Ellipsis,
unit=None,
readonly=True,
export=True):
if isinstance(description, PARAM): if isinstance(description, PARAM):
# make a copy of a PARAM object # make a copy of a PARAM object
self.__dict__.update(description.__dict__) self.__dict__.update(description.__dict__)
@ -70,19 +73,17 @@ class PARAM(object):
def as_dict(self): def as_dict(self):
# used for serialisation only # used for serialisation only
return dict(description=self.description, return dict(
unit=self.unit, description=self.description,
readonly=self.readonly, unit=self.unit,
value=self.value, readonly=self.readonly,
timestamp=format_time( value=self.value,
self.timestamp) if self.timestamp else None, timestamp=format_time(self.timestamp) if self.timestamp else None,
validator=validator_to_str(self.validator), validator=validator_to_str(self.validator), )
)
# storage for CMDs settings (description + call signature...) # storage for CMDs settings (description + call signature...)
class CMD(object): class CMD(object):
def __init__(self, description, arguments, result): def __init__(self, description, arguments, result):
# descriptive text for humans # descriptive text for humans
self.description = description self.description = description
@ -97,17 +98,16 @@ class CMD(object):
def as_dict(self): def as_dict(self):
# used for serialisation only # used for serialisation only
return dict(description=self.description, return dict(
arguments=repr(self.arguments), description=self.description,
resulttype=repr(self.resulttype), arguments=repr(self.arguments),
) resulttype=repr(self.resulttype), )
# Meta class # Meta class
# warning: MAGIC! # warning: MAGIC!
class DeviceMeta(type): class DeviceMeta(type):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
newtype = type.__new__(mcs, name, bases, attrs) newtype = type.__new__(mcs, name, bases, attrs)
if '__constructed__' in attrs: if '__constructed__' in attrs:
@ -140,6 +140,7 @@ class DeviceMeta(type):
else: else:
# return cached value # return cached value
return self.PARAMS[pname].value return self.PARAMS[pname].value
if rfunc: if rfunc:
wrapped_rfunc.__doc__ = rfunc.__doc__ wrapped_rfunc.__doc__ = rfunc.__doc__
setattr(newtype, 'read_' + pname, wrapped_rfunc) setattr(newtype, 'read_' + pname, wrapped_rfunc)
@ -157,6 +158,7 @@ class DeviceMeta(type):
# of self.PARAMS[pname]? # of self.PARAMS[pname]?
setattr(self, pname, value) setattr(self, pname, value)
return value return value
if wfunc: if wfunc:
wrapped_wfunc.__doc__ = wfunc.__doc__ wrapped_wfunc.__doc__ = wfunc.__doc__
setattr(newtype, 'write_' + pname, wrapped_wfunc) setattr(newtype, 'write_' + pname, wrapped_wfunc)
@ -188,8 +190,8 @@ class DeviceMeta(type):
if argspec[0] and argspec[0][0] == 'self': if argspec[0] and argspec[0][0] == 'self':
del argspec[0][0] del argspec[0][0]
newtype.CMDS[name[2:]] = CMD( newtype.CMDS[name[2:]] = CMD(
getattr(value, '__doc__'), getattr(value, '__doc__'), argspec.args,
argspec.args, None) # XXX: find resulttype! None) # XXX: find resulttype!
attrs['__constructed__'] = True attrs['__constructed__'] = True
return newtype return newtype
@ -209,8 +211,9 @@ class Device(object):
__metaclass__ = DeviceMeta __metaclass__ = DeviceMeta
# PARAMS and CMDS are auto-merged upon subclassing # PARAMS and CMDS are auto-merged upon subclassing
PARAMS = { PARAMS = {
'baseclass': PARAM('protocol defined interface class', "interfaceclass": PARAM("protocol defined interface class",
default="Device", validator=str), default="Device",
validator=str),
} }
CMDS = {} CMDS = {}
DISPATCHER = None DISPATCHER = None
@ -226,8 +229,10 @@ class Device(object):
params[k] = PARAM(v) params[k] = PARAM(v)
mycls = self.__class__ mycls = self.__class__
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__) myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
params['class'] = PARAM('implementation specific class name', params['implementationclass'] = PARAM(
default=myclassname, validator=str) 'implementation specific class name',
default=myclassname,
validator=str)
self.PARAMS = params self.PARAMS = params
# check config for problems # check config for problems
@ -243,8 +248,8 @@ class Device(object):
if v.default is Ellipsis and k != 'value': if v.default is Ellipsis and k != 'value':
# Ellipsis is the one single value you can not specify.... # Ellipsis is the one single value you can not specify....
raise ConfigError('Device %s: Parameter %r has no default ' raise ConfigError('Device %s: Parameter %r has no default '
'value and was not given in config!' 'value and was not given in config!' %
% (self.name, k)) (self.name, k))
# assume default value was given # assume default value was given
cfgdict[k] = v.default cfgdict[k] = v.default
@ -261,8 +266,8 @@ class Device(object):
try: try:
v = validator(v) v = validator(v)
except (ValueError, TypeError) as e: except (ValueError, TypeError) as e:
raise ConfigError('Device %s: config parameter %r:\n%r' raise ConfigError('Device %s: config parameter %r:\n%r' %
% (self.name, k, e)) (self.name, k, e))
setattr(self, k, v) setattr(self, k, v)
self._requestLock = threading.RLock() self._requestLock = threading.RLock()
@ -281,10 +286,17 @@ class Readable(Device):
providing the readonly parameter 'value' and 'status' providing the readonly parameter 'value' and 'status'
""" """
PARAMS = { PARAMS = {
'baseclass': PARAM('protocol defined interface class', 'interfaceclass': PARAM(
default="Readable", validator=str), 'protocol defined interface class',
'value': PARAM('current value of the device', readonly=True, default=0.), default="Readable",
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),), validator=str),
'value': PARAM(
'current value of the device', readonly=True, default=0.),
'pollinterval': PARAM(
'sleeptime between polls',
readonly=False,
default=5,
validator=floatrange(0.1, 120), ),
# 'status': PARAM('current status of the device', default=status.OK, # 'status': PARAM('current status of the device', default=status.OK,
# validator=enum(**{'idle': status.OK, # validator=enum(**{'idle': status.OK,
# 'BUSY': status.BUSY, # 'BUSY': status.BUSY,
@ -293,14 +305,19 @@ class Readable(Device):
# 'ERROR': status.ERROR, # 'ERROR': status.ERROR,
# 'UNKNOWN': status.UNKNOWN}), # 'UNKNOWN': status.UNKNOWN}),
# readonly=True), # readonly=True),
'status': PARAM('current status of the device', default=(status.OK, ''), 'status': PARAM(
validator=vector(enum(**{'idle': status.OK, 'current status of the device',
'BUSY': status.BUSY, default=(status.OK, ''),
'WARN': status.WARN, validator=vector(
'UNSTABLE': status.UNSTABLE, enum(**{
'ERROR': status.ERROR, 'idle': status.OK,
'UNKNOWN': status.UNKNOWN}), str), 'BUSY': status.BUSY,
readonly=True), 'WARN': status.WARN,
'UNSTABLE': status.UNSTABLE,
'ERROR': status.ERROR,
'UNKNOWN': status.UNKNOWN
}), str),
readonly=True),
} }
def init(self): def init(self):
@ -310,6 +327,7 @@ class Readable(Device):
self._pollthread.start() self._pollthread.start()
def _pollThread(self): def _pollThread(self):
"""super simple and super stupid per-module polling thread"""
while True: while True:
time.sleep(self.pollinterval) time.sleep(self.pollinterval)
for pname in self.PARAMS: for pname in self.PARAMS:
@ -325,11 +343,25 @@ class Driveable(Readable):
providing a settable 'target' parameter to those of a Readable providing a settable 'target' parameter to those of a Readable
""" """
PARAMS = { PARAMS = {
'baseclass': PARAM('protocol defined interface class', "interfaceclass": PARAM("protocol defined interface class",
default="Driveable", validator=str), default="Driveable",
'target': PARAM('target value of the device', default=0., validator=str,
readonly=False), ),
'target': PARAM('target value of the device',
default=0.,
readonly=False,
),
} }
def doStart(self):
"""normally does nothing,
but there may be modules which _start_ the action here
"""
def doStop(self): def doStop(self):
"""Testing command implementation
wait a second"""
time.sleep(1) # for testing ! time.sleep(1) # for testing !

View File

@ -18,7 +18,6 @@
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# ***************************************************************************** # *****************************************************************************
"""playing implementation of a (simple) simulated cryostat""" """playing implementation of a (simple) simulated cryostat"""
from math import atan from math import atan
@ -26,10 +25,10 @@ import time
import random import random
import threading import threading
from secop.devices.core import Driveable, CONFIG, PARAM from secop.devices.core import Driveable, CMD, PARAM
from secop.protocol import status from secop.protocol import status
from secop.validators import floatrange, positive, enum from secop.validators import floatrange, positive, enum, nonnegative, vector
from secop.lib import clamp from secop.lib import clamp, mkthread
class Cryostat(Driveable): class Cryostat(Driveable):
@ -40,81 +39,112 @@ class Cryostat(Driveable):
- thermal transfer between regulation and samplen - thermal transfer between regulation and samplen
""" """
PARAMS = dict( PARAMS = dict(
jitter=CONFIG("amount of random noise on readout values", jitter=PARAM("amount of random noise on readout values",
validator=floatrange(0, 1), validator=floatrange(0, 1), unit="K",
default=0.1, readonly=False, export=False,
),
T_start=PARAM("starting temperature for simulation",
validator=positive, default=10,
export=False, export=False,
),
looptime=PARAM("timestep for simulation",
validator=floatrange(0.01, 10), unit="s", default=1,
readonly=False, export=False,
), ),
T_start=CONFIG("starting temperature for simulation", ramp=PARAM("ramping speed of the setpoint",
validator=positive, export=False, validator=floatrange(0, 1e3), unit="K/min", default=1,
), readonly=False,
looptime=CONFIG("timestep for simulation", ),
validator=positive, default=1, unit="s", setpoint=PARAM("current setpoint during ramping else target",
export=False, validator=float, default=1, unit='K',
), ),
ramp=PARAM("ramping speed in K/min", maxpower=PARAM("Maximum heater power",
validator=floatrange(0, 1e3), default=1, validator=nonnegative, default=1, unit="W",
readonly=False,
),
heater=PARAM("current heater setting",
validator=floatrange(0, 100), default=0, unit="%",
),
heaterpower=PARAM("current heater power",
validator=nonnegative, default=0, unit="W",
),
target=PARAM("target temperature",
validator=nonnegative, default=0, unit="K",
readonly=False,
),
value=PARAM("regulation temperature",
validator=nonnegative, default=0, unit="K",
), ),
setpoint=PARAM("ramping speed in K/min", pid=PARAM("regulation coefficients",
validator=float, default=1, readonly=True, validator=vector(nonnegative, floatrange(0, 100), floatrange(0, 100)),
), default=(40, 10, 2), readonly=False,
maxpower=PARAM("Maximum heater power in W", # export=False,
validator=float, default=0, readonly=True, unit="W", ),
), p=PARAM("regulation coefficient 'p'",
heater=PARAM("current heater setting in %", validator=nonnegative, default=40, unit="%/K", readonly=False,
validator=float, default=0, readonly=True, unit="%", export=False,
), ),
heaterpower=PARAM("current heater power in W",
validator=float, default=0, readonly=True, unit="W",
),
target=PARAM("target temperature in K",
validator=float, default=0, unit="K",
),
p=PARAM("regulation coefficient 'p' in %/K",
validator=positive, default=40, unit="%/K",
),
i=PARAM("regulation coefficient 'i'", i=PARAM("regulation coefficient 'i'",
validator=floatrange(0, 100), default=10, validator=floatrange(0, 100), default=10, readonly=False,
), export=False,
),
d=PARAM("regulation coefficient 'd'", d=PARAM("regulation coefficient 'd'",
validator=floatrange(0, 100), default=2, validator=floatrange(0, 100), default=2, readonly=False,
), export=False,
),
mode=PARAM("mode of regulation", mode=PARAM("mode of regulation",
validator=enum('ramp', 'pid', 'openloop'), default='pid', validator=enum('ramp', 'pid', 'openloop'), default='ramp',
), readonly=False,
),
pollinterval=PARAM("polling interval",
validator=positive, default=5,
export=False,
),
tolerance=PARAM("temperature range for stability checking", tolerance=PARAM("temperature range for stability checking",
validator=floatrange(0, 100), default=0.1, unit='K', validator=floatrange(0, 100), default=0.1, unit='K',
), readonly=False,
),
window=PARAM("time window for stability checking", window=PARAM("time window for stability checking",
validator=floatrange(1, 900), default=30, unit='s', validator=floatrange(1, 900), default=30, unit='s',
), readonly=False,
export=False,
),
timeout=PARAM("max waiting time for stabilisation check", timeout=PARAM("max waiting time for stabilisation check",
validator=floatrange(1, 36000), default=900, unit='s', validator=floatrange(1, 36000), default=900, unit='s',
), readonly=False,
export=False,
),
)
CMDS = dict(
Stop=CMD("Stop ramping the setpoint\n\nby setting the current setpoint as new target",
[], None),
) )
def init(self): def init(self):
self._stopflag = False self._stopflag = False
self._thread = threading.Thread(target=self.thread) self._thread = mkthread(self.thread)
self._thread.daemon = True
self._thread.start()
def read_status(self): def read_status(self, maxage=0):
# instead of asking a 'Hardware' take the value from the simulation # instead of asking a 'Hardware' take the value from the simulation
return self.status return self.status
def read_value(self, maxage=0): def read_value(self, maxage=0):
# return regulation value (averaged regulation temp) # return regulation value (averaged regulation temp)
return self.regulationtemp + \ return self.regulationtemp + \
self.config_jitter * (0.5 - random.random()) self.jitter * (0.5 - random.random())
def read_target(self, maxage=0): def read_target(self, maxage=0):
return self.target return self.target
def write_target(self, value): def write_target(self, value):
value = round(value, 2)
if value == self.target:
# nothing to do
return value
self.target = value self.target = value
# next request will see this status, until the loop updates it # next read_status will see this status, until the loop updates it
self.status = (status.BUSY, 'new target set') self.status = status.BUSY, 'new target set'
return value
def read_maxpower(self, maxage=0): def read_maxpower(self, maxage=0):
return self.maxpower return self.maxpower
@ -124,6 +154,14 @@ class Cryostat(Driveable):
heat = max(0, min(100, self.heater * self.maxpower / float(newpower))) heat = max(0, min(100, self.heater * self.maxpower / float(newpower)))
self.heater = heat self.heater = heat
self.maxpower = newpower self.maxpower = newpower
return newpower
def write_pid(self, newpid):
self.p, self.i, self.d = newpid
return (self.p, self.i, self.d)
def read_pid(self, maxage=0):
return (self.p, self.i, self.d)
def doStop(self): def doStop(self):
# stop the ramp by setting current setpoint as target # stop the ramp by setting current setpoint as target
@ -137,7 +175,7 @@ class Cryostat(Driveable):
"""returns cooling power in W at given temperature""" """returns cooling power in W at given temperature"""
# quadratic up to 42K, is linear from 40W@42K to 100W@600K # quadratic up to 42K, is linear from 40W@42K to 100W@600K
# return clamp((temp-2)**2 / 32., 0., 40.) + temp * 0.1 # return clamp((temp-2)**2 / 32., 0., 40.) + temp * 0.1
return clamp(15 * atan(temp * 0.01) ** 3, 0., 40.) + temp * 0.1 - 0.2 return clamp(15 * atan(temp * 0.01)**3, 0., 40.) + temp * 0.1 - 0.2
def __coolerCP(self, temp): def __coolerCP(self, temp):
"""heat capacity of cooler at given temp""" """heat capacity of cooler at given temp"""
@ -147,8 +185,8 @@ class Cryostat(Driveable):
"""heatflow from sample to cooler. may be negative...""" """heatflow from sample to cooler. may be negative..."""
flow = (sampletemp - coolertemp) * \ flow = (sampletemp - coolertemp) * \
((coolertemp + sampletemp) ** 2) / 400. ((coolertemp + sampletemp) ** 2) / 400.
cp = clamp(self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp), cp = clamp(
1, 10) self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp), 1, 10)
return clamp(flow, -cp, cp) return clamp(flow, -cp, cp)
def __sampleCP(self, temp): def __sampleCP(self, temp):
@ -159,9 +197,9 @@ class Cryostat(Driveable):
return 0.02 / temp return 0.02 / temp
def thread(self): def thread(self):
self.sampletemp = self.config_T_start self.sampletemp = self.T_start
self.regulationtemp = self.config_T_start self.regulationtemp = self.T_start
self.status = status.OK self.status = status.OK, ''
while not self._stopflag: while not self._stopflag:
try: try:
self.__sim() self.__sim()
@ -176,6 +214,7 @@ class Cryostat(Driveable):
# c) generating status+updated value+ramp # c) generating status+updated value+ramp
# this thread is not supposed to exit! # this thread is not supposed to exit!
self.setpoint = self.target
# local state keeping: # local state keeping:
regulation = self.regulationtemp regulation = self.regulationtemp
sample = self.sampletemp sample = self.sampletemp
@ -204,15 +243,14 @@ class Cryostat(Driveable):
heatflow = self.__heatLink(regulation, sample) heatflow = self.__heatLink(regulation, sample)
self.log.debug('sample = %.5f, regulation = %.5f, heatflow = %.5g' self.log.debug('sample = %.5f, regulation = %.5f, heatflow = %.5g'
% (sample, regulation, heatflow)) % (sample, regulation, heatflow))
newsample = max(0, newsample = max(0, sample + (self.__sampleLeak(sample) - heatflow)
sample + (self.__sampleLeak(sample) - heatflow) / / self.__sampleCP(sample) * h)
self.__sampleCP(sample) * h)
# avoid instabilities due to too small CP # avoid instabilities due to too small CP
newsample = clamp(newsample, sample, regulation) newsample = clamp(newsample, sample, regulation)
regdelta = (heater * 0.01 * self.maxpower + heatflow - regdelta = (heater * 0.01 * self.maxpower + heatflow -
self.__coolerPower(regulation)) self.__coolerPower(regulation))
newregulation = max(0, regulation + newregulation = max(
regdelta / self.__coolerCP(regulation) * h) 0, regulation + regdelta / self.__coolerCP(regulation) * h)
# b) see # b) see
# http://brettbeauregard.com/blog/2011/04/ # http://brettbeauregard.com/blog/2011/04/
# improving-the-beginners-pid-introduction/ # improving-the-beginners-pid-introduction/
@ -231,9 +269,9 @@ class Cryostat(Driveable):
# use a simple filter to smooth delta a little # use a simple filter to smooth delta a little
delta = (delta + regulation - newregulation) / 2. delta = (delta + regulation - newregulation) / 2.
kp = self.p / 10. # LakeShore P = 10*k_p kp = self.p / 10. # LakeShore P = 10*k_p
ki = kp * abs(self.i) / 500. # LakeShore I = 500/T_i 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 P = kp * error
I += ki * error * h I += ki * error * h
D = kd * delta / h D = kd * delta / h
@ -249,7 +287,7 @@ class Cryostat(Driveable):
v = P + I + D v = P + I + D
# in damping mode, use a weighted sum of old + new heaterpower # in damping mode, use a weighted sum of old + new heaterpower
if damper > 1: if damper > 1:
v = ((damper ** 2 - 1) * self.heater + v) / damper ** 2 v = ((damper**2 - 1) * self.heater + v) / damper**2
# damp oscillations due to D switching signs # damp oscillations due to D switching signs
if D * lastD < -0.2: if D * lastD < -0.2:
@ -274,7 +312,7 @@ class Cryostat(Driveable):
heater = self.heater heater = self.heater
last_heaters = (0, 0) last_heaters = (0, 0)
heater = round(heater, 3) heater = round(heater, 1)
sample = newsample sample = newsample
regulation = newregulation regulation = newregulation
lastmode = self.mode lastmode = self.mode
@ -285,9 +323,8 @@ class Cryostat(Driveable):
else: else:
maxdelta = self.ramp / 60. * h maxdelta = self.ramp / 60. * h
try: try:
self.setpoint = round(self.setpoint + self.setpoint = round(self.setpoint + clamp(
clamp(self.target - self.setpoint, self.target - self.setpoint, -maxdelta, maxdelta), 3)
-maxdelta, maxdelta), 3)
self.log.debug('setpoint changes to %r (target %r)' % self.log.debug('setpoint changes to %r (target %r)' %
(self.setpoint, self.target)) (self.setpoint, self.target))
except (TypeError, ValueError): except (TypeError, ValueError):
@ -319,6 +356,7 @@ class Cryostat(Driveable):
self.heaterpower = round(heater * self.maxpower * 0.01, 3) self.heaterpower = round(heater * self.maxpower * 0.01, 3)
self.heater = heater self.heater = heater
timestamp = t timestamp = t
self.read_value()
def shutdown(self): def shutdown(self):
# should be called from server when the server is stopped # should be called from server when the server is stopped

View File

@ -18,7 +18,6 @@
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# ***************************************************************************** # *****************************************************************************
"""testing devices""" """testing devices"""
import time import time
@ -35,16 +34,20 @@ class Switch(Driveable):
""" """
PARAMS = { PARAMS = {
'value': PARAM('current state (on or off)', 'value': PARAM('current state (on or off)',
validator=enum(on=1, off=0), default=0), validator=enum(on=1, off=0), default=0,
),
'target': PARAM('wanted state (on or off)', 'target': PARAM('wanted state (on or off)',
validator=enum(on=1, off=0), validator=enum(on=1, off=0), default=0,
default=0, readonly=False), readonly=False,
),
'switch_on_time': PARAM('seconds to wait after activating the switch', 'switch_on_time': PARAM('seconds to wait after activating the switch',
validator=floatrange(0, 60), unit='s', validator=floatrange(0, 60), unit='s',
default=10, export=False), default=10, export=False,
),
'switch_off_time': PARAM('cool-down time in seconds', 'switch_off_time': PARAM('cool-down time in seconds',
validator=floatrange(0, 60), unit='s', validator=floatrange(0, 60), unit='s',
default=10, export=False), default=10, export=False,
),
} }
def init(self): def init(self):
@ -95,14 +98,24 @@ class MagneticField(Driveable):
"""a liquid magnet """a liquid magnet
""" """
PARAMS = { PARAMS = {
'value': PARAM('current field in T', unit='T', 'value': PARAM('current field in T',
validator=floatrange(-15, 15), default=0), unit='T', validator=floatrange(-15, 15), default=0,
'ramp': PARAM('moving speed in T/min', unit='T/min', ),
validator=floatrange(0, 1), default=0.1, readonly=False), 'target': PARAM('target field in T',
'mode': PARAM('what to do after changing field', default=0, unit='T', validator=floatrange(-15, 15), default=0,
validator=enum(persistent=1, hold=0), readonly=False), readonly=False,
'heatswitch': PARAM('heat switch device', ),
validator=str, export=False), 'ramp': PARAM('ramping speed',
unit='T/min', validator=floatrange(0, 1), default=0.1,
readonly=False,
),
'mode': PARAM('what to do after changing field',
default=1, validator=enum(persistent=1, hold=0),
readonly=False,
),
'heatswitch': PARAM('name of heat switch device',
validator=str, export=False,
),
} }
def init(self): def init(self):
@ -122,8 +135,8 @@ class MagneticField(Driveable):
# note: we may also return the read-back value from the hw here # note: we may also return the read-back value from the hw here
def read_status(self, maxage=0): def read_status(self, maxage=0):
return (status.OK, '') if self._state == 'idle' else ( return (status.OK, '') if self._state == 'idle' else (status.BUSY,
status.BUSY, self._state) self._state)
def _thread(self): def _thread(self):
loopdelay = 1 loopdelay = 1
@ -137,9 +150,8 @@ class MagneticField(Driveable):
if self._state == 'switch_on': if self._state == 'switch_on':
# wait until switch is on # wait until switch is on
if self._heatswitch.read_value() == 'on': if self._heatswitch.read_value() == 'on':
self.log.debug( self.log.debug('heatswitch is on -> ramp to %.3f' %
'heatswitch is on -> ramp to %.3f' % self.target)
self.target)
self._state = 'ramp' self._state = 'ramp'
if self._state == 'ramp': if self._state == 'ramp':
if self.target == self.value: if self.target == self.value:
@ -170,10 +182,12 @@ class CoilTemp(Readable):
"""a coil temperature """a coil temperature
""" """
PARAMS = { PARAMS = {
'value': PARAM('Coil temperatur in K', unit='K', 'value': PARAM('Coil temperatur',
validator=float, default=0), unit='K', validator=float, default=0,
),
'sensor': PARAM("Sensor number or calibration id", 'sensor': PARAM("Sensor number or calibration id",
validator=str, readonly=True), validator=str, readonly=True,
),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):
@ -184,12 +198,16 @@ class SampleTemp(Driveable):
"""a sample temperature """a sample temperature
""" """
PARAMS = { PARAMS = {
'value': PARAM('Sample temperatur in K', unit='K', 'value': PARAM('Sample temperature',
validator=float, default=10), unit='K', validator=float, default=10,
),
'sensor': PARAM("Sensor number or calibration id", 'sensor': PARAM("Sensor number or calibration id",
validator=str, readonly=True), validator=str, readonly=True,
'ramp': PARAM('moving speed in K/min', validator=floatrange(0, 100), ),
unit='K/min', default=0.1, readonly=False), 'ramp': PARAM('moving speed in K/min',
validator=floatrange(0, 100), unit='K/min', default=0.1,
readonly=False,
),
} }
def init(self): def init(self):
@ -225,13 +243,17 @@ class Label(Readable):
""" """
PARAMS = { PARAMS = {
'system': PARAM("Name of the magnet system", 'system': PARAM("Name of the magnet system",
validator=str, export=False), validator=str, export=False,
),
'subdev_mf': PARAM("name of subdevice for magnet status", 'subdev_mf': PARAM("name of subdevice for magnet status",
validator=str, export=False), validator=str, export=False,
),
'subdev_ts': PARAM("name of subdevice for sample temp", 'subdev_ts': PARAM("name of subdevice for sample temp",
validator=str, export=False), validator=str, export=False,
'value': PARAM("Value of out label string", ),
validator=str) 'value': PARAM("final value of label string",
validator=str,
),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):
@ -250,10 +272,10 @@ class Label(Readable):
mf_mode = dev_mf.mode mf_mode = dev_mf.mode
mf_val = dev_mf.value mf_val = dev_mf.value
mf_unit = dev_mf.PARAMS['value'].unit mf_unit = dev_mf.PARAMS['value'].unit
if mf_stat == status.OK: if mf_stat[0] == status.OK:
state = 'Persistent' if mf_mode else 'Non-persistent' state = 'Persistent' if mf_mode else 'Non-persistent'
else: else:
state = 'ramping' state = mf_stat[1] or 'ramping'
strings.append('%s at %.1f %s' % (state, mf_val, mf_unit)) strings.append('%s at %.1f %s' % (state, mf_val, mf_unit))
else: else:
strings.append('No connection to magnetic field!') strings.append('No connection to magnetic field!')
@ -265,12 +287,21 @@ class ValidatorTest(Readable):
""" """
""" """
PARAMS = { PARAMS = {
'oneof': PARAM('oneof', validator=oneof(int, 'X', 2.718), readonly=False, default=4.0), 'oneof': PARAM('oneof',
'enum': PARAM('enum', validator=enum('boo', 'faar', z=9), readonly=False, default=1), validator=oneof(int, 'X', 2.718), readonly=False, default=4.0),
'vector': PARAM('vector of int, float and str', validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')), 'enum': PARAM('enum',
'array': PARAM('array: 2..3 time oneof(0,1)', validator=array(oneof(2, 3), oneof(0, 1)), readonly=False, default=[1, 0, 1]), validator=enum('boo', 'faar', z=9), readonly=False, default=1),
'nonnegative': PARAM('nonnegative', validator=nonnegative, readonly=False, default=0), 'vector': PARAM('vector of int, float and str',
'positive': PARAM('positive', validator=positive, readonly=False, default=1), validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')),
'intrange': PARAM('intrange', validator=intrange(2, 9), readonly=False, default=4), 'array': PARAM('array: 2..3 times oneof(0,1)',
'floatrange': PARAM('floatrange', validator=floatrange(-1, 1), readonly=False, default=0,), validator=array(oneof(2, 3), oneof(0, 1)), readonly=False, default=[1, 0, 1]),
'nonnegative': PARAM('nonnegative',
validator=nonnegative, readonly=False, default=0),
'positive': PARAM('positive',
validator=positive, readonly=False, default=1),
'intrange': PARAM('intrange',
validator=intrange(2, 9), readonly=False, default=4),
'floatrange': PARAM('floatrange',
validator=floatrange(-1, 1), readonly=False, default=0,
),
} }

View File

@ -18,7 +18,6 @@
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# ***************************************************************************** # *****************************************************************************
"""testing devices""" """testing devices"""
import random import random
@ -46,7 +45,8 @@ class Heater(Driveable):
""" """
PARAMS = { PARAMS = {
'maxheaterpower': PARAM('maximum allowed heater power', 'maxheaterpower': PARAM('maximum allowed heater power',
validator=floatrange(0, 100), unit='W'), validator=floatrange(0, 100), unit='W',
),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):
@ -64,9 +64,11 @@ class Temp(Driveable):
""" """
PARAMS = { PARAMS = {
'sensor': PARAM("Sensor number or calibration id", 'sensor': PARAM("Sensor number or calibration id",
validator=str, readonly=True), validator=str, readonly=True,
'target': PARAM("Target temperature", default=300.0, ),
validator=positive, readonly=False, unit='K'), 'target': PARAM("Target temperature",
default=300.0, validator=positive, readonly=False, unit='K',
),
} }
def read_value(self, maxage=0): def read_value(self, maxage=0):

View File

@ -21,7 +21,7 @@
# #
# ***************************************************************************** # *****************************************************************************
from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem, QMessageBox
from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal
from secop.gui.util import loadUi from secop.gui.util import loadUi
@ -30,6 +30,8 @@ from secop.gui.modulectrl import ModuleCtrl
from secop.gui.paramview import ParameterView from secop.gui.paramview import ParameterView
from secop.client.baseclient import Client as SECNode from secop.client.baseclient import Client as SECNode
import sys
ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1 ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1
ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2 ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3 ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
@ -61,21 +63,34 @@ class QSECNode(SECNode, QObject):
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent) super(MainWindow, self).__init__(parent)
loadUi(self, 'mainwindow.ui') loadUi(self, 'mainwindow.ui')
self.toolBar.hide()
self.lineEdit.hide()
self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(0, 1)
self.splitter.setStretchFactor(1, 70) self.splitter.setStretchFactor(1, 70)
self.splitter.setSizes([50, 500])
self._nodes = {} self._nodes = {}
self._nodeCtrls = {} self._nodeCtrls = {}
self._topItems = {}
self._currentWidget = self.splitter.widget(1).layout().takeAt(0) self._currentWidget = self.splitter.widget(1).layout().takeAt(0)
# add localhost if available # add localhost (if available) and SEC nodes given as arguments
self._addNode('localhost') args = sys.argv[1:]
if '-d' in args:
args.remove('-d')
if not args:
args = ['localhost']
for host in args:
try:
self._addNode(host)
except Exception as e:
print e
@qtsig('') @qtsig('')
def on_actionAdd_SEC_node_triggered(self): def on_actionAdd_SEC_node_triggered(self):
@ -85,7 +100,11 @@ class MainWindow(QMainWindow):
if not ok: if not ok:
return return
self._addNode(host) try:
self._addNode(host)
except Exception as e:
QMessageBox.critical(self.parent(),
'Connecting to %s failed!' % host, str(e))
def on_treeWidget_currentItemChanged(self, current, previous): def on_treeWidget_currentItemChanged(self, current, previous):
if current.type() == ITEM_TYPE_NODE: if current.type() == ITEM_TYPE_NODE:
@ -94,8 +113,18 @@ class MainWindow(QMainWindow):
self._displayModule(current.parent().text(0), current.text(0)) self._displayModule(current.parent().text(0), current.text(0))
elif current.type() == ITEM_TYPE_PARAMETER: elif current.type() == ITEM_TYPE_PARAMETER:
self._displayParameter(current.parent().parent().text(0), self._displayParameter(current.parent().parent().text(0),
current.parent().text(0), current.parent().text(0), current.text(0))
current.text(0))
def _removeSubTree(self, toplevelItem):
#....
pass
def _nodeDisconnected_callback(self, host):
node = self._nodes[host]
topItem = self._topItems[node]
self._removeSubTree(topItem)
node.quit()
QMessageBox(self.parent(), repr(host))
def _addNode(self, host): def _addNode(self, host):
@ -109,6 +138,7 @@ class MainWindow(QMainWindow):
host = '%s (%s)' % (node.equipment_id, host) host = '%s (%s)' % (node.equipment_id, host)
self._nodes[host] = node self._nodes[host] = node
node.register_shutdown_callback(self._nodeDisconnected_callback, host)
# fill tree # fill tree
nodeItem = QTreeWidgetItem(None, [host], ITEM_TYPE_NODE) nodeItem = QTreeWidgetItem(None, [host], ITEM_TYPE_NODE)
@ -120,6 +150,7 @@ class MainWindow(QMainWindow):
ITEM_TYPE_PARAMETER) ITEM_TYPE_PARAMETER)
self.treeWidget.addTopLevelItem(nodeItem) self.treeWidget.addTopLevelItem(nodeItem)
self._topItems[node] = nodeItem
def _displayNode(self, node): def _displayNode(self, node):
@ -135,10 +166,7 @@ class MainWindow(QMainWindow):
def _displayParameter(self, node, module, parameter): def _displayParameter(self, node, module, parameter):
self._replaceCtrlWidget( self._replaceCtrlWidget(
ParameterView( ParameterView(self._nodes[node], module, parameter))
self._nodes[node],
module,
parameter))
def _replaceCtrlWidget(self, new): def _replaceCtrlWidget(self, new):
old = self.splitter.widget(1).layout().takeAt(0) old = self.splitter.widget(1).layout().takeAt(0)

View File

@ -21,7 +21,7 @@
# #
# ***************************************************************************** # *****************************************************************************
from PyQt4.QtGui import QWidget, QLabel from PyQt4.QtGui import QWidget, QLabel, QMessageBox
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
from secop.gui.util import loadUi from secop.gui.util import loadUi
@ -30,8 +30,12 @@ from secop.gui.util import loadUi
class ParameterButtons(QWidget): class ParameterButtons(QWidget):
setRequested = pyqtSignal(str, str, str) # module, parameter, target setRequested = pyqtSignal(str, str, str) # module, parameter, target
def __init__(self, module, parameter, initval='', def __init__(self,
readonly=True, parent=None): module,
parameter,
initval='',
readonly=True,
parent=None):
super(ParameterButtons, self).__init__(parent) super(ParameterButtons, self).__init__(parent)
loadUi(self, 'parambuttons.ui') loadUi(self, 'parambuttons.ui')
@ -42,6 +46,9 @@ class ParameterButtons(QWidget):
if readonly: if readonly:
self.setPushButton.setEnabled(False) self.setPushButton.setEnabled(False)
self.setLineEdit.setEnabled(False) self.setLineEdit.setEnabled(False)
else:
self.setLineEdit.returnPressed.connect(
self.on_setPushButton_clicked)
def on_setPushButton_clicked(self): def on_setPushButton_clicked(self):
self.setRequested.emit(self._module, self._parameter, self.setRequested.emit(self._module, self._parameter,
@ -49,12 +56,12 @@ class ParameterButtons(QWidget):
class ModuleCtrl(QWidget): class ModuleCtrl(QWidget):
def __init__(self, node, module, parent=None): def __init__(self, node, module, parent=None):
super(ModuleCtrl, self).__init__(parent) super(ModuleCtrl, self).__init__(parent)
loadUi(self, 'modulectrl.ui') loadUi(self, 'modulectrl.ui')
self._node = node self._node = node
self._module = module self._module = module
self._lastclick = None
self._paramWidgets = {} # widget cache do avoid garbage collection self._paramWidgets = {} # widget cache do avoid garbage collection
@ -71,7 +78,16 @@ class ModuleCtrl(QWidget):
font.setBold(True) font.setBold(True)
for param in sorted(self._node.getParameters(self._module)): for param in sorted(self._node.getParameters(self._module)):
label = QLabel(param + ':') labelstr = param + ':'
unit = self._node.getProperties(self._module, param).get('unit',
'')
descr = self._node.getProperties(self._module,
param).get('description', '')
if unit:
labelstr = "%s (%s):" % (param, unit)
label = QLabel(labelstr)
label.setFont(font) label.setFont(font)
props = self._node.getProperties(self._module, param) props = self._node.getProperties(self._module, param)
@ -80,7 +96,11 @@ class ModuleCtrl(QWidget):
initValues[param].value, initValues[param].value,
props['readonly']) props['readonly'])
buttons.setRequested.connect(self._node.setParameter) # buttons.setRequested.connect(self._node.setParameter)
buttons.setRequested.connect(self._set_Button_pressed)
if descr:
buttons.setToolTip(descr)
self.paramGroupBox.layout().addWidget(label, row, 0) self.paramGroupBox.layout().addWidget(label, row, 0)
self.paramGroupBox.layout().addWidget(buttons, row, 1) self.paramGroupBox.layout().addWidget(buttons, row, 1)
@ -89,6 +109,16 @@ class ModuleCtrl(QWidget):
row += 1 row += 1
def _set_Button_pressed(self, module, parameter, target):
sig = (module, parameter, target)
if self._lastclick == sig:
return
self._lastclick = sig
try:
self._node.setParameter(module, parameter, target)
except Exception as e:
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
def _updateValue(self, module, parameter, value): def _updateValue(self, module, parameter, value):
if module != self._module: if module != self._module:
return return

View File

@ -22,6 +22,7 @@
# ***************************************************************************** # *****************************************************************************
import pprint import pprint
import json
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics
from PyQt4.QtCore import pyqtSignature as qtsig, Qt from PyQt4.QtCore import pyqtSignature as qtsig, Qt
@ -31,7 +32,6 @@ from secop.protocol.errors import SECOPError
class NodeCtrl(QWidget): class NodeCtrl(QWidget):
def __init__(self, node, parent=None): def __init__(self, node, parent=None):
super(NodeCtrl, self).__init__(parent) super(NodeCtrl, self).__init__(parent)
loadUi(self, 'nodectrl.ui') loadUi(self, 'nodectrl.ui')
@ -50,14 +50,26 @@ class NodeCtrl(QWidget):
if not msg: if not msg:
return return
self._addLogEntry('<span style="font-weight:bold">Request:</span> ' self._addLogEntry(
'%s:' % msg, raw=True) '<span style="font-weight:bold">Request:</span> '
# msg = msg.split(' ', 2) '%s:' % msg,
raw=True)
# msg = msg.split(' ', 2)
try: try:
reply = self._node.syncCommunicate(*self._node.decode_message(msg)) reply = self._node.syncCommunicate(*self._node.decode_message(msg))
self._addLogEntry(reply, newline=True, pretty=True) if msg == 'describe':
_, eid, stuff = self._node.decode_message(reply)
reply = '%s %s %s' % (_, eid, json.dumps(
stuff, indent=2, separators=(',', ':'), sort_keys=True))
self._addLogEntry(reply, newline=True, pretty=False)
else:
self._addLogEntry(reply, newline=True, pretty=True)
except SECOPError as e: except SECOPError as e:
self._addLogEntry(e, newline=True, pretty=True, error=True) self._addLogEntry(
'error %s %s' % (e.name, json.dumps(e.args)),
newline=True,
pretty=True,
error=True)
@qtsig('') @qtsig('')
def on_clearPushButton_clicked(self): def on_clearPushButton_clicked(self):
@ -70,19 +82,23 @@ class NodeCtrl(QWidget):
self._addLogEntry('=========================') self._addLogEntry('=========================')
self._addLogEntry('', newline=True) self._addLogEntry('', newline=True)
def _addLogEntry(self, msg, newline=False, def _addLogEntry(self,
pretty=False, raw=False, error=False): msg,
newline=False,
pretty=False,
raw=False,
error=False):
if pretty: if pretty:
msg = pprint.pformat(msg, width=self._getLogWidth()) msg = pprint.pformat(msg, width=self._getLogWidth())
msg = msg[1:-1]
if not raw: if not raw:
if error: if error:
msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % Qt.escape( msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % Qt.escape(
str(msg)).replace('\n', '<br />') str(msg)).replace('\n', '<br />')
else: else:
msg = '<pre>%s</pre>' % Qt.escape(str(msg) msg = '<pre>%s</pre>' % Qt.escape(str(msg)).replace('\n',
).replace('\n', '<br />') '<br />')
content = '' content = ''
if self.logTextBrowser.toPlainText(): if self.logTextBrowser.toPlainText():

View File

@ -29,7 +29,6 @@ from secop.validators import validator_to_str
class ParameterView(QWidget): class ParameterView(QWidget):
def __init__(self, node, module, parameter, parent=None): def __init__(self, node, module, parameter, parent=None):
super(ParameterView, self).__init__(parent) super(ParameterView, self).__init__(parent)
loadUi(self, 'paramview.ui') loadUi(self, 'paramview.ui')
@ -51,8 +50,8 @@ class ParameterView(QWidget):
font = self.font() font = self.font()
font.setBold(True) font.setBold(True)
props = self._node._getDescribingParameterData( props = self._node._getDescribingParameterData(self._module,
self._module, self._parameter) self._parameter)
for prop in sorted(props): for prop in sorted(props):
label = QLabel(prop + ':') label = QLabel(prop + ':')
label.setFont(font) label.setFont(font)

View File

@ -25,7 +25,6 @@ from os import path
from PyQt4 import uic from PyQt4 import uic
uipath = path.dirname(__file__) uipath = path.dirname(__file__)

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define helpers""" """Define helpers"""
import threading import threading
@ -50,13 +49,16 @@ def get_class(spec):
modname, classname = spec.rsplit('.', 1) modname, classname = spec.rsplit('.', 1)
import importlib import importlib
module = importlib.import_module('secop.' + modname) module = importlib.import_module('secop.' + modname)
# module = __import__(spec) # module = __import__(spec)
return getattr(module, classname) return getattr(module, classname)
def mkthread(func, *args, **kwds): def mkthread(func, *args, **kwds):
t = threading.Thread(name='%s:%s' % (func.__module__, func.__name__), t = threading.Thread(
target=func, args=args, kwargs=kwds) name='%s:%s' % (func.__module__, func.__name__),
target=func,
args=args,
kwargs=kwds)
t.daemon = True t.daemon = True
t.start() t.start()
return t return t

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define parsing helpers""" """Define parsing helpers"""
import re import re
@ -58,13 +57,13 @@ class LocalTimezone(tzinfo):
return time.tzname[self._isdst(dt)] return time.tzname[self._isdst(dt)]
def _isdst(self, dt): def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day, tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
dt.hour, dt.minute, dt.second,
dt.weekday(), 0, 0) dt.weekday(), 0, 0)
stamp = time.mktime(tt) stamp = time.mktime(tt)
tt = time.localtime(stamp) tt = time.localtime(stamp)
return tt.tm_isdst > 0 return tt.tm_isdst > 0
LocalTimezone = LocalTimezone() LocalTimezone = LocalTimezone()
@ -81,7 +80,6 @@ def format_time(timestamp=None):
class Timezone(tzinfo): class Timezone(tzinfo):
def __init__(self, offset, name='unknown timezone'): def __init__(self, offset, name='unknown timezone'):
self.offset = offset self.offset = offset
self.name = name self.name = name
@ -94,12 +92,13 @@ class Timezone(tzinfo):
def dst(self, dt): def dst(self, dt):
return timedelta(0) return timedelta(0)
datetime_re = re.compile( datetime_re = re.compile(
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})' r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})' r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d*)?)?' r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d*)?)?'
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$' r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$')
)
def _parse_isostring(isostring): def _parse_isostring(isostring):
@ -125,9 +124,8 @@ def _parse_isostring(isostring):
kw = {k: int(v) for k, v in kw.items() if v is not None} kw = {k: int(v) for k, v in kw.items() if v is not None}
kw['tzinfo'] = _tzinfo kw['tzinfo'] = _tzinfo
return datetime(**kw) return datetime(**kw)
raise ValueError( raise ValueError("%s is not a valid ISO8601 string I can parse!" %
"%s is not a valid ISO8601 string I can parse!" % isostring)
isostring)
def parse_time(isostring): def parse_time(isostring):
@ -137,9 +135,9 @@ def parse_time(isostring):
dt = _parse_isostring(isostring) dt = _parse_isostring(isostring)
return time.mktime(dt.timetuple()) + dt.microsecond * 1e-6 return time.mktime(dt.timetuple()) + dt.microsecond * 1e-6
# possibly unusable stuff below! # possibly unusable stuff below!
def format_args(args): def format_args(args):
if isinstance(args, list): if isinstance(args, list):
return ','.join(format_args(arg) for arg in args).join('[]') return ','.join(format_args(arg) for arg in args).join('[]')
@ -168,7 +166,8 @@ class ArgsParser(object):
DIGITS_CHARS = [c for c in '0123456789'] DIGITS_CHARS = [c for c in '0123456789']
NAME_CHARS = [ NAME_CHARS = [
c for c in '_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'] c for c in '_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
]
NAME_CHARS2 = NAME_CHARS + DIGITS_CHARS NAME_CHARS2 = NAME_CHARS + DIGITS_CHARS
def __init__(self, string=''): def __init__(self, string=''):
@ -393,7 +392,7 @@ if __name__ == '__main__':
print "time_formatting:", print "time_formatting:",
t = time.time() t = time.time()
s = format_time(t) s = format_time(t)
assert(abs(t - parse_time(s)) < 1e-6) assert (abs(t - parse_time(s)) < 1e-6)
print "OK" print "OK"
print "ArgsParser:" print "ArgsParser:"
@ -408,6 +407,6 @@ if __name__ == '__main__':
s = format_args(obj) s = format_args(obj)
p = a.parse(s) p = a.parse(s)
print p, print p,
assert(parse_args(format_args(obj)) == obj) assert (parse_args(format_args(obj)) == obj)
print "OK" print "OK"
print "OK" print "OK"

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define pidfile helpers""" """Define pidfile helpers"""
import os import os
import atexit import atexit

View File

@ -20,7 +20,6 @@
# #
# ***************************************************************************** # *****************************************************************************
import os import os
import sys import sys
import time import time
@ -34,7 +33,6 @@ from logging import Logger, Formatter, Handler, DEBUG, INFO, WARNING, ERROR, \
from . import colors from . import colors
LOGFMT = '%(asctime)s : %(levelname)-7s : %(name)-15s: %(message)s' LOGFMT = '%(asctime)s : %(levelname)-7s : %(name)-15s: %(message)s'
DATEFMT = '%H:%M:%S' DATEFMT = '%H:%M:%S'
DATESTAMP_FMT = '%Y-%m-%d' DATESTAMP_FMT = '%Y-%m-%d'
@ -43,7 +41,6 @@ SECONDS_PER_DAY = 60 * 60 * 24
LOGLEVELS = {'debug': DEBUG, 'info': INFO, 'warning': WARNING, 'error': ERROR} LOGLEVELS = {'debug': DEBUG, 'info': INFO, 'warning': WARNING, 'error': ERROR}
INVLOGLEVELS = {value: key for key, value in LOGLEVELS.items()} INVLOGLEVELS = {value: key for key, value in LOGLEVELS.items()}
log = None log = None
@ -142,16 +139,16 @@ class ConsoleFormatter(Formatter):
elif levelno <= INFO: elif levelno <= INFO:
fmtstr = '%s%%(message)s' % namefmt fmtstr = '%s%%(message)s' % namefmt
elif levelno <= WARNING: elif levelno <= WARNING:
fmtstr = self.colorize('fuchsia', '%s%%(levelname)s: %%(message)s' fmtstr = self.colorize('fuchsia',
% namefmt) '%s%%(levelname)s: %%(message)s' % namefmt)
else: else:
# Add exception type to error (if caused by exception) # Add exception type to error (if caused by exception)
msgPrefix = '' msgPrefix = ''
if record.exc_info: if record.exc_info:
msgPrefix = '%s: ' % record.exc_info[0].__name__ msgPrefix = '%s: ' % record.exc_info[0].__name__
fmtstr = self.colorize('red', '%s%%(levelname)s: %s%%(message)s' fmtstr = self.colorize('red', '%s%%(levelname)s: %s%%(message)s' %
% (namefmt, msgPrefix)) (namefmt, msgPrefix))
fmtstr = datefmt + fmtstr fmtstr = datefmt + fmtstr
if not getattr(record, 'nonl', False): if not getattr(record, 'nonl', False):
fmtstr += '\n' fmtstr += '\n'
@ -209,8 +206,8 @@ class LogfileFormatter(Formatter):
if self.extended_traceback: if self.extended_traceback:
s = format_extended_traceback(*ei) s = format_extended_traceback(*ei)
else: else:
s = ''.join(traceback.format_exception(ei[0], ei[1], ei[2], s = ''.join(
sys.maxsize)) traceback.format_exception(ei[0], ei[1], ei[2], sys.maxsize))
if s.endswith('\n'): if s.endswith('\n'):
s = s[:-1] s = s[:-1]
return s return s
@ -268,8 +265,8 @@ class LogfileHandler(StreamHandler):
StreamHandler.__init__(self) StreamHandler.__init__(self)
# determine time of first midnight from now on # determine time of first midnight from now on
t = time.localtime() t = time.localtime()
self.rollover_at = time.mktime((t[0], t[1], t[2], 0, 0, 0, self.rollover_at = time.mktime(
t[6], t[7], t[8])) + SECONDS_PER_DAY (t[0], t[1], t[2], 0, 0, 0, t[6], t[7], t[8])) + SECONDS_PER_DAY
self.setFormatter(LogfileFormatter(LOGFMT, DATEFMT)) self.setFormatter(LogfileFormatter(LOGFMT, DATEFMT))
self.disabled = False self.disabled = False
@ -338,8 +335,9 @@ class ColoredConsoleHandler(StreamHandler):
def __init__(self): def __init__(self):
StreamHandler.__init__(self, sys.stdout) StreamHandler.__init__(self, sys.stdout)
self.setFormatter(ConsoleFormatter(datefmt=DATEFMT, self.setFormatter(
colorize=colors.colorize)) ConsoleFormatter(
datefmt=DATEFMT, colorize=colors.colorize))
def emit(self, record): def emit(self, record):
msg = self.format(record) msg = self.format(record)

View File

@ -20,10 +20,8 @@
# Georg Brandl <georg@python.org> # Georg Brandl <georg@python.org>
# #
# ***************************************************************************** # *****************************************************************************
"""Console coloring handlers.""" """Console coloring handlers."""
_codes = {} _codes = {}
_attrs = { _attrs = {

View File

@ -19,13 +19,11 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Pathes. how to find what and where...""" """Pathes. how to find what and where..."""
import sys import sys
from os import path from os import path
basepath = path.abspath(path.join(sys.path[0], '..')) basepath = path.abspath(path.join(sys.path[0], '..'))
etc_path = path.join(basepath, 'etc') etc_path = path.join(basepath, 'etc')
pid_path = path.join(basepath, 'pid') pid_path = path.join(basepath, 'pid')

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define SECoP Device classes """Define SECoP Device classes
""" """

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Dispatcher for SECoP Messages """Dispatcher for SECoP Messages
Interface to the service offering part: Interface to the service offering part:
@ -47,7 +46,6 @@ from secop.lib.parsing import format_time
class Dispatcher(object): class Dispatcher(object):
def __init__(self, logger, options): def __init__(self, logger, options):
self.equipment_id = options.pop('equipment_id') self.equipment_id = options.pop('equipment_id')
self.log = logger self.log = logger
@ -85,15 +83,18 @@ class Dispatcher(object):
reply = handler(conn, msg) reply = handler(conn, msg)
except SECOPError as err: except SECOPError as err:
self.log.exception(err) self.log.exception(err)
reply = msg.get_error(errorclass=err.__class__.__name__, reply = msg.get_error(
errorinfo=[repr(err), str(msg)]) errorclass=err.__class__.__name__,
errorinfo=[repr(err), str(msg)])
except (ValueError, TypeError) as err: except (ValueError, TypeError) as err:
reply = msg.get_error(errorclass='BadValue', reply = msg.get_error(
errorinfo=[repr(err), str(msg)]) errorclass='BadValue',
errorinfo=[repr(err), str(msg)])
except Exception as err: except Exception as err:
self.log.exception(err) self.log.exception(err)
reply = msg.get_error(errorclass='InternalError', reply = msg.get_error(
errorinfo=[repr(err), str(msg)]) errorclass='InternalError',
errorinfo=[repr(err), str(msg)])
else: else:
self.log.debug('Can not handle msg %r' % msg) self.log.debug('Can not handle msg %r' % msg)
reply = self.unhandled(conn, msg) reply = self.unhandled(conn, msg)
@ -106,8 +107,8 @@ class Dispatcher(object):
listeners = self._connections listeners = self._connections
else: else:
if getattr(msg, 'command', None) is None: if getattr(msg, 'command', None) is None:
eventname = '%s:%s' % ( eventname = '%s:%s' % (msg.module, msg.parameter
msg.module, msg.parameter if msg.parameter else 'value') if msg.parameter else 'value')
else: else:
eventname = '%s:%s()' % (msg.module, msg.command) eventname = '%s:%s()' % (msg.module, msg.command)
listeners = self._subscriptions.get(eventname, []) listeners = self._subscriptions.get(eventname, [])
@ -198,8 +199,7 @@ class Dispatcher(object):
res = {} res = {}
for cmdname, cmdobj in self.get_module(modulename).CMDS.items(): for cmdname, cmdobj in self.get_module(modulename).CMDS.items():
res[cmdname] = cmdobj.as_dict() res[cmdname] = cmdobj.as_dict()
self.log.debug('list cmds for module %s -> %r' % self.log.debug('list cmds for module %s -> %r' % (modulename, res))
(modulename, res))
return res return res
self.log.debug('-> module is not to be exported!') self.log.debug('-> module is not to be exported!')
return {} return {}
@ -210,12 +210,13 @@ class Dispatcher(object):
for modulename in self._export: for modulename in self._export:
module = self.get_module(modulename) module = self.get_module(modulename)
# some of these need rework ! # some of these need rework !
dd = {'class': module.__class__.__name__, dd = {
'bases': [b.__name__ for b in module.__class__.__bases__], 'class': module.__class__.__name__,
'parameters': self.list_module_params(modulename), 'bases': [b.__name__ for b in module.__class__.__bases__],
'commands': self.list_module_cmds(modulename), 'parameters': self.list_module_params(modulename),
'baseclass': 'Readable', 'commands': self.list_module_cmds(modulename),
} 'interfaceclass': 'Readable',
}
result['modules'][modulename] = dd result['modules'][modulename] = dd
result['equipment_id'] = self.equipment_id result['equipment_id'] = self.equipment_id
result['firmware'] = 'The SECoP playground' result['firmware'] = 'The SECoP playground'
@ -244,8 +245,11 @@ class Dispatcher(object):
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
func = getattr(moduleobj, 'do' + command) func = getattr(moduleobj, 'do' + command)
res = func(*arguments) res = func(*arguments)
res = CommandReply(module=modulename, command=command, res = CommandReply(
result=res, qualifiers=dict(t=time.time())) module=modulename,
command=command,
result=res,
qualifiers=dict(t=time.time()))
# res = Value(modulename, command=command, value=func(*arguments), t=time.time()) # res = Value(modulename, command=command, value=func(*arguments), t=time.time())
return res return res
@ -268,15 +272,11 @@ class Dispatcher(object):
setattr(moduleobj, pname, value) setattr(moduleobj, pname, value)
if pobj.timestamp: if pobj.timestamp:
return WriteReply( return WriteReply(
module=modulename, parameter=pname, value=[ module=modulename,
pobj.value, dict( parameter=pname,
t=format_time(pobj.timestamp))]) value=[pobj.value, dict(t=format_time(pobj.timestamp))])
return WriteReply( return WriteReply(
module=modulename, module=modulename, parameter=pname, value=[pobj.value, {}])
parameter=pname,
value=[
pobj.value,
{}])
def _getParamValue(self, modulename, pname): def _getParamValue(self, modulename, pname):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
@ -370,9 +370,12 @@ class Dispatcher(object):
except SECOPError as e: except SECOPError as e:
self.log.error('decide what to do here!') self.log.error('decide what to do here!')
self.log.exception(e) self.log.exception(e)
res = Value(module=modulename, parameter=pname, res = Value(
value=pobj.value, t=pobj.timestamp, module=modulename,
unit=pobj.unit) parameter=pname,
value=pobj.value,
t=pobj.timestamp,
unit=pobj.unit)
if res.value != Ellipsis: # means we do not have a value at all so skip this if res.value != Ellipsis: # means we do not have a value at all so skip this
self.broadcast_event(res) self.broadcast_event(res)
conn.queue_async_reply(ActivateReply(**msg.as_dict())) conn.queue_async_reply(ActivateReply(**msg.as_dict()))
@ -392,5 +395,5 @@ class Dispatcher(object):
(no handle_<messagename> method was defined) (no handle_<messagename> method was defined)
""" """
self.log.error('IGN: got unhandled request %s' % msg) self.log.error('IGN: got unhandled request %s' % msg)
return msg.get_error(errorclass="InternalError", return msg.get_error(
errorinfo="Unhandled Request") errorclass="InternalError", errorinfo="Unhandled Request")

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -39,6 +38,7 @@ class MessageEncoder(object):
"""decodes the given frame to a message object""" """decodes the given frame to a message object"""
raise NotImplemented raise NotImplemented
from demo_v2 import DemoEncoder as DemoEncoderV2 from demo_v2 import DemoEncoder as DemoEncoderV2
from demo_v3 import DemoEncoder as DemoEncoderV3 from demo_v3 import DemoEncoder as DemoEncoderV3
from demo_v4 import DemoEncoder as DemoEncoderV4 from demo_v4 import DemoEncoder as DemoEncoderV4

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -32,11 +31,11 @@ from secop.lib.parsing import *
import re import re
DEMO_RE = re.compile( DEMO_RE = re.compile(
r'^([!+-])?(\*|[a-z_][a-z_0-9]*)?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\=(.*))?') r'^([!+-])?(\*|[a-z_][a-z_0-9]*)?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\=(.*))?'
)
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
def decode(sef, encoded): def decode(sef, encoded):
# match [!][*|devicename][: *|paramname [: *|propname]] [=value] # match [!][*|devicename][: *|paramname [: *|propname]] [=value]
match = DEMO_RE.match(encoded) match = DEMO_RE.match(encoded)
@ -46,8 +45,8 @@ class DemoEncoder(MessageEncoder):
print "parsing", assign, print "parsing", assign,
assign = parse_args(assign) assign = parse_args(assign)
print "->", assign print "->", assign
return messages.DemoRequest( return messages.DemoRequest(novalue, devname, pname, propname,
novalue, devname, pname, propname, assign) assign)
return messages.HelpRequest() return messages.HelpRequest()
def encode(self, msg): def encode(self, msg):
@ -66,8 +65,13 @@ class DemoEncoder(MessageEncoder):
return '~InternalError~' return '~InternalError~'
return result return result
def _encode_AsyncDataUnit(self, devname, pname, value, timestamp, def _encode_AsyncDataUnit(self,
error=None, unit=''): devname,
pname,
value,
timestamp,
error=None,
unit=''):
return '#%s:%s=%s;t=%.3f' % (devname, pname, value, timestamp) return '#%s:%s=%s;t=%.3f' % (devname, pname, value, timestamp)
def _encode_Error(self, error): def _encode_Error(self, error):
@ -98,5 +102,7 @@ class DemoEncoder(MessageEncoder):
return '~InvalidValueForParamError~ %s:%s=%r' % (device, param, value) return '~InvalidValueForParamError~ %s:%s=%r' % (device, param, value)
def _encode_HelpReply(self): def _encode_HelpReply(self):
return ['Help not yet implemented!', return [
'ask Markus Zolliker about the protocol'] 'Help not yet implemented!',
'ask Markus Zolliker about the protocol'
]

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -93,7 +92,6 @@ DEMO_RE_OTHER = re.compile(
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
MessageEncoder.__init__(self, *args, **kwds) MessageEncoder.__init__(self, *args, **kwds)
self.result = [] # for decoding self.result = [] # for decoding
@ -112,8 +110,8 @@ class DemoEncoder(MessageEncoder):
r.append("help ... more to come") r.append("help ... more to come")
return '\n'.join(r) return '\n'.join(r)
if isinstance(msg, (ListMessage, SubscribeMessage, if isinstance(msg, (ListMessage, SubscribeMessage, UnsubscribeMessage,
UnsubscribeMessage, TriggerMessage)): TriggerMessage)):
msgtype = msg.MSGTYPE msgtype = msg.MSGTYPE
if msg.result: if msg.result:
if msg.devs: if msg.devs:
@ -145,9 +143,7 @@ class DemoEncoder(MessageEncoder):
# encode 1..N replies # encode 1..N replies
result.append( result.append(
encode_value( encode_value(
val, val, 'write', targetvalue=msg.target))
'write',
targetvalue=msg.target))
if not msg.result: if not msg.result:
# encode a request (no results -> reply, else an error would # encode a request (no results -> reply, else an error would
# have been sent) # have been sent)
@ -164,28 +160,17 @@ class DemoEncoder(MessageEncoder):
encode_value( encode_value(
val, val,
'command', 'command',
cmd='%s(%s)' % cmd='%s(%s)' % (msg.cmd, ','.join(msg.args))))
(msg.cmd,
','.join(
msg.args))))
if not msg.result: if not msg.result:
# encode a request (no results -> reply, else an error would # encode a request (no results -> reply, else an error would
# have been sent) # have been sent)
result.append( result.append('%s:%s(%s)' % (devspec(msg, 'command'), msg.cmd,
'%s:%s(%s)' % ','.join(msg.args)))
(devspec(
msg, 'command'), msg.cmd, ','.join(
msg.args)))
return '\n'.join(result) return '\n'.join(result)
if isinstance(msg, ErrorMessage): if isinstance(msg, ErrorMessage):
return ( return ('%s %s' % (devspec(msg, 'error %s' % msg.errortype),
'%s %s' % msg.errorstring)).strip()
(devspec(
msg,
'error %s' %
msg.errortype),
msg.errorstring)).strip()
return 'Can not handle object %r!' % msg return 'Can not handle object %r!' % msg
@ -246,6 +231,7 @@ class DemoEncoder(MessageEncoder):
if sep in stuff: if sep in stuff:
return stuff.split(sep) return stuff.split(sep)
return [stuff] return [stuff]
devs = helper(mgroups.pop('devs')) devs = helper(mgroups.pop('devs'))
pars = helper(mgroups.pop('pars')) pars = helper(mgroups.pop('pars'))
props = helper(mgroups.pop('props')) props = helper(mgroups.pop('props'))
@ -272,12 +258,8 @@ class DemoEncoder(MessageEncoder):
# reformat qualifiers # reformat qualifiers
print mgroups print mgroups
quals = dict( quals = dict(
qual.split( qual.split('=', 1)
'=', for qual in helper(mgroups.pop('qualifiers', ';')))
1) for qual in helper(
mgroups.pop(
'qualifiers',
';')))
# reformat value # reformat value
result = [] result = []
@ -296,48 +278,49 @@ class DemoEncoder(MessageEncoder):
# construct messageobj # construct messageobj
if msgtype in MESSAGE: if msgtype in MESSAGE:
return MESSAGE[msgtype]( return MESSAGE[msgtype](devs=devs,
devs=devs, pars=pars,
pars=pars, props=props,
props=props, result=result,
result=result, **mgroups)
**mgroups)
return ErrorMessage(errortype="SyntaxError", return ErrorMessage(
errorstring="Can't handle %r" % encoded) errortype="SyntaxError", errorstring="Can't handle %r" % encoded)
def tests(self): def tests(self):
testmsg = ['list', testmsg = [
'list *', 'list',
'list device', 'list *',
'list device:param1,param2', 'list device',
'list *:*', 'list device:param1,param2',
'list *=ts,tcoil,mf,lhe,ln2;', 'list *:*',
'read blub=12;t=3', 'list *=ts,tcoil,mf,lhe,ln2;',
'command ts:stop()', 'read blub=12;t=3',
'command mf:quench(1,"now")', 'command ts:stop()',
'error GibbetNich query x:y:z=9 "blubub blah"', 'command mf:quench(1,"now")',
'#3', 'error GibbetNich query x:y:z=9 "blubub blah"',
'read blub:a=12;t=3', '#3',
'read blub:b=13;t=3.1', 'read blub:a=12;t=3',
'read blub:c=14;t=3.3', 'read blub:b=13;t=3.1',
] 'read blub:c=14;t=3.3',
]
for m in testmsg: for m in testmsg:
print repr(m) print repr(m)
print self.decode(m) print self.decode(m)
print print
DEMO_RE_MZ = re.compile(r"""^(?P<type>[a-z]+)? # request type word (read/write/list/...) DEMO_RE_MZ = re.compile(
r"""^(?P<type>[a-z]+)? # request type word (read/write/list/...)
\ ? # optional space \ ? # optional space
(?P<device>[a-z][a-z0-9_]*)? # optional devicename (?P<device>[a-z][a-z0-9_]*)? # optional devicename
(?:\:(?P<param>[a-z0-9_]*) # optional ':'+paramname (?:\:(?P<param>[a-z0-9_]*) # optional ':'+paramname
(?:\:(?P<prop>[a-z0-9_]*))?)? # optinal ':' + propname (?:\:(?P<prop>[a-z0-9_]*))?)? # optinal ':' + propname
(?:(?P<op>[=\?])(?P<value>[^;]+)(?:\;(?P<quals>.*))?)?$""", re.X) (?:(?P<op>[=\?])(?P<value>[^;]+)(?:\;(?P<quals>.*))?)?$""",
re.X)
class DemoEncoder_MZ(MessageEncoder): class DemoEncoder_MZ(MessageEncoder):
def decode(sef, encoded): def decode(sef, encoded):
m = DEMO_RE_MZ.match(encoded) m = DEMO_RE_MZ.match(encoded)
if m: if m:

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -47,7 +46,8 @@ DEMO_RE = re.compile(
IDENTREQUEST = '*IDN?' # literal IDENTREQUEST = '*IDN?' # literal
# literal! first part is fixed! # literal! first part is fixed!
#IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1' #IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1' #IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2017-01-25,rc1'
DESCRIPTIONSREQUEST = 'describe' # literal DESCRIPTIONSREQUEST = 'describe' # literal
DESCRIPTIONREPLY = 'describing' # +<id> +json DESCRIPTIONREPLY = 'describing' # +<id> +json
ENABLEEVENTSREQUEST = 'activate' # literal ENABLEEVENTSREQUEST = 'activate' # literal
@ -69,10 +69,22 @@ HEARTBEATREPLY = 'pong' # +nonce_without_space
ERRORREPLY = 'error' # +errorclass +json_extended_info ERRORREPLY = 'error' # +errorclass +json_extended_info
HELPREQUEST = 'help' # literal HELPREQUEST = 'help' # literal
HELPREPLY = 'helping' # +line number +json_text HELPREPLY = 'helping' # +line number +json_text
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand', ERRORCLASSES = [
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed', 'NoSuchDevice',
'IsBusy', 'IsError', 'ProtocolError', 'InternalError', 'NoSuchParameter',
'CommandRunning', 'Disabled', ] '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 # note: above strings need to be unique in the sense, that none is/or
# starts with another # starts with another
@ -93,33 +105,61 @@ def encode_value_data(vobj):
def encode_error_msg(emsg): def encode_error_msg(emsg):
# note: result is JSON-ified.... # note: result is JSON-ified....
return [emsg.origin, dict((k, getattr(emsg, k)) return [
for k in emsg.ARGS if k != 'origin')] emsg.origin, dict((k, getattr(emsg, k)) for k in emsg.ARGS
if k != 'origin')
]
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
# map of msg to msgtype string as defined above. # map of msg to msgtype string as defined above.
ENCODEMAP = { ENCODEMAP = {
IdentifyRequest: (IDENTREQUEST,), IdentifyRequest: (IDENTREQUEST, ),
IdentifyReply: (IDENTREPLY,), IdentifyReply: (IDENTREPLY, ),
DescribeRequest: (DESCRIPTIONSREQUEST,), DescribeRequest: (DESCRIPTIONSREQUEST, ),
DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',), DescribeReply: (
ActivateRequest: (ENABLEEVENTSREQUEST,), DESCRIPTIONREPLY,
ActivateReply: (ENABLEEVENTSREPLY,), 'equipment_id',
DeactivateRequest: (DISABLEEVENTSREQUEST,), 'description', ),
DeactivateReply: (DISABLEEVENTSREPLY,), ActivateRequest: (ENABLEEVENTSREQUEST, ),
CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',), ActivateReply: (ENABLEEVENTSREPLY, ),
CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), encode_cmd_result,), DeactivateRequest: (DISABLEEVENTSREQUEST, ),
WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',), DeactivateReply: (DISABLEEVENTSREPLY, ),
WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value', ), CommandRequest: (
PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ), COMMANDREQUEST,
HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',), lambda msg: "%s:%s" % (msg.module, msg.command),
HeartbeatReply: (HEARTBEATREPLY, 'nonce',), '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, ), HelpMessage: (HELPREQUEST, ),
ErrorMessage: (ERRORREPLY, "errorclass", encode_error_msg,), ErrorMessage: (
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) ERRORREPLY,
if msg.parameter or msg.command else msg.module, "errorclass",
encode_value_data,), 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 = { DECODEMAP = {
IDENTREQUEST: lambda spec, data: IdentifyRequest(), IDENTREQUEST: lambda spec, data: IdentifyRequest(),
@ -177,8 +217,10 @@ class DemoEncoder(MessageEncoder):
for msgcls, parts in self.ENCODEMAP.items(): for msgcls, parts in self.ENCODEMAP.items():
if isinstance(msg, msgcls): if isinstance(msg, msgcls):
# resolve lambdas # resolve lambdas
parts = [parts[0]] + [p(msg) if callable(p) parts = [parts[0]] + [
else getattr(msg, p) for p in parts[1:]] p(msg) if callable(p) else getattr(msg, p)
for p in parts[1:]
]
if len(parts) > 1: if len(parts) > 1:
parts[1] = str(parts[1]) parts[1] = str(parts[1])
if len(parts) == 3: if len(parts) == 3:
@ -199,10 +241,11 @@ class DemoEncoder(MessageEncoder):
# is_request=True) # is_request=True)
msgtype, msgspec, data = match.groups() msgtype, msgspec, data = match.groups()
if msgspec is None and data: if msgspec is None and data:
return ErrorMessage(errorclass='Internal', return ErrorMessage(
errorinfo='Regex matched json, but not spec!', errorclass='Internal',
is_request=True, errorinfo='Regex matched json, but not spec!',
origin=encoded) is_request=True,
origin=encoded)
if msgtype in self.DECODEMAP: if msgtype in self.DECODEMAP:
if msgspec and ':' in msgspec: if msgspec and ':' in msgspec:
@ -213,16 +256,16 @@ class DemoEncoder(MessageEncoder):
try: try:
data = json.loads(data) data = json.loads(data)
except ValueError as err: except ValueError as err:
return ErrorMessage(errorclass='BadValue', return ErrorMessage(
errorinfo=[repr(err), str(encoded)], errorclass='BadValue',
origin=encoded) errorinfo=[repr(err), str(encoded)],
origin=encoded)
msg = self.DECODEMAP[msgtype](msgspec, data) msg = self.DECODEMAP[msgtype](msgspec, data)
msg.setvalue("origin", encoded) msg.setvalue("origin", encoded)
return msg return msg
return ErrorMessage( return ErrorMessage(
errorclass='Protocol', errorclass='Protocol',
errorinfo='%r: No Such Messagetype defined!' % errorinfo='%r: No Such Messagetype defined!' % encoded,
encoded,
is_request=True, is_request=True,
origin=encoded) origin=encoded)

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -36,7 +35,6 @@ except ImportError:
class PickleEncoder(MessageEncoder): class PickleEncoder(MessageEncoder):
def encode(self, messageobj): def encode(self, messageobj):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
return pickle.dumps(messageobj) return pickle.dumps(messageobj)

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -32,13 +31,12 @@ from secop.lib.parsing import *
import re import re
import ast import ast
SCPMESSAGE = re.compile( SCPMESSAGE = re.compile(
r'^(?:(?P<errorcode>[0-9@])\ )?(?P<device>[a-zA-Z0-9_\*]*)(?:/(?P<param>[a-zA-Z0-9_\*]*))+(?P<op>[-+=\?\ ])?(?P<value>.*)') r'^(?:(?P<errorcode>[0-9@])\ )?(?P<device>[a-zA-Z0-9_\*]*)(?:/(?P<param>[a-zA-Z0-9_\*]*))+(?P<op>[-+=\?\ ])?(?P<value>.*)'
)
class SCPEncoder(MessageEncoder): class SCPEncoder(MessageEncoder):
def encode(self, msg): def encode(self, msg):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
# fun for Humans # fun for Humans
@ -48,20 +46,24 @@ class SCPEncoder(MessageEncoder):
r.append("'/version?' to query the current version") r.append("'/version?' to query the current version")
r.append("'/modules?' to query the list of modules") r.append("'/modules?' to query the list of modules")
r.append( r.append(
"'<module>/parameters?' to query the list of params of a module") "'<module>/parameters?' to query the list of params of a module"
)
r.append("'<module>/value?' to query the value of a module") r.append("'<module>/value?' to query the value of a module")
r.append("'<module>/status?' to query the status of a module") r.append("'<module>/status?' to query the status of a module")
r.append("'<module>/target=<new_value>' to move a module") r.append("'<module>/target=<new_value>' to move a module")
r.append("replies copy the request and are prefixed with an errorcode:")
r.append( r.append(
"0=OK,3=NoSuchCommand,4=NosuchDevice,5=NoSuchParam,6=SyntaxError,7=BadValue,8=Readonly,9=Forbidden,@=Async") "replies copy the request and are prefixed with an errorcode:")
r.append(
"0=OK,3=NoSuchCommand,4=NosuchDevice,5=NoSuchParam,6=SyntaxError,7=BadValue,8=Readonly,9=Forbidden,@=Async"
)
r.append("extensions: @-prefix as error-code,") r.append("extensions: @-prefix as error-code,")
r.append("'<module>/+' subscribe all params of module") r.append("'<module>/+' subscribe all params of module")
r.append("'<module>/<param>+' subscribe a param of a module") r.append("'<module>/<param>+' subscribe a param of a module")
r.append("use '-' instead of '+' to unsubscribe") r.append("use '-' instead of '+' to unsubscribe")
r.append("'<module>/commands?' list of commands") r.append("'<module>/commands?' list of commands")
r.append( r.append(
"'<module>/<command>@[possible args] execute command (ex. 'stop@')") "'<module>/<command>@[possible args] execute command (ex. 'stop@')"
)
return '\n'.join(r) return '\n'.join(r)
return { return {
@ -117,7 +119,7 @@ class SCPEncoder(MessageEncoder):
def decode(self, encoded): def decode(self, encoded):
"""transport layer message -> msg object""" """transport layer message -> msg object"""
match = SCPMESSAGE.match(encoded) match = SCPMESSAGE.match(encoded)
if not(match): if not (match):
return HelpRequest() return HelpRequest()
err, dev, par, op, val = match.groups() err, dev, par, op, val = match.groups()
if val is not None: if val is not None:

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Messages""" """Encoding/decoding Messages"""
# implement as class as they may need some internal 'state' later on # implement as class as they may need some internal 'state' later on
@ -31,7 +30,6 @@ from secop.lib.parsing import *
class TextEncoder(MessageEncoder): class TextEncoder(MessageEncoder):
def __init__(self): def __init__(self):
# build safe namespace # build safe namespace
ns = dict() ns = dict()

View File

@ -19,12 +19,10 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define (internal) SECoP Errors""" """Define (internal) SECoP Errors"""
class SECOPError(RuntimeError): class SECOPError(RuntimeError):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
self.args = args self.args = args
for k, v in kwds.items(): for k, v in kwds.items():
@ -91,8 +89,7 @@ EXCEPTIONS = dict(
BadValue=BadValueError, BadValue=BadValueError,
Readonly=ReadonlyError, Readonly=ReadonlyError,
CommandFailed=CommandFailedError, CommandFailed=CommandFailedError,
InvalidParam=InvalidParamValueError, InvalidParam=InvalidParamValueError, )
)
if __name__ == '__main__': if __name__ == '__main__':
print("Minimal testing of errors....") print("Minimal testing of errors....")

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Frames""" """Encoding/decoding Frames"""
@ -45,7 +44,6 @@ class Framer(object):
"""resets the de/encoding stage (clears internal information)""" """resets the de/encoding stage (clears internal information)"""
raise NotImplemented raise NotImplemented
# now some Implementations # now some Implementations
from null import NullFramer from null import NullFramer
from eol import EOLFramer from eol import EOLFramer

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Frames""" """Encoding/decoding Frames"""
from secop.protocol.framing import Framer from secop.protocol.framing import Framer

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Frames""" """Encoding/decoding Frames"""
from secop.protocol.framing import Framer from secop.protocol.framing import Framer

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Frames""" """Encoding/decoding Frames"""
from secop.protocol.framing import Framer from secop.protocol.framing import Framer

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Encoding/decoding Frames""" """Encoding/decoding Frames"""
from secop.protocol.framing import Framer from secop.protocol.framing import Framer

View File

@ -23,9 +23,7 @@
from tcp import TCPServer from tcp import TCPServer
INTERFACES = { INTERFACES = {'tcp': TCPServer, }
'tcp': TCPServer,
}
# for 'from protocol.interface import *' to only import the dict # for 'from protocol.interface import *' to only import the dict
__ALL__ = ['INTERFACES'] __ALL__ = ['INTERFACES']

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""provides tcp interface to the SECoP Server""" """provides tcp interface to the SECoP Server"""
import os import os
@ -36,7 +35,6 @@ from secop.protocol.messages import HelpMessage
class TCPRequestHandler(SocketServer.BaseRequestHandler): class TCPRequestHandler(SocketServer.BaseRequestHandler):
def setup(self): def setup(self):
self.log = self.server.log self.log = self.server.log
self._queue = collections.deque(maxlen=100) self._queue = collections.deque(maxlen=100)
@ -49,13 +47,13 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
mysocket = self.request mysocket = self.request
clientaddr = self.client_address clientaddr = self.client_address
serverobj = self.server serverobj = self.server
self.log.debug("handling new connection from %s" % repr(clientaddr)) self.log.info("handling new connection from %s:%d" % clientaddr)
# notify dispatcher of us # notify dispatcher of us
serverobj.dispatcher.add_connection(self) serverobj.dispatcher.add_connection(self)
mysocket.settimeout(.3) mysocket.settimeout(.3)
# mysocket.setblocking(False) # mysocket.setblocking(False)
# start serving # start serving
while True: while True:
# send replys fist, then listen for requests, timing out after 0.1s # send replys fist, then listen for requests, timing out after 0.1s
@ -66,7 +64,10 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
outmsg = self._queue.popleft() outmsg = self._queue.popleft()
outframes = self.encoding.encode(outmsg) outframes = self.encoding.encode(outmsg)
outdata = self.framing.encode(outframes) outdata = self.framing.encode(outframes)
mysocket.sendall(outdata) try:
mysocket.sendall(outdata)
except Exception:
return
# XXX: improve: use polling/select here? # XXX: improve: use polling/select here?
try: try:
@ -101,6 +102,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
def finish(self): def finish(self):
"""called when handle() terminates, i.e. the socket closed""" """called when handle() terminates, i.e. the socket closed"""
self.log.info('closing connection from %s:%d' % self.client_address)
# notify dispatcher # notify dispatcher
self.server.dispatcher.remove_connection(self) self.server.dispatcher.remove_connection(self)
# close socket # close socket
@ -132,7 +134,6 @@ class TCPServer(SocketServer.ThreadingTCPServer):
self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__) self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
self.log.debug("TCPServer using encoding=%s" % self.log.debug("TCPServer using encoding=%s" %
self.encodingCLS.__name__) self.encodingCLS.__name__)
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum), SocketServer.ThreadingTCPServer.__init__(
TCPRequestHandler, self, (bindto, portnum), TCPRequestHandler, bind_and_activate=True)
bind_and_activate=True)
self.log.info("TCPServer initiated") self.log.info("TCPServer initiated")

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define SECoP Messages""" """Define SECoP Messages"""
@ -51,8 +50,11 @@ class Message(object):
class Value(object): class Value(object):
def __init__(self,
def __init__(self, module, parameter=None, command=None, value=Ellipsis, module,
parameter=None,
command=None,
value=Ellipsis,
**qualifiers): **qualifiers):
self.module = module self.module = module
self.parameter = parameter self.parameter = parameter
@ -67,9 +69,10 @@ class Value(object):
devspec = '%s:%s' % (devspec, self.parameter) devspec = '%s:%s' % (devspec, self.parameter)
elif self.command: elif self.command:
devspec = '%s:%s()' % (devspec, self.command) devspec = '%s:%s()' % (devspec, self.command)
return '%s:Value(%s)' % (devspec, ', '.join( return '%s:Value(%s)' % (devspec, ', '.join([repr(self.value)] + [
[repr(self.value)] + '%s=%s' % (k, format_time(v) if k == "timestamp" else repr(v))
['%s=%s' % (k, format_time(v) if k == "timestamp" else repr(v)) for k, v in self.qualifiers.items()])) for k, v in self.qualifiers.items()
]))
class Request(Message): class Request(Message):
@ -95,8 +98,7 @@ class Request(Message):
for k in self.ARGS: for k in self.ARGS:
m.setvalue(k, self.__dict__[k]) m.setvalue(k, self.__dict__[k])
m.setvalue("errorclass", errorclass[:-5] m.setvalue("errorclass", errorclass[:-5]
if errorclass.endswith('rror') if errorclass.endswith('rror') else errorclass)
else errorclass)
m.setvalue("errorinfo", errorinfo) m.setvalue("errorinfo", errorinfo)
return m return m

View File

@ -19,7 +19,6 @@
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define SECoP Messages""" """Define SECoP Messages"""
# Request Types # Request Types
@ -79,8 +78,9 @@ class Message(object):
r = 'Device' if self.devs != ['*'] else 'Devices' r = 'Device' if self.devs != ['*'] else 'Devices'
t = '' t = ''
if self.MSGTYPE in [LIST, READ, WRITE, COMMAND, if self.MSGTYPE in [
POLL, SUBSCRIBE, UNSUBSCRIBE, HELP]: LIST, READ, WRITE, COMMAND, POLL, SUBSCRIBE, UNSUBSCRIBE, HELP
]:
t = 'Request' if not self.result else 'Reply' t = 'Request' if not self.result else 'Reply'
if self.errortype is None: if self.errortype is None:
@ -95,7 +95,6 @@ class Message(object):
class Value(object): class Value(object):
def __init__(self, value=Ellipsis, qualifiers=None, **kwds): def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
self.dev = '' self.dev = ''
self.param = '' self.param = ''
@ -111,9 +110,9 @@ class Value(object):
if self.prop: if self.prop:
devspec = '%s:%s' % (devspec, self.prop) devspec = '%s:%s' % (devspec, self.prop)
return '%s:Value(%s)' % ( return '%s:Value(%s)' % (
devspec, ', '.join( devspec,
[repr(self.value)] + ', '.join([repr(self.value)] +
['%s=%r' % (k, v) for k, v in self.qualifiers.items()])) ['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
class ListMessage(Message): class ListMessage(Message):
@ -167,28 +166,29 @@ class HelpMessage(Message):
class NoSuchDeviceError(ErrorMessage): class NoSuchDeviceError(ErrorMessage):
def __init__(self, *devs): def __init__(self, *devs):
ErrorMessage.__init__( ErrorMessage.__init__(
self, devs=devs, errorstring="Device %r does not exist" % self,
devs[0], errortype='NoSuchDevice') devs=devs,
errorstring="Device %r does not exist" % devs[0],
errortype='NoSuchDevice')
class NoSuchParamError(ErrorMessage): class NoSuchParamError(ErrorMessage):
def __init__(self, dev, *params): def __init__(self, dev, *params):
ErrorMessage.__init__( ErrorMessage.__init__(
self, devs=(dev,), self,
params=params, errorstring="Device %r has no parameter %r" % devs=(dev, ),
(dev, params[0]), params=params,
errorstring="Device %r has no parameter %r" % (dev, params[0]),
errortype='NoSuchParam') errortype='NoSuchParam')
class ParamReadonlyError(ErrorMessage): class ParamReadonlyError(ErrorMessage):
def __init__(self, dev, *params): def __init__(self, dev, *params):
ErrorMessage.__init__( ErrorMessage.__init__(
self, devs=(dev,), self,
devs=(dev, ),
params=params, params=params,
errorstring="Device %r, parameter %r is not writeable!" % errorstring="Device %r, parameter %r is not writeable!" %
(dev, params[0]), (dev, params[0]),
@ -196,30 +196,28 @@ class ParamReadonlyError(ErrorMessage):
class InvalidParamValueError(ErrorMessage): class InvalidParamValueError(ErrorMessage):
def __init__(self, dev, param, value, e): def __init__(self, dev, param, value, e):
ErrorMessage.__init__( ErrorMessage.__init__(
self, devs=(dev,), self,
params=params, values=(value), devs=(dev, ),
params=params,
values=(value),
errorstring=str(e), errorstring=str(e),
errortype='InvalidParamValueError') errortype='InvalidParamValueError')
class InternalError(ErrorMessage): class InternalError(ErrorMessage):
def __init__(self, err, **kwds): def __init__(self, err, **kwds):
ErrorMessage.__init__( ErrorMessage.__init__(
self, errorstring=str(err), self, errorstring=str(err), errortype='InternalError', **kwds)
errortype='InternalError', **kwds)
MESSAGE = dict( MESSAGE = dict((cls.MSGTYPE, cls)
(cls.MSGTYPE, cls) for cls in [
for cls HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
in UnsubscribeMessage, SubscribeMessage, PollMessage,
[HelpMessage, ErrorMessage, EventMessage, TriggerMessage, CommandMessage, WriteMessage, ReadMessage, ListMessage
UnsubscribeMessage, SubscribeMessage, PollMessage, CommandMessage, ])
WriteMessage, ReadMessage, ListMessage])
if __name__ == '__main__': if __name__ == '__main__':
print("Minimal testing of messages....") print("Minimal testing of messages....")

View File

@ -23,9 +23,9 @@
# could also be some objects # could also be some objects
OK = 100 OK = 100
BUSY = 200 WARN = 200
WARN = 300 UNSTABLE = 250
UNSTABLE = 350 BUSY = 300
ERROR = 400 ERROR = 400
UNKNOWN = -1 UNKNOWN = -1

View File

@ -20,7 +20,6 @@
# Alexander Lenz <alexander.lenz@frm2.tum.de> # Alexander Lenz <alexander.lenz@frm2.tum.de>
# #
# ***************************************************************************** # *****************************************************************************
"""Define helpers""" """Define helpers"""
import os import os
import time import time
@ -41,7 +40,6 @@ from secop.errors import ConfigError
class Server(object): class Server(object):
def __init__(self, name, workdir, parentLogger=None): def __init__(self, name, workdir, parentLogger=None):
self._name = name self._name = name
self._workdir = workdir self._workdir = workdir
@ -65,9 +63,10 @@ class Server(object):
if pidfile.is_locked(): if pidfile.is_locked():
self.log.error('Pidfile already exists. Exiting') self.log.error('Pidfile already exists. Exiting')
with DaemonContext(working_directory=self._workdir, with DaemonContext(
pidfile=pidfile, working_directory=self._workdir,
files_preserve=self.log.getLogfileStreams()): pidfile=pidfile,
files_preserve=self.log.getLogfileStreams()):
self.run() self.run()
def run(self): def run(self):
@ -85,9 +84,8 @@ class Server(object):
time.sleep(1) time.sleep(1)
for t in self._threads: for t in self._threads:
if not t.is_alive(): if not t.is_alive():
self.log.debug( self.log.debug('thread %r died (%d still running)' %
'thread %r died (%d still running)' % (t, len(self._threads)))
(t, len(self._threads)))
t.join() t.join()
self._threads.discard(t) self._threads.discard(t)
@ -95,9 +93,11 @@ class Server(object):
self.log.debug('Parse config file %s ...' % self._cfgfile) self.log.debug('Parse config file %s ...' % self._cfgfile)
parser = ConfigParser.SafeConfigParser() parser = ConfigParser.SafeConfigParser()
parser.optionxform = str
if not parser.read([self._cfgfile]): if not parser.read([self._cfgfile]):
self.log.error('Couldn\'t read cfg file !') self.log.error("Couldn't read cfg file !")
raise ConfigError('Couldn\'t read cfg file %r' % self._cfgfile) raise ConfigError("Couldn't read cfg file %r" % self._cfgfile)
self._interfaces = [] self._interfaces = []
@ -113,8 +113,8 @@ class Server(object):
if 'class' not in devopts: if 'class' not in devopts:
self.log.error('Device %s needs a class option!') self.log.error('Device %s needs a class option!')
raise ConfigError( raise ConfigError(
'cfgfile %r: Device %s needs a class option!' 'cfgfile %r: Device %s needs a class option!' %
% (self._cfgfile, devname)) (self._cfgfile, devname))
# try to import the class, raise if this fails # try to import the class, raise if this fails
devopts['class'] = get_class(devopts['class']) devopts['class'] = get_class(devopts['class'])
# all went well so far # all went well so far
@ -127,16 +127,15 @@ class Server(object):
if 'interface' not in ifopts: if 'interface' not in ifopts:
self.log.error('Interface %s needs an interface option!') self.log.error('Interface %s needs an interface option!')
raise ConfigError( raise ConfigError(
'cfgfile %r: Interface %s needs an interface option!' 'cfgfile %r: Interface %s needs an interface option!' %
% (self._cfgfile, ifname)) (self._cfgfile, ifname))
# all went well so far # all went well so far
interfaceopts.append([ifname, ifopts]) interfaceopts.append([ifname, ifopts])
if parser.has_option('equipment', 'id'): if parser.has_option('equipment', 'id'):
equipment_id = parser.get('equipment', 'id') equipment_id = parser.get('equipment', 'id').replace(' ', '_')
self._dispatcher = self._buildObject( self._dispatcher = self._buildObject(
'Dispatcher', Dispatcher, dict( 'Dispatcher', Dispatcher, dict(equipment_id=equipment_id))
equipment_id=equipment_id))
self._processInterfaceOptions(interfaceopts) self._processInterfaceOptions(interfaceopts)
self._processDeviceOptions(deviceopts) self._processDeviceOptions(deviceopts)
@ -156,8 +155,8 @@ class Server(object):
for d in ("'", '"'): for d in ("'", '"'):
if v.startswith(d) and v.endswith(d): if v.startswith(d) and v.endswith(d):
devopts[k] = v[1:-1] devopts[k] = v[1:-1]
devobj = devclass(self.log.getChild(devname), devopts, devname, devobj = devclass(
self._dispatcher) self.log.getChild(devname), devopts, devname, self._dispatcher)
devs.append([devname, devobj, export]) devs.append([devname, devobj, export])
# connect devices with dispatcher # connect devices with dispatcher
@ -178,8 +177,8 @@ class Server(object):
for ifname, ifopts in interfaceopts: for ifname, ifopts in interfaceopts:
ifclass = ifopts.pop('interface') ifclass = ifopts.pop('interface')
ifclass = INTERFACES[ifclass] ifclass = INTERFACES[ifclass]
interface = self._buildObject(ifname, ifclass, interface = self._buildObject(ifname, ifclass, ifopts,
ifopts, self._dispatcher) self._dispatcher)
self._interfaces.append(interface) self._interfaces.append(interface)
def _buildObject(self, name, cls, options, *args): def _buildObject(self, name, cls, options, *args):
@ -187,7 +186,6 @@ class Server(object):
# cls.__init__ should pop all used args from options! # cls.__init__ should pop all used args from options!
obj = cls(self.log.getChild(name.lower()), options, *args) obj = cls(self.log.getChild(name.lower()), options, *args)
if options: if options:
raise ConfigError('%s: don\'t know how to handle option(s): %s' % ( raise ConfigError('%s: don\'t know how to handle option(s): %s' %
cls.__name__, (cls.__name__, ', '.join(options.keys())))
', '.join(options.keys())))
return obj return obj

View File

@ -21,7 +21,6 @@
# ***************************************************************************** # *****************************************************************************
"""Define validators.""" """Define validators."""
# a Validator returns a validated object or raises an ValueError # a Validator returns a validated object or raises an ValueError
# easy python validators: int(), float(), str() # easy python validators: int(), float(), str()
# also validators should have a __repr__ returning a 'python' string # also validators should have a __repr__ returning a 'python' string
@ -43,29 +42,25 @@ class Validator(object):
plist = self.params[:] plist = self.params[:]
if len(args) > len(plist): if len(args) > len(plist):
raise ProgrammingError('%s takes %d parameters only (%d given)' % ( raise ProgrammingError('%s takes %d parameters only (%d given)' % (
self.__class__.__name__, self.__class__.__name__, len(plist), len(args)))
len(plist), len(args)))
for pval in args: for pval in args:
pname, pconv = plist.pop(0) pname, pconv = plist.pop(0)
if pname in kwds: if pname in kwds:
raise ProgrammingError('%s: positional parameter %s is given ' raise ProgrammingError('%s: positional parameter %s is given '
'as keyword!' % ( 'as keyword!' %
self.__class__.__name__, (self.__class__.__name__, pname))
pname))
self.__dict__[pname] = pconv(pval) self.__dict__[pname] = pconv(pval)
for pname, pconv in plist: for pname, pconv in plist:
if pname in kwds: if pname in kwds:
pval = kwds.pop(pname) pval = kwds.pop(pname)
self.__dict__[pname] = pconv(pval) self.__dict__[pname] = pconv(pval)
else: else:
raise ProgrammingError('%s: param %s left unspecified!' % ( raise ProgrammingError('%s: param %s left unspecified!' %
self.__class__.__name__, (self.__class__.__name__, pname))
pname))
if kwds: if kwds:
raise ProgrammingError('%s got unknown arguments: %s' % ( raise ProgrammingError('%s got unknown arguments: %s' % (
self.__class__.__name__, self.__class__.__name__, ', '.join(list(kwds.keys()))))
', '.join(list(kwds.keys()))))
params = [] params = []
for pn, pt in self.params: for pn, pt in self.params:
pv = getattr(self, pn) pv = getattr(self, pn)
@ -89,10 +84,17 @@ class floatrange(Validator):
params = [('lower', float), ('upper', float)] params = [('lower', float), ('upper', float)]
def check(self, value): def check(self, value):
if self.lower <= value <= self.upper: try:
return value value = float(value)
raise ValueError('Floatrange: value %r must be within %f and %f' % if self.lower <= value <= self.upper:
(value, self.lower, self.upper)) return value
raise ValueError(
'Floatrange: value %r must be a float within %f and %f' %
(value, self.lower, self.upper))
except TypeError:
raise ValueError(
'Floatrange: value %r must be a float within %f and %f' %
(value, self.lower, self.upper))
class intrange(Validator): class intrange(Validator):
@ -100,10 +102,11 @@ class intrange(Validator):
valuetype = int valuetype = int
def check(self, value): def check(self, value):
if self.lower <= value <= self.upper: if self.lower <= int(value) <= self.upper:
return value return value
raise ValueError('Intrange: value %r must be within %f and %f' % raise ValueError(
(value, self.lower, self.upper)) 'Intrange: value %r must be an integer within %f and %f' %
(value, self.lower, self.upper))
class array(Validator): class array(Validator):
@ -112,8 +115,7 @@ class array(Validator):
The size of the array can also be described by an validator The size of the array can also be described by an validator
""" """
valuetype = list valuetype = list
params = [('size', lambda x: x), params = [('size', lambda x: x), ('datatype', lambda x: x)]
('datatype', lambda x: x)]
def check(self, values): def check(self, values):
requested_size = len(values) requested_size = len(values)
@ -121,15 +123,13 @@ class array(Validator):
try: try:
allowed_size = self.size(requested_size) allowed_size = self.size(requested_size)
except ValueError as e: except ValueError as e:
raise ValueError( raise ValueError('illegal number of elements %d, need %r: (%s)'
'illegal number of elements %d, need %r: (%s)' % % (requested_size, self.size, e))
(requested_size, self.size, e))
else: else:
allowed_size = self.size allowed_size = self.size
if requested_size != allowed_size: if requested_size != allowed_size:
raise ValueError( raise ValueError('need %d elements (got %d)' %
'need %d elements (got %d)' % (allowed_size, requested_size))
(allowed_size, requested_size))
# apply data-type validator to all elements and return # apply data-type validator to all elements and return
res = [] res = []
for idx, el in enumerate(values): for idx, el in enumerate(values):
@ -152,10 +152,13 @@ class vector(Validator):
self.argstr = ', '.join([validator_to_str(e) for e in args]) self.argstr = ', '.join([validator_to_str(e) for e in args])
def __call__(self, args): def __call__(self, args):
if type(args) in (str, unicode):
args = eval(args)
if len(args) != len(self.validators): if len(args) != len(self.validators):
raise ValueError('Vector: need exactly %d elementes (got %d)' % raise ValueError('Vector: need exactly %d elementes (got %d)' %
len(self.validators), len(args)) len(self.validators), len(args))
return tuple(v(e) for v, e in zip(self.validators, args)) res = tuple(v(e) for v, e in zip(self.validators, args))
return res
# XXX: fixme! # XXX: fixme!
@ -197,7 +200,6 @@ class oneof(Validator):
class enum(Validator): class enum(Validator):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
self.mapping = {} self.mapping = {}
# use given kwds directly # use given kwds directly
@ -238,23 +240,27 @@ class enum(Validator):
def positive(value=Ellipsis): def positive(value=Ellipsis):
if value != Ellipsis: if value != Ellipsis:
if value > 0: if value > 0:
return value return float(value)
raise ValueError('Value %r must be > 0!' % value) raise ValueError('Value %r must be > 0!' % value)
return -1e-38 # small number > 0 return -1e-38 # small number > 0
positive.__repr__ = lambda x: validator_to_str(x) positive.__repr__ = lambda x: validator_to_str(x)
def nonnegative(value=Ellipsis): def nonnegative(value=Ellipsis):
if value != Ellipsis: if value != Ellipsis:
if value >= 0: if value >= 0:
return value return float(value)
raise ValueError('Value %r must be >= 0!' % value) raise ValueError('Value %r must be >= 0!' % value)
return 0.0 return 0.0
nonnegative.__repr__ = lambda x: validator_to_str(x) nonnegative.__repr__ = lambda x: validator_to_str(x)
# helpers # helpers
def validator_to_str(validator): def validator_to_str(validator):
if isinstance(validator, Validator): if isinstance(validator, Validator):
return validator.to_string() return validator.to_string()
@ -269,21 +275,28 @@ def validator_to_str(validator):
# XXX: better use a mapping here! # XXX: better use a mapping here!
def validator_from_str(validator_str): def validator_from_str(validator_str):
validator_str = validator_str.replace("<type 'str'>", "str")
validator_str = validator_str.replace("<type 'float'>", "float")
return eval(validator_str) return eval(validator_str)
if __name__ == '__main__': if __name__ == '__main__':
print "minimal testing: validators" print "minimal testing: validators"
for val, good, bad in [(floatrange(3.09, 5.47), 4.13, 9.27), for val, good, bad in [
(intrange(3, 5), 4, 8), (floatrange(3.09, 5.47), 4.13, 9.27),
(array(size=3, datatype=int), (1, 2, 3), (1, 2, 3, 4)), (intrange(3, 5), 4, 8),
(vector(int, int), (12, 6), (1.23, 'X')), (array(
(oneof('a', 'b', 'c', 1), 'b', 'x'), size=3, datatype=int), (1, 2, 3), (1, 2, 3, 4)),
#(record(a=int, b=float), dict(a=2,b=3.97), dict(c=9,d='X')), (vector(int, int), (12, 6), (1.23, 'X')),
(positive, 2, 0), (oneof('a', 'b', 'c', 1), 'b', 'x'),
(nonnegative, 0, -1), #(record(a=int, b=float), dict(a=2,b=3.97), dict(c=9,d='X')),
(enum(a=1, b=20), 1, 12), (positive, 2, 0),
]: (nonnegative, 0, -1),
print validator_to_str(val), repr(validator_from_str(validator_to_str(val))) (enum(
a=1, b=20), 1, 12),
]:
print validator_to_str(val), repr(
validator_from_str(validator_to_str(val)))
print val(good), 'OK' print val(good), 'OK'
try: try:
val(bad) val(bad)