Change-Id: I4e40e0ef8e80999832846eac3a415fdd767c6d98
This commit is contained in:
Enrico Faulhaber 2017-05-24 17:13:06 +02:00
parent 241af728d6
commit 462b6a0a7e
25 changed files with 130 additions and 84 deletions

View File

@ -27,6 +27,7 @@ import code
class NameSpace(dict): class NameSpace(dict):
def __init__(self): def __init__(self):
dict.__init__(self) dict.__init__(self)
self.__const = set() self.__const = set()
@ -63,6 +64,7 @@ from os import path
class ClientConsole(object): class ClientConsole(object):
def __init__(self, cfgname, basepath): def __init__(self, cfgname, basepath):
self.namespace = NameSpace() self.namespace = NameSpace()
self.namespace.setconst('help', self.helpCmd) self.namespace.setconst('help', self.helpCmd)
@ -98,6 +100,7 @@ from secop.protocol.messages import *
class TCPConnection(object): class TCPConnection(object):
def __init__(self, connect, port, encoding, framing, **kwds): def __init__(self, connect, port, encoding, framing, **kwds):
self.log = mlzlog.log.getChild('connection', False) self.log = mlzlog.log.getChild('connection', False)
self.encoder = ENCODERS[encoding]() self.encoder = ENCODERS[encoding]()
@ -164,6 +167,7 @@ class TCPConnection(object):
class Client(object): class Client(object):
def __init__(self, opts): def __init__(self, opts):
self.log = mlzlog.log.getChild('client', True) self.log = mlzlog.log.getChild('client', True)
self._cache = dict() self._cache = dict()

View File

@ -46,6 +46,7 @@ EVENT_ONLY_ON_CHANGED_VALUES = False
class PARAM(object): class PARAM(object):
def __init__(self, def __init__(self,
description, description,
validator=float, validator=float,
@ -93,6 +94,7 @@ class PARAM(object):
# storage for CMDs settings (description + call signature...) # storage for CMDs settings (description + call signature...)
class CMD(object): class CMD(object):
def __init__(self, description, arguments, result): def __init__(self, description, arguments, result):
# descriptive text for humans # descriptive text for humans
self.description = description self.description = description
@ -117,6 +119,7 @@ class CMD(object):
class DeviceMeta(type): class DeviceMeta(type):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
newtype = type.__new__(mcs, name, bases, attrs) newtype = type.__new__(mcs, name, bases, attrs)
if '__constructed__' in attrs: if '__constructed__' in attrs:
@ -221,10 +224,10 @@ class Device(object):
# static PROPERTIES, definitions in derived classes should overwrite earlier ones. # static PROPERTIES, definitions in derived classes should overwrite earlier ones.
# how to configure some stuff which makes sense to take from configfile??? # how to configure some stuff which makes sense to take from configfile???
PROPERTIES = { PROPERTIES = {
'group' : None, # some Modules may be grouped together 'group': None, # some Modules may be grouped together
'meaning' : None, # XXX: ??? 'meaning': None, # XXX: ???
'priority' : None, # XXX: ??? 'priority': None, # XXX: ???
'visibility' : None, # XXX: ???? 'visibility': None, # XXX: ????
# what else? # what else?
} }
# PARAMS and CMDS are auto-merged upon subclassing # PARAMS and CMDS are auto-merged upon subclassing
@ -251,7 +254,8 @@ class Device(object):
self.PARAMS = params self.PARAMS = params
# check and apply properties specified in cfgdict # check and apply properties specified in cfgdict
# moduleproperties are to be specified as '.<propertyname>=<propertyvalue>' # moduleproperties are to be specified as
# '.<propertyname>=<propertyvalue>'
for k, v in cfgdict.items(): for k, v in cfgdict.items():
if k[0] == '.': if k[0] == '.':
if k[1:] in self.PROPERTIES: if k[1:] in self.PROPERTIES:
@ -266,13 +270,13 @@ class Device(object):
self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0] self.PROPERTIES['interface'] = self.PROPERTIES['interfaces'][0]
# remove unset (default) module properties # remove unset (default) module properties
for k,v in self.PROPERTIES.items(): for k, v in self.PROPERTIES.items():
if v == None: if v == None:
del self.PROPERTIES[k] del self.PROPERTIES[k]
# check and apply parameter_properties # check and apply parameter_properties
# specified as '<paramname>.<propertyname> = <propertyvalue>' # specified as '<paramname>.<propertyname> = <propertyvalue>'
for k,v in cfgdict.items()[:]: for k, v in cfgdict.items()[:]:
if '.' in k[1:]: if '.' in k[1:]:
paramname, propname = k.split('.', 1) paramname, propname = k.split('.', 1)
if paramname in self.PARAMS: if paramname in self.PARAMS:

View File

@ -30,9 +30,11 @@ from secop.protocol import status
from secop.validators import floatrange, positive, enum, nonnegative, vector from secop.validators import floatrange, positive, enum, nonnegative, vector
from secop.lib import clamp, mkthread from secop.lib import clamp, mkthread
class CryoBase(Driveable): class CryoBase(Driveable):
pass pass
class Cryostat(CryoBase): class Cryostat(CryoBase):
"""simulated cryostat with: """simulated cryostat with:
@ -81,7 +83,8 @@ class Cryostat(CryoBase):
validator=nonnegative, default=0, unit="K", validator=nonnegative, default=0, unit="K",
), ),
pid=PARAM("regulation coefficients", pid=PARAM("regulation coefficients",
validator=vector(nonnegative, floatrange(0, 100), floatrange(0, 100)), validator=vector(nonnegative, floatrange(
0, 100), floatrange(0, 100)),
default=(40, 10, 2), readonly=False, default=(40, 10, 2), readonly=False,
group='pid', group='pid',
), ),

