polishing for a demo
+ adopting additional requests Change-Id: If5ca29b5d247f1bc429ca101b0081b1d14f6e6f1
This commit is contained in:
6
Makefile
6
Makefile
@ -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
29
etc/cryo.cfg
Normal 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
|
||||||
|
|
12
etc/demo.cfg
12
etc/demo.cfg
@ -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
|
||||||
|
13
etc/test.cfg
13
etc/test.cfg
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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 !
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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():
|
||||||
|
@ -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)
|
||||||
|
@ -25,7 +25,6 @@ from os import path
|
|||||||
|
|
||||||
from PyQt4 import uic
|
from PyQt4 import uic
|
||||||
|
|
||||||
|
|
||||||
uipath = path.dirname(__file__)
|
uipath = path.dirname(__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
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -20,10 +20,8 @@
|
|||||||
# Georg Brandl <georg@python.org>
|
# Georg Brandl <georg@python.org>
|
||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
"""Console coloring handlers."""
|
"""Console coloring handlers."""
|
||||||
|
|
||||||
|
|
||||||
_codes = {}
|
_codes = {}
|
||||||
|
|
||||||
_attrs = {
|
_attrs = {
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
]
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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....")
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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']
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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....")
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user