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:
2022-01-27 18:11:42 +01:00
parent b911bc1838
commit c1307cdd03
21 changed files with 301 additions and 209 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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