provide setup for MLZ_Amagnet to be used @PSI soon
Also implement lots of fixes and improvements. fixes: #3381 Change-Id: Ibe6664da00756ae5813b90f190295045808b2ff0
This commit is contained in:
parent
63418fce04
commit
2bb96bea70
@ -22,9 +22,12 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
|
|
||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
@ -39,7 +42,17 @@ def main(argv=None):
|
|||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
|
||||||
if '-d' in argv:
|
if '-h' in argv or '--help' in argv:
|
||||||
|
print("Usage: secop-gui [-d] [-h] [host:[port]]")
|
||||||
|
print()
|
||||||
|
print("Option GNU long option Meaning")
|
||||||
|
print("-h --help Show this message")
|
||||||
|
print("-d --debug Enable debug output")
|
||||||
|
print()
|
||||||
|
print("if not given, host defaults to 'localhost' and port to 10767")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if '-d' in argv or '--debug' in argv:
|
||||||
mlzlog.initLogging('gui', 'debug')
|
mlzlog.initLogging('gui', 'debug')
|
||||||
else:
|
else:
|
||||||
mlzlog.initLogging('gui', 'info')
|
mlzlog.initLogging('gui', 'info')
|
||||||
|
103
etc/amagnet.cfg
Normal file
103
etc/amagnet.cfg
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
[equipment]
|
||||||
|
id=MLZ_amagnet(Garfield)
|
||||||
|
.visibility=expert
|
||||||
|
foo=bar
|
||||||
|
|
||||||
|
[interface tcp]
|
||||||
|
interface=tcp
|
||||||
|
bindto=0.0.0.0
|
||||||
|
bindport=10767
|
||||||
|
# protocol to use for this interface
|
||||||
|
framing=eol
|
||||||
|
encoding=demo
|
||||||
|
|
||||||
|
[device enable]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice='tango://amagnet.antares.frm2:10000/box/plc/_enable'
|
||||||
|
value.datatype=["enum", {'On':1,'Off':0}]
|
||||||
|
target.datatype=["enum", {'On':1,'Off':0}]
|
||||||
|
.description='Enables to Output of the Powersupply'
|
||||||
|
.visibility='advanced'
|
||||||
|
|
||||||
|
[device polarity]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_polarity
|
||||||
|
value.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||||
|
target.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||||
|
.description=polarity (+/-) switch
|
||||||
|
|
||||||
|
there is an interlock in the plc:
|
||||||
|
if there is current, switching polarity is forbidden
|
||||||
|
if polarity is short, powersupply is disabled
|
||||||
|
.visibility=advanced
|
||||||
|
comtries=50
|
||||||
|
|
||||||
|
|
||||||
|
[device symmetry]
|
||||||
|
class=secop_mlz.entangle.NamedDigitalOutput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_symmetric
|
||||||
|
value.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||||
|
target.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||||
|
.description=par/ser switch selecting (a)symmetric mode
|
||||||
|
|
||||||
|
symmetric is ser, asymmetric is par
|
||||||
|
.visibility=advanced
|
||||||
|
|
||||||
|
[device T1]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_t1
|
||||||
|
.description=Temperature1 of the coils system
|
||||||
|
#warnlimits=(0, 50)
|
||||||
|
#unit=degC
|
||||||
|
|
||||||
|
[device T2]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_t2
|
||||||
|
.description=Temperature2 of the coils system
|
||||||
|
#warnlimits=(0, 50)
|
||||||
|
#unit=degC
|
||||||
|
|
||||||
|
[device T3]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_t3
|
||||||
|
.description=Temperature3 of the coils system
|
||||||
|
#warnlimits=(0, 50)
|
||||||
|
#unit=degC
|
||||||
|
|
||||||
|
[device T4]
|
||||||
|
class=secop_mlz.entangle.AnalogInput
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/plc/_t4
|
||||||
|
.description=Temperature4 of the coils system
|
||||||
|
#warnlimits=(0, 50)
|
||||||
|
#unit=degC
|
||||||
|
|
||||||
|
[device currentsource]
|
||||||
|
class=secop_mlz.entangle.PowerSupply
|
||||||
|
tangodevice=tango://amagnet.antares.frm2:10000/box/lambda/curr
|
||||||
|
.description=Device for the magnet power supply (current mode)
|
||||||
|
abslimits=(0,200)
|
||||||
|
speed=1
|
||||||
|
ramp=60
|
||||||
|
precision=0.02
|
||||||
|
current=0
|
||||||
|
voltage=10
|
||||||
|
#unit=A
|
||||||
|
.visibility=advanced
|
||||||
|
|
||||||
|
[device mf]
|
||||||
|
class=secop_mlz.amagnet.GarfieldMagnet
|
||||||
|
.description=magnetic field device, handling polarity switching and stuff
|
||||||
|
subdev_currentsource=currentsource
|
||||||
|
subdev_enable=enable
|
||||||
|
subdev_polswitch=polarity
|
||||||
|
subdev_symmetry=symmetry
|
||||||
|
#unit=T
|
||||||
|
userlimits=(-0.35, 0.35)
|
||||||
|
calibrationtable={'symmetric':[0.00186517, 0.0431937, -0.185956, 0.0599757, 0.194042],
|
||||||
|
'short': [0.0, 0.0, 0.0, 0.0, 0.0],
|
||||||
|
'asymmetric':[0.00136154, 0.027454, -0.120951, 0.0495289, 0.110689]}
|
||||||
|
.meaning=The magnetic field
|
||||||
|
.priority=100
|
||||||
|
.visibility=user
|
||||||
|
|
||||||
|
abslimits.default=0,0.4
|
@ -1,4 +1,5 @@
|
|||||||
#--extra-index-url https://forge.frm2.tum.de/simple
|
#--extra-index-url https://forge.frm2.tum.de/simple
|
||||||
|
serial
|
||||||
mlzlog >=0.2.0
|
mlzlog >=0.2.0
|
||||||
# for generating docu
|
# for generating docu
|
||||||
markdown>=2.6
|
markdown>=2.6
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
# nothing here yet.
|
# nothing here yet.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import code
|
import code
|
||||||
|
|
||||||
|
|
||||||
@ -47,16 +49,19 @@ class NameSpace(dict):
|
|||||||
dict.__delitem__(self, name)
|
dict.__delitem__(self, name)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
|
except ImportError:
|
||||||
|
import configparser as ConfigParser
|
||||||
|
|
||||||
|
|
||||||
def getClientOpts(cfgfile):
|
def getClientOpts(cfgfile):
|
||||||
parser = ConfigParser.SafeConfigParser()
|
parser = ConfigParser.SafeConfigParser()
|
||||||
if not parser.read([cfgfile + '.cfg']):
|
if not parser.read([cfgfile + '.cfg']):
|
||||||
print "Error reading cfg file %r" % cfgfile
|
print("Error reading cfg file %r" % cfgfile)
|
||||||
return {}
|
return {}
|
||||||
if not parser.has_section('client'):
|
if not parser.has_section('client'):
|
||||||
print "No Server section found!"
|
print("No Server section found!")
|
||||||
return dict(item for item in parser.items('client'))
|
return dict(item for item in parser.items('client'))
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +88,7 @@ class ClientConsole(object):
|
|||||||
|
|
||||||
def helpCmd(self, arg=Ellipsis):
|
def helpCmd(self, arg=Ellipsis):
|
||||||
if arg is Ellipsis:
|
if arg is Ellipsis:
|
||||||
print "No help available yet"
|
print("No help available yet")
|
||||||
else:
|
else:
|
||||||
help(arg)
|
help(arg)
|
||||||
|
|
||||||
|
@ -21,12 +21,20 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define Client side proxies"""
|
"""Define Client side proxies"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
import serial
|
import serial
|
||||||
from select import select
|
from select import select
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# Py2/3
|
||||||
|
try:
|
||||||
import Queue
|
import Queue
|
||||||
|
except ImportError:
|
||||||
|
import queue as Queue
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import mlzlog
|
import mlzlog
|
||||||
@ -71,14 +79,14 @@ class TCPConnection(object):
|
|||||||
if dlist[0] in rlist + wlist:
|
if dlist[0] in rlist + wlist:
|
||||||
newdata = self._io.recv(1024)
|
newdata = self._io.recv(1024)
|
||||||
if dlist[0] in xlist:
|
if dlist[0] in xlist:
|
||||||
print "Problem: exception on socket, reconnecting!"
|
print("Problem: exception on socket, reconnecting!")
|
||||||
for cb, arg in self.callbacks:
|
for cb, arg in self.callbacks:
|
||||||
cb(arg)
|
cb(arg)
|
||||||
return
|
return
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
pass
|
pass
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print err, "reconnecting"
|
print(err, "reconnecting")
|
||||||
for cb, arg in self.callbacks:
|
for cb, arg in self.callbacks:
|
||||||
cb(arg)
|
cb(arg)
|
||||||
return
|
return
|
||||||
@ -260,7 +268,6 @@ class Client(object):
|
|||||||
if spec else "got expected reply '%s'" % msgtype)
|
if spec else "got expected reply '%s'" % msgtype)
|
||||||
entry.extend([False, msgtype, spec, data])
|
entry.extend([False, msgtype, spec, data])
|
||||||
entry[0].set()
|
entry[0].set()
|
||||||
return
|
|
||||||
|
|
||||||
def encode_message(self, requesttype, spec='', data=None):
|
def encode_message(self, requesttype, spec='', data=None):
|
||||||
"""encodes the given message to a string
|
"""encodes the given message to a string
|
||||||
@ -292,12 +299,17 @@ class Client(object):
|
|||||||
|
|
||||||
def _handle_event(self, spec, data):
|
def _handle_event(self, spec, data):
|
||||||
"""handles event"""
|
"""handles event"""
|
||||||
self.log.debug('handle_event %r %r' % (spec, data))
|
# self.log.debug('handle_event %r %r' % (spec, data))
|
||||||
if ':' not in spec:
|
if ':' not in spec:
|
||||||
self.log.warning("deprecated specifier %r" % spec)
|
self.log.warning("deprecated specifier %r" % spec)
|
||||||
spec = '%s:value' % spec
|
spec = '%s:value' % spec
|
||||||
modname, pname = spec.split(':', 1)
|
modname, pname = spec.split(':', 1)
|
||||||
|
previous = '<unset>'
|
||||||
|
if modname in self._cache:
|
||||||
|
if pname in self._cache:
|
||||||
|
previous = self._cache[modname][pname]
|
||||||
self._cache.setdefault(modname, {})[pname] = Value(*data)
|
self._cache.setdefault(modname, {})[pname] = Value(*data)
|
||||||
|
# self.log.info('cache: %s:%s=%r (was: %s)', modname, pname, data, previous)
|
||||||
if spec in self.callbacks:
|
if spec in self.callbacks:
|
||||||
for func in self.callbacks[spec]:
|
for func in self.callbacks[spec]:
|
||||||
try:
|
try:
|
||||||
@ -351,6 +363,12 @@ class Client(object):
|
|||||||
['parameters', 'commands'], module)
|
['parameters', 'commands'], module)
|
||||||
|
|
||||||
self.describing_data = describing_data
|
self.describing_data = describing_data
|
||||||
|
# import pprint
|
||||||
|
# def r(stuff):
|
||||||
|
# if isinstance(stuff, dict):
|
||||||
|
# return dict((k,r(v)) for k,v in stuff.items())
|
||||||
|
# return stuff
|
||||||
|
# pprint.pprint(r(describing_data))
|
||||||
|
|
||||||
for module, moduleData in self.describing_data['modules'].items():
|
for module, moduleData in self.describing_data['modules'].items():
|
||||||
for parameter, parameterData in moduleData[
|
for parameter, parameterData in moduleData[
|
||||||
@ -359,7 +377,7 @@ class Client(object):
|
|||||||
self.describing_data['modules'][module]['parameters'] \
|
self.describing_data['modules'][module]['parameters'] \
|
||||||
[parameter]['datatype'] = datatype
|
[parameter]['datatype'] = datatype
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print formatException(verbose=True)
|
print(formatException(verbose=True))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def register_callback(self, module, parameter, cb):
|
def register_callback(self, module, parameter, cb):
|
||||||
@ -402,6 +420,10 @@ class Client(object):
|
|||||||
if msgtype == "*IDN?":
|
if msgtype == "*IDN?":
|
||||||
return self.secop_id
|
return self.secop_id
|
||||||
|
|
||||||
|
# sanitize input
|
||||||
|
msgtype = str(msgtype)
|
||||||
|
spec = str(spec)
|
||||||
|
|
||||||
if msgtype not in ('*IDN?', 'describe', 'activate', 'deactivate', 'do',
|
if msgtype not in ('*IDN?', 'describe', 'activate', 'deactivate', 'do',
|
||||||
'change', 'read', 'ping', 'help'):
|
'change', 'read', 'ping', 'help'):
|
||||||
raise EXCEPTIONS['Protocol'](args=[
|
raise EXCEPTIONS['Protocol'](args=[
|
||||||
@ -411,9 +433,7 @@ class Client(object):
|
|||||||
errorinfo='%r: No Such Messagetype defined!' % msgtype, ),
|
errorinfo='%r: No Such Messagetype defined!' % msgtype, ),
|
||||||
])
|
])
|
||||||
|
|
||||||
# sanitize input + handle syntactic sugar
|
# handle syntactic sugar
|
||||||
msgtype = str(msgtype)
|
|
||||||
spec = str(spec)
|
|
||||||
if msgtype == 'change' and ':' not in spec:
|
if msgtype == 'change' and ':' not in spec:
|
||||||
spec = spec + ':target'
|
spec = spec + ':target'
|
||||||
if msgtype == 'read' and ':' not in spec:
|
if msgtype == 'read' and ':' not in spec:
|
||||||
@ -460,14 +480,6 @@ class Client(object):
|
|||||||
if self._thread and self._thread.is_alive():
|
if self._thread and self._thread.is_alive():
|
||||||
self.thread.join(self._thread)
|
self.thread.join(self._thread)
|
||||||
|
|
||||||
def handle_async(self, msg):
|
|
||||||
self.log.info("Got async update %r" % msg)
|
|
||||||
device = msg.device
|
|
||||||
param = msg.param
|
|
||||||
value = msg.value
|
|
||||||
self._cache.getdefault(device, {})[param] = value
|
|
||||||
# XXX: further notification-callbacks needed ???
|
|
||||||
|
|
||||||
def startup(self, async=False):
|
def startup(self, async=False):
|
||||||
self._issueDescribe()
|
self._issueDescribe()
|
||||||
# always fill our cache
|
# always fill our cache
|
||||||
@ -524,7 +536,7 @@ class Client(object):
|
|||||||
return self.getModuleProperties(module)['interface']
|
return self.getModuleProperties(module)['interface']
|
||||||
|
|
||||||
def getCommands(self, module):
|
def getCommands(self, module):
|
||||||
return self.describing_data['modules'][module]['commands'].keys()
|
return self.describing_data['modules'][module]['commands']
|
||||||
|
|
||||||
def getProperties(self, module, parameter):
|
def getProperties(self, module, parameter):
|
||||||
return self.describing_data['modules'][module]['parameters'][parameter]
|
return self.describing_data['modules'][module]['parameters'][parameter]
|
||||||
|
@ -32,6 +32,7 @@ __all__ = [
|
|||||||
"BoolType", "EnumType",
|
"BoolType", "EnumType",
|
||||||
"BLOBType", "StringType",
|
"BLOBType", "StringType",
|
||||||
"TupleOf", "ArrayOf", "StructOf",
|
"TupleOf", "ArrayOf", "StructOf",
|
||||||
|
"Command",
|
||||||
]
|
]
|
||||||
|
|
||||||
# base class for all DataTypes
|
# base class for all DataTypes
|
||||||
@ -39,6 +40,7 @@ __all__ = [
|
|||||||
|
|
||||||
class DataType(object):
|
class DataType(object):
|
||||||
as_json = ['undefined']
|
as_json = ['undefined']
|
||||||
|
IS_COMMAND = False
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""validate a external representation and return an internal one"""
|
"""validate a external representation and return an internal one"""
|
||||||
@ -139,7 +141,7 @@ class IntRange(DataType):
|
|||||||
return "IntRange(%d, %d)" % (self.min, self.max)
|
return "IntRange(%d, %d)" % (self.min, self.max)
|
||||||
if self.min is not None:
|
if self.min is not None:
|
||||||
return "IntRange(%d)" % self.min
|
return "IntRange(%d)" % self.min
|
||||||
return "IntRange(%d)" % self.min
|
return "IntRange()"
|
||||||
|
|
||||||
def export(self, value):
|
def export(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -159,7 +161,6 @@ class EnumType(DataType):
|
|||||||
num = 0
|
num = 0
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if not isinstance(arg, str):
|
if not isinstance(arg, str):
|
||||||
print arg, type(arg)
|
|
||||||
raise ValueError('EnumType entries MUST be strings!')
|
raise ValueError('EnumType entries MUST be strings!')
|
||||||
self.entries[num] = arg
|
self.entries[num] = arg
|
||||||
num += 1
|
num += 1
|
||||||
@ -172,8 +173,8 @@ class EnumType(DataType):
|
|||||||
v,
|
v,
|
||||||
self.entries[v])
|
self.entries[v])
|
||||||
self.entries[v] = k
|
self.entries[v] = k
|
||||||
if len(self.entries) == 0:
|
# if len(self.entries) == 0:
|
||||||
raise ValueError('Empty enums ae not allowed!')
|
# raise ValueError('Empty enums ae not allowed!')
|
||||||
self.reversed = {}
|
self.reversed = {}
|
||||||
for k, v in self.entries.items():
|
for k, v in self.entries.items():
|
||||||
if v in self.reversed:
|
if v in self.reversed:
|
||||||
@ -442,7 +443,7 @@ class StructOf(DataType):
|
|||||||
if len(value.keys()) != len(self.named_subtypes.keys()):
|
if len(value.keys()) != len(self.named_subtypes.keys()):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Illegal number of Arguments! Need %d arguments.', len(
|
'Illegal number of Arguments! Need %d arguments.', len(
|
||||||
self.namd_subtypes.keys()))
|
self.named_subtypes.keys()))
|
||||||
# validate elements and return as dict
|
# validate elements and return as dict
|
||||||
return dict((str(k), self.named_subtypes[k].validate(v))
|
return dict((str(k), self.named_subtypes[k].validate(v))
|
||||||
for k, v in value.items())
|
for k, v in value.items())
|
||||||
@ -463,6 +464,60 @@ class StructOf(DataType):
|
|||||||
return self.validate(dict(value))
|
return self.validate(dict(value))
|
||||||
|
|
||||||
|
|
||||||
|
class Command(DataType):
|
||||||
|
IS_COMMAND = True
|
||||||
|
|
||||||
|
def __init__(self, argtypes=[], resulttype=None):
|
||||||
|
for arg in argsin:
|
||||||
|
if not isinstance(arg, DataType):
|
||||||
|
raise ValueError('Command: Argument types must be DataTypes!')
|
||||||
|
if resulttype is not None:
|
||||||
|
if not isinstance(resulttype, DataType):
|
||||||
|
raise ValueError('Command: result type must be DataTypes!')
|
||||||
|
self.argtypes = argtypes
|
||||||
|
self.resulttype = resulttype
|
||||||
|
|
||||||
|
if resulttype is not None:
|
||||||
|
self.as_json = ['command',
|
||||||
|
[t.as_json for t in argtypes],
|
||||||
|
resulttype.as_json]
|
||||||
|
else:
|
||||||
|
self.as_json = ['command',
|
||||||
|
[t.as_json for t in argtypes],
|
||||||
|
None] # XXX: or NoneType ???
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
argstr = ', '.join(repr(arg) for arg in self.argtypes)
|
||||||
|
if self.resulttype is None:
|
||||||
|
return 'Command(%s)' % argstr
|
||||||
|
return 'Command(%s)->%s' % (argstr, repr(self.resulttype))
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
"""return the validated arguments value or raise"""
|
||||||
|
try:
|
||||||
|
if len(value) != len(self.argtypes):
|
||||||
|
raise ValueError(
|
||||||
|
'Illegal number of Arguments! Need %d arguments.', len(
|
||||||
|
self.argtypes))
|
||||||
|
# validate elements and return
|
||||||
|
return [t.validate(v) for t, v in zip(self.argtypes, value)]
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError('Can not validate %s: %s', repr(value), str(exc))
|
||||||
|
|
||||||
|
def export(self, value):
|
||||||
|
"""returns a python object fit for serialisation"""
|
||||||
|
if len(value) != len(self.argtypes):
|
||||||
|
raise ValueError(
|
||||||
|
'Illegal number of Arguments! Need %d arguments.' % len(
|
||||||
|
self.argtypes))
|
||||||
|
# return [t.export(v) for t,v in zip(self.argtypes, value)]
|
||||||
|
|
||||||
|
def from_string(self, text):
|
||||||
|
import ast
|
||||||
|
value = ast.literal_eval(text)
|
||||||
|
return self.validate(value)
|
||||||
|
|
||||||
|
|
||||||
# XXX: derive from above classes automagically!
|
# XXX: derive from above classes automagically!
|
||||||
DATATYPES = dict(
|
DATATYPES = dict(
|
||||||
bool=lambda: BoolType(),
|
bool=lambda: BoolType(),
|
||||||
@ -476,6 +531,7 @@ DATATYPES = dict(
|
|||||||
enum=lambda kwds: EnumType(**kwds),
|
enum=lambda kwds: EnumType(**kwds),
|
||||||
struct=lambda named_subtypes: StructOf(
|
struct=lambda named_subtypes: StructOf(
|
||||||
**dict((n, get_datatype(t)) for n, t in named_subtypes.items())),
|
**dict((n, get_datatype(t)) for n, t in named_subtypes.items())),
|
||||||
|
command=Command,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,42 +39,54 @@ class ProgrammingError(SECoPServerError):
|
|||||||
class SECoPError(SECoPServerError):
|
class SECoPError(SECoPServerError):
|
||||||
errorclass = 'InternalError'
|
errorclass = 'InternalError'
|
||||||
|
|
||||||
|
|
||||||
class NoSuchModuleError(SECoPError):
|
class NoSuchModuleError(SECoPError):
|
||||||
errorclass = 'NoSuchModule'
|
errorclass = 'NoSuchModule'
|
||||||
|
|
||||||
|
|
||||||
class NoSuchParameterError(SECoPError):
|
class NoSuchParameterError(SECoPError):
|
||||||
errorclass = 'NoSuchParameter'
|
errorclass = 'NoSuchParameter'
|
||||||
|
|
||||||
|
|
||||||
class NoSuchCommandError(SECoPError):
|
class NoSuchCommandError(SECoPError):
|
||||||
errorclass = 'NoSuchCommand'
|
errorclass = 'NoSuchCommand'
|
||||||
|
|
||||||
|
|
||||||
class CommandFailedError(SECoPError):
|
class CommandFailedError(SECoPError):
|
||||||
errorclass = 'CommandFailed'
|
errorclass = 'CommandFailed'
|
||||||
|
|
||||||
|
|
||||||
class CommandRunningError(SECoPError):
|
class CommandRunningError(SECoPError):
|
||||||
errorclass = 'CommandRunning'
|
errorclass = 'CommandRunning'
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyError(SECoPError):
|
class ReadOnlyError(SECoPError):
|
||||||
errorclass = 'ReadOnly'
|
errorclass = 'ReadOnly'
|
||||||
|
|
||||||
|
|
||||||
class BadValueError(SECoPError):
|
class BadValueError(SECoPError):
|
||||||
errorclass = 'BadValue'
|
errorclass = 'BadValue'
|
||||||
|
|
||||||
|
|
||||||
class CommunicationError(SECoPError):
|
class CommunicationError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed'
|
errorclass = 'CommunicationFailed'
|
||||||
|
|
||||||
|
|
||||||
class TimeoutError(SECoPError):
|
class TimeoutError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed' # XXX: add to SECop messages
|
errorclass = 'CommunicationFailed' # XXX: add to SECop messages
|
||||||
|
|
||||||
|
|
||||||
class HardwareError(SECoPError):
|
class HardwareError(SECoPError):
|
||||||
errorclass = 'CommunicationFailed' # XXX: Add to SECoP messages
|
errorclass = 'CommunicationFailed' # XXX: Add to SECoP messages
|
||||||
|
|
||||||
|
|
||||||
class IsBusyError(SECoPError):
|
class IsBusyError(SECoPError):
|
||||||
errorclass = 'IsBusy'
|
errorclass = 'IsBusy'
|
||||||
|
|
||||||
|
|
||||||
class IsErrorError(SECoPError):
|
class IsErrorError(SECoPError):
|
||||||
errorclass = 'IsError'
|
errorclass = 'IsError'
|
||||||
|
|
||||||
|
|
||||||
class DisabledError(SECoPError):
|
class DisabledError(SECoPError):
|
||||||
errorclass = 'Disabled'
|
errorclass = 'Disabled'
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem, QMessageBox
|
from PyQt4.QtGui import QMainWindow, QInputDialog, QTreeWidgetItem, QMessageBox
|
||||||
from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal
|
from PyQt4.QtCore import pyqtSignature as qtsig, QObject, pyqtSignal
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ class MainWindow(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
self._addNode(host)
|
self._addNode(host)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
print(e)
|
||||||
|
|
||||||
@qtsig('')
|
@qtsig('')
|
||||||
def on_actionAdd_SEC_node_triggered(self):
|
def on_actionAdd_SEC_node_triggered(self):
|
||||||
@ -108,11 +110,11 @@ class MainWindow(QMainWindow):
|
|||||||
'Connecting to %s failed!' % host, str(e))
|
'Connecting to %s failed!' % host, str(e))
|
||||||
|
|
||||||
def on_validateCheckBox_toggled(self, state):
|
def on_validateCheckBox_toggled(self, state):
|
||||||
print "validateCheckBox_toggled", state
|
print("validateCheckBox_toggled", state)
|
||||||
|
|
||||||
def on_visibilityComboBox_activated(self, level):
|
def on_visibilityComboBox_activated(self, level):
|
||||||
if level in ['user', 'admin', 'expert']:
|
if level in ['user', 'admin', 'expert']:
|
||||||
print "visibility Level now:", level
|
print("visibility Level now:", level)
|
||||||
|
|
||||||
def on_treeWidget_currentItemChanged(self, current, previous):
|
def on_treeWidget_currentItemChanged(self, current, previous):
|
||||||
if current.type() == ITEM_TYPE_NODE:
|
if current.type() == ITEM_TYPE_NODE:
|
||||||
|
@ -21,38 +21,13 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
from PyQt4.QtGui import QWidget, QLabel, QMessageBox, QCheckBox
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from PyQt4.QtGui import QWidget, QLabel, QPushButton as QButton, QLineEdit, QMessageBox, QCheckBox, QSizePolicy
|
||||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
|
from secop.gui.params import ParameterView
|
||||||
|
|
||||||
class ParameterButtons(QWidget):
|
|
||||||
setRequested = pyqtSignal(str, str, str) # module, parameter, target
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
module,
|
|
||||||
parameter,
|
|
||||||
initval='',
|
|
||||||
readonly=True,
|
|
||||||
parent=None):
|
|
||||||
super(ParameterButtons, self).__init__(parent)
|
|
||||||
loadUi(self, 'parambuttons.ui')
|
|
||||||
|
|
||||||
self._module = module
|
|
||||||
self._parameter = parameter
|
|
||||||
|
|
||||||
self.currentLineEdit.setText(str(initval))
|
|
||||||
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,
|
|
||||||
self.setLineEdit.text())
|
|
||||||
|
|
||||||
|
|
||||||
class ParameterGroup(QWidget):
|
class ParameterGroup(QWidget):
|
||||||
@ -79,7 +54,7 @@ class ParameterGroup(QWidget):
|
|||||||
self._row += 1
|
self._row += 1
|
||||||
|
|
||||||
def on_toggle_clicked(self):
|
def on_toggle_clicked(self):
|
||||||
print "ParameterGroup.on_toggle_clicked"
|
print("ParameterGroup.on_toggle_clicked")
|
||||||
if self.paramGroupBox.isChecked():
|
if self.paramGroupBox.isChecked():
|
||||||
for w in self._widgets:
|
for w in self._widgets:
|
||||||
w.show()
|
w.show()
|
||||||
@ -122,6 +97,9 @@ class ModuleCtrl(QWidget):
|
|||||||
if group is not None:
|
if group is not None:
|
||||||
allGroups.add(group)
|
allGroups.add(group)
|
||||||
paramsByGroup.setdefault(group, []).append(param)
|
paramsByGroup.setdefault(group, []).append(param)
|
||||||
|
# enforce reading initial value if not already in cache
|
||||||
|
if param not in initValues:
|
||||||
|
self._node.getParameter(self._module, param)
|
||||||
|
|
||||||
groupWidgets = {} # groupname -> CheckBoxWidget for (un)folding
|
groupWidgets = {} # groupname -> CheckBoxWidget for (un)folding
|
||||||
|
|
||||||
@ -137,9 +115,12 @@ class ModuleCtrl(QWidget):
|
|||||||
|
|
||||||
# check if there is a param of the same name too
|
# check if there is a param of the same name too
|
||||||
if group in params:
|
if group in params:
|
||||||
|
datatype = self._node.getProperties(
|
||||||
|
self._module, group).get(
|
||||||
|
'datatype', None)
|
||||||
# yes: create a widget for this as well
|
# yes: create a widget for this as well
|
||||||
labelstr, buttons = self._makeEntry(
|
labelstr, buttons = self._makeEntry(
|
||||||
param, initValues[param].value, nolabel=True, checkbox=checkbox, invert=True)
|
param, initValues[param].value, datatype=datatype, nolabel=True, checkbox=checkbox, invert=True)
|
||||||
checkbox.setText(labelstr)
|
checkbox.setText(labelstr)
|
||||||
|
|
||||||
# add to Layout (yes: ignore the label!)
|
# add to Layout (yes: ignore the label!)
|
||||||
@ -150,11 +131,19 @@ class ModuleCtrl(QWidget):
|
|||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
# loop over all params and insert and connect
|
# loop over all params and insert and connect
|
||||||
for param in paramsByGroup[param]:
|
for param_ in paramsByGroup[param]:
|
||||||
if param == group:
|
if param_ == group:
|
||||||
continue
|
continue
|
||||||
|
if param_ not in initValues:
|
||||||
|
initval = None
|
||||||
|
print("Warning: %r not in initValues!" % param_)
|
||||||
|
else:
|
||||||
|
initval = initValues[param_].value
|
||||||
|
datatype = self._node.getProperties(
|
||||||
|
self._module, param_).get(
|
||||||
|
'datatype', None)
|
||||||
label, buttons = self._makeEntry(
|
label, buttons = self._makeEntry(
|
||||||
param, initValues[param].value, checkbox=checkbox, invert=False)
|
param_, initval, checkbox=checkbox, invert=False)
|
||||||
|
|
||||||
# add to Layout
|
# add to Layout
|
||||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
self.paramGroupBox.layout().addWidget(label, row, 0)
|
||||||
@ -166,18 +155,43 @@ class ModuleCtrl(QWidget):
|
|||||||
# or is named after a group (otherwise its created above)
|
# or is named after a group (otherwise its created above)
|
||||||
props = self._node.getProperties(self._module, param)
|
props = self._node.getProperties(self._module, param)
|
||||||
if props.get('group', param) == param:
|
if props.get('group', param) == param:
|
||||||
|
datatype = self._node.getProperties(
|
||||||
|
self._module, param).get(
|
||||||
|
'datatype', None)
|
||||||
label, buttons = self._makeEntry(
|
label, buttons = self._makeEntry(
|
||||||
param, initValues[param].value)
|
param, initValues[param].value, datatype=datatype)
|
||||||
|
|
||||||
# add to Layout
|
# add to Layout
|
||||||
self.paramGroupBox.layout().addWidget(label, row, 0)
|
self.paramGroupBox.layout().addWidget(label, row, 0)
|
||||||
self.paramGroupBox.layout().addWidget(buttons, row, 1)
|
self.paramGroupBox.layout().addWidget(buttons, row, 1)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
|
# also populate properties
|
||||||
|
self._propWidgets = {}
|
||||||
|
props = self._node.getModuleProperties(self._module)
|
||||||
|
row = 0
|
||||||
|
for prop in sorted(props):
|
||||||
|
label = QLabel(prop + ':')
|
||||||
|
label.setFont(self._labelfont)
|
||||||
|
label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
|
|
||||||
|
# make 'display' label
|
||||||
|
view = QLabel(str(props[prop]))
|
||||||
|
view.setFont(self.font())
|
||||||
|
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||||
|
view.setWordWrap(True)
|
||||||
|
|
||||||
|
self.propertyGroupBox.layout().addWidget(label, row, 0)
|
||||||
|
self.propertyGroupBox.layout().addWidget(view, row, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
self._propWidgets[prop] = (label, view)
|
||||||
|
|
||||||
def _makeEntry(
|
def _makeEntry(
|
||||||
self,
|
self,
|
||||||
param,
|
param,
|
||||||
initvalue,
|
initvalue,
|
||||||
|
datatype=None,
|
||||||
nolabel=False,
|
nolabel=False,
|
||||||
checkbox=None,
|
checkbox=None,
|
||||||
invert=False):
|
invert=False):
|
||||||
@ -194,8 +208,12 @@ class ModuleCtrl(QWidget):
|
|||||||
if checkbox and not invert:
|
if checkbox and not invert:
|
||||||
labelstr = ' ' + labelstr
|
labelstr = ' ' + labelstr
|
||||||
|
|
||||||
buttons = ParameterButtons(
|
buttons = ParameterView(
|
||||||
self._module, param, initvalue, props['readonly'])
|
self._module,
|
||||||
|
param,
|
||||||
|
datatype=datatype,
|
||||||
|
initvalue=initvalue,
|
||||||
|
readonly=props['readonly'])
|
||||||
buttons.setRequested.connect(self._set_Button_pressed)
|
buttons.setRequested.connect(self._set_Button_pressed)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
@ -243,5 +261,4 @@ class ModuleCtrl(QWidget):
|
|||||||
def _updateValue(self, module, parameter, value):
|
def _updateValue(self, module, parameter, value):
|
||||||
if module != self._module:
|
if module != self._module:
|
||||||
return
|
return
|
||||||
|
self._paramWidgets[parameter][1].updateValue(str(value[0]))
|
||||||
self._paramWidgets[parameter][1].currentLineEdit.setText(str(value[0]))
|
|
||||||
|
@ -24,11 +24,12 @@
|
|||||||
import pprint
|
import pprint
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics
|
from PyQt4.QtGui import QWidget, QTextCursor, QFont, QFontMetrics, QLabel, QPushButton, QLineEdit, QMessageBox, QCheckBox, QSizePolicy
|
||||||
from PyQt4.QtCore import pyqtSignature as qtsig, Qt
|
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||||
|
|
||||||
from secop.gui.util import loadUi
|
from secop.gui.util import loadUi
|
||||||
from secop.protocol.errors import SECOPError
|
from secop.protocol.errors import SECOPError
|
||||||
|
from secop.datatypes import StringType, EnumType
|
||||||
|
|
||||||
|
|
||||||
class NodeCtrl(QWidget):
|
class NodeCtrl(QWidget):
|
||||||
@ -44,6 +45,9 @@ class NodeCtrl(QWidget):
|
|||||||
self.protocolVersionLabel.setText(self._node.protocolVersion)
|
self.protocolVersionLabel.setText(self._node.protocolVersion)
|
||||||
self._clearLog()
|
self._clearLog()
|
||||||
|
|
||||||
|
# now populate modules tab
|
||||||
|
self._init_modules_tab()
|
||||||
|
|
||||||
@qtsig('')
|
@qtsig('')
|
||||||
def on_sendPushButton_clicked(self):
|
def on_sendPushButton_clicked(self):
|
||||||
msg = self.msgLineEdit.text().strip()
|
msg = self.msgLineEdit.text().strip()
|
||||||
@ -118,3 +122,172 @@ class NodeCtrl(QWidget):
|
|||||||
# due to monospace)
|
# due to monospace)
|
||||||
result = self.logTextBrowser.width() / fontMetrics.width('a')
|
result = self.logTextBrowser.width() / fontMetrics.width('a')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _init_modules_tab(self):
|
||||||
|
self._moduleWidgets = []
|
||||||
|
layout = self.scrollAreaWidgetContents.layout()
|
||||||
|
labelfont = self.font()
|
||||||
|
labelfont.setBold(True)
|
||||||
|
row = 0
|
||||||
|
for modname in sorted(self._node.modules):
|
||||||
|
modprops = self._node.getModuleProperties(modname)
|
||||||
|
baseclass = modprops['interface']
|
||||||
|
description = modprops['interface']
|
||||||
|
unit = self._node.getProperties(modname, 'value').get('unit', '')
|
||||||
|
|
||||||
|
if unit:
|
||||||
|
labelstr = '%s (%s):' % (modname, unit)
|
||||||
|
else:
|
||||||
|
labelstr = '%s:' % (modname,)
|
||||||
|
label = QLabel(labelstr)
|
||||||
|
label.setFont(labelfont)
|
||||||
|
|
||||||
|
if baseclass == 'Driveable':
|
||||||
|
widget = DriveableWidget(self._node, modname, self)
|
||||||
|
elif baseclass == 'Readable':
|
||||||
|
widget = ReadableWidget(self._node, modname, self)
|
||||||
|
else:
|
||||||
|
widget = QLabel('Unsupported Interfaceclass %r' % baseclass)
|
||||||
|
|
||||||
|
if description:
|
||||||
|
widget.setToolTip(description)
|
||||||
|
|
||||||
|
layout.addWidget(label, row, 0)
|
||||||
|
layout.addWidget(widget, row, 1)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
self._moduleWidgets.extend((label, widget))
|
||||||
|
|
||||||
|
|
||||||
|
class ReadableWidget(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, node, module, parent=None):
|
||||||
|
super(ReadableWidget, self).__init__(parent)
|
||||||
|
self._node = node
|
||||||
|
self._module = module
|
||||||
|
|
||||||
|
params = self._node.getProperties(self._module, 'value')
|
||||||
|
datatype = params.get('datatype', StringType())
|
||||||
|
self._is_enum = isinstance(datatype, EnumType)
|
||||||
|
|
||||||
|
loadUi(self, 'modulebuttons.ui')
|
||||||
|
|
||||||
|
# populate comboBox, keeping a mapping of Qt-index to EnumValue
|
||||||
|
if self._is_enum:
|
||||||
|
self._map = {} # maps QT-idx to name/value
|
||||||
|
self._revmap = {} # maps value/name to QT-idx
|
||||||
|
for idx, (val, name) in enumerate(
|
||||||
|
sorted(datatype.entries.items())):
|
||||||
|
self._map[idx] = (name, val)
|
||||||
|
self._revmap[name] = idx
|
||||||
|
self._revmap[val] = idx
|
||||||
|
self.targetComboBox.addItem(name, val)
|
||||||
|
|
||||||
|
self._init_status_widgets()
|
||||||
|
self._init_current_widgets()
|
||||||
|
self._init_target_widgets()
|
||||||
|
|
||||||
|
self._node.newData.connect(self._updateValue)
|
||||||
|
|
||||||
|
def _get(self, pname, fallback=Ellipsis):
|
||||||
|
params = self._node.queryCache(self._module)
|
||||||
|
if pname in params:
|
||||||
|
return params[pname].value
|
||||||
|
try:
|
||||||
|
return self._node.getParameter(self._module, pname)
|
||||||
|
except Exception:
|
||||||
|
self.log.exception()
|
||||||
|
if fallback is not Ellipsis:
|
||||||
|
return fallback
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _init_status_widgets(self):
|
||||||
|
self.update_status(self._get('status', (999, '<not supported>')))
|
||||||
|
# XXX: also connect update_status signal to LineEdit ??
|
||||||
|
|
||||||
|
def update_status(self, status, qualifiers={}):
|
||||||
|
self.statusLineEdit.setText(str(status))
|
||||||
|
# may change meaning of cmdPushButton
|
||||||
|
|
||||||
|
def _init_current_widgets(self):
|
||||||
|
self.update_current(self._get('value', ''))
|
||||||
|
|
||||||
|
def update_current(self, value, qualifiers={}):
|
||||||
|
self.currentLineEdit.setText(str(value))
|
||||||
|
|
||||||
|
def _init_target_widgets(self):
|
||||||
|
# Readable has no target: disable widgets
|
||||||
|
self.targetLineEdit.setHidden(True)
|
||||||
|
self.targetComboBox.setHidden(True)
|
||||||
|
self.cmdPushButton.setHidden(True)
|
||||||
|
|
||||||
|
def update_target(self, target, qualifiers={}):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def target_go(self, target):
|
||||||
|
try:
|
||||||
|
self._node.setParameter(self._module, 'target', target)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self.parent(), 'Operation failed', str(e))
|
||||||
|
|
||||||
|
def _updateValue(self, module, parameter, value):
|
||||||
|
if module != self._module:
|
||||||
|
return
|
||||||
|
if parameter == 'status':
|
||||||
|
self.update_status(*value)
|
||||||
|
elif parameter == 'value':
|
||||||
|
self.update_current(*value)
|
||||||
|
elif parameter == 'target':
|
||||||
|
self.update_target(*value)
|
||||||
|
|
||||||
|
|
||||||
|
class DriveableWidget(ReadableWidget):
|
||||||
|
|
||||||
|
def _init_target_widgets(self):
|
||||||
|
params = self._node.getProperties(self._module, 'target')
|
||||||
|
if self._is_enum:
|
||||||
|
# EnumType: disable Linedit
|
||||||
|
self.targetLineEdit.setHidden(True)
|
||||||
|
else:
|
||||||
|
# normal types: disable Combobox
|
||||||
|
self.targetComboBox.setHidden(True)
|
||||||
|
target = self._get('target', None)
|
||||||
|
if target:
|
||||||
|
if isinstance(target, list) and isinstance(target[1], dict):
|
||||||
|
self.update_target(target[0])
|
||||||
|
else:
|
||||||
|
self.update_target(target)
|
||||||
|
|
||||||
|
def update_current(self, value, qualifiers={}):
|
||||||
|
if self._is_enum:
|
||||||
|
self.currentLineEdit.setText(self._map[self._revmap[value]][0])
|
||||||
|
else:
|
||||||
|
self.currentLineEdit.setText(str(value))
|
||||||
|
|
||||||
|
def update_target(self, target, qualifiers={}):
|
||||||
|
if self._is_enum:
|
||||||
|
# update selected item
|
||||||
|
if target in self._revmap:
|
||||||
|
self.targetComboBox.setCurrentIndex(self._revmap[target])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"%s: Got invalid target value %r!" %
|
||||||
|
(self._module, target))
|
||||||
|
else:
|
||||||
|
self.targetLineEdit.setText(str(target))
|
||||||
|
|
||||||
|
def on_cmdPushButton_clicked(self, toggle=False):
|
||||||
|
if toggled:
|
||||||
|
return
|
||||||
|
if self._is_enum:
|
||||||
|
self.on_targetComboBox_activated()
|
||||||
|
else:
|
||||||
|
self.on_targetLineEdit_returnPressed()
|
||||||
|
|
||||||
|
def on_targetLineEdit_returnPressed(self):
|
||||||
|
self.target_go(self.targetLineEdit.text())
|
||||||
|
|
||||||
|
def on_targetComboBox_activated(self, stuff=''):
|
||||||
|
if isinstance(stuff, (str, unicode)):
|
||||||
|
return
|
||||||
|
self.target_go(self._map[self.targetComboBox.currentIndex()][0])
|
||||||
|
182
secop/gui/params/__init__.py
Normal file
182
secop/gui/params/__init__.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
# Copyright (c) 2015-2016 by the authors, see LICENSE
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from PyQt4.QtGui import QWidget, QLabel, QPushButton as QButton, QLineEdit, QMessageBox, QCheckBox, QSizePolicy
|
||||||
|
from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal
|
||||||
|
|
||||||
|
from secop.gui.util import loadUi
|
||||||
|
from secop.datatypes import *
|
||||||
|
|
||||||
|
|
||||||
|
class ParameterWidget(QWidget):
|
||||||
|
setRequested = pyqtSignal(str, str, str) # module, parameter, target
|
||||||
|
cmdRequested = pyqtSignal(str, str, list) # module, command, args
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
module,
|
||||||
|
paramcmd,
|
||||||
|
datatype=None,
|
||||||
|
initvalue=None,
|
||||||
|
readonly=True,
|
||||||
|
parent=None):
|
||||||
|
super(ParameterWidget, self).__init__(parent)
|
||||||
|
self._module = module
|
||||||
|
self._paramcmd = paramcmd
|
||||||
|
self._datatype = datatype
|
||||||
|
self._readonly = readonly
|
||||||
|
|
||||||
|
self._load_ui(initvalue)
|
||||||
|
|
||||||
|
def _load_ui(self, initvalue):
|
||||||
|
# load ui file, set initvalue to right widget
|
||||||
|
pass
|
||||||
|
|
||||||
|
def updateValue(self, valuestr):
|
||||||
|
# async !
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GenericParameterWidget(ParameterWidget):
|
||||||
|
|
||||||
|
def _load_ui(self, initvalue):
|
||||||
|
# using two QLineEdits for current and target value
|
||||||
|
loadUi(self, 'parambuttons.ui')
|
||||||
|
|
||||||
|
if self._readonly:
|
||||||
|
self.setPushButton.setEnabled(False)
|
||||||
|
self.setLineEdit.setEnabled(False)
|
||||||
|
else:
|
||||||
|
self.setLineEdit.returnPressed.connect(
|
||||||
|
self.on_setPushButton_clicked)
|
||||||
|
self.updateValue(str(initvalue))
|
||||||
|
|
||||||
|
def on_setPushButton_clicked(self):
|
||||||
|
self.setRequested.emit(self._module, self._paramcmd,
|
||||||
|
self.setLineEdit.text())
|
||||||
|
|
||||||
|
def updateValue(self, valuestr):
|
||||||
|
self.currentLineEdit.setText(valuestr)
|
||||||
|
|
||||||
|
|
||||||
|
class EnumParameterWidget(GenericParameterWidget):
|
||||||
|
|
||||||
|
def _load_ui(self, initvalue):
|
||||||
|
# using two QLineEdits for current and target value
|
||||||
|
loadUi(self, 'parambuttons_select.ui')
|
||||||
|
|
||||||
|
# transfer allowed settings from datatype to comboBoxes
|
||||||
|
self._map = {} # maps index to enumstring
|
||||||
|
self._revmap = {} # maps enumstring to index
|
||||||
|
index = 0
|
||||||
|
for data, entry in sorted(self._datatype.entries.items()):
|
||||||
|
self.setComboBox.addItem(entry, data)
|
||||||
|
self._map[index] = entry
|
||||||
|
self._revmap[entry] = index
|
||||||
|
self._revmap[data] = index
|
||||||
|
index += 1
|
||||||
|
if self._readonly:
|
||||||
|
self.setLabel.setEnabled(False)
|
||||||
|
self.setComboBox.setEnabled(False)
|
||||||
|
self.setLabel.setHidden(True)
|
||||||
|
self.setComboBox.setHidden(True)
|
||||||
|
else:
|
||||||
|
self.setComboBox.activated.connect(self.on_setPushButton_clicked)
|
||||||
|
|
||||||
|
self.updateValue(str(initvalue))
|
||||||
|
|
||||||
|
def on_setPushButton_clicked(self):
|
||||||
|
self.setRequested.emit(
|
||||||
|
self._module, self._paramcmd, str(
|
||||||
|
self._datatype.reversed[
|
||||||
|
self._map[
|
||||||
|
self.setComboBox.currentIndex()]]))
|
||||||
|
|
||||||
|
def updateValue(self, valuestr):
|
||||||
|
try:
|
||||||
|
self.currentLineEdit.setText(
|
||||||
|
self._datatype.entries.get(
|
||||||
|
int(valuestr), valuestr))
|
||||||
|
except ValueError:
|
||||||
|
self.currentLineEdit.setText('undefined Value: %r' % valuestr)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericCmdWidget(ParameterWidget):
|
||||||
|
|
||||||
|
def _load_ui(self, initvalue):
|
||||||
|
# using two QLineEdits for current and target value
|
||||||
|
loadUi(self, 'cmdbuttons.ui')
|
||||||
|
|
||||||
|
self.cmdLineEdit.setText('')
|
||||||
|
self.cmdLineEdit.setEnabled(self.datatype.argtypes is not None)
|
||||||
|
self.cmdLineEdit.returnPressed.connect(
|
||||||
|
self.on_cmdPushButton_clicked)
|
||||||
|
|
||||||
|
def on_cmdPushButton_clicked(self):
|
||||||
|
# wait until command complete before retrying
|
||||||
|
self.cmdPushButton.setEnabled(False)
|
||||||
|
self.cmdRequested.emit(
|
||||||
|
self._module,
|
||||||
|
self._paramcmd,
|
||||||
|
self._datatype.from_string(
|
||||||
|
self.cmdLineEdit.text()))
|
||||||
|
|
||||||
|
def updateValue(self, valuestr):
|
||||||
|
# open dialog and show value, if any.
|
||||||
|
# then re-activate the command button
|
||||||
|
self.cmdPushButton.setEnabled(True)
|
||||||
|
|
||||||
|
|
||||||
|
def ParameterView(module,
|
||||||
|
paramcmd,
|
||||||
|
datatype=None,
|
||||||
|
initvalue=None,
|
||||||
|
readonly=True,
|
||||||
|
parent=None):
|
||||||
|
# depending on datatype returns an initialized widget fit for display and
|
||||||
|
# interaction
|
||||||
|
|
||||||
|
if datatype is not None:
|
||||||
|
if datatype.IS_COMMAND:
|
||||||
|
return GenericCmdWidget(
|
||||||
|
module,
|
||||||
|
paramcmd, # name of command
|
||||||
|
datatype,
|
||||||
|
initvalue, # not used for comands
|
||||||
|
readonly, # not used for commands
|
||||||
|
parent)
|
||||||
|
if isinstance(datatype, EnumType):
|
||||||
|
return EnumParameterWidget(
|
||||||
|
module,
|
||||||
|
paramcmd, # name of parameter
|
||||||
|
datatype,
|
||||||
|
initvalue,
|
||||||
|
readonly,
|
||||||
|
parent)
|
||||||
|
|
||||||
|
return GenericParameterWidget(
|
||||||
|
module,
|
||||||
|
paramcmd, # name of parameter
|
||||||
|
datatype,
|
||||||
|
initvalue,
|
||||||
|
readonly,
|
||||||
|
parent)
|
60
secop/gui/ui/cmdbuttons.ui
Normal file
60
secop/gui/ui/cmdbuttons.ui
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>730</width>
|
||||||
|
<height>33</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="horizontalSpacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Arguments:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="cmdLineEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>256</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QPushButton" name="cmdPushButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Go</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -29,6 +29,9 @@
|
|||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="visibilityComboBox">
|
<widget class="QComboBox" name="visibilityComboBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>user</string>
|
<string>user</string>
|
||||||
@ -61,6 +64,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="validateCheckBox">
|
<widget class="QCheckBox" name="validateCheckBox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Validate locally</string>
|
<string>Validate locally</string>
|
||||||
</property>
|
</property>
|
||||||
@ -104,7 +110,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1228</width>
|
<width>1228</width>
|
||||||
<height>23</height>
|
<height>33</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
79
secop/gui/ui/modulebuttons.ui
Normal file
79
secop/gui/ui/modulebuttons.ui
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>748</width>
|
||||||
|
<height>74</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLineEdit" name="currentLineEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>256</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="targetLineEdit"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="targetComboBox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QPushButton" name="cmdPushButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Go</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLineEdit" name="statusLineEdit">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -6,53 +6,15 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>230</width>
|
<width>257</width>
|
||||||
<height>195</height>
|
<height>162</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<italic>false</italic>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Module name:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="moduleNameLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>TextLabel</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QGroupBox" name="paramGroupBox">
|
<widget class="QGroupBox" name="paramGroupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Parameters:</string>
|
<string>Parameters:</string>
|
||||||
@ -76,44 +38,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="6" column="0">
|
||||||
<spacer name="verticalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QGroupBox" name="propertyGroupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Properties:</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<spacer name="verticalSpacer_3">
|
<spacer name="verticalSpacer_3">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -126,6 +51,54 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QGroupBox" name="propertyGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Properties:</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<italic>false</italic>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Module name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="moduleNameLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>18</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QGroupBox" name="commandGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Commands:</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -85,18 +85,46 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="consoleTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Console</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="logTextBrowser">
|
||||||
|
<property name="html">
|
||||||
|
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:11pt;"><br /></p></body></html></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="1" column="1">
|
<item row="0" column="2">
|
||||||
|
<widget class="QPushButton" name="clearPushButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
<widget class="QLineEdit" name="msgLineEdit"/>
|
<widget class="QLineEdit" name="msgLineEdit"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>>>></string>
|
<string>>>></string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="2">
|
<item row="0" column="3">
|
||||||
<widget class="QPushButton" name="sendPushButton">
|
<widget class="QPushButton" name="sendPushButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Send</string>
|
<string>Send</string>
|
||||||
@ -109,25 +137,39 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="3">
|
</layout>
|
||||||
<widget class="QPushButton" name="clearPushButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Clear</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" colspan="4">
|
</layout>
|
||||||
<widget class="QTextBrowser" name="logTextBrowser">
|
</widget>
|
||||||
<property name="html">
|
<widget class="QWidget" name="modulesTab">
|
||||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<property name="enabled">
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
<bool>true</bool>
|
||||||
p, li { white-space: pre-wrap; }
|
|
||||||
</style></head><body style=" font-family:'Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
|
||||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
|
||||||
</property>
|
</property>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Modules</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>610</width>
|
||||||
|
<height>324</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5"/>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>730</width>
|
<width>730</width>
|
||||||
<height>33</height>
|
<height>39</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -36,25 +36,15 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Current: </string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="3">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Set: </string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="targetValueComboBox"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="4">
|
<item row="0" column="4">
|
||||||
<widget class="QComboBox" name="comboBox_2"/>
|
<widget class="QComboBox" name="setComboBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="5">
|
<item row="0" column="5">
|
||||||
<spacer name="horizontalSpacer_2">
|
<spacer name="horizontalSpacer_2">
|
||||||
@ -69,6 +59,30 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="currentLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Current: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QLabel" name="setLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="currentLineEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -223,10 +223,10 @@ def getfqdn(name=''):
|
|||||||
return socket.getfqdn(name)
|
return socket.getfqdn(name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
# if __name__ == '__main__':
|
||||||
print "minimal testing: lib"
|
# print "minimal testing: lib"
|
||||||
d = attrdict(a=1, b=2)
|
# d = attrdict(a=1, b=2)
|
||||||
_ = d.a + d['b']
|
# _ = d.a + d['b']
|
||||||
d.c = 9
|
# d.c = 9
|
||||||
d['d'] = 'c'
|
# d['d'] = 'c'
|
||||||
assert d[d.d] == 9
|
# assert d[d.d] == 9
|
||||||
|
@ -177,7 +177,6 @@ class ArgsParser(object):
|
|||||||
self.length = len(string)
|
self.length = len(string)
|
||||||
|
|
||||||
def setstring(self, string):
|
def setstring(self, string):
|
||||||
print repr(string)
|
|
||||||
self.string = string
|
self.string = string
|
||||||
self.idx = 0
|
self.idx = 0
|
||||||
self.length = len(string)
|
self.length = len(string)
|
||||||
@ -191,7 +190,6 @@ class ArgsParser(object):
|
|||||||
def get(self):
|
def get(self):
|
||||||
res = self.peek()
|
res = self.peek()
|
||||||
self.idx += 1
|
self.idx += 1
|
||||||
print "get->", res
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def skip(self):
|
def skip(self):
|
||||||
@ -222,17 +220,14 @@ class ArgsParser(object):
|
|||||||
idx = self.idx
|
idx = self.idx
|
||||||
res = self.parse_array()
|
res = self.parse_array()
|
||||||
if res:
|
if res:
|
||||||
print "is Array"
|
|
||||||
return res
|
return res
|
||||||
self.idx = idx
|
self.idx = idx
|
||||||
res = self.parse_record()
|
res = self.parse_record()
|
||||||
if res:
|
if res:
|
||||||
print "is record"
|
|
||||||
return res
|
return res
|
||||||
self.idx = idx
|
self.idx = idx
|
||||||
res = self.parse_string()
|
res = self.parse_string()
|
||||||
if res:
|
if res:
|
||||||
print "is string"
|
|
||||||
return res
|
return res
|
||||||
self.idx = idx
|
self.idx = idx
|
||||||
return self.parse_number()
|
return self.parse_number()
|
||||||
@ -388,26 +383,26 @@ def parse_args(s):
|
|||||||
|
|
||||||
__ALL__ = ['format_time', 'parse_time', 'parse_args']
|
__ALL__ = ['format_time', 'parse_time', 'parse_args']
|
||||||
|
|
||||||
if __name__ == '__main__':
|
# if __name__ == '__main__':
|
||||||
print "minimal testing: lib/parsing:"
|
# print "minimal testing: lib/parsing:"
|
||||||
print "time_formatting:",
|
# print "time_formatting:",
|
||||||
t = time.time()
|
# t = time.time()
|
||||||
s = format_time(t)
|
# s = format_time(t)
|
||||||
assert (abs(t - parse_time(s)) < 1e-6)
|
# assert (abs(t - parse_time(s)) < 1e-6)
|
||||||
print "OK"
|
# print "OK"#
|
||||||
|
#
|
||||||
|
# print "ArgsParser:"
|
||||||
|
# a = ArgsParser()
|
||||||
|
# print a.parse('[ "\'\\\"A" , "<>\'", \'",C\', [1.23e1, 123.0e-001] , ]')
|
||||||
|
|
||||||
print "ArgsParser:"
|
# #import pdb
|
||||||
a = ArgsParser()
|
# #pdb.run('print a.parse()', globals(), locals())
|
||||||
print a.parse('[ "\'\\\"A" , "<>\'", \'",C\', [1.23e1, 123.0e-001] , ]')
|
|
||||||
|
|
||||||
#import pdb
|
# print "args_formatting:",
|
||||||
#pdb.run('print a.parse()', globals(), locals())
|
# for obj in [1, 2.3, 'X', (1, 2, 3), [1, (3, 4), 'X,y']]:
|
||||||
|
# s = format_args(obj)
|
||||||
print "args_formatting:",
|
# p = a.parse(s)
|
||||||
for obj in [1, 2.3, 'X', (1, 2, 3), [1, (3, 4), 'X,y']]:
|
# print p,
|
||||||
s = format_args(obj)
|
# assert (parse_args(format_args(obj)) == obj)
|
||||||
p = a.parse(s)
|
# print "OK"
|
||||||
print p,
|
# print "OK"
|
||||||
assert (parse_args(format_args(obj)) == obj)
|
|
||||||
print "OK"
|
|
||||||
print "OK"
|
|
||||||
|
@ -36,6 +36,7 @@ class Namespace(object):
|
|||||||
|
|
||||||
|
|
||||||
class Step(object):
|
class Step(object):
|
||||||
|
|
||||||
def __init__(self, desc, waittime, func, *args, **kwds):
|
def __init__(self, desc, waittime, func, *args, **kwds):
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
self.waittime = waittime
|
self.waittime = waittime
|
||||||
@ -126,7 +127,7 @@ class SequencerMixin(object):
|
|||||||
"""Can be called to check if a sequence is currently running."""
|
"""Can be called to check if a sequence is currently running."""
|
||||||
return self._seq_thread and self._seq_thread.isAlive()
|
return self._seq_thread and self._seq_thread.isAlive()
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self, maxage=0):
|
||||||
if self.seq_is_alive():
|
if self.seq_is_alive():
|
||||||
return status.BUSY, 'moving: ' + self._seq_phase
|
return status.BUSY, 'moving: ' + self._seq_phase
|
||||||
elif self._seq_error:
|
elif self._seq_error:
|
||||||
@ -138,7 +139,7 @@ class SequencerMixin(object):
|
|||||||
return status.ERROR, self._seq_stopped
|
return status.ERROR, self._seq_stopped
|
||||||
return status.WARN, self._seq_stopped
|
return status.WARN, self._seq_stopped
|
||||||
if hasattr(self, 'read_hw_status'):
|
if hasattr(self, 'read_hw_status'):
|
||||||
return self.read_hw_status()
|
return self.read_hw_status(maxage)
|
||||||
return OK, ''
|
return OK, ''
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
@ -151,6 +152,9 @@ class SequencerMixin(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.exception('unhandled error in sequence thread: %s', e)
|
self.log.exception('unhandled error in sequence thread: %s', e)
|
||||||
self._seq_error = str(e)
|
self._seq_error = str(e)
|
||||||
|
finally:
|
||||||
|
self._seq_thread = None
|
||||||
|
self.poll(0)
|
||||||
|
|
||||||
def _seq_thread_inner(self, seq, store_init):
|
def _seq_thread_inner(self, seq, store_init):
|
||||||
store = Namespace()
|
store = Namespace()
|
||||||
@ -163,19 +167,24 @@ class SequencerMixin(object):
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
result = step.func(store, *step.args)
|
result = step.func(store, *step.args)
|
||||||
if self._seq_.stopflag:
|
if self._seq_stopflag:
|
||||||
if result:
|
if result:
|
||||||
self._seq_stopped = 'stopped while %s' % step.desc
|
self._seq_stopped = 'stopped while %s' % step.desc
|
||||||
else:
|
else:
|
||||||
self._seq_stopped = 'stopped after %s' % step.desc
|
self._seq_stopped = 'stopped after %s' % step.desc
|
||||||
cleanup_func = step.kwds.get('cleanup', None)
|
cleanup_func = step.kwds.get('cleanup', None)
|
||||||
if callable(cleanup_func):
|
if callable(cleanup_func):
|
||||||
cleanup_func(store, *step.args)
|
try:
|
||||||
|
cleanup_func(store, result, *step.args)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception(e)
|
||||||
|
raise
|
||||||
return
|
return
|
||||||
sleep(step.waittime)
|
sleep(step.waittime)
|
||||||
if not result:
|
if not result:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.exception('error in sequence step: %s', e)
|
self.log.exception(
|
||||||
|
'error in sequence step %r: %s', step.desc, e)
|
||||||
self._seq_error = 'during %s: %s' % (step.desc, e)
|
self._seq_error = 'during %s: %s' % (step.desc, e)
|
||||||
break
|
break
|
||||||
|
@ -31,11 +31,11 @@ import types
|
|||||||
import inspect
|
import inspect
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from secop.lib import formatExtendedStack
|
from secop.lib import formatExtendedStack, mkthread
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.errors import ConfigError, ProgrammingError
|
from secop.errors import ConfigError, ProgrammingError
|
||||||
from secop.protocol import status
|
from secop.protocol import status
|
||||||
from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange, export_datatype
|
from secop.datatypes import DataType, EnumType, TupleOf, StringType, FloatRange, export_datatype, get_datatype
|
||||||
|
|
||||||
|
|
||||||
EVENT_ONLY_ON_CHANGED_VALUES = False
|
EVENT_ONLY_ON_CHANGED_VALUES = False
|
||||||
@ -72,6 +72,7 @@ class PARAM(object):
|
|||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
self.export = export
|
self.export = export
|
||||||
self.group = group
|
self.group = group
|
||||||
|
|
||||||
# note: auto-converts True/False to 1/0 which yield the expected
|
# note: auto-converts True/False to 1/0 which yield the expected
|
||||||
# behaviour...
|
# behaviour...
|
||||||
self.poll = int(poll)
|
self.poll = int(poll)
|
||||||
@ -214,7 +215,9 @@ class DeviceMeta(type):
|
|||||||
else:
|
else:
|
||||||
# return cached value
|
# return cached value
|
||||||
self.log.debug("rfunc(%s): return cached value" % pname)
|
self.log.debug("rfunc(%s): return cached value" % pname)
|
||||||
return self.PARAMS[pname].value
|
value = self.PARAMS[pname].value
|
||||||
|
setattr(self, pname, value)
|
||||||
|
return value
|
||||||
|
|
||||||
if rfunc:
|
if rfunc:
|
||||||
wrapped_rfunc.__doc__ = rfunc.__doc__
|
wrapped_rfunc.__doc__ = rfunc.__doc__
|
||||||
@ -300,10 +303,13 @@ class Device(object):
|
|||||||
'meaning': None, # XXX: ???
|
'meaning': None, # XXX: ???
|
||||||
'priority': None, # XXX: ???
|
'priority': None, # XXX: ???
|
||||||
'visibility': None, # XXX: ????
|
'visibility': None, # XXX: ????
|
||||||
|
'description': "The manufacturer forgot to set a meaningful description. please nag him!",
|
||||||
# what else?
|
# what else?
|
||||||
}
|
}
|
||||||
# PARAMS and CMDS are auto-merged upon subclassing
|
# PARAMS and CMDS are auto-merged upon subclassing
|
||||||
PARAMS = {}
|
# PARAMS = {
|
||||||
|
# 'description': PARAM('short description of this module and its function', datatype=StringType(), default='no specified'),
|
||||||
|
# }
|
||||||
CMDS = {}
|
CMDS = {}
|
||||||
DISPATCHER = None
|
DISPATCHER = None
|
||||||
|
|
||||||
@ -318,6 +324,12 @@ class Device(object):
|
|||||||
params[k] = v.copy()
|
params[k] = v.copy()
|
||||||
|
|
||||||
self.PARAMS = params
|
self.PARAMS = params
|
||||||
|
# make local copies of PROPERTIES
|
||||||
|
props = {}
|
||||||
|
for k, v in self.PROPERTIES.items()[:]:
|
||||||
|
props[k] = v
|
||||||
|
|
||||||
|
self.PROPERTIES = props
|
||||||
|
|
||||||
# check and apply properties specified in cfgdict
|
# check and apply properties specified in cfgdict
|
||||||
# moduleproperties are to be specified as
|
# moduleproperties are to be specified as
|
||||||
@ -347,7 +359,9 @@ class Device(object):
|
|||||||
paramname, propname = k.split('.', 1)
|
paramname, propname = k.split('.', 1)
|
||||||
if paramname in self.PARAMS:
|
if paramname in self.PARAMS:
|
||||||
paramobj = self.PARAMS[paramname]
|
paramobj = self.PARAMS[paramname]
|
||||||
if hasattr(paramobj, propname):
|
if propname == 'datatype':
|
||||||
|
paramobj.datatype = get_datatype(cfgdict.pop(k))
|
||||||
|
elif hasattr(paramobj, propname):
|
||||||
setattr(paramobj, propname, v)
|
setattr(paramobj, propname, v)
|
||||||
del cfgdict[k]
|
del cfgdict[k]
|
||||||
|
|
||||||
@ -355,8 +369,10 @@ class Device(object):
|
|||||||
# only accept config items specified in PARAMS
|
# only accept config items specified in PARAMS
|
||||||
for k, v in cfgdict.items():
|
for k, v in cfgdict.items():
|
||||||
if k not in self.PARAMS:
|
if k not in self.PARAMS:
|
||||||
raise ConfigError('Device %s:config Parameter %r '
|
raise ConfigError(
|
||||||
'not unterstood!' % (self.name, k))
|
'Device %s:config Parameter %r '
|
||||||
|
'not unterstood! (use on of %r)' %
|
||||||
|
(self.name, k, self.PARAMS.keys()))
|
||||||
# complain if a PARAM entry has no default value and
|
# complain if a PARAM entry has no default value and
|
||||||
# is not specified in cfgdict
|
# is not specified in cfgdict
|
||||||
for k, v in self.PARAMS.items():
|
for k, v in self.PARAMS.items():
|
||||||
@ -385,6 +401,7 @@ class Device(object):
|
|||||||
v = datatype.validate(v)
|
v = datatype.validate(v)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
self.log.exception(formatExtendedStack())
|
self.log.exception(formatExtendedStack())
|
||||||
|
raise
|
||||||
raise ConfigError('Device %s: config parameter %r:\n%r' %
|
raise ConfigError('Device %s: config parameter %r:\n%r' %
|
||||||
(self.name, k, e))
|
(self.name, k, e))
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
@ -393,6 +410,10 @@ class Device(object):
|
|||||||
def init(self):
|
def init(self):
|
||||||
# may be overriden in derived classes to init stuff
|
# may be overriden in derived classes to init stuff
|
||||||
self.log.debug('empty init()')
|
self.log.debug('empty init()')
|
||||||
|
mkthread(self.late_init)
|
||||||
|
|
||||||
|
def late_init(self):
|
||||||
|
self.log.debug('late init()')
|
||||||
|
|
||||||
|
|
||||||
class Readable(Device):
|
class Readable(Device):
|
||||||
@ -402,7 +423,7 @@ class Readable(Device):
|
|||||||
"""
|
"""
|
||||||
PARAMS = {
|
PARAMS = {
|
||||||
'value': PARAM('current value of the device', readonly=True, default=0.,
|
'value': PARAM('current value of the device', readonly=True, default=0.,
|
||||||
datatype=FloatRange(), poll=True),
|
datatype=FloatRange(), unit='', poll=True),
|
||||||
'pollinterval': PARAM('sleeptime between polls', default=5,
|
'pollinterval': PARAM('sleeptime between polls', default=5,
|
||||||
readonly=False, datatype=FloatRange(0.1, 120), ),
|
readonly=False, datatype=FloatRange(0.1, 120), ),
|
||||||
'status': PARAM('current status of the device', default=(status.OK, ''),
|
'status': PARAM('current status of the device', default=(status.OK, ''),
|
||||||
@ -427,25 +448,43 @@ class Readable(Device):
|
|||||||
def __pollThread(self):
|
def __pollThread(self):
|
||||||
"""super simple and super stupid per-module polling thread"""
|
"""super simple and super stupid per-module polling thread"""
|
||||||
i = 0
|
i = 0
|
||||||
|
fastpoll = True # first update should be quick
|
||||||
while True:
|
while True:
|
||||||
i = 1
|
i = 1
|
||||||
try:
|
try:
|
||||||
time.sleep(self.pollinterval)
|
time.sleep(self.pollinterval * (0.1 if fastpoll else 1))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
time.sleep(max(self.pollinterval))
|
time.sleep(min(self.pollinterval)
|
||||||
try:
|
if fastpoll else max(self.pollinterval))
|
||||||
self.poll(i)
|
fastpoll = self.poll(i)
|
||||||
except Exception: # really ALL
|
|
||||||
pass
|
|
||||||
|
|
||||||
def poll(self, nr):
|
def poll(self, nr):
|
||||||
|
# poll status first
|
||||||
|
fastpoll = False
|
||||||
|
if 'status' in self.PARAMS:
|
||||||
|
stat = self.read_status(0)
|
||||||
|
# self.log.info('polling read_status -> %r' % (stat,))
|
||||||
|
fastpoll = stat[0] == status.BUSY
|
||||||
|
# if fastpoll:
|
||||||
|
# self.log.info('fastpoll!')
|
||||||
for pname, pobj in self.PARAMS.iteritems():
|
for pname, pobj in self.PARAMS.iteritems():
|
||||||
if not pobj.poll:
|
if not pobj.poll:
|
||||||
continue
|
continue
|
||||||
if 0 == nr % int(pobj.poll):
|
if pname == 'status':
|
||||||
|
# status was already polled above
|
||||||
|
continue
|
||||||
|
if ((int(pobj.poll) < 0) and fastpoll) or (
|
||||||
|
0 == nr % abs(int(pobj.poll))):
|
||||||
|
# poll always if pobj.poll is negative and fastpoll (i.e. device is busy)
|
||||||
|
# otherwise poll every 'pobj.poll' iteration
|
||||||
rfunc = getattr(self, 'read_' + pname, None)
|
rfunc = getattr(self, 'read_' + pname, None)
|
||||||
if rfunc:
|
if rfunc:
|
||||||
|
try:
|
||||||
|
# self.log.info('polling read_%s -> %r' % (pname, rfunc()))
|
||||||
rfunc()
|
rfunc()
|
||||||
|
except Exception: # really all!
|
||||||
|
pass
|
||||||
|
return fastpoll
|
||||||
|
|
||||||
|
|
||||||
class Driveable(Readable):
|
class Driveable(Readable):
|
||||||
|
@ -269,7 +269,7 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
# now call func and wrap result as value
|
# now call func and wrap result as value
|
||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
func = getattr(moduleobj, 'do' + command)
|
func = getattr(moduleobj, 'do_' + command)
|
||||||
res = func(*arguments)
|
res = func(*arguments)
|
||||||
res = CommandReply(
|
res = CommandReply(
|
||||||
module=modulename,
|
module=modulename,
|
||||||
@ -319,12 +319,14 @@ class Dispatcher(object):
|
|||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
readfunc()
|
readfunc()
|
||||||
if pobj.timestamp:
|
if pobj.timestamp:
|
||||||
return Value(
|
res = Value(
|
||||||
modulename,
|
modulename,
|
||||||
parameter=pname,
|
parameter=pname,
|
||||||
value=pobj.export_value,
|
value=pobj.export_value,
|
||||||
t=pobj.timestamp)
|
t=pobj.timestamp)
|
||||||
return Value(modulename, parameter=pname, value=pobj.export_value)
|
else:
|
||||||
|
res = Value(modulename, parameter=pname, value=pobj.export_value)
|
||||||
|
return res
|
||||||
|
|
||||||
# now the (defined) handlers for the different requests
|
# now the (defined) handlers for the different requests
|
||||||
def handle_Help(self, conn, msg):
|
def handle_Help(self, conn, msg):
|
||||||
@ -404,6 +406,10 @@ class Dispatcher(object):
|
|||||||
unit=pobj.unit)
|
unit=pobj.unit)
|
||||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||||
self.broadcast_event(res)
|
self.broadcast_event(res)
|
||||||
|
else:
|
||||||
|
self.log.error(
|
||||||
|
'activate: got no value for %s:%s!' %
|
||||||
|
modulename, pname)
|
||||||
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -39,12 +39,12 @@ class MessageEncoder(object):
|
|||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
|
|
||||||
from demo_v2 import DemoEncoder as DemoEncoderV2
|
from .demo_v2 import DemoEncoder as DemoEncoderV2
|
||||||
from demo_v3 import DemoEncoder as DemoEncoderV3
|
from .demo_v3 import DemoEncoder as DemoEncoderV3
|
||||||
from demo_v4 import DemoEncoder as DemoEncoderV4
|
from .demo_v4 import DemoEncoder as DemoEncoderV4
|
||||||
from text import TextEncoder
|
from .text import TextEncoder
|
||||||
from pickle import PickleEncoder
|
from .pickle import PickleEncoder
|
||||||
from simplecomm import SCPEncoder
|
from .simplecomm import SCPEncoder
|
||||||
|
|
||||||
ENCODERS = {
|
ENCODERS = {
|
||||||
'pickle': PickleEncoder,
|
'pickle': PickleEncoder,
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
# implement as class as they may need some internal 'state' later on
|
# implement as class as they may need some internal 'state' later on
|
||||||
# (think compressors)
|
# (think compressors)
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from secop.protocol.encoding import MessageEncoder
|
from secop.protocol.encoding import MessageEncoder
|
||||||
from secop.protocol import messages
|
from secop.protocol import messages
|
||||||
from secop.lib.parsing import *
|
from secop.lib.parsing import *
|
||||||
@ -43,9 +45,9 @@ class DemoEncoder(MessageEncoder):
|
|||||||
if match:
|
if match:
|
||||||
novalue, devname, pname, propname, assign = match.groups()
|
novalue, devname, pname, propname, assign = match.groups()
|
||||||
if assign:
|
if assign:
|
||||||
print "parsing", assign,
|
print("parsing", assign,)
|
||||||
assign = parse_args(assign)
|
assign = parse_args(assign)
|
||||||
print "->", assign
|
print("->", assign)
|
||||||
return messages.DemoRequest(novalue, devname, pname, propname,
|
return messages.DemoRequest(novalue, devname, pname, propname,
|
||||||
assign)
|
assign)
|
||||||
return messages.HelpRequest()
|
return messages.HelpRequest()
|
||||||
@ -56,13 +58,13 @@ class DemoEncoder(MessageEncoder):
|
|||||||
handler_name = '_encode_' + msg.__class__.__name__
|
handler_name = '_encode_' + msg.__class__.__name__
|
||||||
handler = getattr(self, handler_name, None)
|
handler = getattr(self, handler_name, None)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
print "Handler %s not yet implemented!" % handler_name
|
print("Handler %s not yet implemented!" % handler_name)
|
||||||
try:
|
try:
|
||||||
args = dict((k, msg.__dict__[k]) for k in msg.ARGS)
|
args = dict((k, msg.__dict__[k]) for k in msg.ARGS)
|
||||||
result = handler(**args)
|
result = handler(**args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print "Error encoding %r with %r!" % (msg, handler)
|
print("Error encoding %r with %r!" % (msg, handler))
|
||||||
print e
|
print(e)
|
||||||
return '~InternalError~'
|
return '~InternalError~'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
# implement as class as they may need some internal 'state' later on
|
# implement as class as they may need some internal 'state' later on
|
||||||
# (think compressors)
|
# (think compressors)
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from secop.protocol.encoding import MessageEncoder
|
from secop.protocol.encoding import MessageEncoder
|
||||||
from secop.protocol.messages import *
|
from secop.protocol.messages import *
|
||||||
from secop.protocol.errors import ProtocolError
|
from secop.protocol.errors import ProtocolError
|
||||||
@ -257,7 +259,7 @@ class DemoEncoder(MessageEncoder):
|
|||||||
mgroups['args'] = args
|
mgroups['args'] = args
|
||||||
|
|
||||||
# reformat qualifiers
|
# reformat qualifiers
|
||||||
print mgroups
|
print(mgroups)
|
||||||
quals = dict(
|
quals = dict(
|
||||||
qual.split('=', 1)
|
qual.split('=', 1)
|
||||||
for qual in helper(mgroups.pop('qualifiers', ';')))
|
for qual in helper(mgroups.pop('qualifiers', ';')))
|
||||||
@ -306,9 +308,9 @@ class DemoEncoder(MessageEncoder):
|
|||||||
'read blub:c=14;t=3.3',
|
'read blub:c=14;t=3.3',
|
||||||
]
|
]
|
||||||
for m in testmsg:
|
for m in testmsg:
|
||||||
print repr(m)
|
print(repr(m))
|
||||||
print self.decode(m)
|
print(self.decode(m))
|
||||||
print
|
print()
|
||||||
|
|
||||||
|
|
||||||
DEMO_RE_MZ = re.compile(
|
DEMO_RE_MZ = re.compile(
|
||||||
@ -326,7 +328,7 @@ class DemoEncoder_MZ(MessageEncoder):
|
|||||||
def decode(sef, encoded):
|
def decode(sef, encoded):
|
||||||
m = DEMO_RE_MZ.match(encoded)
|
m = DEMO_RE_MZ.match(encoded)
|
||||||
if m:
|
if m:
|
||||||
print "implement me !"
|
print("implement me !")
|
||||||
return HelpRequest()
|
return HelpRequest()
|
||||||
|
|
||||||
def encode(self, msg):
|
def encode(self, msg):
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
# implement as class as they may need some internal 'state' later on
|
# implement as class as they may need some internal 'state' later on
|
||||||
# (think compressors)
|
# (think compressors)
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
from secop.protocol.encoding import MessageEncoder
|
from secop.protocol.encoding import MessageEncoder
|
||||||
from secop.protocol.messages import *
|
from secop.protocol.messages import *
|
||||||
@ -235,7 +237,7 @@ class DemoEncoder(MessageEncoder):
|
|||||||
# first check beginning
|
# first check beginning
|
||||||
match = DEMO_RE.match(encoded)
|
match = DEMO_RE.match(encoded)
|
||||||
if not match:
|
if not match:
|
||||||
print repr(encoded), repr(IDENTREPLY)
|
print(repr(encoded), repr(IDENTREPLY))
|
||||||
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
||||||
return IdentifyReply(version_string=encoded)
|
return IdentifyReply(version_string=encoded)
|
||||||
|
|
||||||
@ -274,9 +276,9 @@ class DemoEncoder(MessageEncoder):
|
|||||||
origin=encoded)
|
origin=encoded)
|
||||||
|
|
||||||
def tests(self):
|
def tests(self):
|
||||||
print "---- Testing encoding -----"
|
print("---- Testing encoding -----")
|
||||||
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
||||||
print msgclass
|
print(msgclass)
|
||||||
e = self.encode(
|
e = self.encode(
|
||||||
msgclass(
|
msgclass(
|
||||||
module='<module>',
|
module='<module>',
|
||||||
@ -289,17 +291,17 @@ class DemoEncoder(MessageEncoder):
|
|||||||
nonce='<nonce>',
|
nonce='<nonce>',
|
||||||
errorclass='InternalError',
|
errorclass='InternalError',
|
||||||
errorinfo='nix'))
|
errorinfo='nix'))
|
||||||
print e
|
print(e)
|
||||||
print self.decode(e)
|
print(self.decode(e))
|
||||||
print
|
print()
|
||||||
print "---- Testing decoding -----"
|
print("---- Testing decoding -----")
|
||||||
for msgtype, _ in sorted(self.DECODEMAP.items()):
|
for msgtype, _ in sorted(self.DECODEMAP.items()):
|
||||||
msg = '%s a:b 3' % msgtype
|
msg = '%s a:b 3' % msgtype
|
||||||
if msgtype == EVENT:
|
if msgtype == EVENT:
|
||||||
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
||||||
print msg
|
print(msg)
|
||||||
d = self.decode(msg)
|
d = self.decode(msg)
|
||||||
print d
|
print(d)
|
||||||
print self.encode(d)
|
print(self.encode(d))
|
||||||
print
|
print()
|
||||||
print "---- Testing done -----"
|
print("---- Testing done -----")
|
||||||
|
@ -91,9 +91,3 @@ EXCEPTIONS = dict(
|
|||||||
Readonly=ReadonlyError,
|
Readonly=ReadonlyError,
|
||||||
CommandFailed=CommandFailedError,
|
CommandFailed=CommandFailedError,
|
||||||
InvalidParam=InvalidParamValueError, )
|
InvalidParam=InvalidParamValueError, )
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("Minimal testing of errors....")
|
|
||||||
|
|
||||||
print "OK"
|
|
||||||
print
|
|
||||||
|
@ -45,10 +45,10 @@ class Framer(object):
|
|||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
# now some Implementations
|
# now some Implementations
|
||||||
from null import NullFramer
|
from .null import NullFramer
|
||||||
from eol import EOLFramer
|
from .eol import EOLFramer
|
||||||
from rle import RLEFramer
|
from .rle import RLEFramer
|
||||||
from demo import DemoFramer
|
from .demo import DemoFramer
|
||||||
|
|
||||||
FRAMERS = {
|
FRAMERS = {
|
||||||
'null': NullFramer,
|
'null': NullFramer,
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define helpers"""
|
"""Define helpers"""
|
||||||
import os
|
import os
|
||||||
|
import ast
|
||||||
import time
|
import time
|
||||||
import psutil
|
import psutil
|
||||||
import threading
|
import threading
|
||||||
@ -154,9 +155,10 @@ class Server(object):
|
|||||||
devopts['value'] = devopts.pop('default')
|
devopts['value'] = devopts.pop('default')
|
||||||
# strip '"
|
# strip '"
|
||||||
for k, v in devopts.items():
|
for k, v in devopts.items():
|
||||||
for d in ("'", '"'):
|
try:
|
||||||
if v.startswith(d) and v.endswith(d):
|
devopts[k] = ast.literal_eval(v)
|
||||||
devopts[k] = v[1:-1]
|
except Exception:
|
||||||
|
pass
|
||||||
devobj = devclass(
|
devobj = devclass(
|
||||||
self.log.getChild(devname), devopts, devname, self._dispatcher)
|
self.log.getChild(devname), devopts, devname, self._dispatcher)
|
||||||
devs.append([devname, devobj, export])
|
devs.append([devname, devobj, export])
|
||||||
|
328
secop_mlz/amagnet.py
Normal file
328
secop_mlz/amagnet.py
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
"""
|
||||||
|
Supporting classes for FRM2 magnets, currently only Garfield (amagnet).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# partially borrowed from nicos
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from secop.lib import lazy_property, mkthread
|
||||||
|
from secop.lib.sequence import SequencerMixin, Step
|
||||||
|
from secop.protocol import status
|
||||||
|
from secop.datatypes import *
|
||||||
|
from secop.errors import SECoPServerError, ConfigError, ProgrammingError, CommunicationError, HardwareError, DisabledError
|
||||||
|
from secop.modules import PARAM, CMD, OVERRIDE, Device, Readable, Driveable
|
||||||
|
|
||||||
|
|
||||||
|
class GarfieldMagnet(SequencerMixin, Driveable):
|
||||||
|
"""Garfield Magnet
|
||||||
|
|
||||||
|
uses a polarity switch ('+' or '-') to flip polarity and an onoff switch
|
||||||
|
to cut power (to be able to switch polarity) in addition to an
|
||||||
|
unipolar current source.
|
||||||
|
|
||||||
|
B(I) = Ic0 + c1*erf(c2*I) + c3*atan(c4*I)
|
||||||
|
|
||||||
|
Coefficients c0..c4 are given as 'calibration_table' parameter,
|
||||||
|
the symmetry setting selects which.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PARAMS = {
|
||||||
|
'subdev_currentsource': PARAM('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False),
|
||||||
|
'subdev_enable': PARAM('Switch to set for on/off', datatype=StringType(), readonly=True, export=False),
|
||||||
|
'subdev_polswitch': PARAM('Switch to set for polarity', datatype=StringType(), readonly=True, export=False),
|
||||||
|
'subdev_symmetry': PARAM('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False),
|
||||||
|
'userlimits': PARAM('User defined limits of device value',
|
||||||
|
unit='main', datatype=TupleOf(FloatRange(), FloatRange()),
|
||||||
|
default=(float('-Inf'), float('+Inf')), readonly=False, poll=10),
|
||||||
|
'abslimits': PARAM('Absolute limits of device value',
|
||||||
|
unit='main', datatype=TupleOf(FloatRange(), FloatRange()),
|
||||||
|
default=(-0.5, 0.5), poll=True,
|
||||||
|
),
|
||||||
|
'precision': PARAM('Precision of the device value (allowed deviation '
|
||||||
|
'of stable values from target)',
|
||||||
|
unit='main', datatype=FloatRange(0.001), default=0.001, readonly=False,
|
||||||
|
),
|
||||||
|
'ramp': PARAM('Target rate of field change per minute', readonly=False,
|
||||||
|
unit='main/min', datatype=FloatRange(), default=1.0),
|
||||||
|
'calibration': PARAM('Coefficients for calibration '
|
||||||
|
'function: [c0, c1, c2, c3, c4] calculates '
|
||||||
|
'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
|
||||||
|
' in T', poll=1,
|
||||||
|
datatype=ArrayOf(FloatRange(), 5, 5),
|
||||||
|
default=(1.0, 0.0, 0.0, 0.0, 0.0)),
|
||||||
|
'calibrationtable': PARAM('Map of Coefficients for calibration per symmetry setting',
|
||||||
|
datatype=StructOf(symmetric=ArrayOf(FloatRange(), 5, 5),
|
||||||
|
short=ArrayOf(FloatRange(), 5, 5),
|
||||||
|
asymmetric=ArrayOf(FloatRange(), 5, 5)), export=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _current2field(self, current, *coefficients):
|
||||||
|
"""Return field in T for given current in A.
|
||||||
|
|
||||||
|
Should be monotonic and asymetric or _field2current will fail!
|
||||||
|
|
||||||
|
Note: This may be overridden in derived classes.
|
||||||
|
"""
|
||||||
|
v = coefficients or self.calibration
|
||||||
|
if len(v) != 5:
|
||||||
|
self.log.warning('Wrong number of coefficients in calibration '
|
||||||
|
'data! Need exactly 5 coefficients!')
|
||||||
|
return current * v[0] + v[1] * math.erf(v[2] * current) + \
|
||||||
|
v[3] * math.atan(v[4] * current)
|
||||||
|
|
||||||
|
def _field2current(self, field):
|
||||||
|
"""Return required current in A for requested field in T.
|
||||||
|
|
||||||
|
Default implementation does a binary search using _current2field,
|
||||||
|
which must be monotonic for this to work!
|
||||||
|
|
||||||
|
Note: This may be overridden in derived classes.
|
||||||
|
"""
|
||||||
|
# binary search/bisection
|
||||||
|
maxcurr = self._currentsource.abslimits[1]
|
||||||
|
mincurr = -maxcurr
|
||||||
|
maxfield = self._current2field(maxcurr)
|
||||||
|
minfield = -maxfield
|
||||||
|
if not minfield <= field <= maxfield:
|
||||||
|
raise ValueError(self,
|
||||||
|
'requested field %g T out of range %g..%g T' %
|
||||||
|
(field, minfield, maxfield))
|
||||||
|
while minfield <= field <= maxfield:
|
||||||
|
# binary search
|
||||||
|
trycurr = 0.5 * (mincurr + maxcurr)
|
||||||
|
tryfield = self._current2field(trycurr)
|
||||||
|
if field == tryfield:
|
||||||
|
self.log.debug('current for %g T is %g A', field, trycurr)
|
||||||
|
return trycurr # Gotcha!
|
||||||
|
elif field > tryfield:
|
||||||
|
# retry upper interval
|
||||||
|
mincurr = trycurr
|
||||||
|
minfield = tryfield
|
||||||
|
else:
|
||||||
|
# retry lower interval
|
||||||
|
maxcurr = trycurr
|
||||||
|
maxfield = tryfield
|
||||||
|
# if interval is so small, that any error within is acceptable:
|
||||||
|
if maxfield - minfield < 1e-4:
|
||||||
|
ratio = (field - minfield) / (maxfield - minfield)
|
||||||
|
trycurr = (maxcurr - mincurr) * ratio + mincurr
|
||||||
|
self.log.debug('current for %g T is %g A', field, trycurr)
|
||||||
|
return trycurr # interpolated
|
||||||
|
raise ConfigurationError(self,
|
||||||
|
'_current2field polynome not monotonic!')
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
super(GarfieldMagnet, self).init()
|
||||||
|
self._enable = self.DISPATCHER.get_module(self.subdev_enable)
|
||||||
|
self._symmetry = self.DISPATCHER.get_module(self.subdev_symmetry)
|
||||||
|
self._polswitch = self.DISPATCHER.get_module(self.subdev_polswitch)
|
||||||
|
self._currentsource = self.DISPATCHER.get_module(
|
||||||
|
self.subdev_currentsource)
|
||||||
|
self.init_sequencer(fault_on_error=False, fault_on_stop=False)
|
||||||
|
self._symmetry.read_value(0)
|
||||||
|
|
||||||
|
def read_calibration(self, maxage=0):
|
||||||
|
try:
|
||||||
|
return self.calibrationtable[self._symmetry.value]
|
||||||
|
except KeyError:
|
||||||
|
minslope = min(entry[0]
|
||||||
|
for entry in self.calibrationtable.values())
|
||||||
|
self.log.error(
|
||||||
|
'unconfigured calibration for symmetry %r' %
|
||||||
|
self._symmetry.value)
|
||||||
|
return [minslope, 0, 0, 0, 0]
|
||||||
|
|
||||||
|
def _checkLimits(self, limits):
|
||||||
|
umin, umax = limits
|
||||||
|
amin, amax = self.abslimits
|
||||||
|
if umin > umax:
|
||||||
|
raise ValueError(
|
||||||
|
self, 'user minimum (%s) above the user '
|
||||||
|
'maximum (%s)' % (umin, umax))
|
||||||
|
if umin < amin - abs(amin * 1e-12):
|
||||||
|
umin = amin
|
||||||
|
if umax > amax + abs(amax * 1e-12):
|
||||||
|
umax = amax
|
||||||
|
return (umin, umax)
|
||||||
|
|
||||||
|
def write_userlimits(self, value):
|
||||||
|
limits = self._checkLimits(value)
|
||||||
|
return limits
|
||||||
|
|
||||||
|
def read_abslimits(self, maxage=0):
|
||||||
|
maxfield = self._current2field(self._currentsource.abslimits[1])
|
||||||
|
# limit to configured value (if any)
|
||||||
|
maxfield = min(maxfield, max(self.PARAMS['abslimits'].default))
|
||||||
|
return -maxfield, maxfield
|
||||||
|
|
||||||
|
def read_ramp(self, maxage=0):
|
||||||
|
# This is an approximation!
|
||||||
|
return self.calibration[0] * abs(self._currentsource.ramp)
|
||||||
|
|
||||||
|
def write_ramp(self, newramp):
|
||||||
|
# This is an approximation!
|
||||||
|
self._currentsource.ramp = newramp / self.calibration[0]
|
||||||
|
|
||||||
|
def _get_field_polarity(self):
|
||||||
|
sign = int(self._polswitch.read_value())
|
||||||
|
if self._enable.read_value():
|
||||||
|
return sign
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _set_field_polarity(self, polarity):
|
||||||
|
current_pol = self._get_field_polarity()
|
||||||
|
if current_pol == polarity:
|
||||||
|
return
|
||||||
|
if polarity == 0:
|
||||||
|
return
|
||||||
|
if current_pol == 0:
|
||||||
|
# safe to switch
|
||||||
|
self._polswitch.write_target(
|
||||||
|
'+1' if polarity == 1 else str(polarity))
|
||||||
|
return 0
|
||||||
|
if self._currentsource.value < 0.1:
|
||||||
|
self._polswitch.write_target('0')
|
||||||
|
return current_pol
|
||||||
|
# unsafe to switch, go to safe state first
|
||||||
|
self._currentsource.write_target(0)
|
||||||
|
|
||||||
|
def read_value(self, maxage=0):
|
||||||
|
return self._current2field(
|
||||||
|
self._currentsource.read_value(maxage) *
|
||||||
|
self._get_field_polarity())
|
||||||
|
|
||||||
|
def read_hw_status(self, maxage=0):
|
||||||
|
# called from SequencerMixin.read_status if no sequence is running
|
||||||
|
if self._enable.value == 'Off':
|
||||||
|
return status.WARN, 'Disabled'
|
||||||
|
if self._enable.read_status(maxage)[0] != status.OK:
|
||||||
|
return self._enable.status
|
||||||
|
if self._polswitch.value in ['0', 0]:
|
||||||
|
return self._currentsource.status[
|
||||||
|
0], 'Shorted, ' + self._currentsource.status[1]
|
||||||
|
if self._symmetry.value in ['short', 0]:
|
||||||
|
return self._currentsource.status[
|
||||||
|
0], 'Shorted, ' + self._currentsource.status[1]
|
||||||
|
return self._currentsource.read_status(maxage)
|
||||||
|
|
||||||
|
def write_target(self, target):
|
||||||
|
if target != 0 and self._symmetry.read_value(0) in ['short', 0]:
|
||||||
|
raise DisabledError(
|
||||||
|
'Symmetry is shorted, please select another symmetry first!')
|
||||||
|
|
||||||
|
wanted_current = self._field2current(abs(target))
|
||||||
|
wanted_polarity = '-1' if target < 0 else ('+1' if target else '0')
|
||||||
|
current_polarity = self._get_field_polarity()
|
||||||
|
|
||||||
|
# generate Step sequence and start it
|
||||||
|
seq = []
|
||||||
|
seq.append(Step('preparing', 0, self._prepare_ramp))
|
||||||
|
seq.append(Step('recover', 0, self._recover))
|
||||||
|
if current_polarity != wanted_polarity:
|
||||||
|
if self._currentsource.read_value(0) > 0.1:
|
||||||
|
# switching only allowed if current is low enough -> ramp down
|
||||||
|
# first
|
||||||
|
seq.append(
|
||||||
|
Step(
|
||||||
|
'ramping down',
|
||||||
|
0.3,
|
||||||
|
self._ramp_current,
|
||||||
|
0,
|
||||||
|
cleanup=self._ramp_current_cleanup))
|
||||||
|
seq.append(
|
||||||
|
Step(
|
||||||
|
'set polarity %s' %
|
||||||
|
wanted_polarity,
|
||||||
|
0.3,
|
||||||
|
self._set_polarity,
|
||||||
|
wanted_polarity)) # no cleanup
|
||||||
|
seq.append(
|
||||||
|
Step(
|
||||||
|
'ramping to %.3fT (%.2fA)' %
|
||||||
|
(target,
|
||||||
|
wanted_current),
|
||||||
|
0.3,
|
||||||
|
self._ramp_current,
|
||||||
|
wanted_current,
|
||||||
|
cleanup=self._ramp_current_cleanup))
|
||||||
|
seq.append(Step('finalize', 0, self._finish_ramp))
|
||||||
|
|
||||||
|
self.start_sequence(seq)
|
||||||
|
self.status = 'BUSY', 'ramping'
|
||||||
|
|
||||||
|
# steps for the sequencing
|
||||||
|
def _prepare_ramp(self, store, *args):
|
||||||
|
store.old_window = self._currentsource.window
|
||||||
|
self._currentsource.window = 1
|
||||||
|
|
||||||
|
def _finish_ramp(self, store, *args):
|
||||||
|
self._currentsource.window = max(store.old_window, 10)
|
||||||
|
|
||||||
|
def _recover(self, store):
|
||||||
|
# check for interlock
|
||||||
|
if self._currentsource.read_status(0)[0] != status.ERROR:
|
||||||
|
return
|
||||||
|
# recover from interlock
|
||||||
|
ramp = self._currentsource.ramp
|
||||||
|
self._polswitch.write_target('0') # short is safe...
|
||||||
|
self._polswitch._hw_wait()
|
||||||
|
self._enable.write_target('On') # else setting ramp won't work
|
||||||
|
self._enable._hw_wait()
|
||||||
|
self._currentsource.ramp = 60000
|
||||||
|
self._currentsource.target = 0
|
||||||
|
self._currentsource.ramp = ramp
|
||||||
|
# safe state.... if anything of the above fails, the tamperatures may
|
||||||
|
# be too hot!
|
||||||
|
|
||||||
|
def _ramp_current(self, store, target):
|
||||||
|
if abs(self._currentsource.value - target) <= 0.05:
|
||||||
|
# done with this step if no longer BUSY
|
||||||
|
return self._currentsource.read_status(0)[0] == 'BUSY'
|
||||||
|
if self._currentsource.status[0] != 'BUSY':
|
||||||
|
if self._enable.status[0] == 'ERROR':
|
||||||
|
self._enable.do_reset()
|
||||||
|
self._enable.read_status(0)
|
||||||
|
self._enable.write_target('On')
|
||||||
|
self._enable._hw_wait()
|
||||||
|
self._currentsource.write_target(target)
|
||||||
|
return True # repeat
|
||||||
|
|
||||||
|
def _ramp_current_cleanup(self, store, step_was_busy, target):
|
||||||
|
# don't cleanup if step finished
|
||||||
|
if step_was_busy:
|
||||||
|
self._currentsource.write_target(self._currentsource.read_value(0))
|
||||||
|
self._currentsource.window = max(store.old_window, 10)
|
||||||
|
|
||||||
|
def _set_polarity(self, store, target):
|
||||||
|
if self._polswitch.read_status(0)[0] == status.BUSY:
|
||||||
|
return True
|
||||||
|
if self._polswitch.value == target:
|
||||||
|
return False # done with this step
|
||||||
|
if self._polswitch.read_value(0) != 0:
|
||||||
|
self._polswitch.write_target(0)
|
||||||
|
else:
|
||||||
|
self._polswitch.write_target(target)
|
||||||
|
return True # repeat
|
@ -215,8 +215,8 @@ class PyTangoDevice(Device):
|
|||||||
|
|
||||||
def _hw_wait(self):
|
def _hw_wait(self):
|
||||||
"""Wait until hardware status is not BUSY."""
|
"""Wait until hardware status is not BUSY."""
|
||||||
while PyTangoDevice.doStatus(self, 0)[0] == status.BUSY:
|
while self.read_status(0)[0] == 'BUSY':
|
||||||
sleep(self._base_loop_delay)
|
sleep(0.3)
|
||||||
|
|
||||||
def _getProperty(self, name, dev=None):
|
def _getProperty(self, name, dev=None):
|
||||||
"""
|
"""
|
||||||
@ -366,8 +366,8 @@ class AnalogInput(PyTangoDevice, Readable):
|
|||||||
The AnalogInput handles all devices only delivering an analogue value.
|
The AnalogInput handles all devices only delivering an analogue value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def init(self):
|
def late_init(self):
|
||||||
super(AnalogInput, self).init()
|
super(AnalogInput, self).late_init()
|
||||||
# query unit from tango and update value property
|
# query unit from tango and update value property
|
||||||
attrInfo = self._dev.attribute_query('value')
|
attrInfo = self._dev.attribute_query('value')
|
||||||
# prefer configured unit if nothing is set on the Tango device, else
|
# prefer configured unit if nothing is set on the Tango device, else
|
||||||
@ -442,27 +442,15 @@ class AnalogOutput(PyTangoDevice, Driveable):
|
|||||||
900),
|
900),
|
||||||
readonly=False,
|
readonly=False,
|
||||||
),
|
),
|
||||||
'pollinterval': PARAM(
|
|
||||||
'[min, max] sleeptime between polls',
|
|
||||||
default=[
|
|
||||||
0.5,
|
|
||||||
5],
|
|
||||||
readonly=False,
|
|
||||||
datatype=TupleOf(
|
|
||||||
FloatRange(
|
|
||||||
0,
|
|
||||||
20),
|
|
||||||
FloatRange(
|
|
||||||
0.1,
|
|
||||||
120)),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
OVERRIDES = {
|
|
||||||
'value': OVERRIDE(poll=False),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
super(AnalogInput, self).init()
|
super(AnalogOutput, self).init()
|
||||||
|
# init history
|
||||||
|
self._history = [] # will keep (timestamp, value) tuple
|
||||||
|
|
||||||
|
def late_init(self):
|
||||||
|
super(AnalogOutput, self).late_init()
|
||||||
# query unit from tango and update value property
|
# query unit from tango and update value property
|
||||||
attrInfo = self._dev.attribute_query('value')
|
attrInfo = self._dev.attribute_query('value')
|
||||||
# prefer configured unit if nothing is set on the Tango device, else
|
# prefer configured unit if nothing is set on the Tango device, else
|
||||||
@ -470,30 +458,14 @@ class AnalogOutput(PyTangoDevice, Driveable):
|
|||||||
if attrInfo.unit != 'No unit':
|
if attrInfo.unit != 'No unit':
|
||||||
self.PARAMS['value'].unit = attrInfo.unit
|
self.PARAMS['value'].unit = attrInfo.unit
|
||||||
|
|
||||||
# init history
|
def poll(self, nr):
|
||||||
self._history = [] # will keep (timestamp, value) tuple
|
super(AnalogOutput, self).poll(nr)
|
||||||
mkthread(self._history_thread)
|
|
||||||
|
|
||||||
def _history_thread(self):
|
|
||||||
while True:
|
|
||||||
# adaptive sleeping interval
|
|
||||||
if self.status[0] == status.BUSY:
|
|
||||||
sleep(min(self.pollinterval))
|
|
||||||
else:
|
|
||||||
sleep(min(max(self.pollinterval) / 2.,
|
|
||||||
max(self.window / 10., min(pollinterval))))
|
|
||||||
try:
|
|
||||||
self.read_value(0) # also append to self._history
|
|
||||||
# shorten history
|
|
||||||
while len(self._history) > 2:
|
while len(self._history) > 2:
|
||||||
# if history would be too short, break
|
# if history would be too short, break
|
||||||
if self._history[-1][0] - \
|
if self._history[-1][0] - self._history[1][0] < self.window:
|
||||||
self._history[1][0] < self.window:
|
|
||||||
break
|
break
|
||||||
# remove a stale point
|
# else: remove a stale point
|
||||||
self._history.pop(0)
|
self._history.pop(0)
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
value = self._dev.value
|
value = self._dev.value
|
||||||
@ -560,17 +532,17 @@ class AnalogOutput(PyTangoDevice, Driveable):
|
|||||||
return self._checkLimits(value)
|
return self._checkLimits(value)
|
||||||
|
|
||||||
def write_target(self, value=FloatRange()):
|
def write_target(self, value=FloatRange()):
|
||||||
try:
|
if self.status[0] == status.BUSY:
|
||||||
self._dev.value = value
|
|
||||||
except HardwareError:
|
|
||||||
# changing target value during movement is not allowed by the
|
# changing target value during movement is not allowed by the
|
||||||
# Tango base class state machine. If we are moving, stop first.
|
# Tango base class state machine. If we are moving, stop first.
|
||||||
if self.read_status(0)[0] == status.BUSY:
|
self.do_stop()
|
||||||
self.stop()
|
|
||||||
self._hw_wait()
|
self._hw_wait()
|
||||||
self._dev.value = value
|
self._dev.value = value
|
||||||
else:
|
self.read_status(0) # poll our status to keep it updated
|
||||||
raise
|
|
||||||
|
def _hw_wait(self):
|
||||||
|
while self.read_status(0)[0] == status.BUSY:
|
||||||
|
sleep(0.3)
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
self._dev.Stop()
|
self._dev.Stop()
|
||||||
@ -601,21 +573,21 @@ class Actuator(AnalogOutput):
|
|||||||
poll=30),
|
poll=30),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_speed(self):
|
def read_speed(self, maxage=0):
|
||||||
return self._dev.speed
|
return self._dev.speed
|
||||||
|
|
||||||
def write_speed(self, value):
|
def write_speed(self, value):
|
||||||
self._dev.speed = value
|
self._dev.speed = value
|
||||||
|
|
||||||
def read_ramp(self):
|
def read_ramp(self, maxage=0):
|
||||||
return self.read_speed() * 60
|
return self.read_speed() * 60
|
||||||
|
|
||||||
def write_ramp(self, value):
|
def write_ramp(self, value):
|
||||||
self.write_speed(value / 60.)
|
self.write_speed(value / 60.)
|
||||||
return self.speed * 60
|
return self.read_speed(0) * 60
|
||||||
|
|
||||||
def do_setposition(self, value):
|
# def do_setposition(self, value=FloatRange()):
|
||||||
self._dev.Adjust(value)
|
# self._dev.Adjust(value)
|
||||||
|
|
||||||
|
|
||||||
class Motor(Actuator):
|
class Motor(Actuator):
|
||||||
@ -643,16 +615,16 @@ class Motor(Actuator):
|
|||||||
unit='main/s^2'),
|
unit='main/s^2'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_refpos(self):
|
def read_refpos(self, maxage=0):
|
||||||
return float(self._getProperty('refpos'))
|
return float(self._getProperty('refpos'))
|
||||||
|
|
||||||
def read_accel(self):
|
def read_accel(self, maxage=0):
|
||||||
return self._dev.accel
|
return self._dev.accel
|
||||||
|
|
||||||
def write_accel(self, value):
|
def write_accel(self, value):
|
||||||
self._dev.accel = value
|
self._dev.accel = value
|
||||||
|
|
||||||
def read_decel(self):
|
def read_decel(self, maxage=0):
|
||||||
return self._dev.decel
|
return self._dev.decel
|
||||||
|
|
||||||
def write_decel(self, value):
|
def write_decel(self, value):
|
||||||
@ -695,32 +667,32 @@ class TemperatureController(Actuator):
|
|||||||
'precision': OVERRIDE(default=0.1),
|
'precision': OVERRIDE(default=0.1),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_ramp(self):
|
def read_ramp(self, maxage=0):
|
||||||
return self._dev.ramp
|
return self._dev.ramp
|
||||||
|
|
||||||
def write_ramp(self, value):
|
def write_ramp(self, value):
|
||||||
self._dev.ramp = value
|
self._dev.ramp = value
|
||||||
return self._dev.ramp
|
return self._dev.ramp
|
||||||
|
|
||||||
def read_p(self):
|
def read_p(self, maxage=0):
|
||||||
return self._dev.p
|
return self._dev.p
|
||||||
|
|
||||||
def write_p(self, value):
|
def write_p(self, value):
|
||||||
self._dev.p = value
|
self._dev.p = value
|
||||||
|
|
||||||
def read_i(self):
|
def read_i(self, maxage=0):
|
||||||
return self._dev.i
|
return self._dev.i
|
||||||
|
|
||||||
def write_i(self, value):
|
def write_i(self, value):
|
||||||
self._dev.i = value
|
self._dev.i = value
|
||||||
|
|
||||||
def read_d(self):
|
def read_d(self, maxage=0):
|
||||||
return self._dev.d
|
return self._dev.d
|
||||||
|
|
||||||
def write_d(self, value):
|
def write_d(self, value):
|
||||||
self._dev.d = value
|
self._dev.d = value
|
||||||
|
|
||||||
def read_pid(self):
|
def read_pid(self, maxage=0):
|
||||||
self.read_p()
|
self.read_p()
|
||||||
self.read_i()
|
self.read_i()
|
||||||
self.read_d()
|
self.read_d()
|
||||||
@ -731,10 +703,10 @@ class TemperatureController(Actuator):
|
|||||||
self._dev.i = value[1]
|
self._dev.i = value[1]
|
||||||
self._dev.d = value[2]
|
self._dev.d = value[2]
|
||||||
|
|
||||||
def read_setpoint(self):
|
def read_setpoint(self, maxage=0):
|
||||||
return self._dev.setpoint
|
return self._dev.setpoint
|
||||||
|
|
||||||
def read_heateroutput(self):
|
def read_heateroutput(self, maxage=0):
|
||||||
return self._dev.heaterOutput
|
return self._dev.heaterOutput
|
||||||
|
|
||||||
|
|
||||||
@ -747,21 +719,21 @@ class PowerSupply(Actuator):
|
|||||||
'ramp': PARAM('Current/voltage ramp', unit='main/min',
|
'ramp': PARAM('Current/voltage ramp', unit='main/min',
|
||||||
datatype=FloatRange(), readonly=False, poll=30,),
|
datatype=FloatRange(), readonly=False, poll=30,),
|
||||||
'voltage': PARAM('Actual voltage', unit='V',
|
'voltage': PARAM('Actual voltage', unit='V',
|
||||||
datatype=FloatRange(), poll=5),
|
datatype=FloatRange(), poll=-5),
|
||||||
'current': PARAM('Actual current', unit='A',
|
'current': PARAM('Actual current', unit='A',
|
||||||
datatype=FloatRange(), poll=5),
|
datatype=FloatRange(), poll=-5),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_ramp(self):
|
def read_ramp(self, maxage=0):
|
||||||
return self._dev.ramp
|
return self._dev.ramp
|
||||||
|
|
||||||
def write_ramp(self, value):
|
def write_ramp(self, value):
|
||||||
self._dev.ramp = value
|
self._dev.ramp = value
|
||||||
|
|
||||||
def read_voltage(self):
|
def read_voltage(self, maxage=0):
|
||||||
return self._dev.voltage
|
return self._dev.voltage
|
||||||
|
|
||||||
def read_current(self):
|
def read_current(self, maxage=0):
|
||||||
return self._dev.current
|
return self._dev.current
|
||||||
|
|
||||||
|
|
||||||
@ -771,7 +743,7 @@ class DigitalInput(PyTangoDevice, Readable):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
OVERRIDES = {
|
OVERRIDES = {
|
||||||
'value': OVERRIDE(datatype=IntRange(0)),
|
'value': OVERRIDE(datatype=IntRange()),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
@ -835,8 +807,8 @@ class DigitalOutput(PyTangoDevice, Driveable):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
OVERRIDES = {
|
OVERRIDES = {
|
||||||
'value': OVERRIDE(datatype=IntRange(0)),
|
'value': OVERRIDE(datatype=IntRange()),
|
||||||
'target': OVERRIDE(datatype=IntRange(0)),
|
'target': OVERRIDE(datatype=IntRange()),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_value(self, maxage=0):
|
def read_value(self, maxage=0):
|
||||||
@ -856,17 +828,23 @@ class NamedDigitalOutput(DigitalOutput):
|
|||||||
A DigitalOutput with numeric values mapped to names.
|
A DigitalOutput with numeric values mapped to names.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PARAMS = {
|
# PARAMS = {
|
||||||
'mapping': PARAM('A dictionary mapping state names to integers',
|
# 'mapping': PARAM('A dictionary mapping state names to integers',
|
||||||
datatype=StringType(), export=False), # XXX: !!!
|
# datatype=EnumType(), export=False), # XXX: !!!
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
|
# def init(self):
|
||||||
|
# super(NamedDigitalOutput, self).init()
|
||||||
|
# try: # XXX: !!!
|
||||||
|
# self.PARAMS['value'].datatype = EnumType(**eval(self.mapping))
|
||||||
|
# except Exception as e:
|
||||||
|
# raise ValueError('Illegal Value for mapping: %r' % e)
|
||||||
|
|
||||||
def init(self):
|
def write_target(self, target):
|
||||||
super(NamedDigitalOutput, self).init()
|
# map from enum-str to integer value
|
||||||
try: # XXX: !!!
|
self._dev.value = self.PARAMS[
|
||||||
self.PARAMS['value'].datatype = EnumType(**eval(self.mapping))
|
'target'].datatype.reversed.get(target, target)
|
||||||
except Exception as e:
|
self.read_value()
|
||||||
raise ValueError('Illegal Value for mapping: %r' % e)
|
|
||||||
|
|
||||||
|
|
||||||
class PartialDigitalOutput(NamedDigitalOutput):
|
class PartialDigitalOutput(NamedDigitalOutput):
|
||||||
@ -930,19 +908,19 @@ class StringIO(PyTangoDevice, Device):
|
|||||||
group='communication'),
|
group='communication'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_bustimeout(self):
|
def read_bustimeout(self, maxage=0):
|
||||||
return self._dev.communicationTimeout
|
return self._dev.communicationTimeout
|
||||||
|
|
||||||
def write_bustimeout(self, value):
|
def write_bustimeout(self, value):
|
||||||
self._dev.communicationTimeout = value
|
self._dev.communicationTimeout = value
|
||||||
|
|
||||||
def read_endofline(self):
|
def read_endofline(self, maxage=0):
|
||||||
return self._dev.endOfLine
|
return self._dev.endOfLine
|
||||||
|
|
||||||
def write_endofline(self, value):
|
def write_endofline(self, value):
|
||||||
self._dev.endOfLine = value
|
self._dev.endOfLine = value
|
||||||
|
|
||||||
def read_startofline(self):
|
def read_startofline(self, maxage=0):
|
||||||
return self._dev.startOfLine
|
return self._dev.startOfLine
|
||||||
|
|
||||||
def write_startofline(self, value):
|
def write_startofline(self, value):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user