View File

@ -28,16 +28,20 @@ from secop.devices.core import Readable, Device, Driveable, PARAM
from secop.protocol import status from secop.protocol import status
try: try:
from pvaccess import Channel #import EPIVSv4 functionallity, PV access from pvaccess import Channel # import EPIVSv4 functionallity, PV access
except ImportError: except ImportError:
class Channel(object): class Channel(object):
def __init__(self, pv_name): def __init__(self, pv_name):
self.pv_name = pv_name self.pv_name = pv_name
self.value = 0.0 self.value = 0.0
def get(self): def get(self):
return self return self
def getDouble(self): def getDouble(self):
return self.value return self.value
def put(self, value): def put(self, value):
try: try:
self.value = value self.value = value
@ -48,10 +52,12 @@ try:
from epics import PV from epics import PV
except ImportError: except ImportError:
class PV(object): class PV(object):
def __init__(self, pv_name): def __init__(self, pv_name):
self.pv_name = pv_name self.pv_name = pv_name
self.value = 0.0 self.value = 0.0
class EpicsReadable(Readable): class EpicsReadable(Readable):
"""EpicsDriveable handles a Driveable interfacing to EPICS v4""" """EpicsDriveable handles a Driveable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices # Commmon PARAMS for all EPICS devices
@ -71,7 +77,8 @@ class EpicsReadable(Readable):
def _read_pv(self, pv_name): def _read_pv(self, pv_name):
if self.epics_version == 'v4': if self.epics_version == 'v4':
pv_channel = Channel(pv_name) pv_channel = Channel(pv_name)
# TODO: cannot handle read of string (is there a .getText() or .getString() ?) # TODO: cannot handle read of string (is there a .getText() or
# .getString() ?)
return_value = pv_channel.get().getDouble() return_value = pv_channel.get().getDouble()
else: # Not EPICS v4 else: # Not EPICS v4
# TODO: fix this, it does not work # TODO: fix this, it does not work
@ -95,7 +102,6 @@ class EpicsReadable(Readable):
pv = PV(pv_name + ".VAL") pv = PV(pv_name + ".VAL")
pv.value = write_value pv.value = write_value
def read_value(self, maxage=0): def read_value(self, maxage=0):
return self._read_pv(self.value_pv) return self._read_pv(self.value_pv)
@ -109,7 +115,6 @@ class EpicsReadable(Readable):
return (status.OK, 'no pv set') return (status.OK, 'no pv set')
class EpicsDriveable(Driveable): class EpicsDriveable(Driveable):
"""EpicsDriveable handles a Driveable interfacing to EPICS v4""" """EpicsDriveable handles a Driveable interfacing to EPICS v4"""
# Commmon PARAMS for all EPICS devices # Commmon PARAMS for all EPICS devices
@ -133,7 +138,8 @@ class EpicsDriveable(Driveable):
def _read_pv(self, pv_name): def _read_pv(self, pv_name):
if self.epics_version == 'v4': if self.epics_version == 'v4':
pv_channel = Channel(pv_name) pv_channel = Channel(pv_name)
# TODO: cannot handle read of string (is there a .getText() or .getString() ?) # TODO: cannot handle read of string (is there a .getText() or
# .getString() ?)
return_value = pv_channel.get().getDouble() return_value = pv_channel.get().getDouble()
else: # Not EPICS v4 else: # Not EPICS v4
# TODO: fix this, it does not work # TODO: fix this, it does not work
@ -177,9 +183,11 @@ class EpicsDriveable(Driveable):
(status.BUSY, 'Moving') (status.BUSY, 'Moving')
"""Temperature control loop""" """Temperature control loop"""
# should also derive from secop.core.temperaturecontroller, once its features are agreed upon # should also derive from secop.core.temperaturecontroller, once its
# features are agreed upon
class EpicsTempCtrl(EpicsDriveable): class EpicsTempCtrl(EpicsDriveable):
PARAMS = { PARAMS = {
@ -187,7 +195,7 @@ class EpicsTempCtrl(EpicsDriveable):
'heaterrange': PARAM('Heater range', validator=str, 'heaterrange': PARAM('Heater range', validator=str,
default='Off', readonly=False,), default='Off', readonly=False,),
'tolerance': PARAM('allowed deviation between value and target', 'tolerance': PARAM('allowed deviation between value and target',
validator=floatrange(1e-6,1e6), default=0.1, validator=floatrange(1e-6, 1e6), default=0.1,
readonly=False,), readonly=False,),
# 'private' parameters: not remotely accessible # 'private' parameters: not remotely accessible
'heaterrange_pv': PARAM('EPICS pv_name of heater range', 'heaterrange_pv': PARAM('EPICS pv_name of heater range',
@ -213,10 +221,9 @@ class EpicsTempCtrl(EpicsDriveable):
return (status.OK, 'at Target') if at_target else (status.BUSY, 'Moving') return (status.OK, 'at Target') if at_target else (status.BUSY, 'Moving')
# TODO: add support for strings over epics pv # TODO: add support for strings over epics pv
#def read_heaterrange(self, maxage=0): # def read_heaterrange(self, maxage=0):
# return self._read_pv(self.heaterrange_pv) # return self._read_pv(self.heaterrange_pv)
# TODO: add support for strings over epics pv # TODO: add support for strings over epics pv
#def write_heaterrange(self, range_value): # def write_heaterrange(self, range_value):
# self._write_pv(self.heaterrange_pv, range_value) # self._write_pv(self.heaterrange_pv, range_value)

View File

@ -37,6 +37,7 @@ ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2
ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3 ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3
class QSECNode(SECNode, QObject): class QSECNode(SECNode, QObject):
newData = pyqtSignal(str, str, object) # module, parameter, data newData = pyqtSignal(str, str, object) # module, parameter, data
@ -63,6 +64,7 @@ class QSECNode(SECNode, QObject):
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent) super(MainWindow, self).__init__(parent)

View File

@ -56,6 +56,7 @@ class ParameterButtons(QWidget):
class ModuleCtrl(QWidget): class ModuleCtrl(QWidget):
def __init__(self, node, module, parent=None): def __init__(self, node, module, parent=None):
super(ModuleCtrl, self).__init__(parent) super(ModuleCtrl, self).__init__(parent)
loadUi(self, 'modulectrl.ui') loadUi(self, 'modulectrl.ui')

View File

@ -32,6 +32,7 @@ from secop.protocol.errors import SECOPError
class NodeCtrl(QWidget): class NodeCtrl(QWidget):
def __init__(self, node, parent=None): def __init__(self, node, parent=None):
super(NodeCtrl, self).__init__(parent) super(NodeCtrl, self).__init__(parent)
loadUi(self, 'nodectrl.ui') loadUi(self, 'nodectrl.ui')

View File

@ -29,6 +29,7 @@ from secop.validators import validator_to_str
class ParameterView(QWidget): class ParameterView(QWidget):
def __init__(self, node, module, parameter, parent=None): def __init__(self, node, module, parameter, parent=None):
super(ParameterView, self).__init__(parent) super(ParameterView, self).__init__(parent)
loadUi(self, 'paramview.ui') loadUi(self, 'paramview.ui')

View File

@ -80,6 +80,7 @@ def format_time(timestamp=None):
class Timezone(tzinfo): class Timezone(tzinfo):
def __init__(self, offset, name='unknown timezone'): def __init__(self, offset, name='unknown timezone'):
self.offset = offset self.offset = offset
self.name = name self.name = name

View File

@ -46,6 +46,7 @@ from secop.lib.parsing import format_time
class Dispatcher(object): class Dispatcher(object):
def __init__(self, logger, options): def __init__(self, logger, options):
self.equipment_id = options.pop('equipment_id') self.equipment_id = options.pop('equipment_id')
self.log = logger self.log = logger
@ -213,7 +214,7 @@ class Dispatcher(object):
dd = { dd = {
'parameters': self.list_module_params(modulename, only_static=True), 'parameters': self.list_module_params(modulename, only_static=True),
'commands': self.list_module_cmds(modulename), 'commands': self.list_module_cmds(modulename),
'properties' : module.PROPERTIES, 'properties': module.PROPERTIES,
} }
result['modules'][modulename] = dd result['modules'][modulename] = dd
result['equipment_id'] = self.equipment_id result['equipment_id'] = self.equipment_id

View File

@ -36,6 +36,7 @@ DEMO_RE = re.compile(
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
def decode(sef, encoded): def decode(sef, encoded):
# match [!][*|devicename][: *|paramname [: *|propname]] [=value] # match [!][*|devicename][: *|paramname [: *|propname]] [=value]
match = DEMO_RE.match(encoded) match = DEMO_RE.match(encoded)

View File

@ -92,6 +92,7 @@ DEMO_RE_OTHER = re.compile(
class DemoEncoder(MessageEncoder): class DemoEncoder(MessageEncoder):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
MessageEncoder.__init__(self, *args, **kwds) MessageEncoder.__init__(self, *args, **kwds)
self.result = [] # for decoding self.result = [] # for decoding
@ -321,6 +322,7 @@ DEMO_RE_MZ = re.compile(
class DemoEncoder_MZ(MessageEncoder): class DemoEncoder_MZ(MessageEncoder):
def decode(sef, encoded): def decode(sef, encoded):
m = DEMO_RE_MZ.match(encoded) m = DEMO_RE_MZ.match(encoded)
if m: if m:

View File

@ -135,15 +135,18 @@ class DemoEncoder(MessageEncoder):
encode_cmd_result, ), encode_cmd_result, ),
WriteRequest: ( WriteRequest: (
WRITEREQUEST, WRITEREQUEST,
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, lambda msg: "%s:%s" % (
msg.module, msg.parameter) if msg.parameter else msg.module,
'value', ), 'value', ),
WriteReply: ( WriteReply: (
WRITEREPLY, WRITEREPLY,
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, lambda msg: "%s:%s" % (
msg.module, msg.parameter) if msg.parameter else msg.module,
'value', ), 'value', ),
PollRequest: ( PollRequest: (
TRIGGERREQUEST, TRIGGERREQUEST,
lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, lambda msg: "%s:%s" % (
msg.module, msg.parameter) if msg.parameter else msg.module,
), ),
HeartbeatRequest: ( HeartbeatRequest: (
HEARTBEATREQUEST, HEARTBEATREQUEST,
@ -158,7 +161,8 @@ class DemoEncoder(MessageEncoder):
encode_error_msg, ), encode_error_msg, ),
Value: ( Value: (
EVENT, EVENT,
lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()')) if msg.parameter or msg.command else msg.module, lambda msg: "%s:%s" % (msg.module, msg.parameter or (
msg.command + '()')) if msg.parameter or msg.command else msg.module,
encode_value_data, ), encode_value_data, ),
} }
DECODEMAP = { DECODEMAP = {

View File

@ -35,6 +35,7 @@ except ImportError:
class PickleEncoder(MessageEncoder): class PickleEncoder(MessageEncoder):
def encode(self, messageobj): def encode(self, messageobj):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
return pickle.dumps(messageobj) return pickle.dumps(messageobj)

View File

@ -37,6 +37,7 @@ SCPMESSAGE = re.compile(
class SCPEncoder(MessageEncoder): class SCPEncoder(MessageEncoder):
def encode(self, msg): def encode(self, msg):
"""msg object -> transport layer message""" """msg object -> transport layer message"""
# fun for Humans # fun for Humans

View File

@ -30,6 +30,7 @@ from secop.lib.parsing import *
class TextEncoder(MessageEncoder): class TextEncoder(MessageEncoder):
def __init__(self): def __init__(self):
# build safe namespace # build safe namespace
ns = dict() ns = dict()

View File

@ -23,6 +23,7 @@
class SECOPError(RuntimeError): class SECOPError(RuntimeError):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
self.args = args self.args = args
for k, v in kwds.items(): for k, v in kwds.items():

View File

@ -35,6 +35,7 @@ from secop.protocol.messages import HelpMessage
class TCPRequestHandler(SocketServer.BaseRequestHandler): class TCPRequestHandler(SocketServer.BaseRequestHandler):
def setup(self): def setup(self):
self.log = self.server.log self.log = self.server.log
self._queue = collections.deque(maxlen=100) self._queue = collections.deque(maxlen=100)

View File

@ -50,6 +50,7 @@ class Message(object):
class Value(object): class Value(object):
def __init__(self, def __init__(self,
module, module,
parameter=None, parameter=None,

View File

@ -95,6 +95,7 @@ class Message(object):
class Value(object): class Value(object):
def __init__(self, value=Ellipsis, qualifiers=None, **kwds): def __init__(self, value=Ellipsis, qualifiers=None, **kwds):
self.dev = '' self.dev = ''
self.param = '' self.param = ''
@ -166,6 +167,7 @@ class HelpMessage(Message):
class NoSuchDeviceError(ErrorMessage): class NoSuchDeviceError(ErrorMessage):
def __init__(self, *devs): def __init__(self, *devs):
ErrorMessage.__init__( ErrorMessage.__init__(
self, self,
@ -175,6 +177,7 @@ class NoSuchDeviceError(ErrorMessage):
class NoSuchParamError(ErrorMessage): class NoSuchParamError(ErrorMessage):
def __init__(self, dev, *params): def __init__(self, dev, *params):
ErrorMessage.__init__( ErrorMessage.__init__(
self, self,
@ -185,6 +188,7 @@ class NoSuchParamError(ErrorMessage):
class ParamReadonlyError(ErrorMessage): class ParamReadonlyError(ErrorMessage):
def __init__(self, dev, *params): def __init__(self, dev, *params):
ErrorMessage.__init__( ErrorMessage.__init__(
self, self,
@ -196,6 +200,7 @@ class ParamReadonlyError(ErrorMessage):
class InvalidParamValueError(ErrorMessage): class InvalidParamValueError(ErrorMessage):
def __init__(self, dev, param, value, e): def __init__(self, dev, param, value, e):
ErrorMessage.__init__( ErrorMessage.__init__(
self, self,
@ -207,6 +212,7 @@ class InvalidParamValueError(ErrorMessage):
class InternalError(ErrorMessage): class InternalError(ErrorMessage):
def __init__(self, err, **kwds): def __init__(self, err, **kwds):
ErrorMessage.__init__( ErrorMessage.__init__(
self, errorstring=str(err), errortype='InternalError', **kwds) self, errorstring=str(err), errortype='InternalError', **kwds)
@ -217,7 +223,7 @@ MESSAGE = dict((cls.MSGTYPE, cls)
HelpMessage, ErrorMessage, EventMessage, TriggerMessage, HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
UnsubscribeMessage, SubscribeMessage, PollMessage, UnsubscribeMessage, SubscribeMessage, PollMessage,
CommandMessage, WriteMessage, ReadMessage, ListMessage CommandMessage, WriteMessage, ReadMessage, ListMessage
]) ])
if __name__ == '__main__': if __name__ == '__main__':
print("Minimal testing of messages....") print("Minimal testing of messages....")

View File

@ -39,6 +39,7 @@ from secop.errors import ConfigError
class Server(object): class Server(object):
def __init__(self, name, workdir, parentLogger=None): def __init__(self, name, workdir, parentLogger=None):
self._name = name self._name = name
self._workdir = workdir self._workdir = workdir

View File

@ -200,6 +200,7 @@ class oneof(Validator):
class enum(Validator): class enum(Validator):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
self.mapping = {} self.mapping = {}
# use given kwds directly # use given kwds directly