diff --git a/bin/secop-server b/bin/secop-server index ed218af..f983bb5 100755 --- a/bin/secop-server +++ b/bin/secop-server @@ -93,6 +93,7 @@ def main(argv=None): if args.relaxed: generalConfig.defaults['lazy_number_validation'] = True generalConfig.defaults['disable_value_range_check'] = True + generalConfig.defaults['legacy_hasiodev'] = True generalConfig.init(args.gencfg) logger.init(loglevel) diff --git a/cfg/ls370sim.cfg b/cfg/ls370sim.cfg index 66ce0ca..e78e366 100644 --- a/cfg/ls370sim.cfg +++ b/cfg/ls370sim.cfg @@ -1,24 +1,23 @@ -[node LscSIM.psi.ch] +[NODE] +id = LscSIM.psi.ch description = Lsc Simulation at PSI -[interface tcp] -type = tcp -bindto = 0.0.0.0 -bindport = 5000 +[INTERFACE] +uri = tcp://5000 -[module res] +[res] class = secop_psi.ls370res.ResChannel -.channel = 3 -.description = resistivity -.main = lsmain -.iodev = lscom +channel = 3 +description = resistivity +main = lsmain +io = lscom -[module lsmain] +[lsmain] class = secop_psi.ls370res.Main -.description = main control of Lsc controller -.iodev = lscom +description = main control of Lsc controller +io = lscom -[module lscom] +[lscom] class = secop_psi.ls370sim.Ls370Sim -.description = simulated serial communicator to a LS 370 -.visibility = 3 +description = simulated serial communicator to a LS 370 +visibility = 3 diff --git a/cfg/ppms.cfg b/cfg/ppms.cfg index 8f8a927..82cc65e 100644 --- a/cfg/ppms.cfg +++ b/cfg/ppms.cfg @@ -8,117 +8,117 @@ uri = tcp://5000 [tt] class = secop_psi.ppms.Temp description = main temperature -iodev = ppms +io = ppms [mf] class = secop_psi.ppms.Field target.min = -9 target.max = 9 -.description = magnetic field -.iodev = ppms +description = magnetic field +io = ppms [pos] class = secop_psi.ppms.Position -.description = sample rotator -.iodev = ppms +description = sample rotator +io = ppms [lev] class = secop_psi.ppms.Level -.description = helium level -.iodev = ppms +description = helium level +io = ppms [chamber] class = secop_psi.ppms.Chamber -.description = chamber state -.iodev = ppms +description = chamber state +io = ppms [r1] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 1 -.no = 1 +description = resistivity channel 1 +no = 1 value.unit = Ohm -.iodev = ppms +io = ppms [r2] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 2 -.no = 2 +description = resistivity channel 2 +no = 2 value.unit = Ohm -.iodev = ppms +io = ppms [r3] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 3 -.no = 3 +description = resistivity channel 3 +no = 3 value.unit = Ohm -.iodev = ppms +io = ppms [r4] class = secop_psi.ppms.BridgeChannel -.description = resistivity channel 4 -.no = 4 +description = resistivity channel 4 +no = 4 value.unit = Ohm -.iodev = ppms +io = ppms [i1] class = secop_psi.ppms.Channel -.description = current channel 1 -.no = 1 +description = current channel 1 +no = 1 value.unit = uA -.iodev = ppms +io = ppms [i2] class = secop_psi.ppms.Channel -.description = current channel 2 -.no = 2 +description = current channel 2 +no = 2 value.unit = uA -.iodev = ppms +io = ppms [i3] class = secop_psi.ppms.Channel -.description = current channel 3 -.no = 3 +description = current channel 3 +no = 3 value.unit = uA -.iodev = ppms +io = ppms [i4] class = secop_psi.ppms.Channel -.description = current channel 4 -.no = 4 +description = current channel 4 +no = 4 value.unit = uA -.iodev = ppms +io = ppms [v1] class = secop_psi.ppms.DriverChannel -.description = voltage channel 1 -.no = 1 +description = voltage channel 1 +no = 1 value.unit = V -.iodev = ppms +io = ppms [v2] class = secop_psi.ppms.DriverChannel -.description = voltage channel 2 -.no = 2 +description = voltage channel 2 +no = 2 value.unit = V -.iodev = ppms +io = ppms [tv] class = secop_psi.ppms.UserChannel -.description = VTI temperature +description = VTI temperature enabled = 1 value.unit = K -.iodev = ppms +io = ppms [ts] class = secop_psi.ppms.UserChannel -.description = sample temperature +description = sample temperature enabled = 1 value.unit = K -.iodev = ppms +io = ppms [ppms] class = secop_psi.ppms.Main -.description = the main and poller module -.class_id = QD.MULTIVU.PPMS.1 -.visibility = 3 +description = the main and poller module +class_id = QD.MULTIVU.PPMS.1 +visibility = 3 pollinterval = 2 diff --git a/doc/source/reference.rst b/doc/source/reference.rst index 0f4fc94..9326ba2 100644 --- a/doc/source/reference.rst +++ b/doc/source/reference.rst @@ -59,7 +59,7 @@ Communication :show-inheritance: :members: communicate, multicomm -.. autoclass:: secop.io.HasIodev +.. autoclass:: secop.io.HasIO :show-inheritance: .. autoclass:: secop.iohandler.IOHandlerBase diff --git a/doc/source/tutorial_helevel.rst b/doc/source/tutorial_helevel.rst index 77aadea..3ac85e0 100644 --- a/doc/source/tutorial_helevel.rst +++ b/doc/source/tutorial_helevel.rst @@ -22,7 +22,7 @@ CCU4 luckily has a very simple and logical protocol: .. code:: python # 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): @@ -34,14 +34,13 @@ CCU4 luckily has a very simple and logical protocol: identification = [('cid', r'CCU4.*')] - # inheriting the HasIodev mixin creates us a private attribute *_iodev* - # for talking with the hardware + # inheriting HasIO allows us to use the communicate method for talking with the hardware # Readable as a base class defines the value and status parameters - class HeLevel(HasIodev, Readable): + class HeLevel(HasIO, Readable): """He Level channel of CCU4""" # define the communication class to create the IO module - iodevClass = CCU4IO + ioClass = CCU4IO # define or alter the parameters # 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): # method for reading the main value - reply = self._iodev.communicate('h') # send 'h\n' and get the reply 'h=\n' + reply = self.communicate('h') # send 'h\n' and get the reply 'h=\n' name, txtvalue = reply.split('=') assert name == 'h' # check that we got a reply to our command 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): - name, txtvalue = self._iodev.communicate('hsf').split('=') + name, txtvalue = self.communicate('hsf').split('=') assert name == 'hsf' return self.STATUS_MAP(int(txtvalue)) def read_empty_length(self): - name, txtvalue = self._iodev.communicate('hem').split('=') + name, txtvalue = self.communicate('hem').split('=') assert name == 'hem' return txtvalue 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' return txtvalue @@ -152,7 +151,7 @@ which means it might be worth to create a *query* method, and then the for changing a 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 return txtvalue # Frappy will automatically convert the string to the needed data type diff --git a/secop/core.py b/secop/core.py index 986f57c..73ee8bb 100644 --- a/secop/core.py +++ b/secop/core.py @@ -36,5 +36,5 @@ from secop.params import Command, Parameter from secop.poller import AUTO, DYNAMIC, REGULAR, SLOW from secop.properties import Property 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 diff --git a/secop/io.py b/secop/io.py index 620e01f..27c8adc 100644 --- a/secop/io.py +++ b/secop/io.py @@ -29,56 +29,79 @@ import time import threading from secop.lib.asynconn import AsynConn, ConnectionClosed -from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, StringType, TupleOf, ValueType -from secop.errors import CommunicationFailedError, CommunicationSilentError, ConfigError +from secop.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, \ + StringType, TupleOf, ValueType +from secop.errors import CommunicationFailedError, CommunicationSilentError, \ + ConfigError, ProgrammingError from secop.modules import Attached, Command, \ Communicator, Done, Module, Parameter, Property 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]$') -class HasIodev(Module): +class HasIO(Module): """Mixin for modules using a communicator""" - iodev = Attached() + io = Attached() uri = Property('uri for automatic creation of the attached communication module', StringType(), default='') - iodevDict = {} + ioDict = {} + ioClass = None def __init__(self, name, logger, opts, srv): - iodev = opts.get('iodev') + io = opts.get('io') super().__init__(name, logger, opts, srv) if self.uri: opts = {'uri': self.uri, 'description': 'communication device for %s' % name, 'export': False} - ioname = self.iodevDict.get(self.uri) + ioname = self.ioDict.get(self.uri) if not ioname: - ioname = iodev or name + '_iodev' - iodev = self.iodevClass(ioname, srv.log.getChild(ioname), opts, srv) - iodev.callingModule = [] - srv.modules[ioname] = iodev - self.iodevDict[self.uri] = ioname - self.iodev = ioname - elif not self.iodev: - raise ConfigError("Module %s needs a value for either 'uri' or 'iodev'" % name) + ioname = io or name + '_io' + io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable + io.callingModule = [] + srv.modules[ioname] = io + self.ioDict[self.uri] = ioname + self.io = ioname + elif not io: + raise ConfigError("Module %s needs a value for either 'uri' or 'io'" % name) def initModule(self): try: - self._iodev.read_is_connected() + self.io.read_is_connected() except (CommunicationFailedError, AttributeError): - # AttributeError: for missing _iodev? + # AttributeError: read_is_connected is not required for an io object pass super().initModule() def communicate(self, *args): - return self._iodev.communicate(*args) + return self.io.communicate(*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): diff --git a/secop/iohandler.py b/secop/iohandler.py index 594554b..770f2c1 100644 --- a/secop/iohandler.py +++ b/secop/iohandler.py @@ -126,7 +126,7 @@ class CmdParser: try: argformat % ((0,) * len(casts)) # validate argformat except ValueError as e: - raise ValueError("%s in %r" % (e, argformat)) + raise ValueError("%s in %r" % (e, argformat)) from None def format(self, *values): return self.fmt % values @@ -242,7 +242,7 @@ class IOHandler(IOHandlerBase): contain the command separator at the end. """ querycmd = self.make_query(module) - reply = module.sendRecv(changecmd + querycmd) + reply = module.communicate(changecmd + querycmd) return self.parse_reply(reply) def send_change(self, module, *values): @@ -253,7 +253,7 @@ class IOHandler(IOHandlerBase): """ changecmd = self.make_change(module, *values) 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, changecmd + self.CMDSEPARATOR) diff --git a/secop/modules.py b/secop/modules.py index 6e2bf29..637d155 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -771,19 +771,19 @@ class Communicator(HasComlog, Module): 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, 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 - 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) + module = None - def __repr__(self): - return 'Attached(%s)' % (repr(self.attrname) if self.attrname else '') + def __init__(self, description='attached module'): + 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 diff --git a/secop/poller.py b/secop/poller.py index 1a1abea..5ef57c3 100644 --- a/secop/poller.py +++ b/secop/poller.py @@ -30,7 +30,7 @@ Usage examples: 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 @@ -58,11 +58,11 @@ class PollerBase: table is a dict, with (, ) as the key, and the poller as value. - is module.iodev or module.name, if iodev is not present + is module.io.name or module.name, if io is not present """ - # for modules with the same iodev, a common poller is used, - # modules without iodev all get their own poller - name = getattr(module, 'iodev', module.name) + # for modules with the same io, a common poller is used, + # modules without io all get their own poller + name = getattr(module, 'io', module).name poller = table.get((cls, name), None) if poller is None: poller = cls(name) diff --git a/secop/proxy.py b/secop/proxy.py index 9a3362c..05a4292 100644 --- a/secop/proxy.py +++ b/secop/proxy.py @@ -29,17 +29,17 @@ from secop.lib import get_class from secop.modules import Drivable, Module, Readable, Writable from secop.params import Command, Parameter 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='') pollerClass = None _consistency_check_done = False _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']) return SecNode(name, logger, opts, srv) @@ -54,7 +54,7 @@ class ProxyModule(HasIodev, Module): def initModule(self): if not self.module: self.module = self.name - self._secnode = self._iodev.secnode + self._secnode = self.io.secnode self._secnode.register_callback(self.module, self.updateEvent, self.descriptiveDataChange, self.nodeStateChange) super().initModule() @@ -227,5 +227,5 @@ def Proxy(name, logger, cfgdict, srv): remote_class = cfgdict.pop('remote_class') if 'description' not in cfgdict: 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) diff --git a/secop/server.py b/secop/server.py index 2660f5f..9734e1d 100644 --- a/secop/server.py +++ b/secop/server.py @@ -30,10 +30,9 @@ import sys import traceback 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.multievent import MultiEvent -from secop.modules import Attached from secop.params import PREDEFINED_ACCESSIBLES try: @@ -271,24 +270,17 @@ class Server: for modname, modobj in self.modules.items(): self.log.info('registering module %r' % modname) 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 modobj.earlyInit() if not modobj.earlyInitDone: missing_super.add('%s was not called, probably missing super call' % modobj.earlyInit.__qualname__) - # handle attached modules + # handle polling for modname, modobj in self.modules.items(): - for propname, propobj in modobj.propertyDict.items(): - if isinstance(propobj, Attached): - try: - 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))) + 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) # call init on each module after registering all for modname, modobj in self.modules.items(): diff --git a/secop_psi/ah2700.py b/secop_psi/ah2700.py index f726f0f..f2179fd 100644 --- a/secop_psi/ah2700.py +++ b/secop_psi/ah2700.py @@ -20,7 +20,7 @@ # ***************************************************************************** """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): @@ -28,19 +28,19 @@ class Ah2700IO(StringIO): timeout = 5 -class Capacitance(HasIodev, Readable): +class Capacitance(HasIO, Readable): value = Parameter('capacitance', FloatRange(unit='pF'), poll=True) freq = Parameter('frequency', FloatRange(unit='Hz'), readonly=False, default=0) voltage = Parameter('voltage', FloatRange(unit='V'), readonly=False, default=0) loss = Parameter('loss', FloatRange(unit='deg'), default=0) - iodevClass = Ah2700IO + ioClass = Ah2700IO def parse_reply(self, reply): if reply.startswith('SI'): # this is an echo - self.sendRecv('SERIAL ECHO OFF') - reply = self.sendRecv('SI') + self.communicate('SERIAL ECHO OFF') + reply = self.communicate('SI') if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH" self.status = [self.Status.ERROR, reply] return @@ -59,14 +59,14 @@ class Capacitance(HasIodev, Readable): if lossunit == 'DS': self.loss = loss 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: self.loss = reply[7] except IndexError: pass # don't worry, loss will be updated next time def read_value(self): - self.parse_reply(self.sendRecv('SI')) # SI = single trigger + self.parse_reply(self.communicate('SI')) # SI = single trigger return Done def read_freq(self): @@ -77,14 +77,14 @@ class Capacitance(HasIodev, Readable): self.read_value() return Done - def read_volt(self): + def read_voltage(self): self.read_value() return Done 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 - def write_volt(self, value): - self.parse_reply(self.sendRecv('V %g;SI' % value)) + def write_voltage(self, value): + self.parse_reply(self.communicate('V %g;SI' % value)) return Done diff --git a/secop_psi/ccu4.py b/secop_psi/ccu4.py index 0aaaa52..e263537 100644 --- a/secop_psi/ccu4.py +++ b/secop_psi/ccu4.py @@ -23,7 +23,7 @@ """drivers for CCU4, the cryostat control unit at SINQ""" # the most common Frappy classes can be imported from secop.core from secop.core import EnumType, FloatRange, \ - HasIodev, Parameter, Readable, StringIO + HasIO, Parameter, Readable, StringIO class CCU4IO(StringIO): @@ -34,14 +34,13 @@ class CCU4IO(StringIO): identification = [('cid', r'CCU4.*')] -# inheriting the HasIodev mixin creates us a private attribute *_iodev* -# for talking with the hardware +# inheriting HasIO allows us to use the communicate method for talking with the hardware # Readable as a base class defines the value and status parameters -class HeLevel(HasIodev, Readable): +class HeLevel(HasIO, Readable): """He Level channel of CCU4""" # define the communication class to create the IO module - iodevClass = CCU4IO + ioClass = CCU4IO # define or alter the parameters # as Readable.value exists already, we give only the modified property 'unit' @@ -71,7 +70,7 @@ class HeLevel(HasIodev, Readable): for changing a 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 return txtvalue # Frappy will automatically convert the string to the needed data type diff --git a/secop_psi/k2601b.py b/secop_psi/k2601b.py index 6a984e5..99b10d9 100644 --- a/secop_psi/k2601b.py +++ b/secop_psi/k2601b.py @@ -23,7 +23,7 @@ not tested yet""" from secop.core import Attached, BoolType, EnumType, FloatRange, \ - HasIodev, Module, Parameter, StringIO, Writable + HasIO, Module, Parameter, StringIO, Writable 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) power = Parameter('readback power', FloatRange(unit='W'), poll=True) @@ -49,19 +49,19 @@ class SourceMeter(HasIodev, Module): readonly=False, default=0) active = Parameter('output enable', BoolType(), readonly=False, poll=True) - iodevClass = K2601bIO + ioClass = K2601bIO def read_resistivity(self): - return self.sendRecv('print(smua.measure.r())') + return self.communicate('print(smua.measure.r())') def read_power(self): - return self.sendRecv('print(smua.measure.p())') + return self.communicate('print(smua.measure.p())') def read_active(self): - return self.sendRecv('print(smua.source.output)') + return self.communicate('print(smua.source.output)') 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 @@ -69,11 +69,11 @@ class SourceMeter(HasIodev, Module): if value == 0: self.write_active(0) else: - self.sendRecv(SOURCECMDS[value] + ' print(0)') + self.communicate(SOURCECMDS[value] + ' print(0)') return value -class Current(HasIodev, Writable): +class Current(HasIO, Writable): sourcemeter = Attached() 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) def read_value(self): - return self.sendRecv('print(smua.measure.i())') + return self.communicate('print(smua.measure.i())') def read_target(self): - return self.sendRecv('print(smua.source.leveli)') + return self.communicate('print(smua.source.leveli)') def write_target(self, value): if not self.active: raise ValueError('current source is disabled') if value > self.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): if self.active: return self.limit - return self.sendRecv('print(smua.source.limiti)') + return self.communicate('print(smua.source.limiti)') def write_limit(self, value): if self.active: 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): - 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): - if self._sourcemeter.mode != 1: + if self.sourcemeter.mode != 1: if value: - self._sourcemeter.write_mode(1) # switch to current + self.sourcemeter.write_mode(1) # switch to current else: return 0 - return self._sourcemeter.write_active(value) + return self.sourcemeter.write_active(value) -class Voltage(HasIodev, Writable): +class Voltage(HasIO, Writable): sourcemeter = Attached() 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) def read_value(self): - return self.sendRecv('print(smua.measure.v())') + return self.communicate('print(smua.measure.v())') def read_target(self): - return self.sendRecv('print(smua.source.levelv)') + return self.communicate('print(smua.source.levelv)') def write_target(self, value): if not self.active: raise ValueError('voltage source is disabled') if value > self.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): if self.active: return self.limit - return self.sendRecv('print(smua.source.limitv)') + return self.communicate('print(smua.source.limitv)') def write_limit(self, value): if self.active: 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): - 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): - if self._sourcemeter.mode != 2: + if self.sourcemeter.mode != 2: if value: - self._sourcemeter.write_mode(2) # switch to voltage + self.sourcemeter.write_mode(2) # switch to voltage else: return 0 - return self._sourcemeter.write_active(value) + return self.sourcemeter.write_active(value) diff --git a/secop_psi/ls370res.py b/secop_psi/ls370res.py index 91cea8a..0855fd8 100644 --- a/secop_psi/ls370res.py +++ b/secop_psi/ls370res.py @@ -28,7 +28,7 @@ from secop.lib import formatStatusBits from secop.modules import Attached, Done, \ Drivable, Parameter, Property, Readable from secop.poller import REGULAR, Poller -from secop.io import HasIodev +from secop.io import HasIO Status = Drivable.Status @@ -58,7 +58,7 @@ class StringIO(secop.io.StringIO): wait_before = 0.05 -class Main(HasIodev, Drivable): +class Main(HasIO, Drivable): value = Parameter('the current channel', poll=REGULAR, 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) pollerClass = Poller - iodevClass = StringIO + ioClass = StringIO _channel_changed = 0 # time of last channel change _channels = None # dict of @@ -81,7 +81,7 @@ class Main(HasIodev, Drivable): super().startModule(start_events) for ch in range(1, 16): 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): channel, auto = scan.send_command(self) @@ -114,7 +114,7 @@ class Main(HasIodev, Drivable): def write_target(self, channel): 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: self.value = 0 self._channel_changed = time.time() @@ -123,11 +123,11 @@ class Main(HasIodev, Drivable): def write_autoscan(self, 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 -class ResChannel(HasIodev, Readable): +class ResChannel(HasIO, Readable): """temperature channel on Lakeshore 336""" 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]))} pollerClass = Poller - iodevClass = StringIO + ioClass = StringIO _main = None # main module _last_range_change = 0 # time of last range change @@ -183,7 +183,7 @@ class ResChannel(HasIodev, Readable): return Done # we got here, when we missed the idle state of self._main self._trigger_read = False - result = self.sendRecv('RDGR?%d' % self.channel) + result = self.communicate('RDGR?%d' % self.channel) result = float(result) if self.autorange == 'soft': now = time.time() @@ -216,9 +216,9 @@ class ResChannel(HasIodev, Readable): def read_status(self): if not self.enabled: return [self.Status.DISABLED, 'disabled'] - if self.channel != self._main.value: + if self.channel != self.main.value: 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) statustext = ' '.join(formatStatusBits(result, STATUS_BIT_LABELS)) if statustext: @@ -283,5 +283,5 @@ class ResChannel(HasIodev, Readable): def write_enabled(self, value): inset.write(self, 'enabled', value) if value: - self._main.write_target(self.channel) + self.main.write_target(self.channel) return Done diff --git a/secop_psi/ppms.py b/secop_psi/ppms.py index 25f61b5..60459b8 100644 --- a/secop_psi/ppms.py +++ b/secop_psi/ppms.py @@ -40,10 +40,10 @@ from secop.datatypes import BoolType, EnumType, \ from secop.errors import HardwareError from secop.lib import clamp from secop.lib.enum import Enum -from secop.modules import Attached, Communicator, Done, \ +from secop.modules import Communicator, Done, \ Drivable, Parameter, Property, Readable from secop.poller import Poller -from secop.io import HasIodev +from secop.io import HasIO try: import secop_psi.ppmswindows as ppmshw @@ -130,10 +130,8 @@ class Main(Communicator): return data # return data as string -class PpmsBase(HasIodev, Readable): +class PpmsBase(HasIO, Readable): """common base for all ppms modules""" - iodev = Attached() - # polling is done by the main module # and PPMS does not deliver really more fresh values when polled more often value = Parameter(poll=False, needscfg=False) @@ -150,7 +148,7 @@ class PpmsBase(HasIodev, Readable): def initModule(self): super().initModule() - self._iodev.register(self) + self.io.register(self) def update_value_status(self, value, packed_status): # update value and status @@ -197,7 +195,7 @@ class UserChannel(Channel): datatype=StringType(), export=False, default='') def write_enabled(self, enabled): - other = self._iodev.modules.get(self.linkenable, None) + other = self.io.modules.get(self.linkenable, None) if other: other.enabled = enabled return enabled diff --git a/secop_psi/trinamic.py b/secop_psi/trinamic.py index fea6719..57ba270 100644 --- a/secop_psi/trinamic.py +++ b/secop_psi/trinamic.py @@ -26,7 +26,7 @@ import time import struct 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.errors import CommunicationFailedError, HardwareError, BadValueError, IsBusyError from secop.rwhandler import ReadHandler, WriteHandler @@ -77,7 +77,7 @@ def writable(*args, **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) 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') pollinterval = Parameter(group='more') - iodevClass = BytesIO + ioClass = BytesIO fast_pollfactor = 0.001 # poll as fast as possible when busy _started = 0 _calcTimeout = True @@ -131,20 +131,20 @@ class Motor(PersistentMixin, HasIodev, Drivable): :param value: if given, the parameter is written, else it is returned :return: the returned value """ - if self._calcTimeout and self._iodev._conn: + if self._calcTimeout and self.io._conn: self._calcTimeout = False - baudrate = getattr(self._iodev._conn.connection, 'baudrate', None) + baudrate = getattr(self.io._conn.connection, 'baudrate', None) if baudrate: if baudrate not in BAUDRATES: raise CommunicationFailedError('unsupported baud rate: %d' % baudrate) - self._iodev.timeout = 0.03 + 200 / baudrate + self.io.timeout = 0.03 + 200 / baudrate exc = None byt = struct.pack('>BBBBi', self.address, cmd, adr, bank, round(value)) byt += bytes([sum(byt) & 0xff]) for itry in range(3,0,-1): try: - reply = self._iodev.communicate(byt, 9) + reply = self.communicate(byt, 9) if sum(reply[:-1]) & 0xff != reply[-1]: raise CommunicationFailedError('checksum error') # will try again diff --git a/test/test_attach.py b/test/test_attach.py new file mode 100644 index 0000000..9c6c69c --- /dev/null +++ b/test/test_attach.py @@ -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 +# +# ***************************************************************************** + +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 diff --git a/test/test_iohandler.py b/test/test_iohandler.py index f860a10..da773ac 100644 --- a/test/test_iohandler.py +++ b/test/test_iohandler.py @@ -120,7 +120,7 @@ def test_IOHandler(): 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) - def sendRecv(self, command): + def communicate(self, command): assert data.pop('command') == command return data.pop('reply') @@ -146,7 +146,7 @@ def test_IOHandler(): print(updates) updates.clear() # get rid of updates from initialisation - # for sendRecv + # for communicate data.push('command', 'SIMPLE?') data.push('reply', '4.51') # for analyze_group1 @@ -159,7 +159,7 @@ def test_IOHandler(): assert updates.pop('simple') == 45.1 assert not updates - # for sendRecv + # for communicate data.push('command', 'CMD?3') data.push('reply', '1.23,text,5') # for analyze_group2 @@ -172,7 +172,7 @@ def test_IOHandler(): assert data.empty() assert not updates - # for sendRecv + # for communicate data.push('command', 'CMD?3') data.push('reply', '1.23,text,5') # for analyze_group2 @@ -183,7 +183,7 @@ def test_IOHandler(): data.push('self', 12.3, 'string') data.push('new', 12.3, 'FOO') data.push('changed', 1.23, 'foo', 9) - # for sendRecv + # for communicate data.push('command', 'CMD 3,1.23,foo,9|CMD?3') data.push('reply', '1.23,foo,9') # for analyze_group2 diff --git a/test/test_poller.py b/test/test_poller.py index 2987a9e..b35495a 100644 --- a/test/test_poller.py +++ b/test/test_poller.py @@ -117,7 +117,10 @@ class Parameter: class Module: properties = {} pollerClass = Poller - iodev = 'common_iodev' + + class io: + name = 'common_io' + def __init__(self, name, pollinterval=5, fastfactor=0.25, slowfactor=4, busy=False, counts=(), auto=None): '''create a dummy module @@ -203,7 +206,7 @@ def test_Poller(modules): count[pobj.polltype] += 1 pobj.reset() assert len(pollTable) == 1 - poller = pollTable[(Poller, 'common_iodev')] + poller = pollTable[(Poller, 'common_io')] artime.stop = poller.stop poller._event = Event() # patch Event.wait