unify name and module on Attached property
- setting the attribute using the name of an attached module - getting the attribute results in the module object + change names iodev to io, iodevClass to ioClass, sendRecv to communicate, HasIodev to HasIO Change-Id: I200b63a5a7dc1453bf6ac998782b065645201900 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27575 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
@ -93,6 +93,7 @@ def main(argv=None):
|
|||||||
if args.relaxed:
|
if args.relaxed:
|
||||||
generalConfig.defaults['lazy_number_validation'] = True
|
generalConfig.defaults['lazy_number_validation'] = True
|
||||||
generalConfig.defaults['disable_value_range_check'] = True
|
generalConfig.defaults['disable_value_range_check'] = True
|
||||||
|
generalConfig.defaults['legacy_hasiodev'] = True
|
||||||
generalConfig.init(args.gencfg)
|
generalConfig.init(args.gencfg)
|
||||||
logger.init(loglevel)
|
logger.init(loglevel)
|
||||||
|
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
[node LscSIM.psi.ch]
|
[NODE]
|
||||||
|
id = LscSIM.psi.ch
|
||||||
description = Lsc Simulation at PSI
|
description = Lsc Simulation at PSI
|
||||||
|
|
||||||
[interface tcp]
|
[INTERFACE]
|
||||||
type = tcp
|
uri = tcp://5000
|
||||||
bindto = 0.0.0.0
|
|
||||||
bindport = 5000
|
|
||||||
|
|
||||||
[module res]
|
[res]
|
||||||
class = secop_psi.ls370res.ResChannel
|
class = secop_psi.ls370res.ResChannel
|
||||||
.channel = 3
|
channel = 3
|
||||||
.description = resistivity
|
description = resistivity
|
||||||
.main = lsmain
|
main = lsmain
|
||||||
.iodev = lscom
|
io = lscom
|
||||||
|
|
||||||
[module lsmain]
|
[lsmain]
|
||||||
class = secop_psi.ls370res.Main
|
class = secop_psi.ls370res.Main
|
||||||
.description = main control of Lsc controller
|
description = main control of Lsc controller
|
||||||
.iodev = lscom
|
io = lscom
|
||||||
|
|
||||||
[module lscom]
|
[lscom]
|
||||||
class = secop_psi.ls370sim.Ls370Sim
|
class = secop_psi.ls370sim.Ls370Sim
|
||||||
.description = simulated serial communicator to a LS 370
|
description = simulated serial communicator to a LS 370
|
||||||
.visibility = 3
|
visibility = 3
|
||||||
|
92
cfg/ppms.cfg
92
cfg/ppms.cfg
@ -8,117 +8,117 @@ uri = tcp://5000
|
|||||||
[tt]
|
[tt]
|
||||||
class = secop_psi.ppms.Temp
|
class = secop_psi.ppms.Temp
|
||||||
description = main temperature
|
description = main temperature
|
||||||
iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[mf]
|
[mf]
|
||||||
class = secop_psi.ppms.Field
|
class = secop_psi.ppms.Field
|
||||||
target.min = -9
|
target.min = -9
|
||||||
target.max = 9
|
target.max = 9
|
||||||
.description = magnetic field
|
description = magnetic field
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[pos]
|
[pos]
|
||||||
class = secop_psi.ppms.Position
|
class = secop_psi.ppms.Position
|
||||||
.description = sample rotator
|
description = sample rotator
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[lev]
|
[lev]
|
||||||
class = secop_psi.ppms.Level
|
class = secop_psi.ppms.Level
|
||||||
.description = helium level
|
description = helium level
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[chamber]
|
[chamber]
|
||||||
class = secop_psi.ppms.Chamber
|
class = secop_psi.ppms.Chamber
|
||||||
.description = chamber state
|
description = chamber state
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r1]
|
[r1]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = secop_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 1
|
description = resistivity channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r2]
|
[r2]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = secop_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 2
|
description = resistivity channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r3]
|
[r3]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = secop_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 3
|
description = resistivity channel 3
|
||||||
.no = 3
|
no = 3
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r4]
|
[r4]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = secop_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 4
|
description = resistivity channel 4
|
||||||
.no = 4
|
no = 4
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i1]
|
[i1]
|
||||||
class = secop_psi.ppms.Channel
|
class = secop_psi.ppms.Channel
|
||||||
.description = current channel 1
|
description = current channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i2]
|
[i2]
|
||||||
class = secop_psi.ppms.Channel
|
class = secop_psi.ppms.Channel
|
||||||
.description = current channel 2
|
description = current channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i3]
|
[i3]
|
||||||
class = secop_psi.ppms.Channel
|
class = secop_psi.ppms.Channel
|
||||||
.description = current channel 3
|
description = current channel 3
|
||||||
.no = 3
|
no = 3
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i4]
|
[i4]
|
||||||
class = secop_psi.ppms.Channel
|
class = secop_psi.ppms.Channel
|
||||||
.description = current channel 4
|
description = current channel 4
|
||||||
.no = 4
|
no = 4
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[v1]
|
[v1]
|
||||||
class = secop_psi.ppms.DriverChannel
|
class = secop_psi.ppms.DriverChannel
|
||||||
.description = voltage channel 1
|
description = voltage channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = V
|
value.unit = V
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[v2]
|
[v2]
|
||||||
class = secop_psi.ppms.DriverChannel
|
class = secop_psi.ppms.DriverChannel
|
||||||
.description = voltage channel 2
|
description = voltage channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = V
|
value.unit = V
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[tv]
|
[tv]
|
||||||
class = secop_psi.ppms.UserChannel
|
class = secop_psi.ppms.UserChannel
|
||||||
.description = VTI temperature
|
description = VTI temperature
|
||||||
enabled = 1
|
enabled = 1
|
||||||
value.unit = K
|
value.unit = K
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[ts]
|
[ts]
|
||||||
class = secop_psi.ppms.UserChannel
|
class = secop_psi.ppms.UserChannel
|
||||||
.description = sample temperature
|
description = sample temperature
|
||||||
enabled = 1
|
enabled = 1
|
||||||
value.unit = K
|
value.unit = K
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[ppms]
|
[ppms]
|
||||||
class = secop_psi.ppms.Main
|
class = secop_psi.ppms.Main
|
||||||
.description = the main and poller module
|
description = the main and poller module
|
||||||
.class_id = QD.MULTIVU.PPMS.1
|
class_id = QD.MULTIVU.PPMS.1
|
||||||
.visibility = 3
|
visibility = 3
|
||||||
pollinterval = 2
|
pollinterval = 2
|
||||||
|
@ -59,7 +59,7 @@ Communication
|
|||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: communicate, multicomm
|
:members: communicate, multicomm
|
||||||
|
|
||||||
.. autoclass:: secop.io.HasIodev
|
.. autoclass:: secop.io.HasIO
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: secop.iohandler.IOHandlerBase
|
.. autoclass:: secop.iohandler.IOHandlerBase
|
||||||
|
@ -22,7 +22,7 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
# the most common Frappy classes can be imported from secop.core
|
# the most common Frappy classes can be imported from secop.core
|
||||||
from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIodev
|
from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO
|
||||||
|
|
||||||
|
|
||||||
class CCU4IO(StringIO):
|
class CCU4IO(StringIO):
|
||||||
@ -34,14 +34,13 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
identification = [('cid', r'CCU4.*')]
|
identification = [('cid', r'CCU4.*')]
|
||||||
|
|
||||||
|
|
||||||
# inheriting the HasIodev mixin creates us a private attribute *_iodev*
|
# inheriting HasIO allows us to use the communicate method for talking with the hardware
|
||||||
# for talking with the hardware
|
|
||||||
# Readable as a base class defines the value and status parameters
|
# Readable as a base class defines the value and status parameters
|
||||||
class HeLevel(HasIodev, Readable):
|
class HeLevel(HasIO, Readable):
|
||||||
"""He Level channel of CCU4"""
|
"""He Level channel of CCU4"""
|
||||||
|
|
||||||
# define the communication class to create the IO module
|
# define the communication class to create the IO module
|
||||||
iodevClass = CCU4IO
|
ioClass = CCU4IO
|
||||||
|
|
||||||
# define or alter the parameters
|
# define or alter the parameters
|
||||||
# as Readable.value exists already, we give only the modified property 'unit'
|
# as Readable.value exists already, we give only the modified property 'unit'
|
||||||
@ -49,7 +48,7 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
# method for reading the main value
|
# method for reading the main value
|
||||||
reply = self._iodev.communicate('h') # send 'h\n' and get the reply 'h=<value>\n'
|
reply = self.communicate('h') # send 'h\n' and get the reply 'h=<value>\n'
|
||||||
name, txtvalue = reply.split('=')
|
name, txtvalue = reply.split('=')
|
||||||
assert name == 'h' # check that we got a reply to our command
|
assert name == 'h' # check that we got a reply to our command
|
||||||
return txtvalue # the framework will automatically convert the string to a float
|
return txtvalue # the framework will automatically convert the string to a float
|
||||||
@ -115,17 +114,17 @@ the status codes from the hardware to the standard SECoP status codes.
|
|||||||
}
|
}
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
name, txtvalue = self._iodev.communicate('hsf').split('=')
|
name, txtvalue = self.communicate('hsf').split('=')
|
||||||
assert name == 'hsf'
|
assert name == 'hsf'
|
||||||
return self.STATUS_MAP(int(txtvalue))
|
return self.STATUS_MAP(int(txtvalue))
|
||||||
|
|
||||||
def read_empty_length(self):
|
def read_empty_length(self):
|
||||||
name, txtvalue = self._iodev.communicate('hem').split('=')
|
name, txtvalue = self.communicate('hem').split('=')
|
||||||
assert name == 'hem'
|
assert name == 'hem'
|
||||||
return txtvalue
|
return txtvalue
|
||||||
|
|
||||||
def write_empty_length(self, value):
|
def write_empty_length(self, value):
|
||||||
name, txtvalue = self._iodev.communicate('hem=%g' % value).split('=')
|
name, txtvalue = self.communicate('hem=%g' % value).split('=')
|
||||||
assert name == 'hem'
|
assert name == 'hem'
|
||||||
return txtvalue
|
return txtvalue
|
||||||
|
|
||||||
@ -152,7 +151,7 @@ which means it might be worth to create a *query* method, and then the
|
|||||||
for changing a parameter
|
for changing a parameter
|
||||||
:returns: the (new) value of the parameter
|
:returns: the (new) value of the parameter
|
||||||
"""
|
"""
|
||||||
name, txtvalue = self._iodev.communicate(cmd).split('=')
|
name, txtvalue = self.communicate(cmd).split('=')
|
||||||
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||||
return txtvalue # Frappy will automatically convert the string to the needed data type
|
return txtvalue # Frappy will automatically convert the string to the needed data type
|
||||||
|
|
||||||
|
@ -36,5 +36,5 @@ from secop.params import Command, Parameter
|
|||||||
from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
|
from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW
|
||||||
from secop.properties import Property
|
from secop.properties import Property
|
||||||
from secop.proxy import Proxy, SecNode, proxy_class
|
from secop.proxy import Proxy, SecNode, proxy_class
|
||||||
from secop.io import HasIodev, StringIO, BytesIO
|
from secop.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
|
||||||
from secop.persistent import PersistentMixin, PersistentParam
|
from secop.persistent import PersistentMixin, PersistentParam
|
||||||
|
63
secop/io.py
63
secop/io.py
@ -29,56 +29,79 @@ import time
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from secop.lib.asynconn import AsynConn, ConnectionClosed
|
from secop.lib.asynconn import AsynConn, ConnectionClosed
|
||||||
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, StringType, TupleOf, ValueType
|
from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, \
|
||||||
from secop.errors import CommunicationFailedError, CommunicationSilentError, ConfigError
|
StringType, TupleOf, ValueType
|
||||||
|
from secop.errors import CommunicationFailedError, CommunicationSilentError, \
|
||||||
|
ConfigError, ProgrammingError
|
||||||
from secop.modules import Attached, Command, \
|
from secop.modules import Attached, Command, \
|
||||||
Communicator, Done, Module, Parameter, Property
|
Communicator, Done, Module, Parameter, Property
|
||||||
from secop.poller import REGULAR
|
from secop.poller import REGULAR
|
||||||
|
from secop.lib import generalConfig
|
||||||
|
|
||||||
|
|
||||||
|
generalConfig.defaults['legacy_hasiodev'] = False
|
||||||
HEX_CODE = re.compile(r'[0-9a-fA-F][0-9a-fA-F]$')
|
HEX_CODE = re.compile(r'[0-9a-fA-F][0-9a-fA-F]$')
|
||||||
|
|
||||||
|
|
||||||
class HasIodev(Module):
|
class HasIO(Module):
|
||||||
"""Mixin for modules using a communicator"""
|
"""Mixin for modules using a communicator"""
|
||||||
iodev = Attached()
|
io = Attached()
|
||||||
uri = Property('uri for automatic creation of the attached communication module',
|
uri = Property('uri for automatic creation of the attached communication module',
|
||||||
StringType(), default='')
|
StringType(), default='')
|
||||||
|
|
||||||
iodevDict = {}
|
ioDict = {}
|
||||||
|
ioClass = None
|
||||||
|
|
||||||
def __init__(self, name, logger, opts, srv):
|
def __init__(self, name, logger, opts, srv):
|
||||||
iodev = opts.get('iodev')
|
io = opts.get('io')
|
||||||
super().__init__(name, logger, opts, srv)
|
super().__init__(name, logger, opts, srv)
|
||||||
if self.uri:
|
if self.uri:
|
||||||
opts = {'uri': self.uri, 'description': 'communication device for %s' % name,
|
opts = {'uri': self.uri, 'description': 'communication device for %s' % name,
|
||||||
'export': False}
|
'export': False}
|
||||||
ioname = self.iodevDict.get(self.uri)
|
ioname = self.ioDict.get(self.uri)
|
||||||
if not ioname:
|
if not ioname:
|
||||||
ioname = iodev or name + '_iodev'
|
ioname = io or name + '_io'
|
||||||
iodev = self.iodevClass(ioname, srv.log.getChild(ioname), opts, srv)
|
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
|
||||||
iodev.callingModule = []
|
io.callingModule = []
|
||||||
srv.modules[ioname] = iodev
|
srv.modules[ioname] = io
|
||||||
self.iodevDict[self.uri] = ioname
|
self.ioDict[self.uri] = ioname
|
||||||
self.iodev = ioname
|
self.io = ioname
|
||||||
elif not self.iodev:
|
elif not io:
|
||||||
raise ConfigError("Module %s needs a value for either 'uri' or 'iodev'" % name)
|
raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name)
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
try:
|
try:
|
||||||
self._iodev.read_is_connected()
|
self.io.read_is_connected()
|
||||||
except (CommunicationFailedError, AttributeError):
|
except (CommunicationFailedError, AttributeError):
|
||||||
# AttributeError: for missing _iodev?
|
# AttributeError: read_is_connected is not required for an io object
|
||||||
pass
|
pass
|
||||||
super().initModule()
|
super().initModule()
|
||||||
|
|
||||||
def communicate(self, *args):
|
def communicate(self, *args):
|
||||||
return self._iodev.communicate(*args)
|
return self.io.communicate(*args)
|
||||||
|
|
||||||
def multicomm(self, *args):
|
def multicomm(self, *args):
|
||||||
return self._iodev.multicomm(*args)
|
return self.io.multicomm(*args)
|
||||||
|
|
||||||
sendRecv = communicate # TODO: remove legacy stuff
|
|
||||||
|
class HasIodev(HasIO):
|
||||||
|
# TODO: remove this legacy mixin
|
||||||
|
iodevClass = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _iodev(self):
|
||||||
|
return self.io
|
||||||
|
|
||||||
|
def __init__(self, name, logger, opts, srv):
|
||||||
|
self.ioClass = self.iodevClass
|
||||||
|
super().__init__(name, logger, opts, srv)
|
||||||
|
if generalConfig.legacy_hasiodev:
|
||||||
|
self.log.warn('using the HasIodev mixin is deprecated - use HasIO instead')
|
||||||
|
else:
|
||||||
|
self.log.error('legacy HasIodev no longer supported')
|
||||||
|
self.log.error('you may suppress this error message by running the server with --relaxed')
|
||||||
|
raise ProgrammingError('legacy HasIodev no longer supported')
|
||||||
|
self.sendRecv = self.communicate
|
||||||
|
|
||||||
|
|
||||||
class IOBase(Communicator):
|
class IOBase(Communicator):
|
||||||
|
@ -126,7 +126,7 @@ class CmdParser:
|
|||||||
try:
|
try:
|
||||||
argformat % ((0,) * len(casts)) # validate argformat
|
argformat % ((0,) * len(casts)) # validate argformat
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ValueError("%s in %r" % (e, argformat))
|
raise ValueError("%s in %r" % (e, argformat)) from None
|
||||||
|
|
||||||
def format(self, *values):
|
def format(self, *values):
|
||||||
return self.fmt % values
|
return self.fmt % values
|
||||||
@ -242,7 +242,7 @@ class IOHandler(IOHandlerBase):
|
|||||||
contain the command separator at the end.
|
contain the command separator at the end.
|
||||||
"""
|
"""
|
||||||
querycmd = self.make_query(module)
|
querycmd = self.make_query(module)
|
||||||
reply = module.sendRecv(changecmd + querycmd)
|
reply = module.communicate(changecmd + querycmd)
|
||||||
return self.parse_reply(reply)
|
return self.parse_reply(reply)
|
||||||
|
|
||||||
def send_change(self, module, *values):
|
def send_change(self, module, *values):
|
||||||
@ -253,7 +253,7 @@ class IOHandler(IOHandlerBase):
|
|||||||
"""
|
"""
|
||||||
changecmd = self.make_change(module, *values)
|
changecmd = self.make_change(module, *values)
|
||||||
if self.CMDSEPARATOR is None:
|
if self.CMDSEPARATOR is None:
|
||||||
module.sendRecv(changecmd) # ignore result
|
module.communicate(changecmd) # ignore result
|
||||||
return self.send_command(module)
|
return self.send_command(module)
|
||||||
return self.send_command(module, changecmd + self.CMDSEPARATOR)
|
return self.send_command(module, changecmd + self.CMDSEPARATOR)
|
||||||
|
|
||||||
|
@ -771,19 +771,19 @@ class Communicator(HasComlog, Module):
|
|||||||
|
|
||||||
|
|
||||||
class Attached(Property):
|
class Attached(Property):
|
||||||
"""a special property, defining an attached modle
|
"""a special property, defining an attached module
|
||||||
|
|
||||||
assign a module name to this property in the cfg file,
|
assign a module name to this property in the cfg file,
|
||||||
and the server will create an attribute with this module
|
and the server will create an attribute with this module
|
||||||
|
|
||||||
:param attrname: the name of the to be created attribute. if not given
|
|
||||||
the attribute name is the property name prepended by an underscore.
|
|
||||||
"""
|
"""
|
||||||
# we can not put this to properties.py, as it needs datatypes
|
module = None
|
||||||
def __init__(self, attrname=None):
|
|
||||||
self.attrname = attrname
|
|
||||||
# we can not make it mandatory, as the check in Module.__init__ will be before auto-assign in HasIodev
|
|
||||||
super().__init__('attached module', StringType(), mandatory=False)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __init__(self, description='attached module'):
|
||||||
return 'Attached(%s)' % (repr(self.attrname) if self.attrname else '')
|
super().__init__(description, StringType(), mandatory=False)
|
||||||
|
|
||||||
|
def __get__(self, obj, owner):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
if self.module is None:
|
||||||
|
self.module = obj.DISPATCHER.get_module(super().__get__(obj, owner))
|
||||||
|
return self.module
|
||||||
|
@ -30,7 +30,7 @@ Usage examples:
|
|||||||
pollerClass = poller.Poller
|
pollerClass = poller.Poller
|
||||||
...
|
...
|
||||||
|
|
||||||
modules having a parameter 'iodev' with the same value will share the same poller
|
modules having a parameter 'io' with the same value will share the same poller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@ -58,11 +58,11 @@ class PollerBase:
|
|||||||
|
|
||||||
table is a dict, with (<pollerClass>, <name>) as the key, and the
|
table is a dict, with (<pollerClass>, <name>) as the key, and the
|
||||||
poller as value.
|
poller as value.
|
||||||
<name> is module.iodev or module.name, if iodev is not present
|
<name> is module.io.name or module.name, if io is not present
|
||||||
"""
|
"""
|
||||||
# for modules with the same iodev, a common poller is used,
|
# for modules with the same io, a common poller is used,
|
||||||
# modules without iodev all get their own poller
|
# modules without io all get their own poller
|
||||||
name = getattr(module, 'iodev', module.name)
|
name = getattr(module, 'io', module).name
|
||||||
poller = table.get((cls, name), None)
|
poller = table.get((cls, name), None)
|
||||||
if poller is None:
|
if poller is None:
|
||||||
poller = cls(name)
|
poller = cls(name)
|
||||||
|
@ -29,17 +29,17 @@ from secop.lib import get_class
|
|||||||
from secop.modules import Drivable, Module, Readable, Writable
|
from secop.modules import Drivable, Module, Readable, Writable
|
||||||
from secop.params import Command, Parameter
|
from secop.params import Command, Parameter
|
||||||
from secop.properties import Property
|
from secop.properties import Property
|
||||||
from secop.io import HasIodev
|
from secop.io import HasIO
|
||||||
|
|
||||||
|
|
||||||
class ProxyModule(HasIodev, Module):
|
class ProxyModule(HasIO, Module):
|
||||||
module = Property('remote module name', datatype=StringType(), default='')
|
module = Property('remote module name', datatype=StringType(), default='')
|
||||||
|
|
||||||
pollerClass = None
|
pollerClass = None
|
||||||
_consistency_check_done = False
|
_consistency_check_done = False
|
||||||
_secnode = None
|
_secnode = None
|
||||||
|
|
||||||
def iodevClass(self, name, logger, opts, srv):
|
def ioClass(self, name, logger, opts, srv):
|
||||||
opts['description'] = 'secnode %s on %s' % (opts.get('module', name), opts['uri'])
|
opts['description'] = 'secnode %s on %s' % (opts.get('module', name), opts['uri'])
|
||||||
return SecNode(name, logger, opts, srv)
|
return SecNode(name, logger, opts, srv)
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ class ProxyModule(HasIodev, Module):
|
|||||||
def initModule(self):
|
def initModule(self):
|
||||||
if not self.module:
|
if not self.module:
|
||||||
self.module = self.name
|
self.module = self.name
|
||||||
self._secnode = self._iodev.secnode
|
self._secnode = self.io.secnode
|
||||||
self._secnode.register_callback(self.module, self.updateEvent,
|
self._secnode.register_callback(self.module, self.updateEvent,
|
||||||
self.descriptiveDataChange, self.nodeStateChange)
|
self.descriptiveDataChange, self.nodeStateChange)
|
||||||
super().initModule()
|
super().initModule()
|
||||||
@ -227,5 +227,5 @@ def Proxy(name, logger, cfgdict, srv):
|
|||||||
remote_class = cfgdict.pop('remote_class')
|
remote_class = cfgdict.pop('remote_class')
|
||||||
if 'description' not in cfgdict:
|
if 'description' not in cfgdict:
|
||||||
cfgdict['description'] = 'remote module %s on %s' % (
|
cfgdict['description'] = 'remote module %s on %s' % (
|
||||||
cfgdict.get('module', name), cfgdict.get('iodev', '?'))
|
cfgdict.get('module', name), cfgdict.get('io', '?'))
|
||||||
return proxy_class(remote_class)(name, logger, cfgdict, srv)
|
return proxy_class(remote_class)(name, logger, cfgdict, srv)
|
||||||
|
@ -30,10 +30,9 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from secop.errors import ConfigError, SECoPError
|
from secop.errors import ConfigError
|
||||||
from secop.lib import formatException, get_class, generalConfig
|
from secop.lib import formatException, get_class, generalConfig
|
||||||
from secop.lib.multievent import MultiEvent
|
from secop.lib.multievent import MultiEvent
|
||||||
from secop.modules import Attached
|
|
||||||
from secop.params import PREDEFINED_ACCESSIBLES
|
from secop.params import PREDEFINED_ACCESSIBLES
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -271,24 +270,17 @@ class Server:
|
|||||||
for modname, modobj in self.modules.items():
|
for modname, modobj in self.modules.items():
|
||||||
self.log.info('registering module %r' % modname)
|
self.log.info('registering module %r' % modname)
|
||||||
self.dispatcher.register_module(modobj, modname, modobj.export)
|
self.dispatcher.register_module(modobj, modname, modobj.export)
|
||||||
if modobj.pollerClass is not None:
|
|
||||||
# a module might be explicitly excluded from polling by setting pollerClass to None
|
|
||||||
modobj.pollerClass.add_to_table(poll_table, modobj)
|
|
||||||
# also call earlyInit on the modules
|
# also call earlyInit on the modules
|
||||||
modobj.earlyInit()
|
modobj.earlyInit()
|
||||||
if not modobj.earlyInitDone:
|
if not modobj.earlyInitDone:
|
||||||
missing_super.add('%s was not called, probably missing super call'
|
missing_super.add('%s was not called, probably missing super call'
|
||||||
% modobj.earlyInit.__qualname__)
|
% modobj.earlyInit.__qualname__)
|
||||||
|
|
||||||
# handle attached modules
|
# handle polling
|
||||||
for modname, modobj in self.modules.items():
|
for modname, modobj in self.modules.items():
|
||||||
for propname, propobj in modobj.propertyDict.items():
|
if modobj.pollerClass is not None:
|
||||||
if isinstance(propobj, Attached):
|
# a module might be explicitly excluded from polling by setting pollerClass to None
|
||||||
try:
|
modobj.pollerClass.add_to_table(poll_table, modobj)
|
||||||
setattr(modobj, propobj.attrname or '_' + propname,
|
|
||||||
self.dispatcher.get_module(getattr(modobj, propname)))
|
|
||||||
except SECoPError as e:
|
|
||||||
errors.append('module %s, attached %s: %s' % (modname, propname, str(e)))
|
|
||||||
|
|
||||||
# call init on each module after registering all
|
# call init on each module after registering all
|
||||||
for modname, modobj in self.modules.items():
|
for modname, modobj in self.modules.items():
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Andeen Hagerling capacitance bridge"""
|
"""Andeen Hagerling capacitance bridge"""
|
||||||
|
|
||||||
from secop.core import Done, FloatRange, HasIodev, Parameter, Readable, StringIO
|
from secop.core import Done, FloatRange, HasIO, Parameter, Readable, StringIO
|
||||||
|
|
||||||
|
|
||||||
class Ah2700IO(StringIO):
|
class Ah2700IO(StringIO):
|
||||||
@ -28,19 +28,19 @@ class Ah2700IO(StringIO):
|
|||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
|
|
||||||
class Capacitance(HasIodev, Readable):
|
class Capacitance(HasIO, Readable):
|
||||||
|
|
||||||
value = Parameter('capacitance', FloatRange(unit='pF'), poll=True)
|
value = Parameter('capacitance', FloatRange(unit='pF'), poll=True)
|
||||||
freq = Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0)
|
freq = Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0)
|
||||||
voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0)
|
voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0)
|
||||||
loss = Parameter('loss', FloatRange(unit='deg'), default=0)
|
loss = Parameter('loss', FloatRange(unit='deg'), default=0)
|
||||||
|
|
||||||
iodevClass = Ah2700IO
|
ioClass = Ah2700IO
|
||||||
|
|
||||||
def parse_reply(self, reply):
|
def parse_reply(self, reply):
|
||||||
if reply.startswith('SI'): # this is an echo
|
if reply.startswith('SI'): # this is an echo
|
||||||
self.sendRecv('SERIAL ECHO OFF')
|
self.communicate('SERIAL ECHO OFF')
|
||||||
reply = self.sendRecv('SI')
|
reply = self.communicate('SI')
|
||||||
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
||||||
self.status = [self.Status.ERROR, reply]
|
self.status = [self.Status.ERROR, reply]
|
||||||
return
|
return
|
||||||
@ -59,14 +59,14 @@ class Capacitance(HasIodev, Readable):
|
|||||||
if lossunit == 'DS':
|
if lossunit == 'DS':
|
||||||
self.loss = loss
|
self.loss = loss
|
||||||
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
||||||
reply = self.sendRecv('UN DS').split() # UN DS returns a reply similar to SI
|
reply = self.communicate('UN DS').split() # UN DS returns a reply similar to SI
|
||||||
try:
|
try:
|
||||||
self.loss = reply[7]
|
self.loss = reply[7]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass # don't worry, loss will be updated next time
|
pass # don't worry, loss will be updated next time
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
self.parse_reply(self.sendRecv('SI')) # SI = single trigger
|
self.parse_reply(self.communicate('SI')) # SI = single trigger
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def read_freq(self):
|
def read_freq(self):
|
||||||
@ -77,14 +77,14 @@ class Capacitance(HasIodev, Readable):
|
|||||||
self.read_value()
|
self.read_value()
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def read_volt(self):
|
def read_voltage(self):
|
||||||
self.read_value()
|
self.read_value()
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def write_freq(self, value):
|
def write_freq(self, value):
|
||||||
self.parse_reply(self.sendRecv('FR %g;SI' % value))
|
self.parse_reply(self.communicate('FR %g;SI' % value))
|
||||||
return Done
|
return Done
|
||||||
|
|
||||||
def write_volt(self, value):
|
def write_voltage(self, value):
|
||||||
self.parse_reply(self.sendRecv('V %g;SI' % value))
|
self.parse_reply(self.communicate('V %g;SI' % value))
|
||||||
return Done
|
return Done
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"""drivers for CCU4, the cryostat control unit at SINQ"""
|
"""drivers for CCU4, the cryostat control unit at SINQ"""
|
||||||
# the most common Frappy classes can be imported from secop.core
|
# the most common Frappy classes can be imported from secop.core
|
||||||
from secop.core import EnumType, FloatRange, \
|
from secop.core import EnumType, FloatRange, \
|
||||||
HasIodev, Parameter, Readable, StringIO
|
HasIO, Parameter, Readable, StringIO
|
||||||
|
|
||||||
|
|
||||||
class CCU4IO(StringIO):
|
class CCU4IO(StringIO):
|
||||||
@ -34,14 +34,13 @@ class CCU4IO(StringIO):
|
|||||||
identification = [('cid', r'CCU4.*')]
|
identification = [('cid', r'CCU4.*')]
|
||||||
|
|
||||||
|
|
||||||
# inheriting the HasIodev mixin creates us a private attribute *_iodev*
|
# inheriting HasIO allows us to use the communicate method for talking with the hardware
|
||||||
# for talking with the hardware
|
|
||||||
# Readable as a base class defines the value and status parameters
|
# Readable as a base class defines the value and status parameters
|
||||||
class HeLevel(HasIodev, Readable):
|
class HeLevel(HasIO, Readable):
|
||||||
"""He Level channel of CCU4"""
|
"""He Level channel of CCU4"""
|
||||||
|
|
||||||
# define the communication class to create the IO module
|
# define the communication class to create the IO module
|
||||||
iodevClass = CCU4IO
|
ioClass = CCU4IO
|
||||||
|
|
||||||
# define or alter the parameters
|
# define or alter the parameters
|
||||||
# as Readable.value exists already, we give only the modified property 'unit'
|
# as Readable.value exists already, we give only the modified property 'unit'
|
||||||
@ -71,7 +70,7 @@ class HeLevel(HasIodev, Readable):
|
|||||||
for changing a parameter
|
for changing a parameter
|
||||||
:returns: the (new) value of the parameter
|
:returns: the (new) value of the parameter
|
||||||
"""
|
"""
|
||||||
name, txtvalue = self._iodev.communicate(cmd).split('=')
|
name, txtvalue = self.communicate(cmd).split('=')
|
||||||
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||||
return txtvalue # Frappy will automatically convert the string to the needed data type
|
return txtvalue # Frappy will automatically convert the string to the needed data type
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
not tested yet"""
|
not tested yet"""
|
||||||
|
|
||||||
from secop.core import Attached, BoolType, EnumType, FloatRange, \
|
from secop.core import Attached, BoolType, EnumType, FloatRange, \
|
||||||
HasIodev, Module, Parameter, StringIO, Writable
|
HasIO, Module, Parameter, StringIO, Writable
|
||||||
|
|
||||||
|
|
||||||
class K2601bIO(StringIO):
|
class K2601bIO(StringIO):
|
||||||
@ -41,7 +41,7 @@ SOURCECMDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SourceMeter(HasIodev, Module):
|
class SourceMeter(HasIO, Module):
|
||||||
|
|
||||||
resistivity = Parameter('readback resistivity', FloatRange(unit='Ohm'), poll=True)
|
resistivity = Parameter('readback resistivity', FloatRange(unit='Ohm'), poll=True)
|
||||||
power = Parameter('readback power', FloatRange(unit='W'), poll=True)
|
power = Parameter('readback power', FloatRange(unit='W'), poll=True)
|
||||||
@ -49,19 +49,19 @@ class SourceMeter(HasIodev, Module):
|
|||||||
readonly=False, default=0)
|
readonly=False, default=0)
|
||||||
active = Parameter('output enable', BoolType(), readonly=False, poll=True)
|
active = Parameter('output enable', BoolType(), readonly=False, poll=True)
|
||||||
|
|
||||||
iodevClass = K2601bIO
|
ioClass = K2601bIO
|
||||||
|
|
||||||
def read_resistivity(self):
|
def read_resistivity(self):
|
||||||
return self.sendRecv('print(smua.measure.r())')
|
return self.communicate('print(smua.measure.r())')
|
||||||
|
|
||||||
def read_power(self):
|
def read_power(self):
|
||||||
return self.sendRecv('print(smua.measure.p())')
|
return self.communicate('print(smua.measure.p())')
|
||||||
|
|
||||||
def read_active(self):
|
def read_active(self):
|
||||||
return self.sendRecv('print(smua.source.output)')
|
return self.communicate('print(smua.source.output)')
|
||||||
|
|
||||||
def write_active(self, value):
|
def write_active(self, value):
|
||||||
return self.sendRecv('smua.source.output = %d print(smua.source.output)' % value)
|
return self.communicate('smua.source.output = %d print(smua.source.output)' % value)
|
||||||
|
|
||||||
# for now, mode will not be read from hardware
|
# for now, mode will not be read from hardware
|
||||||
|
|
||||||
@ -69,11 +69,11 @@ class SourceMeter(HasIodev, Module):
|
|||||||
if value == 0:
|
if value == 0:
|
||||||
self.write_active(0)
|
self.write_active(0)
|
||||||
else:
|
else:
|
||||||
self.sendRecv(SOURCECMDS[value] + ' print(0)')
|
self.communicate(SOURCECMDS[value] + ' print(0)')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Current(HasIodev, Writable):
|
class Current(HasIO, Writable):
|
||||||
sourcemeter = Attached()
|
sourcemeter = Attached()
|
||||||
|
|
||||||
value = Parameter('measured current', FloatRange(unit='A'), poll=True)
|
value = Parameter('measured current', FloatRange(unit='A'), poll=True)
|
||||||
@ -82,41 +82,41 @@ class Current(HasIodev, Writable):
|
|||||||
limit = Parameter('current limit', FloatRange(0, 2.0, unit='A'), default=2, poll=True)
|
limit = Parameter('current limit', FloatRange(0, 2.0, unit='A'), default=2, poll=True)
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self.sendRecv('print(smua.measure.i())')
|
return self.communicate('print(smua.measure.i())')
|
||||||
|
|
||||||
def read_target(self):
|
def read_target(self):
|
||||||
return self.sendRecv('print(smua.source.leveli)')
|
return self.communicate('print(smua.source.leveli)')
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
if not self.active:
|
if not self.active:
|
||||||
raise ValueError('current source is disabled')
|
raise ValueError('current source is disabled')
|
||||||
if value > self.limit:
|
if value > self.limit:
|
||||||
raise ValueError('current exceeds limit')
|
raise ValueError('current exceeds limit')
|
||||||
return self.sendRecv('smua.source.leveli = %g print(smua.source.leveli)' % value)
|
return self.communicate('smua.source.leveli = %g print(smua.source.leveli)' % value)
|
||||||
|
|
||||||
def read_limit(self):
|
def read_limit(self):
|
||||||
if self.active:
|
if self.active:
|
||||||
return self.limit
|
return self.limit
|
||||||
return self.sendRecv('print(smua.source.limiti)')
|
return self.communicate('print(smua.source.limiti)')
|
||||||
|
|
||||||
def write_limit(self, value):
|
def write_limit(self, value):
|
||||||
if self.active:
|
if self.active:
|
||||||
return value
|
return value
|
||||||
return self.sendRecv('smua.source.limiti = %g print(smua.source.limiti)' % value)
|
return self.communicate('smua.source.limiti = %g print(smua.source.limiti)' % value)
|
||||||
|
|
||||||
def read_active(self):
|
def read_active(self):
|
||||||
return self._sourcemeter.mode == 1 and self._sourcemeter.read_active()
|
return self.sourcemeter.mode == 1 and self.sourcemeter.read_active()
|
||||||
|
|
||||||
def write_active(self, value):
|
def write_active(self, value):
|
||||||
if self._sourcemeter.mode != 1:
|
if self.sourcemeter.mode != 1:
|
||||||
if value:
|
if value:
|
||||||
self._sourcemeter.write_mode(1) # switch to current
|
self.sourcemeter.write_mode(1) # switch to current
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
return self._sourcemeter.write_active(value)
|
return self.sourcemeter.write_active(value)
|
||||||
|
|
||||||
|
|
||||||
class Voltage(HasIodev, Writable):
|
class Voltage(HasIO, Writable):
|
||||||
sourcemeter = Attached()
|
sourcemeter = Attached()
|
||||||
|
|
||||||
value = Parameter('measured voltage', FloatRange(unit='V'), poll=True)
|
value = Parameter('measured voltage', FloatRange(unit='V'), poll=True)
|
||||||
@ -125,35 +125,35 @@ class Voltage(HasIodev, Writable):
|
|||||||
limit = Parameter('current limit', FloatRange(0, 2.0, unit='V'), default=2, poll=True)
|
limit = Parameter('current limit', FloatRange(0, 2.0, unit='V'), default=2, poll=True)
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self.sendRecv('print(smua.measure.v())')
|
return self.communicate('print(smua.measure.v())')
|
||||||
|
|
||||||
def read_target(self):
|
def read_target(self):
|
||||||
return self.sendRecv('print(smua.source.levelv)')
|
return self.communicate('print(smua.source.levelv)')
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
if not self.active:
|
if not self.active:
|
||||||
raise ValueError('voltage source is disabled')
|
raise ValueError('voltage source is disabled')
|
||||||
if value > self.limit:
|
if value > self.limit:
|
||||||
raise ValueError('voltage exceeds limit')
|
raise ValueError('voltage exceeds limit')
|
||||||
return self.sendRecv('smua.source.levelv = %g print(smua.source.levelv)' % value)
|
return self.communicate('smua.source.levelv = %g print(smua.source.levelv)' % value)
|
||||||
|
|
||||||
def read_limit(self):
|
def read_limit(self):
|
||||||
if self.active:
|
if self.active:
|
||||||
return self.limit
|
return self.limit
|
||||||
return self.sendRecv('print(smua.source.limitv)')
|
return self.communicate('print(smua.source.limitv)')
|
||||||
|
|
||||||
def write_limit(self, value):
|
def write_limit(self, value):
|
||||||
if self.active:
|
if self.active:
|
||||||
return value
|
return value
|
||||||
return self.sendRecv('smua.source.limitv = %g print(smua.source.limitv)' % value)
|
return self.communicate('smua.source.limitv = %g print(smua.source.limitv)' % value)
|
||||||
|
|
||||||
def read_active(self):
|
def read_active(self):
|
||||||
return self._sourcemeter.mode == 2 and self._sourcemeter.read_active()
|
return self.sourcemeter.mode == 2 and self.sourcemeter.read_active()
|
||||||
|
|
||||||
def write_active(self, value):
|
def write_active(self, value):
|
||||||
if self._sourcemeter.mode != 2:
|
if self.sourcemeter.mode != 2:
|
||||||
if value:
|
if value:
|
||||||
self._sourcemeter.write_mode(2) # switch to voltage
|
self.sourcemeter.write_mode(2) # switch to voltage
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
return self._sourcemeter.write_active(value)
|
return self.sourcemeter.write_active(value)
|
||||||
|
@ -28,7 +28,7 @@ from secop.lib import formatStatusBits
|
|||||||
from secop.modules import Attached, Done, \
|
from secop.modules import Attached, Done, \
|
||||||
Drivable, Parameter, Property, Readable
|
Drivable, Parameter, Property, Readable
|
||||||
from secop.poller import REGULAR, Poller
|
from secop.poller import REGULAR, Poller
|
||||||
from secop.io import HasIodev
|
from secop.io import HasIO
|
||||||
|
|
||||||
Status = Drivable.Status
|
Status = Drivable.Status
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class StringIO(secop.io.StringIO):
|
|||||||
wait_before = 0.05
|
wait_before = 0.05
|
||||||
|
|
||||||
|
|
||||||
class Main(HasIodev, Drivable):
|
class Main(HasIO, Drivable):
|
||||||
|
|
||||||
value = Parameter('the current channel', poll=REGULAR, datatype=IntRange(0, 17))
|
value = Parameter('the current channel', poll=REGULAR, datatype=IntRange(0, 17))
|
||||||
target = Parameter('channel to select', datatype=IntRange(0, 17))
|
target = Parameter('channel to select', datatype=IntRange(0, 17))
|
||||||
@ -66,7 +66,7 @@ class Main(HasIodev, Drivable):
|
|||||||
pollinterval = Parameter(default=1, export=False)
|
pollinterval = Parameter(default=1, export=False)
|
||||||
|
|
||||||
pollerClass = Poller
|
pollerClass = Poller
|
||||||
iodevClass = StringIO
|
ioClass = StringIO
|
||||||
_channel_changed = 0 # time of last channel change
|
_channel_changed = 0 # time of last channel change
|
||||||
_channels = None # dict <channel no> of <module object>
|
_channels = None # dict <channel no> of <module object>
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class Main(HasIodev, Drivable):
|
|||||||
super().startModule(start_events)
|
super().startModule(start_events)
|
||||||
for ch in range(1, 16):
|
for ch in range(1, 16):
|
||||||
if ch not in self._channels:
|
if ch not in self._channels:
|
||||||
self.sendRecv('INSET %d,0,0,0,0,0;INSET?%d' % (ch, ch))
|
self.communicate('INSET %d,0,0,0,0,0;INSET?%d' % (ch, ch))
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
channel, auto = scan.send_command(self)
|
channel, auto = scan.send_command(self)
|
||||||
@ -114,7 +114,7 @@ class Main(HasIodev, Drivable):
|
|||||||
|
|
||||||
def write_target(self, channel):
|
def write_target(self, channel):
|
||||||
scan.send_change(self, channel, self.autoscan)
|
scan.send_change(self, channel, self.autoscan)
|
||||||
# self.sendRecv('SCAN %d,%d;SCAN?' % (channel, self.autoscan))
|
# self.communicate('SCAN %d,%d;SCAN?' % (channel, self.autoscan))
|
||||||
if channel != self.value:
|
if channel != self.value:
|
||||||
self.value = 0
|
self.value = 0
|
||||||
self._channel_changed = time.time()
|
self._channel_changed = time.time()
|
||||||
@ -123,11 +123,11 @@ class Main(HasIodev, Drivable):
|
|||||||
|
|
||||||
def write_autoscan(self, value):
|
def write_autoscan(self, value):
|
||||||
scan.send_change(self, self.value, value)
|
scan.send_change(self, self.value, value)
|
||||||
# self.sendRecv('SCAN %d,%d;SCAN?' % (channel, self.autoscan))
|
# self.communicate('SCAN %d,%d;SCAN?' % (channel, self.autoscan))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ResChannel(HasIodev, Readable):
|
class ResChannel(HasIO, Readable):
|
||||||
"""temperature channel on Lakeshore 336"""
|
"""temperature channel on Lakeshore 336"""
|
||||||
|
|
||||||
RES_RANGE = {key: i+1 for i, key in list(
|
RES_RANGE = {key: i+1 for i, key in list(
|
||||||
@ -142,7 +142,7 @@ class ResChannel(HasIodev, Readable):
|
|||||||
for val in [2, 6.32, 20, 63.2, 200, 632]))}
|
for val in [2, 6.32, 20, 63.2, 200, 632]))}
|
||||||
|
|
||||||
pollerClass = Poller
|
pollerClass = Poller
|
||||||
iodevClass = StringIO
|
ioClass = StringIO
|
||||||
_main = None # main module
|
_main = None # main module
|
||||||
_last_range_change = 0 # time of last range change
|
_last_range_change = 0 # time of last range change
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ class ResChannel(HasIodev, Readable):
|
|||||||
return Done
|
return Done
|
||||||
# we got here, when we missed the idle state of self._main
|
# we got here, when we missed the idle state of self._main
|
||||||
self._trigger_read = False
|
self._trigger_read = False
|
||||||
result = self.sendRecv('RDGR?%d' % self.channel)
|
result = self.communicate('RDGR?%d' % self.channel)
|
||||||
result = float(result)
|
result = float(result)
|
||||||
if self.autorange == 'soft':
|
if self.autorange == 'soft':
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@ -216,9 +216,9 @@ class ResChannel(HasIodev, Readable):
|
|||||||
def read_status(self):
|
def read_status(self):
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
return [self.Status.DISABLED, 'disabled']
|
return [self.Status.DISABLED, 'disabled']
|
||||||
if self.channel != self._main.value:
|
if self.channel != self.main.value:
|
||||||
return Done
|
return Done
|
||||||
result = int(self.sendRecv('RDGST?%d' % self.channel))
|
result = int(self.communicate('RDGST?%d' % self.channel))
|
||||||
result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities)
|
result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities)
|
||||||
statustext = ' '.join(formatStatusBits(result, STATUS_BIT_LABELS))
|
statustext = ' '.join(formatStatusBits(result, STATUS_BIT_LABELS))
|
||||||
if statustext:
|
if statustext:
|
||||||
@ -283,5 +283,5 @@ class ResChannel(HasIodev, Readable):
|
|||||||
def write_enabled(self, value):
|
def write_enabled(self, value):
|
||||||
inset.write(self, 'enabled', value)
|
inset.write(self, 'enabled', value)
|
||||||
if value:
|
if value:
|
||||||
self._main.write_target(self.channel)
|
self.main.write_target(self.channel)
|
||||||
return Done
|
return Done
|
||||||
|
@ -40,10 +40,10 @@ from secop.datatypes import BoolType, EnumType, \
|
|||||||
from secop.errors import HardwareError
|
from secop.errors import HardwareError
|
||||||
from secop.lib import clamp
|
from secop.lib import clamp
|
||||||
from secop.lib.enum import Enum
|
from secop.lib.enum import Enum
|
||||||
from secop.modules import Attached, Communicator, Done, \
|
from secop.modules import Communicator, Done, \
|
||||||
Drivable, Parameter, Property, Readable
|
Drivable, Parameter, Property, Readable
|
||||||
from secop.poller import Poller
|
from secop.poller import Poller
|
||||||
from secop.io import HasIodev
|
from secop.io import HasIO
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import secop_psi.ppmswindows as ppmshw
|
import secop_psi.ppmswindows as ppmshw
|
||||||
@ -130,10 +130,8 @@ class Main(Communicator):
|
|||||||
return data # return data as string
|
return data # return data as string
|
||||||
|
|
||||||
|
|
||||||
class PpmsBase(HasIodev, Readable):
|
class PpmsBase(HasIO, Readable):
|
||||||
"""common base for all ppms modules"""
|
"""common base for all ppms modules"""
|
||||||
iodev = Attached()
|
|
||||||
|
|
||||||
# polling is done by the main module
|
# polling is done by the main module
|
||||||
# and PPMS does not deliver really more fresh values when polled more often
|
# and PPMS does not deliver really more fresh values when polled more often
|
||||||
value = Parameter(poll=False, needscfg=False)
|
value = Parameter(poll=False, needscfg=False)
|
||||||
@ -150,7 +148,7 @@ class PpmsBase(HasIodev, Readable):
|
|||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super().initModule()
|
super().initModule()
|
||||||
self._iodev.register(self)
|
self.io.register(self)
|
||||||
|
|
||||||
def update_value_status(self, value, packed_status):
|
def update_value_status(self, value, packed_status):
|
||||||
# update value and status
|
# update value and status
|
||||||
@ -197,7 +195,7 @@ class UserChannel(Channel):
|
|||||||
datatype=StringType(), export=False, default='')
|
datatype=StringType(), export=False, default='')
|
||||||
|
|
||||||
def write_enabled(self, enabled):
|
def write_enabled(self, enabled):
|
||||||
other = self._iodev.modules.get(self.linkenable, None)
|
other = self.io.modules.get(self.linkenable, None)
|
||||||
if other:
|
if other:
|
||||||
other.enabled = enabled
|
other.enabled = enabled
|
||||||
return enabled
|
return enabled
|
||||||
|
@ -26,7 +26,7 @@ import time
|
|||||||
import struct
|
import struct
|
||||||
|
|
||||||
from secop.core import BoolType, Command, EnumType, FloatRange, IntRange, \
|
from secop.core import BoolType, Command, EnumType, FloatRange, IntRange, \
|
||||||
HasIodev, Parameter, Property, Drivable, PersistentMixin, PersistentParam, Done
|
HasIO, Parameter, Property, Drivable, PersistentMixin, PersistentParam, Done
|
||||||
from secop.io import BytesIO
|
from secop.io import BytesIO
|
||||||
from secop.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError
|
from secop.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError
|
||||||
from secop.rwhandler import ReadHandler, WriteHandler
|
from secop.rwhandler import ReadHandler, WriteHandler
|
||||||
@ -77,7 +77,7 @@ def writable(*args, **kwds):
|
|||||||
return PersistentParam(*args, readonly=False, poll=True, initwrite=True, **kwds)
|
return PersistentParam(*args, readonly=False, poll=True, initwrite=True, **kwds)
|
||||||
|
|
||||||
|
|
||||||
class Motor(PersistentMixin, HasIodev, Drivable):
|
class Motor(PersistentMixin, HasIO, Drivable):
|
||||||
address = Property('module address', IntRange(0, 255), default=1)
|
address = Property('module address', IntRange(0, 255), default=1)
|
||||||
|
|
||||||
value = Parameter('motor position', FloatRange(unit='deg', fmtstr='%.3f'))
|
value = Parameter('motor position', FloatRange(unit='deg', fmtstr='%.3f'))
|
||||||
@ -116,7 +116,7 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
readonly=False, default=0, poll=True, visibility=3, group='more')
|
readonly=False, default=0, poll=True, visibility=3, group='more')
|
||||||
pollinterval = Parameter(group='more')
|
pollinterval = Parameter(group='more')
|
||||||
|
|
||||||
iodevClass = BytesIO
|
ioClass = BytesIO
|
||||||
fast_pollfactor = 0.001 # poll as fast as possible when busy
|
fast_pollfactor = 0.001 # poll as fast as possible when busy
|
||||||
_started = 0
|
_started = 0
|
||||||
_calcTimeout = True
|
_calcTimeout = True
|
||||||
@ -131,20 +131,20 @@ class Motor(PersistentMixin, HasIodev, Drivable):
|
|||||||
:param value: if given, the parameter is written, else it is returned
|
:param value: if given, the parameter is written, else it is returned
|
||||||
:return: the returned value
|
:return: the returned value
|
||||||
"""
|
"""
|
||||||
if self._calcTimeout and self._iodev._conn:
|
if self._calcTimeout and self.io._conn:
|
||||||
self._calcTimeout = False
|
self._calcTimeout = False
|
||||||
baudrate = getattr(self._iodev._conn.connection, 'baudrate', None)
|
baudrate = getattr(self.io._conn.connection, 'baudrate', None)
|
||||||
if baudrate:
|
if baudrate:
|
||||||
if baudrate not in BAUDRATES:
|
if baudrate not in BAUDRATES:
|
||||||
raise CommunicationFailedError('unsupported baud rate: %d' % baudrate)
|
raise CommunicationFailedError('unsupported baud rate: %d' % baudrate)
|
||||||
self._iodev.timeout = 0.03 + 200 / baudrate
|
self.io.timeout = 0.03 + 200 / baudrate
|
||||||
|
|
||||||
exc = None
|
exc = None
|
||||||
byt = struct.pack('>BBBBi', self.address, cmd, adr, bank, round(value))
|
byt = struct.pack('>BBBBi', self.address, cmd, adr, bank, round(value))
|
||||||
byt += bytes([sum(byt) & 0xff])
|
byt += bytes([sum(byt) & 0xff])
|
||||||
for itry in range(3,0,-1):
|
for itry in range(3,0,-1):
|
||||||
try:
|
try:
|
||||||
reply = self._iodev.communicate(byt, 9)
|
reply = self.communicate(byt, 9)
|
||||||
if sum(reply[:-1]) & 0xff != reply[-1]:
|
if sum(reply[:-1]) & 0xff != reply[-1]:
|
||||||
raise CommunicationFailedError('checksum error')
|
raise CommunicationFailedError('checksum error')
|
||||||
# will try again
|
# will try again
|
||||||
|
78
test/test_attach.py
Normal file
78
test/test_attach.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# -*- 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:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from secop.modules import Module, Attached
|
||||||
|
from secop.protocol.dispatcher import Dispatcher
|
||||||
|
|
||||||
|
|
||||||
|
# class DispatcherStub:
|
||||||
|
# # omit_unchanged_within = 0
|
||||||
|
#
|
||||||
|
# # def __init__(self, updates):
|
||||||
|
# # self.updates = updates
|
||||||
|
# #
|
||||||
|
# # def announce_update(self, modulename, pname, pobj):
|
||||||
|
# # self.updates.setdefault(modulename, {})
|
||||||
|
# # if pobj.readerror:
|
||||||
|
# # self.updates[modulename]['error', pname] = str(pobj.readerror)
|
||||||
|
# # else:
|
||||||
|
# # self.updates[modulename][pname] = pobj.value
|
||||||
|
#
|
||||||
|
# def __init__(self):
|
||||||
|
# self.modules = {}
|
||||||
|
#
|
||||||
|
# def get_module(self, name):
|
||||||
|
# return self.modules[name]
|
||||||
|
#
|
||||||
|
# def register_module(self, name, module):
|
||||||
|
# self.modules[name] = module
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerStub:
|
||||||
|
def debug(self, fmt, *args):
|
||||||
|
print(fmt % args)
|
||||||
|
info = warning = exception = debug
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerStub()
|
||||||
|
|
||||||
|
|
||||||
|
class ServerStub:
|
||||||
|
restart = None
|
||||||
|
shutdown = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.dispatcher = Dispatcher('dispatcher', logger, {}, self)
|
||||||
|
|
||||||
|
|
||||||
|
def test_attach():
|
||||||
|
class Mod(Module):
|
||||||
|
att = Attached()
|
||||||
|
|
||||||
|
srv = ServerStub()
|
||||||
|
a = Module('a', logger, {'description': ''}, srv)
|
||||||
|
m = Mod('m', logger, {'description': '', 'att': 'a'}, srv)
|
||||||
|
assert m.propertyValues['att'] == 'a'
|
||||||
|
srv.dispatcher.register_module(a, 'a')
|
||||||
|
srv.dispatcher.register_module(m, 'm')
|
||||||
|
assert m.att == a
|
@ -120,7 +120,7 @@ def test_IOHandler():
|
|||||||
real = Parameter('a float value', FloatRange(), default=12.3, handler=group2, readonly=False)
|
real = Parameter('a float value', FloatRange(), default=12.3, handler=group2, readonly=False)
|
||||||
text = Parameter('a string value', StringType(), default='x', handler=group2, readonly=False)
|
text = Parameter('a string value', StringType(), default='x', handler=group2, readonly=False)
|
||||||
|
|
||||||
def sendRecv(self, command):
|
def communicate(self, command):
|
||||||
assert data.pop('command') == command
|
assert data.pop('command') == command
|
||||||
return data.pop('reply')
|
return data.pop('reply')
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ def test_IOHandler():
|
|||||||
print(updates)
|
print(updates)
|
||||||
updates.clear() # get rid of updates from initialisation
|
updates.clear() # get rid of updates from initialisation
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'SIMPLE?')
|
data.push('command', 'SIMPLE?')
|
||||||
data.push('reply', '4.51')
|
data.push('reply', '4.51')
|
||||||
# for analyze_group1
|
# for analyze_group1
|
||||||
@ -159,7 +159,7 @@ def test_IOHandler():
|
|||||||
assert updates.pop('simple') == 45.1
|
assert updates.pop('simple') == 45.1
|
||||||
assert not updates
|
assert not updates
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD?3')
|
data.push('command', 'CMD?3')
|
||||||
data.push('reply', '1.23,text,5')
|
data.push('reply', '1.23,text,5')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
@ -172,7 +172,7 @@ def test_IOHandler():
|
|||||||
assert data.empty()
|
assert data.empty()
|
||||||
assert not updates
|
assert not updates
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD?3')
|
data.push('command', 'CMD?3')
|
||||||
data.push('reply', '1.23,text,5')
|
data.push('reply', '1.23,text,5')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
@ -183,7 +183,7 @@ def test_IOHandler():
|
|||||||
data.push('self', 12.3, 'string')
|
data.push('self', 12.3, 'string')
|
||||||
data.push('new', 12.3, 'FOO')
|
data.push('new', 12.3, 'FOO')
|
||||||
data.push('changed', 1.23, 'foo', 9)
|
data.push('changed', 1.23, 'foo', 9)
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD 3,1.23,foo,9|CMD?3')
|
data.push('command', 'CMD 3,1.23,foo,9|CMD?3')
|
||||||
data.push('reply', '1.23,foo,9')
|
data.push('reply', '1.23,foo,9')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
|
@ -117,7 +117,10 @@ class Parameter:
|
|||||||
class Module:
|
class Module:
|
||||||
properties = {}
|
properties = {}
|
||||||
pollerClass = Poller
|
pollerClass = Poller
|
||||||
iodev = 'common_iodev'
|
|
||||||
|
class io:
|
||||||
|
name = 'common_io'
|
||||||
|
|
||||||
def __init__(self, name, pollinterval=5, fastfactor=0.25, slowfactor=4, busy=False,
|
def __init__(self, name, pollinterval=5, fastfactor=0.25, slowfactor=4, busy=False,
|
||||||
counts=(), auto=None):
|
counts=(), auto=None):
|
||||||
'''create a dummy module
|
'''create a dummy module
|
||||||
@ -203,7 +206,7 @@ def test_Poller(modules):
|
|||||||
count[pobj.polltype] += 1
|
count[pobj.polltype] += 1
|
||||||
pobj.reset()
|
pobj.reset()
|
||||||
assert len(pollTable) == 1
|
assert len(pollTable) == 1
|
||||||
poller = pollTable[(Poller, 'common_iodev')]
|
poller = pollTable[(Poller, 'common_io')]
|
||||||
artime.stop = poller.stop
|
artime.stop = poller.stop
|
||||||
poller._event = Event() # patch Event.wait
|
poller._event = Event() # patch Event.wait
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user