polishing for a demo

+ adopting additional requests

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

View File

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

29
etc/cryo.cfg Normal file
View File

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

View File

@ -1,7 +1,7 @@
[equipment]
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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 !

View File

@ -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

View File

@ -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,
),
}

View File

@ -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):

View File

@ -21,7 +21,7 @@
#
# *****************************************************************************
from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem
from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem, QMessageBox
from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal
from 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)

View File

@ -21,7 +21,7 @@
#
# *****************************************************************************
from PyQt4.QtGui import QWidget, QLabel
from PyQt4.QtGui import QWidget, QLabel, QMessageBox
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
from 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

View File

@ -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():

View File

@ -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)

View File

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

View 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

View File

@ -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"

View File

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

View File

@ -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)

View File

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

View File

@ -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')

View File

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

View File

@ -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")

View File

@ -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

View File

@ -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'
]

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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....")

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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']

View File

@ -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")

View File

@ -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

View File

@ -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....")

View File

@ -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

View File

@ -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

View File

@ -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)