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"
|
||||
@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]
|
||||
id=demonstration
|
||||
id=Equipment_ID_for_demonstration
|
||||
|
||||
[interface testing]
|
||||
[interface tcp]
|
||||
interface=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
@ -11,8 +11,8 @@ encoding=demo
|
||||
|
||||
[device heatswitch]
|
||||
class=devices.demo.Switch
|
||||
switch_on_time=3
|
||||
switch_off_time=5
|
||||
switch_on_time=5
|
||||
switch_off_time=10
|
||||
|
||||
[device mf]
|
||||
class=devices.demo.MagneticField
|
||||
@ -39,5 +39,5 @@ system=Cryomagnet MX15
|
||||
subdev_mf=mf
|
||||
subdev_ts=ts
|
||||
|
||||
[device vt]
|
||||
class=devices.demo.ValidatorTest
|
||||
#[device vt]
|
||||
#class=devices.demo.ValidatorTest
|
||||
|
13
etc/test.cfg
13
etc/test.cfg
@ -1,17 +1,10 @@
|
||||
[equipment]
|
||||
id=Fancy_ID_without_spaces-like:MLZ_furnace7
|
||||
id=test config
|
||||
|
||||
[client]
|
||||
connectto=0.0.0.0
|
||||
port=10767
|
||||
interface = tcp
|
||||
framing=eol
|
||||
encoding=text
|
||||
|
||||
[interface testing]
|
||||
[interface tcp]
|
||||
interface=tcp
|
||||
bindto=0.0.0.0
|
||||
bindport=10767
|
||||
bindport=10768
|
||||
# protocol to use for this interface
|
||||
framing=eol
|
||||
encoding=demo
|
||||
|
@ -19,17 +19,14 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define Client side proxies"""
|
||||
|
||||
# nothing here yet.
|
||||
|
||||
|
||||
import code
|
||||
|
||||
|
||||
class NameSpace(dict):
|
||||
|
||||
def __init__(self):
|
||||
dict.__init__(self)
|
||||
self.__const = set()
|
||||
@ -66,7 +63,6 @@ from os import path
|
||||
|
||||
|
||||
class ClientConsole(object):
|
||||
|
||||
def __init__(self, cfgname, basepath):
|
||||
self.namespace = NameSpace()
|
||||
self.namespace.setconst('help', self.helpCmd)
|
||||
@ -89,6 +85,7 @@ class ClientConsole(object):
|
||||
else:
|
||||
help(arg)
|
||||
|
||||
|
||||
import socket
|
||||
import threading
|
||||
from collections import deque
|
||||
@ -99,7 +96,6 @@ from secop.protocol.messages import *
|
||||
|
||||
|
||||
class TCPConnection(object):
|
||||
|
||||
def __init__(self, connect, port, encoding, framing, **kwds):
|
||||
self.log = loggers.log.getChild('connection', False)
|
||||
self.encoder = ENCODERS[encoding]()
|
||||
@ -147,8 +143,7 @@ class TCPConnection(object):
|
||||
cb(msg)
|
||||
except Exception as e:
|
||||
self.log.debug(
|
||||
"handle_async: got exception %r" %
|
||||
e, exception=true)
|
||||
"handle_async: got exception %r" % e, exception=true)
|
||||
else:
|
||||
self.queue.append(msg)
|
||||
|
||||
@ -167,7 +162,6 @@ class TCPConnection(object):
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, opts):
|
||||
self.log = loggers.log.getChild('client', True)
|
||||
self._cache = dict()
|
||||
@ -184,7 +178,7 @@ class Client(object):
|
||||
|
||||
def populateNamespace(self, namespace):
|
||||
self.connection.send(ListDevicesRequest())
|
||||
# reply = self.connection.read()
|
||||
# self.log.info("found devices %r" % reply)
|
||||
# reply = self.connection.read()
|
||||
# self.log.info("found devices %r" % reply)
|
||||
# create proxies, populate cache....
|
||||
namespace.setconst('connection', self.connection)
|
||||
|
@ -19,12 +19,12 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define Client side proxies"""
|
||||
|
||||
import json
|
||||
import socket
|
||||
import serial
|
||||
from select import select
|
||||
import threading
|
||||
import Queue
|
||||
|
||||
@ -45,6 +45,7 @@ class TCPConnection(object):
|
||||
self._host = host
|
||||
self._port = int(port)
|
||||
self._thread = None
|
||||
self.callbacks = [] # called if SEC-node shuts down
|
||||
self.connect()
|
||||
|
||||
def connect(self):
|
||||
@ -62,24 +63,33 @@ class TCPConnection(object):
|
||||
data = u''
|
||||
while True:
|
||||
try:
|
||||
newdata = self._io.recv(1024)
|
||||
except socket.timeout:
|
||||
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
|
||||
except Exception as err:
|
||||
print err, "reconnecting"
|
||||
self.connect()
|
||||
data = u''
|
||||
continue
|
||||
for cb, arg in self.callbacks:
|
||||
cb(arg)
|
||||
return
|
||||
data += newdata
|
||||
while '\n' in data:
|
||||
line, data = data.split('\n', 1)
|
||||
try:
|
||||
self._readbuffer.put(
|
||||
line.strip('\r'), block=True, timeout=1)
|
||||
self._readbuffer.put(line.strip('\r'),
|
||||
block=True,
|
||||
timeout=1)
|
||||
except Queue.Full:
|
||||
self.log.debug(
|
||||
'rcv queue full! dropping line: %r' % line)
|
||||
self.log.debug('rcv queue full! dropping line: %r' %
|
||||
line)
|
||||
finally:
|
||||
self._thread = None
|
||||
|
||||
@ -147,8 +157,9 @@ class Client(object):
|
||||
devport = opts.pop('device')
|
||||
baudrate = int(opts.pop('baudrate', 115200))
|
||||
self.contactPoint = "serial://%s:%s" % (devport, baudrate)
|
||||
self.connection = serial.Serial(devport, baudrate=baudrate,
|
||||
timeout=1)
|
||||
self.connection = serial.Serial(
|
||||
devport, baudrate=baudrate, timeout=1)
|
||||
self.connection.callbacks = []
|
||||
else:
|
||||
host = opts.pop('connectto', 'localhost')
|
||||
port = int(opts.pop('port', 10767))
|
||||
@ -198,7 +209,7 @@ class Client(object):
|
||||
self.secop_id = line
|
||||
continue
|
||||
msgtype, spec, data = self.decode_message(line)
|
||||
if msgtype in ('update', 'changed'):
|
||||
if msgtype in ('event', 'update', 'changed'):
|
||||
# handle async stuff
|
||||
self._handle_event(spec, data)
|
||||
# handle sync stuff
|
||||
@ -217,21 +228,20 @@ class Client(object):
|
||||
if entry:
|
||||
self.log.error("request %r resulted in Error %r" %
|
||||
(data[0], spec))
|
||||
entry.extend([True, EXCEPTIONS[spec](data)])
|
||||
entry.extend([True, EXCEPTIONS[spec](*data)])
|
||||
entry[0].set()
|
||||
return
|
||||
self.log.error("got an unexpected error %s %r" %
|
||||
(spec, data[0]))
|
||||
self.log.error("got an unexpected error %s %r" % (spec, data[0]))
|
||||
return
|
||||
if msgtype == "describing":
|
||||
data = [spec, data]
|
||||
spec = ''
|
||||
entry = self.expected_replies.get((msgtype, spec), None)
|
||||
entry = self.expected_replies.get((msgtype, ''), None)
|
||||
else:
|
||||
entry = self.expected_replies.get((msgtype, spec), None)
|
||||
|
||||
if entry:
|
||||
self.log.debug("got expected reply '%s %s'" %
|
||||
(msgtype, spec) if spec else
|
||||
"got expected reply '%s'" % msgtype)
|
||||
entry.extend([False, data])
|
||||
self.log.debug("got expected reply '%s %s'" % (msgtype, spec)
|
||||
if spec else "got expected reply '%s'" % msgtype)
|
||||
entry.extend([False, msgtype, spec, data])
|
||||
entry[0].set()
|
||||
return
|
||||
|
||||
@ -282,8 +292,8 @@ class Client(object):
|
||||
try:
|
||||
mkthread(func, data)
|
||||
except Exception as err:
|
||||
self.log.exception(
|
||||
'Exception in Single-shot Callback!', err)
|
||||
self.log.exception('Exception in Single-shot Callback!',
|
||||
err)
|
||||
run.add(func)
|
||||
self.single_shots[spec].difference_update(run)
|
||||
|
||||
@ -294,7 +304,8 @@ class Client(object):
|
||||
return self._getDescribingModuleData(module)['parameters'][parameter]
|
||||
|
||||
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 parameter, parameterData in moduleData['parameters'].items():
|
||||
@ -303,17 +314,18 @@ class Client(object):
|
||||
[parameter]['validator'] = validator
|
||||
|
||||
def register_callback(self, module, parameter, cb):
|
||||
self.log.debug(
|
||||
'registering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.log.debug('registering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
|
||||
|
||||
def unregister_callback(self, module, parameter, cb):
|
||||
self.log.debug(
|
||||
'unregistering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' %
|
||||
(module, parameter), set()).discard(cb)
|
||||
self.log.debug('unregistering callback %r for %s:%s' %
|
||||
(cb, module, parameter))
|
||||
self.callbacks.setdefault('%s:%s' % (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):
|
||||
# maps each (sync) request to the corresponding reply
|
||||
@ -331,18 +343,24 @@ class Client(object):
|
||||
return REPLYMAP.get(requesttype, requesttype)
|
||||
|
||||
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))
|
||||
if self.stopflag:
|
||||
raise RuntimeError('alreading stopping!')
|
||||
if msgtype == "*IDN?":
|
||||
return self.secop_id
|
||||
|
||||
if msgtype not in ('*IDN?', 'describe', 'activate',
|
||||
'deactivate', 'do', 'change', 'read', 'ping', 'help'):
|
||||
raise EXCEPTIONS['Protocol'](errorclass='Protocol',
|
||||
errorinfo='%r: No Such Messagetype defined!' %
|
||||
msgtype,
|
||||
origin=self.encode_message(msgtype, spec, data))
|
||||
if msgtype not in ('*IDN?', 'describe', 'activate', 'deactivate', 'do',
|
||||
'change', 'read', 'ping', 'help'):
|
||||
raise EXCEPTIONS['Protocol'](args=[
|
||||
self.encode_message(msgtype, spec, data),
|
||||
dict(
|
||||
errorclass='Protocol',
|
||||
errorinfo='%r: No Such Messagetype defined!' % msgtype, ),
|
||||
])
|
||||
|
||||
# sanitize input + handle syntactic sugar
|
||||
msgtype = str(msgtype)
|
||||
@ -356,7 +374,8 @@ class Client(object):
|
||||
rply = self._get_reply_from_request(msgtype)
|
||||
if (rply, spec) in self.expected_replies:
|
||||
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
|
||||
event = threading.Event()
|
||||
@ -371,11 +390,14 @@ class Client(object):
|
||||
# wait for reply. timeout after 10s
|
||||
if event.wait(10):
|
||||
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 error, result contains the rigth Exception to raise
|
||||
raise result
|
||||
return result
|
||||
# if error, entry[2] contains the rigth Exception to raise
|
||||
raise entry[2]
|
||||
# valid reply: entry[2:5] contain msgtype, spec, data
|
||||
return tuple(entry[2:5])
|
||||
|
||||
# timed out
|
||||
del self.expected_replies[(rply, spec)]
|
||||
@ -447,17 +469,21 @@ class Client(object):
|
||||
return self.describing_data['modules'][module]['parameters'].keys()
|
||||
|
||||
def getModuleBaseClass(self, module):
|
||||
return self.describing_data['modules'][module]['baseclass']
|
||||
return self.describing_data['modules'][module]['interfaceclass']
|
||||
|
||||
def getCommands(self, module):
|
||||
return self.describing_data['modules'][module]['commands'].keys()
|
||||
|
||||
def getProperties(self, module, parameter):
|
||||
return self.describing_data['modules'][
|
||||
module]['parameters'][parameter]
|
||||
return self.describing_data['modules'][module]['parameters'][parameter]
|
||||
|
||||
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]):
|
||||
pingctr[0] = pingctr[0] + 1
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define Baseclasses for real devices implemented in the server"""
|
||||
|
||||
# XXX: connect with 'protocol'-Devices.
|
||||
@ -47,9 +46,13 @@ EVENT_ONLY_ON_CHANGED_VALUES = False
|
||||
|
||||
|
||||
class PARAM(object):
|
||||
|
||||
def __init__(self, description, validator=float, default=Ellipsis,
|
||||
unit=None, readonly=True, export=True):
|
||||
def __init__(self,
|
||||
description,
|
||||
validator=float,
|
||||
default=Ellipsis,
|
||||
unit=None,
|
||||
readonly=True,
|
||||
export=True):
|
||||
if isinstance(description, PARAM):
|
||||
# make a copy of a PARAM object
|
||||
self.__dict__.update(description.__dict__)
|
||||
@ -70,19 +73,17 @@ class PARAM(object):
|
||||
|
||||
def as_dict(self):
|
||||
# used for serialisation only
|
||||
return dict(description=self.description,
|
||||
unit=self.unit,
|
||||
readonly=self.readonly,
|
||||
value=self.value,
|
||||
timestamp=format_time(
|
||||
self.timestamp) if self.timestamp else None,
|
||||
validator=validator_to_str(self.validator),
|
||||
)
|
||||
return dict(
|
||||
description=self.description,
|
||||
unit=self.unit,
|
||||
readonly=self.readonly,
|
||||
value=self.value,
|
||||
timestamp=format_time(self.timestamp) if self.timestamp else None,
|
||||
validator=validator_to_str(self.validator), )
|
||||
|
||||
|
||||
# storage for CMDs settings (description + call signature...)
|
||||
class CMD(object):
|
||||
|
||||
def __init__(self, description, arguments, result):
|
||||
# descriptive text for humans
|
||||
self.description = description
|
||||
@ -97,17 +98,16 @@ class CMD(object):
|
||||
|
||||
def as_dict(self):
|
||||
# used for serialisation only
|
||||
return dict(description=self.description,
|
||||
arguments=repr(self.arguments),
|
||||
resulttype=repr(self.resulttype),
|
||||
)
|
||||
return dict(
|
||||
description=self.description,
|
||||
arguments=repr(self.arguments),
|
||||
resulttype=repr(self.resulttype), )
|
||||
|
||||
# Meta class
|
||||
# warning: MAGIC!
|
||||
|
||||
|
||||
class DeviceMeta(type):
|
||||
|
||||
def __new__(mcs, name, bases, attrs):
|
||||
newtype = type.__new__(mcs, name, bases, attrs)
|
||||
if '__constructed__' in attrs:
|
||||
@ -140,6 +140,7 @@ class DeviceMeta(type):
|
||||
else:
|
||||
# return cached value
|
||||
return self.PARAMS[pname].value
|
||||
|
||||
if rfunc:
|
||||
wrapped_rfunc.__doc__ = rfunc.__doc__
|
||||
setattr(newtype, 'read_' + pname, wrapped_rfunc)
|
||||
@ -157,6 +158,7 @@ class DeviceMeta(type):
|
||||
# of self.PARAMS[pname]?
|
||||
setattr(self, pname, value)
|
||||
return value
|
||||
|
||||
if wfunc:
|
||||
wrapped_wfunc.__doc__ = wfunc.__doc__
|
||||
setattr(newtype, 'write_' + pname, wrapped_wfunc)
|
||||
@ -188,8 +190,8 @@ class DeviceMeta(type):
|
||||
if argspec[0] and argspec[0][0] == 'self':
|
||||
del argspec[0][0]
|
||||
newtype.CMDS[name[2:]] = CMD(
|
||||
getattr(value, '__doc__'),
|
||||
argspec.args, None) # XXX: find resulttype!
|
||||
getattr(value, '__doc__'), argspec.args,
|
||||
None) # XXX: find resulttype!
|
||||
attrs['__constructed__'] = True
|
||||
return newtype
|
||||
|
||||
@ -209,8 +211,9 @@ class Device(object):
|
||||
__metaclass__ = DeviceMeta
|
||||
# PARAMS and CMDS are auto-merged upon subclassing
|
||||
PARAMS = {
|
||||
'baseclass': PARAM('protocol defined interface class',
|
||||
default="Device", validator=str),
|
||||
"interfaceclass": PARAM("protocol defined interface class",
|
||||
default="Device",
|
||||
validator=str),
|
||||
}
|
||||
CMDS = {}
|
||||
DISPATCHER = None
|
||||
@ -226,8 +229,10 @@ class Device(object):
|
||||
params[k] = PARAM(v)
|
||||
mycls = self.__class__
|
||||
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
||||
params['class'] = PARAM('implementation specific class name',
|
||||
default=myclassname, validator=str)
|
||||
params['implementationclass'] = PARAM(
|
||||
'implementation specific class name',
|
||||
default=myclassname,
|
||||
validator=str)
|
||||
|
||||
self.PARAMS = params
|
||||
# check config for problems
|
||||
@ -243,8 +248,8 @@ class Device(object):
|
||||
if v.default is Ellipsis and k != 'value':
|
||||
# Ellipsis is the one single value you can not specify....
|
||||
raise ConfigError('Device %s: Parameter %r has no default '
|
||||
'value and was not given in config!'
|
||||
% (self.name, k))
|
||||
'value and was not given in config!' %
|
||||
(self.name, k))
|
||||
# assume default value was given
|
||||
cfgdict[k] = v.default
|
||||
|
||||
@ -261,8 +266,8 @@ class Device(object):
|
||||
try:
|
||||
v = validator(v)
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ConfigError('Device %s: config parameter %r:\n%r'
|
||||
% (self.name, k, e))
|
||||
raise ConfigError('Device %s: config parameter %r:\n%r' %
|
||||
(self.name, k, e))
|
||||
setattr(self, k, v)
|
||||
self._requestLock = threading.RLock()
|
||||
|
||||
@ -281,10 +286,17 @@ class Readable(Device):
|
||||
providing the readonly parameter 'value' and 'status'
|
||||
"""
|
||||
PARAMS = {
|
||||
'baseclass': PARAM('protocol defined interface class',
|
||||
default="Readable", validator=str),
|
||||
'value': PARAM('current value of the device', readonly=True, default=0.),
|
||||
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),),
|
||||
'interfaceclass': PARAM(
|
||||
'protocol defined interface class',
|
||||
default="Readable",
|
||||
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,
|
||||
# validator=enum(**{'idle': status.OK,
|
||||
# 'BUSY': status.BUSY,
|
||||
@ -293,14 +305,19 @@ class Readable(Device):
|
||||
# 'ERROR': status.ERROR,
|
||||
# 'UNKNOWN': status.UNKNOWN}),
|
||||
# readonly=True),
|
||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||
validator=vector(enum(**{'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
'WARN': status.WARN,
|
||||
'UNSTABLE': status.UNSTABLE,
|
||||
'ERROR': status.ERROR,
|
||||
'UNKNOWN': status.UNKNOWN}), str),
|
||||
readonly=True),
|
||||
'status': PARAM(
|
||||
'current status of the device',
|
||||
default=(status.OK, ''),
|
||||
validator=vector(
|
||||
enum(**{
|
||||
'idle': status.OK,
|
||||
'BUSY': status.BUSY,
|
||||
'WARN': status.WARN,
|
||||
'UNSTABLE': status.UNSTABLE,
|
||||
'ERROR': status.ERROR,
|
||||
'UNKNOWN': status.UNKNOWN
|
||||
}), str),
|
||||
readonly=True),
|
||||
}
|
||||
|
||||
def init(self):
|
||||
@ -310,6 +327,7 @@ class Readable(Device):
|
||||
self._pollthread.start()
|
||||
|
||||
def _pollThread(self):
|
||||
"""super simple and super stupid per-module polling thread"""
|
||||
while True:
|
||||
time.sleep(self.pollinterval)
|
||||
for pname in self.PARAMS:
|
||||
@ -325,11 +343,25 @@ class Driveable(Readable):
|
||||
providing a settable 'target' parameter to those of a Readable
|
||||
"""
|
||||
PARAMS = {
|
||||
'baseclass': PARAM('protocol defined interface class',
|
||||
default="Driveable", validator=str),
|
||||
'target': PARAM('target value of the device', default=0.,
|
||||
readonly=False),
|
||||
"interfaceclass": PARAM("protocol defined interface class",
|
||||
default="Driveable",
|
||||
validator=str,
|
||||
),
|
||||
'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):
|
||||
"""Testing command implementation
|
||||
|
||||
wait a second"""
|
||||
time.sleep(1) # for testing !
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# *****************************************************************************
|
||||
|
||||
"""playing implementation of a (simple) simulated cryostat"""
|
||||
|
||||
from math import atan
|
||||
@ -26,10 +25,10 @@ import time
|
||||
import random
|
||||
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.validators import floatrange, positive, enum
|
||||
from secop.lib import clamp
|
||||
from secop.validators import floatrange, positive, enum, nonnegative, vector
|
||||
from secop.lib import clamp, mkthread
|
||||
|
||||
|
||||
class Cryostat(Driveable):
|
||||
@ -40,81 +39,112 @@ class Cryostat(Driveable):
|
||||
- thermal transfer between regulation and samplen
|
||||
"""
|
||||
PARAMS = dict(
|
||||
jitter=CONFIG("amount of random noise on readout values",
|
||||
validator=floatrange(0, 1),
|
||||
jitter=PARAM("amount of random noise on readout values",
|
||||
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,
|
||||
),
|
||||
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",
|
||||
validator=positive, export=False,
|
||||
),
|
||||
looptime=CONFIG("timestep for simulation",
|
||||
validator=positive, default=1, unit="s",
|
||||
export=False,
|
||||
),
|
||||
ramp=PARAM("ramping speed in K/min",
|
||||
validator=floatrange(0, 1e3), default=1,
|
||||
ramp=PARAM("ramping speed of the setpoint",
|
||||
validator=floatrange(0, 1e3), unit="K/min", default=1,
|
||||
readonly=False,
|
||||
),
|
||||
setpoint=PARAM("current setpoint during ramping else target",
|
||||
validator=float, default=1, unit='K',
|
||||
),
|
||||
maxpower=PARAM("Maximum heater power",
|
||||
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",
|
||||
validator=float, default=1, readonly=True,
|
||||
),
|
||||
maxpower=PARAM("Maximum heater power in W",
|
||||
validator=float, default=0, readonly=True, unit="W",
|
||||
),
|
||||
heater=PARAM("current heater setting in %",
|
||||
validator=float, default=0, readonly=True, unit="%",
|
||||
),
|
||||
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",
|
||||
),
|
||||
pid=PARAM("regulation coefficients",
|
||||
validator=vector(nonnegative, floatrange(0, 100), floatrange(0, 100)),
|
||||
default=(40, 10, 2), readonly=False,
|
||||
# export=False,
|
||||
),
|
||||
p=PARAM("regulation coefficient 'p'",
|
||||
validator=nonnegative, default=40, unit="%/K", readonly=False,
|
||||
export=False,
|
||||
),
|
||||
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'",
|
||||
validator=floatrange(0, 100), default=2,
|
||||
),
|
||||
validator=floatrange(0, 100), default=2, readonly=False,
|
||||
export=False,
|
||||
),
|
||||
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",
|
||||
validator=floatrange(0, 100), default=0.1, unit='K',
|
||||
),
|
||||
readonly=False,
|
||||
),
|
||||
window=PARAM("time window for stability checking",
|
||||
validator=floatrange(1, 900), default=30, unit='s',
|
||||
),
|
||||
readonly=False,
|
||||
export=False,
|
||||
),
|
||||
timeout=PARAM("max waiting time for stabilisation check",
|
||||
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):
|
||||
self._stopflag = False
|
||||
self._thread = threading.Thread(target=self.thread)
|
||||
self._thread.daemon = True
|
||||
self._thread.start()
|
||||
self._thread = mkthread(self.thread)
|
||||
|
||||
def read_status(self):
|
||||
def read_status(self, maxage=0):
|
||||
# instead of asking a 'Hardware' take the value from the simulation
|
||||
return self.status
|
||||
|
||||
def read_value(self, maxage=0):
|
||||
# return regulation value (averaged regulation temp)
|
||||
return self.regulationtemp + \
|
||||
self.config_jitter * (0.5 - random.random())
|
||||
self.jitter * (0.5 - random.random())
|
||||
|
||||
def read_target(self, maxage=0):
|
||||
return self.target
|
||||
|
||||
def write_target(self, value):
|
||||
value = round(value, 2)
|
||||
if value == self.target:
|
||||
# nothing to do
|
||||
return value
|
||||
self.target = value
|
||||
# next request will see this status, until the loop updates it
|
||||
self.status = (status.BUSY, 'new target set')
|
||||
# next read_status will see this status, until the loop updates it
|
||||
self.status = status.BUSY, 'new target set'
|
||||
return value
|
||||
|
||||
def read_maxpower(self, maxage=0):
|
||||
return self.maxpower
|
||||
@ -124,6 +154,14 @@ class Cryostat(Driveable):
|
||||
heat = max(0, min(100, self.heater * self.maxpower / float(newpower)))
|
||||
self.heater = heat
|
||||
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):
|
||||
# stop the ramp by setting current setpoint as target
|
||||
@ -137,7 +175,7 @@ class Cryostat(Driveable):
|
||||
"""returns cooling power in W at given temperature"""
|
||||
# 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(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):
|
||||
"""heat capacity of cooler at given temp"""
|
||||
@ -147,8 +185,8 @@ class Cryostat(Driveable):
|
||||
"""heatflow from sample to cooler. may be negative..."""
|
||||
flow = (sampletemp - coolertemp) * \
|
||||
((coolertemp + sampletemp) ** 2) / 400.
|
||||
cp = clamp(self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp),
|
||||
1, 10)
|
||||
cp = clamp(
|
||||
self.__coolerCP(coolertemp) * self.__sampleCP(sampletemp), 1, 10)
|
||||
return clamp(flow, -cp, cp)
|
||||
|
||||
def __sampleCP(self, temp):
|
||||
@ -159,9 +197,9 @@ class Cryostat(Driveable):
|
||||
return 0.02 / temp
|
||||
|
||||
def thread(self):
|
||||
self.sampletemp = self.config_T_start
|
||||
self.regulationtemp = self.config_T_start
|
||||
self.status = status.OK
|
||||
self.sampletemp = self.T_start
|
||||
self.regulationtemp = self.T_start
|
||||
self.status = status.OK, ''
|
||||
while not self._stopflag:
|
||||
try:
|
||||
self.__sim()
|
||||
@ -176,6 +214,7 @@ class Cryostat(Driveable):
|
||||
# c) generating status+updated value+ramp
|
||||
# this thread is not supposed to exit!
|
||||
|
||||
self.setpoint = self.target
|
||||
# local state keeping:
|
||||
regulation = self.regulationtemp
|
||||
sample = self.sampletemp
|
||||
@ -204,15 +243,14 @@ class Cryostat(Driveable):
|
||||
heatflow = self.__heatLink(regulation, sample)
|
||||
self.log.debug('sample = %.5f, regulation = %.5f, heatflow = %.5g'
|
||||
% (sample, regulation, heatflow))
|
||||
newsample = max(0,
|
||||
sample + (self.__sampleLeak(sample) - heatflow) /
|
||||
self.__sampleCP(sample) * h)
|
||||
newsample = max(0, sample + (self.__sampleLeak(sample) - heatflow)
|
||||
/ self.__sampleCP(sample) * h)
|
||||
# avoid instabilities due to too small CP
|
||||
newsample = clamp(newsample, sample, regulation)
|
||||
regdelta = (heater * 0.01 * self.maxpower + heatflow -
|
||||
self.__coolerPower(regulation))
|
||||
newregulation = max(0, regulation +
|
||||
regdelta / self.__coolerCP(regulation) * h)
|
||||
newregulation = max(
|
||||
0, regulation + regdelta / self.__coolerCP(regulation) * h)
|
||||
# b) see
|
||||
# http://brettbeauregard.com/blog/2011/04/
|
||||
# improving-the-beginners-pid-introduction/
|
||||
@ -231,9 +269,9 @@ class Cryostat(Driveable):
|
||||
# use a simple filter to smooth delta a little
|
||||
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
|
||||
kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d
|
||||
kd = kp * abs(self.d) / 2. # LakeShore D = 2*T_d
|
||||
P = kp * error
|
||||
I += ki * error * h
|
||||
D = kd * delta / h
|
||||
@ -249,7 +287,7 @@ class Cryostat(Driveable):
|
||||
v = P + I + D
|
||||
# in damping mode, use a weighted sum of old + new heaterpower
|
||||
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
|
||||
if D * lastD < -0.2:
|
||||
@ -274,7 +312,7 @@ class Cryostat(Driveable):
|
||||
heater = self.heater
|
||||
last_heaters = (0, 0)
|
||||
|
||||
heater = round(heater, 3)
|
||||
heater = round(heater, 1)
|
||||
sample = newsample
|
||||
regulation = newregulation
|
||||
lastmode = self.mode
|
||||
@ -285,9 +323,8 @@ class Cryostat(Driveable):
|
||||
else:
|
||||
maxdelta = self.ramp / 60. * h
|
||||
try:
|
||||
self.setpoint = round(self.setpoint +
|
||||
clamp(self.target - self.setpoint,
|
||||
-maxdelta, maxdelta), 3)
|
||||
self.setpoint = round(self.setpoint + clamp(
|
||||
self.target - self.setpoint, -maxdelta, maxdelta), 3)
|
||||
self.log.debug('setpoint changes to %r (target %r)' %
|
||||
(self.setpoint, self.target))
|
||||
except (TypeError, ValueError):
|
||||
@ -319,6 +356,7 @@ class Cryostat(Driveable):
|
||||
self.heaterpower = round(heater * self.maxpower * 0.01, 3)
|
||||
self.heater = heater
|
||||
timestamp = t
|
||||
self.read_value()
|
||||
|
||||
def shutdown(self):
|
||||
# should be called from server when the server is stopped
|
||||
|
@ -18,7 +18,6 @@
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# *****************************************************************************
|
||||
|
||||
"""testing devices"""
|
||||
|
||||
import time
|
||||
@ -35,16 +34,20 @@ class Switch(Driveable):
|
||||
"""
|
||||
PARAMS = {
|
||||
'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)',
|
||||
validator=enum(on=1, off=0),
|
||||
default=0, readonly=False),
|
||||
validator=enum(on=1, off=0), default=0,
|
||||
readonly=False,
|
||||
),
|
||||
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
||||
validator=floatrange(0, 60), unit='s',
|
||||
default=10, export=False),
|
||||
default=10, export=False,
|
||||
),
|
||||
'switch_off_time': PARAM('cool-down time in seconds',
|
||||
validator=floatrange(0, 60), unit='s',
|
||||
default=10, export=False),
|
||||
default=10, export=False,
|
||||
),
|
||||
}
|
||||
|
||||
def init(self):
|
||||
@ -95,14 +98,24 @@ class MagneticField(Driveable):
|
||||
"""a liquid magnet
|
||||
"""
|
||||
PARAMS = {
|
||||
'value': PARAM('current field in T', 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),
|
||||
'mode': PARAM('what to do after changing field', default=0,
|
||||
validator=enum(persistent=1, hold=0), readonly=False),
|
||||
'heatswitch': PARAM('heat switch device',
|
||||
validator=str, export=False),
|
||||
'value': PARAM('current field in T',
|
||||
unit='T', validator=floatrange(-15, 15), default=0,
|
||||
),
|
||||
'target': PARAM('target field in T',
|
||||
unit='T', validator=floatrange(-15, 15), default=0,
|
||||
readonly=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):
|
||||
@ -122,8 +135,8 @@ class MagneticField(Driveable):
|
||||
# note: we may also return the read-back value from the hw here
|
||||
|
||||
def read_status(self, maxage=0):
|
||||
return (status.OK, '') if self._state == 'idle' else (
|
||||
status.BUSY, self._state)
|
||||
return (status.OK, '') if self._state == 'idle' else (status.BUSY,
|
||||
self._state)
|
||||
|
||||
def _thread(self):
|
||||
loopdelay = 1
|
||||
@ -137,9 +150,8 @@ class MagneticField(Driveable):
|
||||
if self._state == 'switch_on':
|
||||
# wait until switch is on
|
||||
if self._heatswitch.read_value() == 'on':
|
||||
self.log.debug(
|
||||
'heatswitch is on -> ramp to %.3f' %
|
||||
self.target)
|
||||
self.log.debug('heatswitch is on -> ramp to %.3f' %
|
||||
self.target)
|
||||
self._state = 'ramp'
|
||||
if self._state == 'ramp':
|
||||
if self.target == self.value:
|
||||
@ -170,10 +182,12 @@ class CoilTemp(Readable):
|
||||
"""a coil temperature
|
||||
"""
|
||||
PARAMS = {
|
||||
'value': PARAM('Coil temperatur in K', unit='K',
|
||||
validator=float, default=0),
|
||||
'value': PARAM('Coil temperatur',
|
||||
unit='K', validator=float, default=0,
|
||||
),
|
||||
'sensor': PARAM("Sensor number or calibration id",
|
||||
validator=str, readonly=True),
|
||||
validator=str, readonly=True,
|
||||
),
|
||||
}
|
||||
|
||||
def read_value(self, maxage=0):
|
||||
@ -184,12 +198,16 @@ class SampleTemp(Driveable):
|
||||
"""a sample temperature
|
||||
"""
|
||||
PARAMS = {
|
||||
'value': PARAM('Sample temperatur in K', unit='K',
|
||||
validator=float, default=10),
|
||||
'value': PARAM('Sample temperature',
|
||||
unit='K', validator=float, default=10,
|
||||
),
|
||||
'sensor': PARAM("Sensor number or calibration id",
|
||||
validator=str, readonly=True),
|
||||
'ramp': PARAM('moving speed in K/min', validator=floatrange(0, 100),
|
||||
unit='K/min', default=0.1, readonly=False),
|
||||
validator=str, readonly=True,
|
||||
),
|
||||
'ramp': PARAM('moving speed in K/min',
|
||||
validator=floatrange(0, 100), unit='K/min', default=0.1,
|
||||
readonly=False,
|
||||
),
|
||||
}
|
||||
|
||||
def init(self):
|
||||
@ -225,13 +243,17 @@ class Label(Readable):
|
||||
"""
|
||||
PARAMS = {
|
||||
'system': PARAM("Name of the magnet system",
|
||||
validator=str, export=False),
|
||||
validator=str, export=False,
|
||||
),
|
||||
'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",
|
||||
validator=str, export=False),
|
||||
'value': PARAM("Value of out label string",
|
||||
validator=str)
|
||||
validator=str, export=False,
|
||||
),
|
||||
'value': PARAM("final value of label string",
|
||||
validator=str,
|
||||
),
|
||||
}
|
||||
|
||||
def read_value(self, maxage=0):
|
||||
@ -250,10 +272,10 @@ class Label(Readable):
|
||||
mf_mode = dev_mf.mode
|
||||
mf_val = dev_mf.value
|
||||
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'
|
||||
else:
|
||||
state = 'ramping'
|
||||
state = mf_stat[1] or 'ramping'
|
||||
strings.append('%s at %.1f %s' % (state, mf_val, mf_unit))
|
||||
else:
|
||||
strings.append('No connection to magnetic field!')
|
||||
@ -265,12 +287,21 @@ class ValidatorTest(Readable):
|
||||
"""
|
||||
"""
|
||||
PARAMS = {
|
||||
'oneof': PARAM('oneof', validator=oneof(int, 'X', 2.718), readonly=False, default=4.0),
|
||||
'enum': PARAM('enum', validator=enum('boo', 'faar', z=9), readonly=False, default=1),
|
||||
'vector': PARAM('vector of int, float and str', validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')),
|
||||
'array': PARAM('array: 2..3 time oneof(0,1)', 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,),
|
||||
'oneof': PARAM('oneof',
|
||||
validator=oneof(int, 'X', 2.718), readonly=False, default=4.0),
|
||||
'enum': PARAM('enum',
|
||||
validator=enum('boo', 'faar', z=9), readonly=False, default=1),
|
||||
'vector': PARAM('vector of int, float and str',
|
||||
validator=vector(int, float, str), readonly=False, default=(1, 2.3, 'a')),
|
||||
'array': PARAM('array: 2..3 times oneof(0,1)',
|
||||
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:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# *****************************************************************************
|
||||
|
||||
"""testing devices"""
|
||||
|
||||
import random
|
||||
@ -46,7 +45,8 @@ class Heater(Driveable):
|
||||
"""
|
||||
PARAMS = {
|
||||
'maxheaterpower': PARAM('maximum allowed heater power',
|
||||
validator=floatrange(0, 100), unit='W'),
|
||||
validator=floatrange(0, 100), unit='W',
|
||||
),
|
||||
}
|
||||
|
||||
def read_value(self, maxage=0):
|
||||
@ -64,9 +64,11 @@ class Temp(Driveable):
|
||||
"""
|
||||
PARAMS = {
|
||||
'sensor': PARAM("Sensor number or calibration id",
|
||||
validator=str, readonly=True),
|
||||
'target': PARAM("Target temperature", default=300.0,
|
||||
validator=positive, readonly=False, unit='K'),
|
||||
validator=str, readonly=True,
|
||||
),
|
||||
'target': PARAM("Target temperature",
|
||||
default=300.0, validator=positive, readonly=False, unit='K',
|
||||
),
|
||||
}
|
||||
|
||||
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 secop.gui.util import loadUi
|
||||
@ -30,6 +30,8 @@ from secop.gui.modulectrl import ModuleCtrl
|
||||
from secop.gui.paramview import ParameterView
|
||||
from secop.client.baseclient import Client as SECNode
|
||||
|
||||
import sys
|
||||
|
||||
ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1
|
||||
ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2
|
||||
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
|
||||
@ -61,21 +63,34 @@ class QSECNode(SECNode, QObject):
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(MainWindow, self).__init__(parent)
|
||||
|
||||
loadUi(self, 'mainwindow.ui')
|
||||
|
||||
self.toolBar.hide()
|
||||
self.lineEdit.hide()
|
||||
|
||||
self.splitter.setStretchFactor(0, 1)
|
||||
self.splitter.setStretchFactor(1, 70)
|
||||
self.splitter.setSizes([50, 500])
|
||||
|
||||
self._nodes = {}
|
||||
self._nodeCtrls = {}
|
||||
self._topItems = {}
|
||||
self._currentWidget = self.splitter.widget(1).layout().takeAt(0)
|
||||
|
||||
# add localhost if available
|
||||
self._addNode('localhost')
|
||||
# add localhost (if available) and SEC nodes given as arguments
|
||||
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('')
|
||||
def on_actionAdd_SEC_node_triggered(self):
|
||||
@ -85,7 +100,11 @@ class MainWindow(QMainWindow):
|
||||
if not ok:
|
||||
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):
|
||||
if current.type() == ITEM_TYPE_NODE:
|
||||
@ -94,8 +113,18 @@ class MainWindow(QMainWindow):
|
||||
self._displayModule(current.parent().text(0), current.text(0))
|
||||
elif current.type() == ITEM_TYPE_PARAMETER:
|
||||
self._displayParameter(current.parent().parent().text(0),
|
||||
current.parent().text(0),
|
||||
current.text(0))
|
||||
current.parent().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):
|
||||
|
||||
@ -109,6 +138,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
host = '%s (%s)' % (node.equipment_id, host)
|
||||
self._nodes[host] = node
|
||||
node.register_shutdown_callback(self._nodeDisconnected_callback, host)
|
||||
|
||||
# fill tree
|
||||
nodeItem = QTreeWidgetItem(None, [host], ITEM_TYPE_NODE)
|
||||
@ -120,6 +150,7 @@ class MainWindow(QMainWindow):
|
||||
ITEM_TYPE_PARAMETER)
|
||||
|
||||
self.treeWidget.addTopLevelItem(nodeItem)
|
||||
self._topItems[node] = nodeItem
|
||||
|
||||
def _displayNode(self, node):
|
||||
|
||||
@ -135,10 +166,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _displayParameter(self, node, module, parameter):
|
||||
self._replaceCtrlWidget(
|
||||
ParameterView(
|
||||
self._nodes[node],
|
||||
module,
|
||||
parameter))
|
||||
ParameterView(self._nodes[node], module, parameter))
|
||||
|
||||
def _replaceCtrlWidget(self, new):
|
||||
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 secop.gui.util import loadUi
|
||||
@ -30,8 +30,12 @@ from secop.gui.util import loadUi
|
||||
class ParameterButtons(QWidget):
|
||||
setRequested = pyqtSignal(str, str, str) # module, parameter, target
|
||||
|
||||
def __init__(self, module, parameter, initval='',
|
||||
readonly=True, parent=None):
|
||||
def __init__(self,
|
||||
module,
|
||||
parameter,
|
||||
initval='',
|
||||
readonly=True,
|
||||
parent=None):
|
||||
super(ParameterButtons, self).__init__(parent)
|
||||
loadUi(self, 'parambuttons.ui')
|
||||
|
||||
@ -42,6 +46,9 @@ class ParameterButtons(QWidget):
|
||||
if readonly:
|
||||
self.setPushButton.setEnabled(False)
|
||||
self.setLineEdit.setEnabled(False)
|
||||
else:
|
||||
self.setLineEdit.returnPressed.connect(
|
||||
self.on_setPushButton_clicked)
|
||||
|
||||
def on_setPushButton_clicked(self):
|
||||
self.setRequested.emit(self._module, self._parameter,
|
||||
@ -49,12 +56,12 @@ class ParameterButtons(QWidget):
|
||||
|
||||
|
||||
class ModuleCtrl(QWidget):
|
||||
|
||||
def __init__(self, node, module, parent=None):
|
||||
super(ModuleCtrl, self).__init__(parent)
|
||||
loadUi(self, 'modulectrl.ui')
|
||||
self._node = node
|
||||
self._module = module
|
||||
self._lastclick = None
|
||||
|
||||
self._paramWidgets = {} # widget cache do avoid garbage collection
|
||||
|
||||
@ -71,7 +78,16 @@ class ModuleCtrl(QWidget):
|
||||
font.setBold(True)
|
||||
|
||||
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)
|
||||
|
||||
props = self._node.getProperties(self._module, param)
|
||||
@ -80,7 +96,11 @@ class ModuleCtrl(QWidget):
|
||||
initValues[param].value,
|
||||
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(buttons, row, 1)
|
||||
@ -89,6 +109,16 @@ class ModuleCtrl(QWidget):
|
||||
|
||||
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):
|
||||
if module != self._module:
|
||||
return
|
||||
|
@ -22,6 +22,7 @@
|
||||
# *****************************************************************************
|
||||
|
||||
import pprint
|
||||
import json
|
||||
|
||||
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics
|
||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt
|
||||
@ -31,7 +32,6 @@ from secop.protocol.errors import SECOPError
|
||||
|
||||
|
||||
class NodeCtrl(QWidget):
|
||||
|
||||
def __init__(self, node, parent=None):
|
||||
super(NodeCtrl, self).__init__(parent)
|
||||
loadUi(self, 'nodectrl.ui')
|
||||
@ -50,14 +50,26 @@ class NodeCtrl(QWidget):
|
||||
if not msg:
|
||||
return
|
||||
|
||||
self._addLogEntry('<span style="font-weight:bold">Request:</span> '
|
||||
'%s:' % msg, raw=True)
|
||||
# msg = msg.split(' ', 2)
|
||||
self._addLogEntry(
|
||||
'<span style="font-weight:bold">Request:</span> '
|
||||
'%s:' % msg,
|
||||
raw=True)
|
||||
# msg = msg.split(' ', 2)
|
||||
try:
|
||||
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:
|
||||
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('')
|
||||
def on_clearPushButton_clicked(self):
|
||||
@ -70,19 +82,23 @@ class NodeCtrl(QWidget):
|
||||
self._addLogEntry('=========================')
|
||||
self._addLogEntry('', newline=True)
|
||||
|
||||
def _addLogEntry(self, msg, newline=False,
|
||||
pretty=False, raw=False, error=False):
|
||||
|
||||
def _addLogEntry(self,
|
||||
msg,
|
||||
newline=False,
|
||||
pretty=False,
|
||||
raw=False,
|
||||
error=False):
|
||||
if pretty:
|
||||
msg = pprint.pformat(msg, width=self._getLogWidth())
|
||||
msg = msg[1:-1]
|
||||
|
||||
if not raw:
|
||||
if error:
|
||||
msg = '<div style="color:#FF0000"><b><pre>%s</pre></b></div>' % Qt.escape(
|
||||
str(msg)).replace('\n', '<br />')
|
||||
else:
|
||||
msg = '<pre>%s</pre>' % Qt.escape(str(msg)
|
||||
).replace('\n', '<br />')
|
||||
msg = '<pre>%s</pre>' % Qt.escape(str(msg)).replace('\n',
|
||||
'<br />')
|
||||
|
||||
content = ''
|
||||
if self.logTextBrowser.toPlainText():
|
||||
|
@ -29,7 +29,6 @@ from secop.validators import validator_to_str
|
||||
|
||||
|
||||
class ParameterView(QWidget):
|
||||
|
||||
def __init__(self, node, module, parameter, parent=None):
|
||||
super(ParameterView, self).__init__(parent)
|
||||
loadUi(self, 'paramview.ui')
|
||||
@ -51,8 +50,8 @@ class ParameterView(QWidget):
|
||||
font = self.font()
|
||||
font.setBold(True)
|
||||
|
||||
props = self._node._getDescribingParameterData(
|
||||
self._module, self._parameter)
|
||||
props = self._node._getDescribingParameterData(self._module,
|
||||
self._parameter)
|
||||
for prop in sorted(props):
|
||||
label = QLabel(prop + ':')
|
||||
label.setFont(font)
|
||||
|
@ -25,7 +25,6 @@ from os import path
|
||||
|
||||
from PyQt4 import uic
|
||||
|
||||
|
||||
uipath = path.dirname(__file__)
|
||||
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define helpers"""
|
||||
|
||||
import threading
|
||||
@ -50,13 +49,16 @@ def get_class(spec):
|
||||
modname, classname = spec.rsplit('.', 1)
|
||||
import importlib
|
||||
module = importlib.import_module('secop.' + modname)
|
||||
# module = __import__(spec)
|
||||
# module = __import__(spec)
|
||||
return getattr(module, classname)
|
||||
|
||||
|
||||
def mkthread(func, *args, **kwds):
|
||||
t = threading.Thread(name='%s:%s' % (func.__module__, func.__name__),
|
||||
target=func, args=args, kwargs=kwds)
|
||||
t = threading.Thread(
|
||||
name='%s:%s' % (func.__module__, func.__name__),
|
||||
target=func,
|
||||
args=args,
|
||||
kwargs=kwds)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
return t
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define parsing helpers"""
|
||||
|
||||
import re
|
||||
@ -58,13 +57,13 @@ class LocalTimezone(tzinfo):
|
||||
return time.tzname[self._isdst(dt)]
|
||||
|
||||
def _isdst(self, dt):
|
||||
tt = (dt.year, dt.month, dt.day,
|
||||
dt.hour, dt.minute, dt.second,
|
||||
tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
|
||||
dt.weekday(), 0, 0)
|
||||
stamp = time.mktime(tt)
|
||||
tt = time.localtime(stamp)
|
||||
return tt.tm_isdst > 0
|
||||
|
||||
|
||||
LocalTimezone = LocalTimezone()
|
||||
|
||||
|
||||
@ -81,7 +80,6 @@ def format_time(timestamp=None):
|
||||
|
||||
|
||||
class Timezone(tzinfo):
|
||||
|
||||
def __init__(self, offset, name='unknown timezone'):
|
||||
self.offset = offset
|
||||
self.name = name
|
||||
@ -94,12 +92,13 @@ class Timezone(tzinfo):
|
||||
|
||||
def dst(self, dt):
|
||||
return timedelta(0)
|
||||
|
||||
|
||||
datetime_re = re.compile(
|
||||
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'(?::(?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):
|
||||
@ -125,9 +124,8 @@ def _parse_isostring(isostring):
|
||||
kw = {k: int(v) for k, v in kw.items() if v is not None}
|
||||
kw['tzinfo'] = _tzinfo
|
||||
return datetime(**kw)
|
||||
raise ValueError(
|
||||
"%s is not a valid ISO8601 string I can parse!" %
|
||||
isostring)
|
||||
raise ValueError("%s is not a valid ISO8601 string I can parse!" %
|
||||
isostring)
|
||||
|
||||
|
||||
def parse_time(isostring):
|
||||
@ -137,9 +135,9 @@ def parse_time(isostring):
|
||||
dt = _parse_isostring(isostring)
|
||||
return time.mktime(dt.timetuple()) + dt.microsecond * 1e-6
|
||||
|
||||
|
||||
# possibly unusable stuff below!
|
||||
|
||||
|
||||
def format_args(args):
|
||||
if isinstance(args, list):
|
||||
return ','.join(format_args(arg) for arg in args).join('[]')
|
||||
@ -168,7 +166,8 @@ class ArgsParser(object):
|
||||
|
||||
DIGITS_CHARS = [c for c in '0123456789']
|
||||
NAME_CHARS = [
|
||||
c for c in '_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz']
|
||||
c for c in '_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
]
|
||||
NAME_CHARS2 = NAME_CHARS + DIGITS_CHARS
|
||||
|
||||
def __init__(self, string=''):
|
||||
@ -393,7 +392,7 @@ if __name__ == '__main__':
|
||||
print "time_formatting:",
|
||||
t = time.time()
|
||||
s = format_time(t)
|
||||
assert(abs(t - parse_time(s)) < 1e-6)
|
||||
assert (abs(t - parse_time(s)) < 1e-6)
|
||||
print "OK"
|
||||
|
||||
print "ArgsParser:"
|
||||
@ -408,6 +407,6 @@ if __name__ == '__main__':
|
||||
s = format_args(obj)
|
||||
p = a.parse(s)
|
||||
print p,
|
||||
assert(parse_args(format_args(obj)) == obj)
|
||||
assert (parse_args(format_args(obj)) == obj)
|
||||
print "OK"
|
||||
print "OK"
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define pidfile helpers"""
|
||||
import os
|
||||
import atexit
|
||||
|
@ -20,7 +20,6 @@
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
@ -34,7 +33,6 @@ from logging import Logger, Formatter, Handler, DEBUG, INFO, WARNING, ERROR, \
|
||||
|
||||
from . import colors
|
||||
|
||||
|
||||
LOGFMT = '%(asctime)s : %(levelname)-7s : %(name)-15s: %(message)s'
|
||||
DATEFMT = '%H:%M:%S'
|
||||
DATESTAMP_FMT = '%Y-%m-%d'
|
||||
@ -43,7 +41,6 @@ SECONDS_PER_DAY = 60 * 60 * 24
|
||||
LOGLEVELS = {'debug': DEBUG, 'info': INFO, 'warning': WARNING, 'error': ERROR}
|
||||
INVLOGLEVELS = {value: key for key, value in LOGLEVELS.items()}
|
||||
|
||||
|
||||
log = None
|
||||
|
||||
|
||||
@ -142,16 +139,16 @@ class ConsoleFormatter(Formatter):
|
||||
elif levelno <= INFO:
|
||||
fmtstr = '%s%%(message)s' % namefmt
|
||||
elif levelno <= WARNING:
|
||||
fmtstr = self.colorize('fuchsia', '%s%%(levelname)s: %%(message)s'
|
||||
% namefmt)
|
||||
fmtstr = self.colorize('fuchsia',
|
||||
'%s%%(levelname)s: %%(message)s' % namefmt)
|
||||
else:
|
||||
# Add exception type to error (if caused by exception)
|
||||
msgPrefix = ''
|
||||
if record.exc_info:
|
||||
msgPrefix = '%s: ' % record.exc_info[0].__name__
|
||||
|
||||
fmtstr = self.colorize('red', '%s%%(levelname)s: %s%%(message)s'
|
||||
% (namefmt, msgPrefix))
|
||||
fmtstr = self.colorize('red', '%s%%(levelname)s: %s%%(message)s' %
|
||||
(namefmt, msgPrefix))
|
||||
fmtstr = datefmt + fmtstr
|
||||
if not getattr(record, 'nonl', False):
|
||||
fmtstr += '\n'
|
||||
@ -209,8 +206,8 @@ class LogfileFormatter(Formatter):
|
||||
if self.extended_traceback:
|
||||
s = format_extended_traceback(*ei)
|
||||
else:
|
||||
s = ''.join(traceback.format_exception(ei[0], ei[1], ei[2],
|
||||
sys.maxsize))
|
||||
s = ''.join(
|
||||
traceback.format_exception(ei[0], ei[1], ei[2], sys.maxsize))
|
||||
if s.endswith('\n'):
|
||||
s = s[:-1]
|
||||
return s
|
||||
@ -268,8 +265,8 @@ class LogfileHandler(StreamHandler):
|
||||
StreamHandler.__init__(self)
|
||||
# determine time of first midnight from now on
|
||||
t = time.localtime()
|
||||
self.rollover_at = time.mktime((t[0], t[1], t[2], 0, 0, 0,
|
||||
t[6], t[7], t[8])) + SECONDS_PER_DAY
|
||||
self.rollover_at = time.mktime(
|
||||
(t[0], t[1], t[2], 0, 0, 0, t[6], t[7], t[8])) + SECONDS_PER_DAY
|
||||
self.setFormatter(LogfileFormatter(LOGFMT, DATEFMT))
|
||||
self.disabled = False
|
||||
|
||||
@ -338,8 +335,9 @@ class ColoredConsoleHandler(StreamHandler):
|
||||
|
||||
def __init__(self):
|
||||
StreamHandler.__init__(self, sys.stdout)
|
||||
self.setFormatter(ConsoleFormatter(datefmt=DATEFMT,
|
||||
colorize=colors.colorize))
|
||||
self.setFormatter(
|
||||
ConsoleFormatter(
|
||||
datefmt=DATEFMT, colorize=colors.colorize))
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
|
@ -20,10 +20,8 @@
|
||||
# Georg Brandl <georg@python.org>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Console coloring handlers."""
|
||||
|
||||
|
||||
_codes = {}
|
||||
|
||||
_attrs = {
|
||||
|
@ -19,13 +19,11 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Pathes. how to find what and where..."""
|
||||
|
||||
import sys
|
||||
from os import path
|
||||
|
||||
|
||||
basepath = path.abspath(path.join(sys.path[0], '..'))
|
||||
etc_path = path.join(basepath, 'etc')
|
||||
pid_path = path.join(basepath, 'pid')
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Device classes
|
||||
|
||||
"""
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Dispatcher for SECoP Messages
|
||||
|
||||
Interface to the service offering part:
|
||||
@ -47,7 +46,6 @@ from secop.lib.parsing import format_time
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
|
||||
def __init__(self, logger, options):
|
||||
self.equipment_id = options.pop('equipment_id')
|
||||
self.log = logger
|
||||
@ -85,15 +83,18 @@ class Dispatcher(object):
|
||||
reply = handler(conn, msg)
|
||||
except SECOPError as err:
|
||||
self.log.exception(err)
|
||||
reply = msg.get_error(errorclass=err.__class__.__name__,
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
reply = msg.get_error(
|
||||
errorclass=err.__class__.__name__,
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
except (ValueError, TypeError) as err:
|
||||
reply = msg.get_error(errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
reply = msg.get_error(
|
||||
errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
except Exception as err:
|
||||
self.log.exception(err)
|
||||
reply = msg.get_error(errorclass='InternalError',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
reply = msg.get_error(
|
||||
errorclass='InternalError',
|
||||
errorinfo=[repr(err), str(msg)])
|
||||
else:
|
||||
self.log.debug('Can not handle msg %r' % msg)
|
||||
reply = self.unhandled(conn, msg)
|
||||
@ -106,8 +107,8 @@ class Dispatcher(object):
|
||||
listeners = self._connections
|
||||
else:
|
||||
if getattr(msg, 'command', None) is None:
|
||||
eventname = '%s:%s' % (
|
||||
msg.module, msg.parameter if msg.parameter else 'value')
|
||||
eventname = '%s:%s' % (msg.module, msg.parameter
|
||||
if msg.parameter else 'value')
|
||||
else:
|
||||
eventname = '%s:%s()' % (msg.module, msg.command)
|
||||
listeners = self._subscriptions.get(eventname, [])
|
||||
@ -198,8 +199,7 @@ class Dispatcher(object):
|
||||
res = {}
|
||||
for cmdname, cmdobj in self.get_module(modulename).CMDS.items():
|
||||
res[cmdname] = cmdobj.as_dict()
|
||||
self.log.debug('list cmds for module %s -> %r' %
|
||||
(modulename, res))
|
||||
self.log.debug('list cmds for module %s -> %r' % (modulename, res))
|
||||
return res
|
||||
self.log.debug('-> module is not to be exported!')
|
||||
return {}
|
||||
@ -210,12 +210,13 @@ class Dispatcher(object):
|
||||
for modulename in self._export:
|
||||
module = self.get_module(modulename)
|
||||
# some of these need rework !
|
||||
dd = {'class': module.__class__.__name__,
|
||||
'bases': [b.__name__ for b in module.__class__.__bases__],
|
||||
'parameters': self.list_module_params(modulename),
|
||||
'commands': self.list_module_cmds(modulename),
|
||||
'baseclass': 'Readable',
|
||||
}
|
||||
dd = {
|
||||
'class': module.__class__.__name__,
|
||||
'bases': [b.__name__ for b in module.__class__.__bases__],
|
||||
'parameters': self.list_module_params(modulename),
|
||||
'commands': self.list_module_cmds(modulename),
|
||||
'interfaceclass': 'Readable',
|
||||
}
|
||||
result['modules'][modulename] = dd
|
||||
result['equipment_id'] = self.equipment_id
|
||||
result['firmware'] = 'The SECoP playground'
|
||||
@ -244,8 +245,11 @@ class Dispatcher(object):
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
func = getattr(moduleobj, 'do' + command)
|
||||
res = func(*arguments)
|
||||
res = CommandReply(module=modulename, command=command,
|
||||
result=res, qualifiers=dict(t=time.time()))
|
||||
res = CommandReply(
|
||||
module=modulename,
|
||||
command=command,
|
||||
result=res,
|
||||
qualifiers=dict(t=time.time()))
|
||||
# res = Value(modulename, command=command, value=func(*arguments), t=time.time())
|
||||
return res
|
||||
|
||||
@ -268,15 +272,11 @@ class Dispatcher(object):
|
||||
setattr(moduleobj, pname, value)
|
||||
if pobj.timestamp:
|
||||
return WriteReply(
|
||||
module=modulename, parameter=pname, value=[
|
||||
pobj.value, dict(
|
||||
t=format_time(pobj.timestamp))])
|
||||
module=modulename,
|
||||
parameter=pname,
|
||||
value=[pobj.value, dict(t=format_time(pobj.timestamp))])
|
||||
return WriteReply(
|
||||
module=modulename,
|
||||
parameter=pname,
|
||||
value=[
|
||||
pobj.value,
|
||||
{}])
|
||||
module=modulename, parameter=pname, value=[pobj.value, {}])
|
||||
|
||||
def _getParamValue(self, modulename, pname):
|
||||
moduleobj = self.get_module(modulename)
|
||||
@ -370,9 +370,12 @@ class Dispatcher(object):
|
||||
except SECOPError as e:
|
||||
self.log.error('decide what to do here!')
|
||||
self.log.exception(e)
|
||||
res = Value(module=modulename, parameter=pname,
|
||||
value=pobj.value, t=pobj.timestamp,
|
||||
unit=pobj.unit)
|
||||
res = Value(
|
||||
module=modulename,
|
||||
parameter=pname,
|
||||
value=pobj.value,
|
||||
t=pobj.timestamp,
|
||||
unit=pobj.unit)
|
||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||
self.broadcast_event(res)
|
||||
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
||||
@ -392,5 +395,5 @@ class Dispatcher(object):
|
||||
(no handle_<messagename> method was defined)
|
||||
"""
|
||||
self.log.error('IGN: got unhandled request %s' % msg)
|
||||
return msg.get_error(errorclass="InternalError",
|
||||
errorinfo="Unhandled Request")
|
||||
return msg.get_error(
|
||||
errorclass="InternalError", errorinfo="Unhandled Request")
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# 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"""
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
from demo_v2 import DemoEncoder as DemoEncoderV2
|
||||
from demo_v3 import DemoEncoder as DemoEncoderV3
|
||||
from demo_v4 import DemoEncoder as DemoEncoderV4
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -32,11 +31,11 @@ from secop.lib.parsing import *
|
||||
import re
|
||||
|
||||
DEMO_RE = re.compile(
|
||||
r'^([!+-])?(\*|[a-z_][a-z_0-9]*)?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\=(.*))?')
|
||||
r'^([!+-])?(\*|[a-z_][a-z_0-9]*)?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\:(\*|[a-z_][a-z_0-9]*))?(?:\=(.*))?'
|
||||
)
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
|
||||
def decode(sef, encoded):
|
||||
# match [!][*|devicename][: *|paramname [: *|propname]] [=value]
|
||||
match = DEMO_RE.match(encoded)
|
||||
@ -46,8 +45,8 @@ class DemoEncoder(MessageEncoder):
|
||||
print "parsing", assign,
|
||||
assign = parse_args(assign)
|
||||
print "->", assign
|
||||
return messages.DemoRequest(
|
||||
novalue, devname, pname, propname, assign)
|
||||
return messages.DemoRequest(novalue, devname, pname, propname,
|
||||
assign)
|
||||
return messages.HelpRequest()
|
||||
|
||||
def encode(self, msg):
|
||||
@ -66,8 +65,13 @@ class DemoEncoder(MessageEncoder):
|
||||
return '~InternalError~'
|
||||
return result
|
||||
|
||||
def _encode_AsyncDataUnit(self, devname, pname, value, timestamp,
|
||||
error=None, unit=''):
|
||||
def _encode_AsyncDataUnit(self,
|
||||
devname,
|
||||
pname,
|
||||
value,
|
||||
timestamp,
|
||||
error=None,
|
||||
unit=''):
|
||||
return '#%s:%s=%s;t=%.3f' % (devname, pname, value, timestamp)
|
||||
|
||||
def _encode_Error(self, error):
|
||||
@ -98,5 +102,7 @@ class DemoEncoder(MessageEncoder):
|
||||
return '~InvalidValueForParamError~ %s:%s=%r' % (device, param, value)
|
||||
|
||||
def _encode_HelpReply(self):
|
||||
return ['Help not yet implemented!',
|
||||
'ask Markus Zolliker about the protocol']
|
||||
return [
|
||||
'Help not yet implemented!',
|
||||
'ask Markus Zolliker about the protocol'
|
||||
]
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -93,7 +92,6 @@ DEMO_RE_OTHER = re.compile(
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
MessageEncoder.__init__(self, *args, **kwds)
|
||||
self.result = [] # for decoding
|
||||
@ -112,8 +110,8 @@ class DemoEncoder(MessageEncoder):
|
||||
r.append("help ... more to come")
|
||||
return '\n'.join(r)
|
||||
|
||||
if isinstance(msg, (ListMessage, SubscribeMessage,
|
||||
UnsubscribeMessage, TriggerMessage)):
|
||||
if isinstance(msg, (ListMessage, SubscribeMessage, UnsubscribeMessage,
|
||||
TriggerMessage)):
|
||||
msgtype = msg.MSGTYPE
|
||||
if msg.result:
|
||||
if msg.devs:
|
||||
@ -145,9 +143,7 @@ class DemoEncoder(MessageEncoder):
|
||||
# encode 1..N replies
|
||||
result.append(
|
||||
encode_value(
|
||||
val,
|
||||
'write',
|
||||
targetvalue=msg.target))
|
||||
val, 'write', targetvalue=msg.target))
|
||||
if not msg.result:
|
||||
# encode a request (no results -> reply, else an error would
|
||||
# have been sent)
|
||||
@ -164,28 +160,17 @@ class DemoEncoder(MessageEncoder):
|
||||
encode_value(
|
||||
val,
|
||||
'command',
|
||||
cmd='%s(%s)' %
|
||||
(msg.cmd,
|
||||
','.join(
|
||||
msg.args))))
|
||||
cmd='%s(%s)' % (msg.cmd, ','.join(msg.args))))
|
||||
if not msg.result:
|
||||
# encode a request (no results -> reply, else an error would
|
||||
# have been sent)
|
||||
result.append(
|
||||
'%s:%s(%s)' %
|
||||
(devspec(
|
||||
msg, 'command'), msg.cmd, ','.join(
|
||||
msg.args)))
|
||||
result.append('%s:%s(%s)' % (devspec(msg, 'command'), msg.cmd,
|
||||
','.join(msg.args)))
|
||||
return '\n'.join(result)
|
||||
|
||||
if isinstance(msg, ErrorMessage):
|
||||
return (
|
||||
'%s %s' %
|
||||
(devspec(
|
||||
msg,
|
||||
'error %s' %
|
||||
msg.errortype),
|
||||
msg.errorstring)).strip()
|
||||
return ('%s %s' % (devspec(msg, 'error %s' % msg.errortype),
|
||||
msg.errorstring)).strip()
|
||||
|
||||
return 'Can not handle object %r!' % msg
|
||||
|
||||
@ -246,6 +231,7 @@ class DemoEncoder(MessageEncoder):
|
||||
if sep in stuff:
|
||||
return stuff.split(sep)
|
||||
return [stuff]
|
||||
|
||||
devs = helper(mgroups.pop('devs'))
|
||||
pars = helper(mgroups.pop('pars'))
|
||||
props = helper(mgroups.pop('props'))
|
||||
@ -272,12 +258,8 @@ class DemoEncoder(MessageEncoder):
|
||||
# reformat qualifiers
|
||||
print mgroups
|
||||
quals = dict(
|
||||
qual.split(
|
||||
'=',
|
||||
1) for qual in helper(
|
||||
mgroups.pop(
|
||||
'qualifiers',
|
||||
';')))
|
||||
qual.split('=', 1)
|
||||
for qual in helper(mgroups.pop('qualifiers', ';')))
|
||||
|
||||
# reformat value
|
||||
result = []
|
||||
@ -296,48 +278,49 @@ class DemoEncoder(MessageEncoder):
|
||||
|
||||
# construct messageobj
|
||||
if msgtype in MESSAGE:
|
||||
return MESSAGE[msgtype](
|
||||
devs=devs,
|
||||
pars=pars,
|
||||
props=props,
|
||||
result=result,
|
||||
**mgroups)
|
||||
return MESSAGE[msgtype](devs=devs,
|
||||
pars=pars,
|
||||
props=props,
|
||||
result=result,
|
||||
**mgroups)
|
||||
|
||||
return ErrorMessage(errortype="SyntaxError",
|
||||
errorstring="Can't handle %r" % encoded)
|
||||
return ErrorMessage(
|
||||
errortype="SyntaxError", errorstring="Can't handle %r" % encoded)
|
||||
|
||||
def tests(self):
|
||||
testmsg = ['list',
|
||||
'list *',
|
||||
'list device',
|
||||
'list device:param1,param2',
|
||||
'list *:*',
|
||||
'list *=ts,tcoil,mf,lhe,ln2;',
|
||||
'read blub=12;t=3',
|
||||
'command ts:stop()',
|
||||
'command mf:quench(1,"now")',
|
||||
'error GibbetNich query x:y:z=9 "blubub blah"',
|
||||
'#3',
|
||||
'read blub:a=12;t=3',
|
||||
'read blub:b=13;t=3.1',
|
||||
'read blub:c=14;t=3.3',
|
||||
]
|
||||
testmsg = [
|
||||
'list',
|
||||
'list *',
|
||||
'list device',
|
||||
'list device:param1,param2',
|
||||
'list *:*',
|
||||
'list *=ts,tcoil,mf,lhe,ln2;',
|
||||
'read blub=12;t=3',
|
||||
'command ts:stop()',
|
||||
'command mf:quench(1,"now")',
|
||||
'error GibbetNich query x:y:z=9 "blubub blah"',
|
||||
'#3',
|
||||
'read blub:a=12;t=3',
|
||||
'read blub:b=13;t=3.1',
|
||||
'read blub:c=14;t=3.3',
|
||||
]
|
||||
for m in testmsg:
|
||||
print repr(m)
|
||||
print self.decode(m)
|
||||
print
|
||||
|
||||
|
||||
DEMO_RE_MZ = re.compile(r"""^(?P<type>[a-z]+)? # request type word (read/write/list/...)
|
||||
DEMO_RE_MZ = re.compile(
|
||||
r"""^(?P<type>[a-z]+)? # request type word (read/write/list/...)
|
||||
\ ? # optional space
|
||||
(?P<device>[a-z][a-z0-9_]*)? # optional devicename
|
||||
(?:\:(?P<param>[a-z0-9_]*) # optional ':'+paramname
|
||||
(?:\:(?P<prop>[a-z0-9_]*))?)? # optinal ':' + propname
|
||||
(?:(?P<op>[=\?])(?P<value>[^;]+)(?:\;(?P<quals>.*))?)?$""", re.X)
|
||||
(?:(?P<op>[=\?])(?P<value>[^;]+)(?:\;(?P<quals>.*))?)?$""",
|
||||
re.X)
|
||||
|
||||
|
||||
class DemoEncoder_MZ(MessageEncoder):
|
||||
|
||||
def decode(sef, encoded):
|
||||
m = DEMO_RE_MZ.match(encoded)
|
||||
if m:
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -47,7 +46,8 @@ DEMO_RE = re.compile(
|
||||
IDENTREQUEST = '*IDN?' # literal
|
||||
# literal! first part is fixed!
|
||||
#IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
|
||||
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
|
||||
#IDENTREPLY = 'SINE2020&ISSE,SECoP,V2016-11-30,rc1'
|
||||
IDENTREPLY = 'SINE2020&ISSE,SECoP,V2017-01-25,rc1'
|
||||
DESCRIPTIONSREQUEST = 'describe' # literal
|
||||
DESCRIPTIONREPLY = 'describing' # +<id> +json
|
||||
ENABLEEVENTSREQUEST = 'activate' # literal
|
||||
@ -69,10 +69,22 @@ HEARTBEATREPLY = 'pong' # +nonce_without_space
|
||||
ERRORREPLY = 'error' # +errorclass +json_extended_info
|
||||
HELPREQUEST = 'help' # literal
|
||||
HELPREPLY = 'helping' # +line number +json_text
|
||||
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
||||
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
||||
'IsBusy', 'IsError', 'ProtocolError', 'InternalError',
|
||||
'CommandRunning', 'Disabled', ]
|
||||
ERRORCLASSES = [
|
||||
'NoSuchDevice',
|
||||
'NoSuchParameter',
|
||||
'NoSuchCommand',
|
||||
'CommandFailed',
|
||||
'ReadOnly',
|
||||
'BadValue',
|
||||
'CommunicationFailed',
|
||||
'IsBusy',
|
||||
'IsError',
|
||||
'ProtocolError',
|
||||
'InternalError',
|
||||
'CommandRunning',
|
||||
'Disabled',
|
||||
]
|
||||
|
||||
# note: above strings need to be unique in the sense, that none is/or
|
||||
# starts with another
|
||||
|
||||
@ -93,33 +105,61 @@ def encode_value_data(vobj):
|
||||
|
||||
def encode_error_msg(emsg):
|
||||
# note: result is JSON-ified....
|
||||
return [emsg.origin, dict((k, getattr(emsg, k))
|
||||
for k in emsg.ARGS if k != 'origin')]
|
||||
return [
|
||||
emsg.origin, dict((k, getattr(emsg, k)) for k in emsg.ARGS
|
||||
if k != 'origin')
|
||||
]
|
||||
|
||||
|
||||
class DemoEncoder(MessageEncoder):
|
||||
# map of msg to msgtype string as defined above.
|
||||
ENCODEMAP = {
|
||||
IdentifyRequest: (IDENTREQUEST,),
|
||||
IdentifyReply: (IDENTREPLY,),
|
||||
DescribeRequest: (DESCRIPTIONSREQUEST,),
|
||||
DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
||||
ActivateRequest: (ENABLEEVENTSREQUEST,),
|
||||
ActivateReply: (ENABLEEVENTSREPLY,),
|
||||
DeactivateRequest: (DISABLEEVENTSREQUEST,),
|
||||
DeactivateReply: (DISABLEEVENTSREPLY,),
|
||||
CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||
CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 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',),
|
||||
IdentifyRequest: (IDENTREQUEST, ),
|
||||
IdentifyReply: (IDENTREPLY, ),
|
||||
DescribeRequest: (DESCRIPTIONSREQUEST, ),
|
||||
DescribeReply: (
|
||||
DESCRIPTIONREPLY,
|
||||
'equipment_id',
|
||||
'description', ),
|
||||
ActivateRequest: (ENABLEEVENTSREQUEST, ),
|
||||
ActivateReply: (ENABLEEVENTSREPLY, ),
|
||||
DeactivateRequest: (DISABLEEVENTSREQUEST, ),
|
||||
DeactivateReply: (DISABLEEVENTSREPLY, ),
|
||||
CommandRequest: (
|
||||
COMMANDREQUEST,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.command),
|
||||
'arguments', ),
|
||||
CommandReply: (
|
||||
COMMANDREPLY,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.command),
|
||||
encode_cmd_result, ),
|
||||
WriteRequest: (
|
||||
WRITEREQUEST,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||
'value', ),
|
||||
WriteReply: (
|
||||
WRITEREPLY,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||
'value', ),
|
||||
PollRequest: (
|
||||
TRIGGERREQUEST,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module,
|
||||
),
|
||||
HeartbeatRequest: (
|
||||
HEARTBEATREQUEST,
|
||||
'nonce', ),
|
||||
HeartbeatReply: (
|
||||
HEARTBEATREPLY,
|
||||
'nonce', ),
|
||||
HelpMessage: (HELPREQUEST, ),
|
||||
ErrorMessage: (ERRORREPLY, "errorclass", encode_error_msg,),
|
||||
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()'))
|
||||
if msg.parameter or msg.command else msg.module,
|
||||
encode_value_data,),
|
||||
ErrorMessage: (
|
||||
ERRORREPLY,
|
||||
"errorclass",
|
||||
encode_error_msg, ),
|
||||
Value: (
|
||||
EVENT,
|
||||
lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) if msg.parameter or msg.command else msg.module,
|
||||
encode_value_data, ),
|
||||
}
|
||||
DECODEMAP = {
|
||||
IDENTREQUEST: lambda spec, data: IdentifyRequest(),
|
||||
@ -177,8 +217,10 @@ class DemoEncoder(MessageEncoder):
|
||||
for msgcls, parts in self.ENCODEMAP.items():
|
||||
if isinstance(msg, msgcls):
|
||||
# resolve lambdas
|
||||
parts = [parts[0]] + [p(msg) if callable(p)
|
||||
else getattr(msg, p) for p in parts[1:]]
|
||||
parts = [parts[0]] + [
|
||||
p(msg) if callable(p) else getattr(msg, p)
|
||||
for p in parts[1:]
|
||||
]
|
||||
if len(parts) > 1:
|
||||
parts[1] = str(parts[1])
|
||||
if len(parts) == 3:
|
||||
@ -199,10 +241,11 @@ class DemoEncoder(MessageEncoder):
|
||||
# is_request=True)
|
||||
msgtype, msgspec, data = match.groups()
|
||||
if msgspec is None and data:
|
||||
return ErrorMessage(errorclass='Internal',
|
||||
errorinfo='Regex matched json, but not spec!',
|
||||
is_request=True,
|
||||
origin=encoded)
|
||||
return ErrorMessage(
|
||||
errorclass='Internal',
|
||||
errorinfo='Regex matched json, but not spec!',
|
||||
is_request=True,
|
||||
origin=encoded)
|
||||
|
||||
if msgtype in self.DECODEMAP:
|
||||
if msgspec and ':' in msgspec:
|
||||
@ -213,16 +256,16 @@ class DemoEncoder(MessageEncoder):
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except ValueError as err:
|
||||
return ErrorMessage(errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(encoded)],
|
||||
origin=encoded)
|
||||
return ErrorMessage(
|
||||
errorclass='BadValue',
|
||||
errorinfo=[repr(err), str(encoded)],
|
||||
origin=encoded)
|
||||
msg = self.DECODEMAP[msgtype](msgspec, data)
|
||||
msg.setvalue("origin", encoded)
|
||||
return msg
|
||||
return ErrorMessage(
|
||||
errorclass='Protocol',
|
||||
errorinfo='%r: No Such Messagetype defined!' %
|
||||
encoded,
|
||||
errorinfo='%r: No Such Messagetype defined!' % encoded,
|
||||
is_request=True,
|
||||
origin=encoded)
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -36,7 +35,6 @@ except ImportError:
|
||||
|
||||
|
||||
class PickleEncoder(MessageEncoder):
|
||||
|
||||
def encode(self, messageobj):
|
||||
"""msg object -> transport layer message"""
|
||||
return pickle.dumps(messageobj)
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -32,13 +31,12 @@ from secop.lib.parsing import *
|
||||
import re
|
||||
import ast
|
||||
|
||||
|
||||
SCPMESSAGE = re.compile(
|
||||
r'^(?:(?P<errorcode>[0-9@])\ )?(?P<device>[a-zA-Z0-9_\*]*)(?:/(?P<param>[a-zA-Z0-9_\*]*))+(?P<op>[-+=\?\ ])?(?P<value>.*)')
|
||||
r'^(?:(?P<errorcode>[0-9@])\ )?(?P<device>[a-zA-Z0-9_\*]*)(?:/(?P<param>[a-zA-Z0-9_\*]*))+(?P<op>[-+=\?\ ])?(?P<value>.*)'
|
||||
)
|
||||
|
||||
|
||||
class SCPEncoder(MessageEncoder):
|
||||
|
||||
def encode(self, msg):
|
||||
"""msg object -> transport layer message"""
|
||||
# fun for Humans
|
||||
@ -48,20 +46,24 @@ class SCPEncoder(MessageEncoder):
|
||||
r.append("'/version?' to query the current version")
|
||||
r.append("'/modules?' to query the list of modules")
|
||||
r.append(
|
||||
"'<module>/parameters?' to query the list of params of a module")
|
||||
"'<module>/parameters?' to query the list of params of a module"
|
||||
)
|
||||
r.append("'<module>/value?' to query the value of a module")
|
||||
r.append("'<module>/status?' to query the status of a module")
|
||||
r.append("'<module>/target=<new_value>' to move a module")
|
||||
r.append("replies copy the request and are prefixed with an errorcode:")
|
||||
r.append(
|
||||
"0=OK,3=NoSuchCommand,4=NosuchDevice,5=NoSuchParam,6=SyntaxError,7=BadValue,8=Readonly,9=Forbidden,@=Async")
|
||||
"replies copy the request and are prefixed with an errorcode:")
|
||||
r.append(
|
||||
"0=OK,3=NoSuchCommand,4=NosuchDevice,5=NoSuchParam,6=SyntaxError,7=BadValue,8=Readonly,9=Forbidden,@=Async"
|
||||
)
|
||||
r.append("extensions: @-prefix as error-code,")
|
||||
r.append("'<module>/+' subscribe all params of module")
|
||||
r.append("'<module>/<param>+' subscribe a param of a module")
|
||||
r.append("use '-' instead of '+' to unsubscribe")
|
||||
r.append("'<module>/commands?' list of commands")
|
||||
r.append(
|
||||
"'<module>/<command>@[possible args] execute command (ex. 'stop@')")
|
||||
"'<module>/<command>@[possible args] execute command (ex. 'stop@')"
|
||||
)
|
||||
return '\n'.join(r)
|
||||
|
||||
return {
|
||||
@ -117,7 +119,7 @@ class SCPEncoder(MessageEncoder):
|
||||
def decode(self, encoded):
|
||||
"""transport layer message -> msg object"""
|
||||
match = SCPMESSAGE.match(encoded)
|
||||
if not(match):
|
||||
if not (match):
|
||||
return HelpRequest()
|
||||
err, dev, par, op, val = match.groups()
|
||||
if val is not None:
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Messages"""
|
||||
|
||||
# implement as class as they may need some internal 'state' later on
|
||||
@ -31,7 +30,6 @@ from secop.lib.parsing import *
|
||||
|
||||
|
||||
class TextEncoder(MessageEncoder):
|
||||
|
||||
def __init__(self):
|
||||
# build safe namespace
|
||||
ns = dict()
|
||||
|
@ -19,12 +19,10 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define (internal) SECoP Errors"""
|
||||
|
||||
|
||||
class SECOPError(RuntimeError):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.args = args
|
||||
for k, v in kwds.items():
|
||||
@ -91,8 +89,7 @@ EXCEPTIONS = dict(
|
||||
BadValue=BadValueError,
|
||||
Readonly=ReadonlyError,
|
||||
CommandFailed=CommandFailedError,
|
||||
InvalidParam=InvalidParamValueError,
|
||||
)
|
||||
InvalidParam=InvalidParamValueError, )
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Minimal testing of errors....")
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Frames"""
|
||||
|
||||
|
||||
@ -45,7 +44,6 @@ class Framer(object):
|
||||
"""resets the de/encoding stage (clears internal information)"""
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
# now some Implementations
|
||||
from null import NullFramer
|
||||
from eol import EOLFramer
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Encoding/decoding Frames"""
|
||||
|
||||
from secop.protocol.framing import Framer
|
||||
|
@ -23,9 +23,7 @@
|
||||
|
||||
from tcp import TCPServer
|
||||
|
||||
INTERFACES = {
|
||||
'tcp': TCPServer,
|
||||
}
|
||||
INTERFACES = {'tcp': TCPServer, }
|
||||
|
||||
# for 'from protocol.interface import *' to only import the dict
|
||||
__ALL__ = ['INTERFACES']
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""provides tcp interface to the SECoP Server"""
|
||||
|
||||
import os
|
||||
@ -36,7 +35,6 @@ from secop.protocol.messages import HelpMessage
|
||||
|
||||
|
||||
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
|
||||
def setup(self):
|
||||
self.log = self.server.log
|
||||
self._queue = collections.deque(maxlen=100)
|
||||
@ -49,13 +47,13 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
mysocket = self.request
|
||||
clientaddr = self.client_address
|
||||
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
|
||||
serverobj.dispatcher.add_connection(self)
|
||||
|
||||
mysocket.settimeout(.3)
|
||||
# mysocket.setblocking(False)
|
||||
# mysocket.setblocking(False)
|
||||
# start serving
|
||||
while True:
|
||||
# send replys fist, then listen for requests, timing out after 0.1s
|
||||
@ -66,7 +64,10 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
outmsg = self._queue.popleft()
|
||||
outframes = self.encoding.encode(outmsg)
|
||||
outdata = self.framing.encode(outframes)
|
||||
mysocket.sendall(outdata)
|
||||
try:
|
||||
mysocket.sendall(outdata)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
# XXX: improve: use polling/select here?
|
||||
try:
|
||||
@ -101,6 +102,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||
|
||||
def finish(self):
|
||||
"""called when handle() terminates, i.e. the socket closed"""
|
||||
self.log.info('closing connection from %s:%d' % self.client_address)
|
||||
# notify dispatcher
|
||||
self.server.dispatcher.remove_connection(self)
|
||||
# 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 encoding=%s" %
|
||||
self.encodingCLS.__name__)
|
||||
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
|
||||
TCPRequestHandler,
|
||||
bind_and_activate=True)
|
||||
SocketServer.ThreadingTCPServer.__init__(
|
||||
self, (bindto, portnum), TCPRequestHandler, bind_and_activate=True)
|
||||
self.log.info("TCPServer initiated")
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Messages"""
|
||||
|
||||
|
||||
@ -51,8 +50,11 @@ class Message(object):
|
||||
|
||||
|
||||
class Value(object):
|
||||
|
||||
def __init__(self, module, parameter=None, command=None, value=Ellipsis,
|
||||
def __init__(self,
|
||||
module,
|
||||
parameter=None,
|
||||
command=None,
|
||||
value=Ellipsis,
|
||||
**qualifiers):
|
||||
self.module = module
|
||||
self.parameter = parameter
|
||||
@ -67,9 +69,10 @@ class Value(object):
|
||||
devspec = '%s:%s' % (devspec, self.parameter)
|
||||
elif self.command:
|
||||
devspec = '%s:%s()' % (devspec, self.command)
|
||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
||||
[repr(self.value)] +
|
||||
['%s=%s' % (k, format_time(v) if k == "timestamp" else repr(v)) for k, v in self.qualifiers.items()]))
|
||||
return '%s:Value(%s)' % (devspec, ', '.join([repr(self.value)] + [
|
||||
'%s=%s' % (k, format_time(v) if k == "timestamp" else repr(v))
|
||||
for k, v in self.qualifiers.items()
|
||||
]))
|
||||
|
||||
|
||||
class Request(Message):
|
||||
@ -95,8 +98,7 @@ class Request(Message):
|
||||
for k in self.ARGS:
|
||||
m.setvalue(k, self.__dict__[k])
|
||||
m.setvalue("errorclass", errorclass[:-5]
|
||||
if errorclass.endswith('rror')
|
||||
else errorclass)
|
||||
if errorclass.endswith('rror') else errorclass)
|
||||
m.setvalue("errorinfo", errorinfo)
|
||||
return m
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define SECoP Messages"""
|
||||
|
||||
# Request Types
|
||||
@ -79,8 +78,9 @@ class Message(object):
|
||||
r = 'Device' if self.devs != ['*'] else 'Devices'
|
||||
|
||||
t = ''
|
||||
if self.MSGTYPE in [LIST, READ, WRITE, COMMAND,
|
||||
POLL, SUBSCRIBE, UNSUBSCRIBE, HELP]:
|
||||
if self.MSGTYPE in [
|
||||
LIST, READ, WRITE, COMMAND, POLL, SUBSCRIBE, UNSUBSCRIBE, HELP
|
||||
]:
|
||||
t = 'Request' if not self.result else 'Reply'
|
||||
|
||||
if self.errortype is None:
|
||||
@ -95,7 +95,6 @@ class Message(object):
|
||||
|
||||
|
||||
class Value(object):
|
||||
|
||||
def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
|
||||
self.dev = ''
|
||||
self.param = ''
|
||||
@ -111,9 +110,9 @@ class Value(object):
|
||||
if self.prop:
|
||||
devspec = '%s:%s' % (devspec, self.prop)
|
||||
return '%s:Value(%s)' % (
|
||||
devspec, ', '.join(
|
||||
[repr(self.value)] +
|
||||
['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
|
||||
devspec,
|
||||
', '.join([repr(self.value)] +
|
||||
['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
|
||||
|
||||
|
||||
class ListMessage(Message):
|
||||
@ -167,28 +166,29 @@ class HelpMessage(Message):
|
||||
|
||||
|
||||
class NoSuchDeviceError(ErrorMessage):
|
||||
|
||||
def __init__(self, *devs):
|
||||
ErrorMessage.__init__(
|
||||
self, devs=devs, errorstring="Device %r does not exist" %
|
||||
devs[0], errortype='NoSuchDevice')
|
||||
self,
|
||||
devs=devs,
|
||||
errorstring="Device %r does not exist" % devs[0],
|
||||
errortype='NoSuchDevice')
|
||||
|
||||
|
||||
class NoSuchParamError(ErrorMessage):
|
||||
|
||||
def __init__(self, dev, *params):
|
||||
ErrorMessage.__init__(
|
||||
self, devs=(dev,),
|
||||
params=params, errorstring="Device %r has no parameter %r" %
|
||||
(dev, params[0]),
|
||||
self,
|
||||
devs=(dev, ),
|
||||
params=params,
|
||||
errorstring="Device %r has no parameter %r" % (dev, params[0]),
|
||||
errortype='NoSuchParam')
|
||||
|
||||
|
||||
class ParamReadonlyError(ErrorMessage):
|
||||
|
||||
def __init__(self, dev, *params):
|
||||
ErrorMessage.__init__(
|
||||
self, devs=(dev,),
|
||||
self,
|
||||
devs=(dev, ),
|
||||
params=params,
|
||||
errorstring="Device %r, parameter %r is not writeable!" %
|
||||
(dev, params[0]),
|
||||
@ -196,30 +196,28 @@ class ParamReadonlyError(ErrorMessage):
|
||||
|
||||
|
||||
class InvalidParamValueError(ErrorMessage):
|
||||
|
||||
def __init__(self, dev, param, value, e):
|
||||
ErrorMessage.__init__(
|
||||
self, devs=(dev,),
|
||||
params=params, values=(value),
|
||||
self,
|
||||
devs=(dev, ),
|
||||
params=params,
|
||||
values=(value),
|
||||
errorstring=str(e),
|
||||
errortype='InvalidParamValueError')
|
||||
|
||||
|
||||
class InternalError(ErrorMessage):
|
||||
|
||||
def __init__(self, err, **kwds):
|
||||
ErrorMessage.__init__(
|
||||
self, errorstring=str(err),
|
||||
errortype='InternalError', **kwds)
|
||||
self, errorstring=str(err), errortype='InternalError', **kwds)
|
||||
|
||||
|
||||
MESSAGE = dict(
|
||||
(cls.MSGTYPE, cls)
|
||||
for cls
|
||||
in
|
||||
[HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
|
||||
UnsubscribeMessage, SubscribeMessage, PollMessage, CommandMessage,
|
||||
WriteMessage, ReadMessage, ListMessage])
|
||||
MESSAGE = dict((cls.MSGTYPE, cls)
|
||||
for cls in [
|
||||
HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
|
||||
UnsubscribeMessage, SubscribeMessage, PollMessage,
|
||||
CommandMessage, WriteMessage, ReadMessage, ListMessage
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Minimal testing of messages....")
|
||||
|
@ -23,9 +23,9 @@
|
||||
|
||||
# could also be some objects
|
||||
OK = 100
|
||||
BUSY = 200
|
||||
WARN = 300
|
||||
UNSTABLE = 350
|
||||
WARN = 200
|
||||
UNSTABLE = 250
|
||||
BUSY = 300
|
||||
ERROR = 400
|
||||
UNKNOWN = -1
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
# Alexander Lenz <alexander.lenz@frm2.tum.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
"""Define helpers"""
|
||||
import os
|
||||
import time
|
||||
@ -41,7 +40,6 @@ from secop.errors import ConfigError
|
||||
|
||||
|
||||
class Server(object):
|
||||
|
||||
def __init__(self, name, workdir, parentLogger=None):
|
||||
self._name = name
|
||||
self._workdir = workdir
|
||||
@ -65,9 +63,10 @@ class Server(object):
|
||||
if pidfile.is_locked():
|
||||
self.log.error('Pidfile already exists. Exiting')
|
||||
|
||||
with DaemonContext(working_directory=self._workdir,
|
||||
pidfile=pidfile,
|
||||
files_preserve=self.log.getLogfileStreams()):
|
||||
with DaemonContext(
|
||||
working_directory=self._workdir,
|
||||
pidfile=pidfile,
|
||||
files_preserve=self.log.getLogfileStreams()):
|
||||
self.run()
|
||||
|
||||
def run(self):
|
||||
@ -85,9 +84,8 @@ class Server(object):
|
||||
time.sleep(1)
|
||||
for t in self._threads:
|
||||
if not t.is_alive():
|
||||
self.log.debug(
|
||||
'thread %r died (%d still running)' %
|
||||
(t, len(self._threads)))
|
||||
self.log.debug('thread %r died (%d still running)' %
|
||||
(t, len(self._threads)))
|
||||
t.join()
|
||||
self._threads.discard(t)
|
||||
|
||||
@ -95,9 +93,11 @@ class Server(object):
|
||||
self.log.debug('Parse config file %s ...' % self._cfgfile)
|
||||
|
||||
parser = ConfigParser.SafeConfigParser()
|
||||
parser.optionxform = str
|
||||
|
||||
if not parser.read([self._cfgfile]):
|
||||
self.log.error('Couldn\'t read cfg file !')
|
||||
raise ConfigError('Couldn\'t read cfg file %r' % self._cfgfile)
|
||||
self.log.error("Couldn't read cfg file !")
|
||||
raise ConfigError("Couldn't read cfg file %r" % self._cfgfile)
|
||||
|
||||
self._interfaces = []
|
||||
|
||||
@ -113,8 +113,8 @@ class Server(object):
|
||||
if 'class' not in devopts:
|
||||
self.log.error('Device %s needs a class option!')
|
||||
raise ConfigError(
|
||||
'cfgfile %r: Device %s needs a class option!'
|
||||
% (self._cfgfile, devname))
|
||||
'cfgfile %r: Device %s needs a class option!' %
|
||||
(self._cfgfile, devname))
|
||||
# try to import the class, raise if this fails
|
||||
devopts['class'] = get_class(devopts['class'])
|
||||
# all went well so far
|
||||
@ -127,16 +127,15 @@ class Server(object):
|
||||
if 'interface' not in ifopts:
|
||||
self.log.error('Interface %s needs an interface option!')
|
||||
raise ConfigError(
|
||||
'cfgfile %r: Interface %s needs an interface option!'
|
||||
% (self._cfgfile, ifname))
|
||||
'cfgfile %r: Interface %s needs an interface option!' %
|
||||
(self._cfgfile, ifname))
|
||||
# all went well so far
|
||||
interfaceopts.append([ifname, ifopts])
|
||||
if parser.has_option('equipment', 'id'):
|
||||
equipment_id = parser.get('equipment', 'id')
|
||||
equipment_id = parser.get('equipment', 'id').replace(' ', '_')
|
||||
|
||||
self._dispatcher = self._buildObject(
|
||||
'Dispatcher', Dispatcher, dict(
|
||||
equipment_id=equipment_id))
|
||||
'Dispatcher', Dispatcher, dict(equipment_id=equipment_id))
|
||||
self._processInterfaceOptions(interfaceopts)
|
||||
self._processDeviceOptions(deviceopts)
|
||||
|
||||
@ -156,8 +155,8 @@ class Server(object):
|
||||
for d in ("'", '"'):
|
||||
if v.startswith(d) and v.endswith(d):
|
||||
devopts[k] = v[1:-1]
|
||||
devobj = devclass(self.log.getChild(devname), devopts, devname,
|
||||
self._dispatcher)
|
||||
devobj = devclass(
|
||||
self.log.getChild(devname), devopts, devname, self._dispatcher)
|
||||
devs.append([devname, devobj, export])
|
||||
|
||||
# connect devices with dispatcher
|
||||
@ -178,8 +177,8 @@ class Server(object):
|
||||
for ifname, ifopts in interfaceopts:
|
||||
ifclass = ifopts.pop('interface')
|
||||
ifclass = INTERFACES[ifclass]
|
||||
interface = self._buildObject(ifname, ifclass,
|
||||
ifopts, self._dispatcher)
|
||||
interface = self._buildObject(ifname, ifclass, ifopts,
|
||||
self._dispatcher)
|
||||
self._interfaces.append(interface)
|
||||
|
||||
def _buildObject(self, name, cls, options, *args):
|
||||
@ -187,7 +186,6 @@ class Server(object):
|
||||
# cls.__init__ should pop all used args from options!
|
||||
obj = cls(self.log.getChild(name.lower()), options, *args)
|
||||
if options:
|
||||
raise ConfigError('%s: don\'t know how to handle option(s): %s' % (
|
||||
cls.__name__,
|
||||
', '.join(options.keys())))
|
||||
raise ConfigError('%s: don\'t know how to handle option(s): %s' %
|
||||
(cls.__name__, ', '.join(options.keys())))
|
||||
return obj
|
||||
|
@ -21,7 +21,6 @@
|
||||
# *****************************************************************************
|
||||
"""Define validators."""
|
||||
|
||||
|
||||
# a Validator returns a validated object or raises an ValueError
|
||||
# easy python validators: int(), float(), str()
|
||||
# also validators should have a __repr__ returning a 'python' string
|
||||
@ -43,29 +42,25 @@ class Validator(object):
|
||||
plist = self.params[:]
|
||||
if len(args) > len(plist):
|
||||
raise ProgrammingError('%s takes %d parameters only (%d given)' % (
|
||||
self.__class__.__name__,
|
||||
len(plist), len(args)))
|
||||
self.__class__.__name__, len(plist), len(args)))
|
||||
for pval in args:
|
||||
pname, pconv = plist.pop(0)
|
||||
if pname in kwds:
|
||||
raise ProgrammingError('%s: positional parameter %s is given '
|
||||
'as keyword!' % (
|
||||
self.__class__.__name__,
|
||||
pname))
|
||||
'as keyword!' %
|
||||
(self.__class__.__name__, pname))
|
||||
self.__dict__[pname] = pconv(pval)
|
||||
for pname, pconv in plist:
|
||||
if pname in kwds:
|
||||
pval = kwds.pop(pname)
|
||||
self.__dict__[pname] = pconv(pval)
|
||||
else:
|
||||
raise ProgrammingError('%s: param %s left unspecified!' % (
|
||||
self.__class__.__name__,
|
||||
pname))
|
||||
raise ProgrammingError('%s: param %s left unspecified!' %
|
||||
(self.__class__.__name__, pname))
|
||||
|
||||
if kwds:
|
||||
raise ProgrammingError('%s got unknown arguments: %s' % (
|
||||
self.__class__.__name__,
|
||||
', '.join(list(kwds.keys()))))
|
||||
self.__class__.__name__, ', '.join(list(kwds.keys()))))
|
||||
params = []
|
||||
for pn, pt in self.params:
|
||||
pv = getattr(self, pn)
|
||||
@ -89,10 +84,17 @@ class floatrange(Validator):
|
||||
params = [('lower', float), ('upper', float)]
|
||||
|
||||
def check(self, value):
|
||||
if self.lower <= value <= self.upper:
|
||||
return value
|
||||
raise ValueError('Floatrange: value %r must be within %f and %f' %
|
||||
(value, self.lower, self.upper))
|
||||
try:
|
||||
value = float(value)
|
||||
if self.lower <= value <= 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):
|
||||
@ -100,10 +102,11 @@ class intrange(Validator):
|
||||
valuetype = int
|
||||
|
||||
def check(self, value):
|
||||
if self.lower <= value <= self.upper:
|
||||
if self.lower <= int(value) <= self.upper:
|
||||
return value
|
||||
raise ValueError('Intrange: value %r must be within %f and %f' %
|
||||
(value, self.lower, self.upper))
|
||||
raise ValueError(
|
||||
'Intrange: value %r must be an integer within %f and %f' %
|
||||
(value, self.lower, self.upper))
|
||||
|
||||
|
||||
class array(Validator):
|
||||
@ -112,8 +115,7 @@ class array(Validator):
|
||||
The size of the array can also be described by an validator
|
||||
"""
|
||||
valuetype = list
|
||||
params = [('size', lambda x: x),
|
||||
('datatype', lambda x: x)]
|
||||
params = [('size', lambda x: x), ('datatype', lambda x: x)]
|
||||
|
||||
def check(self, values):
|
||||
requested_size = len(values)
|
||||
@ -121,15 +123,13 @@ class array(Validator):
|
||||
try:
|
||||
allowed_size = self.size(requested_size)
|
||||
except ValueError as e:
|
||||
raise ValueError(
|
||||
'illegal number of elements %d, need %r: (%s)' %
|
||||
(requested_size, self.size, e))
|
||||
raise ValueError('illegal number of elements %d, need %r: (%s)'
|
||||
% (requested_size, self.size, e))
|
||||
else:
|
||||
allowed_size = self.size
|
||||
if requested_size != allowed_size:
|
||||
raise ValueError(
|
||||
'need %d elements (got %d)' %
|
||||
(allowed_size, requested_size))
|
||||
raise ValueError('need %d elements (got %d)' %
|
||||
(allowed_size, requested_size))
|
||||
# apply data-type validator to all elements and return
|
||||
res = []
|
||||
for idx, el in enumerate(values):
|
||||
@ -152,10 +152,13 @@ class vector(Validator):
|
||||
self.argstr = ', '.join([validator_to_str(e) for e in args])
|
||||
|
||||
def __call__(self, args):
|
||||
if type(args) in (str, unicode):
|
||||
args = eval(args)
|
||||
if len(args) != len(self.validators):
|
||||
raise ValueError('Vector: need exactly %d elementes (got %d)' %
|
||||
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!
|
||||
@ -197,7 +200,6 @@ class oneof(Validator):
|
||||
|
||||
|
||||
class enum(Validator):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
self.mapping = {}
|
||||
# use given kwds directly
|
||||
@ -238,23 +240,27 @@ class enum(Validator):
|
||||
def positive(value=Ellipsis):
|
||||
if value != Ellipsis:
|
||||
if value > 0:
|
||||
return value
|
||||
return float(value)
|
||||
raise ValueError('Value %r must be > 0!' % value)
|
||||
return -1e-38 # small number > 0
|
||||
|
||||
|
||||
positive.__repr__ = lambda x: validator_to_str(x)
|
||||
|
||||
|
||||
def nonnegative(value=Ellipsis):
|
||||
if value != Ellipsis:
|
||||
if value >= 0:
|
||||
return value
|
||||
return float(value)
|
||||
raise ValueError('Value %r must be >= 0!' % value)
|
||||
return 0.0
|
||||
|
||||
|
||||
nonnegative.__repr__ = lambda x: validator_to_str(x)
|
||||
|
||||
|
||||
# helpers
|
||||
|
||||
|
||||
def validator_to_str(validator):
|
||||
if isinstance(validator, Validator):
|
||||
return validator.to_string()
|
||||
@ -269,21 +275,28 @@ def validator_to_str(validator):
|
||||
|
||||
# XXX: better use a mapping here!
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "minimal testing: validators"
|
||||
for val, good, bad in [(floatrange(3.09, 5.47), 4.13, 9.27),
|
||||
(intrange(3, 5), 4, 8),
|
||||
(array(size=3, datatype=int), (1, 2, 3), (1, 2, 3, 4)),
|
||||
(vector(int, int), (12, 6), (1.23, 'X')),
|
||||
(oneof('a', 'b', 'c', 1), 'b', 'x'),
|
||||
#(record(a=int, b=float), dict(a=2,b=3.97), dict(c=9,d='X')),
|
||||
(positive, 2, 0),
|
||||
(nonnegative, 0, -1),
|
||||
(enum(a=1, b=20), 1, 12),
|
||||
]:
|
||||
print validator_to_str(val), repr(validator_from_str(validator_to_str(val)))
|
||||
for val, good, bad in [
|
||||
(floatrange(3.09, 5.47), 4.13, 9.27),
|
||||
(intrange(3, 5), 4, 8),
|
||||
(array(
|
||||
size=3, datatype=int), (1, 2, 3), (1, 2, 3, 4)),
|
||||
(vector(int, int), (12, 6), (1.23, 'X')),
|
||||
(oneof('a', 'b', 'c', 1), 'b', 'x'),
|
||||
#(record(a=int, b=float), dict(a=2,b=3.97), dict(c=9,d='X')),
|
||||
(positive, 2, 0),
|
||||
(nonnegative, 0, -1),
|
||||
(enum(
|
||||
a=1, b=20), 1, 12),
|
||||
]:
|
||||
print validator_to_str(val), repr(
|
||||
validator_from_str(validator_to_str(val)))
|
||||
print val(good), 'OK'
|
||||
try:
|
||||
val(bad)
|
||||
|
Reference in New Issue
Block a user