From 62d57f649bb55c32801d5a80ac1d0927fd717052 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 28 Mar 2018 11:36:00 +0200 Subject: [PATCH 01/29] drivers: add structure of the driver package. --- i3py/drivers/__init__.py | 14 ++++++++++++++ i3py/drivers/agilent/__init__.py | 17 +++++++++++++++++ i3py/drivers/alazar_tech/__init__.py | 17 +++++++++++++++++ i3py/drivers/anritsu/__init__.py | 17 +++++++++++++++++ i3py/drivers/common/__init__.py | 13 +++++++++++++ i3py/drivers/common/scpi/__init__.py | 11 +++++++++++ i3py/drivers/hp/__init__.py | 17 +++++++++++++++++ i3py/drivers/itest/__init__.py | 17 +++++++++++++++++ i3py/drivers/itest/modules/__init__.py | 17 +++++++++++++++++ i3py/drivers/keysight/__init__.py | 17 +++++++++++++++++ i3py/drivers/oxford/__init__.py | 17 +++++++++++++++++ i3py/drivers/rigol/__init__.py | 17 +++++++++++++++++ i3py/drivers/rohde_schwarz/__init__.py | 17 +++++++++++++++++ i3py/drivers/signal_recovery/__init__.py | 17 +++++++++++++++++ i3py/drivers/sp_devices/__init__.py | 17 +++++++++++++++++ i3py/drivers/stanford_research/__init__.py | 17 +++++++++++++++++ i3py/drivers/tektronix/__init__.py | 17 +++++++++++++++++ i3py/drivers/yokogawa/__init__.py | 17 +++++++++++++++++ 18 files changed, 293 insertions(+) create mode 100644 i3py/drivers/__init__.py create mode 100644 i3py/drivers/agilent/__init__.py create mode 100644 i3py/drivers/alazar_tech/__init__.py create mode 100644 i3py/drivers/anritsu/__init__.py create mode 100644 i3py/drivers/common/__init__.py create mode 100644 i3py/drivers/common/scpi/__init__.py create mode 100644 i3py/drivers/hp/__init__.py create mode 100644 i3py/drivers/itest/__init__.py create mode 100644 i3py/drivers/itest/modules/__init__.py create mode 100644 i3py/drivers/keysight/__init__.py create mode 100644 i3py/drivers/oxford/__init__.py create mode 100644 i3py/drivers/rigol/__init__.py create mode 100644 i3py/drivers/rohde_schwarz/__init__.py create mode 100644 i3py/drivers/signal_recovery/__init__.py create mode 100644 i3py/drivers/sp_devices/__init__.py create mode 100644 i3py/drivers/stanford_research/__init__.py create mode 100644 i3py/drivers/tektronix/__init__.py create mode 100644 i3py/drivers/yokogawa/__init__.py diff --git a/i3py/drivers/__init__.py b/i3py/drivers/__init__.py new file mode 100644 index 0000000..0174c7f --- /dev/null +++ b/i3py/drivers/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package storing all the implemented drivers by manufacturer. + +The package contains also the standards definitions and common utilities such +as support for IEEE488 and SCPI commands. + +""" diff --git a/i3py/drivers/agilent/__init__.py b/i3py/drivers/agilent/__init__.py new file mode 100644 index 0000000..3f55077 --- /dev/null +++ b/i3py/drivers/agilent/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Alias package for the Keysight package. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +from .. import keysight + +sys.modules[__name__] = LazyPackage({}, __name__, __doc__, locals()) diff --git a/i3py/drivers/alazar_tech/__init__.py b/i3py/drivers/alazar_tech/__init__.py new file mode 100644 index 0000000..49a26a2 --- /dev/null +++ b/i3py/drivers/alazar_tech/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Alazar Tech instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/anritsu/__init__.py b/i3py/drivers/anritsu/__init__.py new file mode 100644 index 0000000..d4b66db --- /dev/null +++ b/i3py/drivers/anritsu/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Anritsu instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/common/__init__.py b/i3py/drivers/common/__init__.py new file mode 100644 index 0000000..bedd3cd --- /dev/null +++ b/i3py/drivers/common/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Common utility to deal with drivers implementation. + +""" + +# This package is always needed so there is no point making it lazy diff --git a/i3py/drivers/common/scpi/__init__.py b/i3py/drivers/common/scpi/__init__.py new file mode 100644 index 0000000..595678b --- /dev/null +++ b/i3py/drivers/common/scpi/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Common utility to deal with drivers based on SCPI protocol. + +""" diff --git a/i3py/drivers/hp/__init__.py b/i3py/drivers/hp/__init__.py new file mode 100644 index 0000000..3f55077 --- /dev/null +++ b/i3py/drivers/hp/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Alias package for the Keysight package. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +from .. import keysight + +sys.modules[__name__] = LazyPackage({}, __name__, __doc__, locals()) diff --git a/i3py/drivers/itest/__init__.py b/i3py/drivers/itest/__init__.py new file mode 100644 index 0000000..b9aa581 --- /dev/null +++ b/i3py/drivers/itest/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Itest instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {'BN100': 'bn100.BN100'} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/itest/modules/__init__.py b/i3py/drivers/itest/modules/__init__.py new file mode 100644 index 0000000..84aa2bb --- /dev/null +++ b/i3py/drivers/itest/modules/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of ITest modules. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/keysight/__init__.py b/i3py/drivers/keysight/__init__.py new file mode 100644 index 0000000..ad1b9ec --- /dev/null +++ b/i3py/drivers/keysight/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Keysight/Agilent/HP instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/oxford/__init__.py b/i3py/drivers/oxford/__init__.py new file mode 100644 index 0000000..4701e3d --- /dev/null +++ b/i3py/drivers/oxford/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Oxford instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/rigol/__init__.py b/i3py/drivers/rigol/__init__.py new file mode 100644 index 0000000..126a56a --- /dev/null +++ b/i3py/drivers/rigol/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Rigol instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/rohde_schwarz/__init__.py b/i3py/drivers/rohde_schwarz/__init__.py new file mode 100644 index 0000000..86b6af4 --- /dev/null +++ b/i3py/drivers/rohde_schwarz/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Rohde&Schwarz instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/signal_recovery/__init__.py b/i3py/drivers/signal_recovery/__init__.py new file mode 100644 index 0000000..2e1702e --- /dev/null +++ b/i3py/drivers/signal_recovery/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Signal Recovery instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/sp_devices/__init__.py b/i3py/drivers/sp_devices/__init__.py new file mode 100644 index 0000000..62e4405 --- /dev/null +++ b/i3py/drivers/sp_devices/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of SP devices instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/stanford_research/__init__.py b/i3py/drivers/stanford_research/__init__.py new file mode 100644 index 0000000..8b338bf --- /dev/null +++ b/i3py/drivers/stanford_research/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Stanford research instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/tektronix/__init__.py b/i3py/drivers/tektronix/__init__.py new file mode 100644 index 0000000..ef2867e --- /dev/null +++ b/i3py/drivers/tektronix/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Tektronix instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/yokogawa/__init__.py b/i3py/drivers/yokogawa/__init__.py new file mode 100644 index 0000000..27a44e5 --- /dev/null +++ b/i3py/drivers/yokogawa/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the drivers of Yokogawa instruments. + +""" +import sys +from i3py.core.lazy_package import LazyPackage + +DRIVERS = {} + +sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) From a953c6bb2bc984e96aa6fa4d56b1f38960ad0ae5 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Sat, 14 Apr 2018 20:49:14 +0200 Subject: [PATCH 02/29] drivers: add standards for identity subsystem and dc sources --- i3py/drivers/base/dc_sources.py | 148 ++++++++++++++++++++++++++++++++ i3py/drivers/base/identity.py | 38 ++++++++ 2 files changed, 186 insertions(+) create mode 100644 i3py/drivers/base/dc_sources.py create mode 100644 i3py/drivers/base/identity.py diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py new file mode 100644 index 0000000..7c29f2f --- /dev/null +++ b/i3py/drivers/base/dc_sources.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""I3py standard for DC sources. + +""" +from i3py.core import HasFeatures, SubSystem, channel +from i3py.core.unit import FLOAT_QUANTITY +from i3py.core.actions import Action +from i3py.core.features import Bool, Float, Str, constant + + +class DCPowerSource(HasFeatures): + """Standard interface expected from all DC Power sources. + + """ + + #: Outputs of the source. By default we declare a single output on index 0. + output = channel((0,)) + + with output as o: + + #: Is the output on or off. Note that this value does not + o.enabled = Bool(aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + #: Target voltage for the output. If the source is a "current" source + #: this will likely be a fixed value. + o.voltage = Float(unit='V') + + #: Range in which the voltage can be set. + o.voltage_range = Float(unit='V') + + #: How does the source behave if it cannot reach the target voltage + #: because its reached the target current first. + #: - regulate: we stop at the reached voltage when the target current + #: is reached. + #: - trip: the output is disabled if the current reaches or gets + #: greater than the specified current. + o.current_limit_behavior = Str(constant('regulate'), + values=('regulate', 'trip')) + + #: Target voltage for the output. If the source is a "voltage" source + #: this will likely be a fixed value. + o.current = Float(unit='A') + + #: Range in which the current can be set. + o.current_range = Float(unit='A') + + #: How does the source behave if it cannot reach the target voltage + #: because its reached the target current first. + #: - regulate: we stop at the reached voltage when the target current + #: is reached. + #: - trip: the output is disabled if the voltage reaches or gets + #: greater than the specified voltage. + o.voltage_limit_behavior = Str(constant('regulate'), + values=('regulate', 'trip')) + + @o + @Action() + def read_output_status(self) -> str: + """Determine the status of the output. + + Returns + ------- + status : {'constant_voltage', 'constant_current', + 'over_voltage', 'over_current', 'unregulated'} + The possible values for the output status are the following. + - 'constant_voltage': the target voltage was reached before the + target current and voltage_limit_behavior is 'regulate'. + - 'constant_current': the target current was reached before the + target voltage and current_limit_behavior is 'regulate'. + - 'over_voltage': the output tripped after reaching the + voltage limit. + - 'over_current': the output tripped after reaching the + current limit. + - 'unregulated': The output of the instrument is not stable. + + Notes + ----- + Calling this function is meaningful only if the output is enabled. + + """ + raise NotImplementedError() + + +class DCPowerSourceWithMeasure(DCPowerSource): + """DC power source supporting to measure the output current/voltage. + + """ + #: Outputs of the source. By default we declare a single output on index 0. + output = channel((0,)) + + with output as o: + + @o + @Action() + def measure(self, quantity: str, **kwargs) -> FLOAT_QUANTITY: + """Measure the output voltage/current. + + Parameters + ---------- + quantity : str, {'voltage', 'current'} + Quantity to measure. + + **kwargs : + Optional kwargs to specify the conditions of the measure + (integration time, averages, etc) if applicable. + + Returns + ------- + value : float or pint.Quantity + Measured value. If units are supported the value is a Quantity + object. + + """ + raise NotImplementedError() + + +# TODO complete once we have a real use for it +class DCSourceTriggerSubsystem(SubSystem): + """Subsystem handing the usual triggering mechanism of DC sources. + + It should be added to a DCPowerSource subclass under the name trigger. + + """ + #: Working mode for the trigger. This usually defines how the instrument + #: will answer to a trigger event. + mode = Str(values=('disabled',)) + + #: Possible origin for trigger events. + source = Str(values=('immediate', 'software')) # Will extend later + + #: Delay between the trigger and the time at which the instrument start to + #: modify its output. + delay = Float(unit='s') + + @Action() + def arm(self): + """Make the system ready to receive a trigger event. + + """ + pass diff --git a/i3py/drivers/base/identity.py b/i3py/drivers/base/identity.py new file mode 100644 index 0000000..88aea0c --- /dev/null +++ b/i3py/drivers/base/identity.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Standard interface of the identity subsystem. + +""" +from i3py.core.base_subsystem import SubSystem +from i3py.core.features import Str + + +class Identity(SubSystem): + """Standard subsystem defining the expected identity info. + + This should be used as a base class for the identity subsystem of + instruments providing identity information. + + Notes + ----- + Some of those info might not be available for a given instrument. In such + a case the Feature should return ''. + + """ + #: Manufacturer as returned by the instrument. + manufacturer = Str(True) + + #: Model name as returned by the instrument. + model = Str(True) + + #: Instrument serial number. + serial = Str(True) + + #: Version of the installed firmware. + firmware = Str(True) From 48d0ab66c3cca8064b6d7db83b9628bc6936cee0 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Sat, 14 Apr 2018 20:49:55 +0200 Subject: [PATCH 03/29] drivers: add common functionality for IEEE and some scpi commands --- i3py/drivers/common/ieee488.py | 498 ++++++++++++++++++++++ i3py/drivers/common/rs232.py | 46 ++ i3py/drivers/common/scpi/error_reading.py | 39 ++ i3py/drivers/common/scpi/rs232.py | 20 + 4 files changed, 603 insertions(+) create mode 100644 i3py/drivers/common/ieee488.py create mode 100644 i3py/drivers/common/rs232.py create mode 100644 i3py/drivers/common/scpi/error_reading.py create mode 100644 i3py/drivers/common/scpi/rs232.py diff --git a/i3py/drivers/common/ieee488.py b/i3py/drivers/common/ieee488.py new file mode 100644 index 0000000..0e5f5d3 --- /dev/null +++ b/i3py/drivers/common/ieee488.py @@ -0,0 +1,498 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Base classes for instruments supporting standards command such *IDN?. + +A lot of those class are heavily inspired from the Slave package. + +The standards specifies that if one command of a group is implemented all +commends should be implemented. However this is not always enforced. The +base classes subdivide a bit more the commands to take this fact into +account. + +Reporting Commands + * `*CLS` - Clears the data status structure [#]_ . + * `*ESE` - Write the event status enable register [#]_ . + * `*ESE?` - Query the event status enable register [#]_ . + * `*ESR?` - Query the standard event status register [#]_ . + * `*SRE` - Write the status enable register [#]_ . + * `*SRE?` - Query the status enable register [#]_ . + * `*STB` - Query the status register [#]_ . + +Internal operation commands + * `*IDN?` - Identification query [#]_ . + * `*RST` - Perform a device reset [#]_ . + * `*TST?` - Perform internal self-test [#]_ . + +Synchronization commands + * `*OPC` - Set operation complete flag high [#]_ . + * `*OPC?` - Query operation complete flag [#]_ . + * `*WAI` - Wait to continue [#]_ . + +Power on common commands + * `*PSC` - Set the power-on status clear bit [#]_ . + * `*PSC?` - Query the power-on status clear bit [#]_ . + +Parallel poll common commands NOT IMPLEMENTED + * `*IST?` - Query the individual status message bit [#]_ . + * `*PRE` - Set the parallel poll enable register [#]_ . + * `*PRE?` - Query the parallel poll enable register [#]_ . + +Resource description common commands + * `*RDT` - Store the resource description in the device [#]_ . + * `*RDT?` - Query the stored resource description [#]_ . + +Protected user data commands + * `*PUD` - Store protected user data in the device [#]_ . + * `*PUD?` - Query the protected user data [#]_ . + +Calibration command + * `*CAL?` - Perform internal self calibration [#]_ . + +Trigger command + * `*TRG` - Execute trigger command [#]_ . + +Trigger macro commands + * `*DDT` - Define device trigger [#]_ . + * `*DDT?` - Define device trigger query [#]_ . + +Macro Commands NOT IMPLEMENTED + * `*DMC` - Define device trigger [#]_ . + * `*EMC` - Define device trigger query [#]_ . + * `*EMC?` - Define device trigger [#]_ . + * `*GMC?` - Define device trigger query [#]_ . + * `*LMC?` - Define device trigger [#]_ . + * `*PMC` - Define device trigger query [#]_ . + +Option Identification command + * `*OPT?` - Option identification query [#]_ . + +Stored settings commands + * `*RCL` - Restore device settings from local memory [#]_ . + * `*SAV` - Store current settings of the device in local memory [#]_ . + +Learn command NOT IMPLEMENTED + * `*LRN?` - Learn device setup query [#]_ . + +System configuration commands NOT IMPLEMENTED + * `*AAD` - Accept address command [#]_ . + * `*DLF` - Disable listener function command [#]_ . + +Passing control command NOT IMPLEMENTED + * `*PCB` - Pass control back [#]_ . + +Reference: + +.. [#] IEC 60488-2:2004(E) section 10.3 +.. [#] IEC 60488-2:2004(E) section 10.10 +.. [#] IEC 60488-2:2004(E) section 10.11 +.. [#] IEC 60488-2:2004(E) section 10.12 +.. [#] IEC 60488-2:2004(E) section 10.34 +.. [#] IEC 60488-2:2004(E) section 10.35 +.. [#] IEC 60488-2:2004(E) section 10.36 +.. [#] IEC 60488-2:2004(E) section 10.14 +.. [#] IEC 60488-2:2004(E) section 10.32 +.. [#] IEC 60488-2:2004(E) section 10.38 +.. [#] IEC 60488-2:2004(E) section 10.18 +.. [#] IEC 60488-2:2004(E) section 10.19 +.. [#] IEC 60488-2:2004(E) section 10.39 +.. [#] IEC 60488-2:2004(E) section 10.25 +.. [#] IEC 60488-2:2004(E) section 10.26 +.. [#] IEC 60488-2:2004(E) section 10.15 +.. [#] IEC 60488-2:2004(E) section 10.23 +.. [#] IEC 60488-2:2004(E) section 10.24 +.. [#] IEC 60488-2:2004(E) section 10.30 +.. [#] IEC 60488-2:2004(E) section 10.31 +.. [#] IEC 60488-2:2004(E) section 10.27 +.. [#] IEC 60488-2:2004(E) section 10.28 +.. [#] IEC 60488-2:2004(E) section 10.2 +.. [#] IEC 60488-2:2004(E) section 10.37 +.. [#] IEC 60488-2:2004(E) section 10.4 +.. [#] IEC 60488-2:2004(E) section 10.5 +.. [#] IEC 60488-2:2004(E) section 10.7 +.. [#] IEC 60488-2:2004(E) section 10.8 +.. [#] IEC 60488-2:2004(E) section 10.9 +.. [#] IEC 60488-2:2004(E) section 10.13 +.. [#] IEC 60488-2:2004(E) section 10.16 +.. [#] IEC 60488-2:2004(E) section 10.22 +.. [#] IEC 60488-2:2004(E) section 10.20 +.. [#] IEC 60488-2:2004(E) section 10.29 +.. [#] IEC 60488-2:2004(E) section 10.33 +.. [#] IEC 60488-2:2004(E) section 10.17 +.. [#] IEC 60488-2:2004(E) section 10.1 +.. [#] IEC 60488-2:2004(E) section 10.6 +.. [#] IEC 60488-2:2004(E) section 10.21 + +.. _IEC 60488-2: http://dx.doi.org/10.1109/IEEESTD.2004.95390 + +""" +from time import sleep +from typing import ClassVar, Dict + +from i3py.backends.visa import VisaMessageDriver +from i3py.core import subsystem, customize, set_feat +from i3py.core.actions import Action, RegisterAction +from i3py.core.features import Bool, Options, Register, Str +from stringparser import Parser + +from ..base.identity import Identity + + +# ============================================================================= +# --- Status reporting -------------------------------------------------------- +# ============================================================================= + +class IEEEStatusReporting(VisaMessageDriver): + """Class implementing the status reporting commands. + + * `*ESE` - See IEC 60488-2:2004(E) section 10.10 + * `*ESE?` - See IEC 60488-2:2004(E) section 10.11 + * `*ESR?` - See IEC 60488-2:2004(E) section 10.12 + * `*SRE` - See IEC 60488-2:2004(E) section 10.34 + * `*SRE?` - See IEC 60488-2:2004(E) section 10.35 + + """ + #: Define which bits of the status byte cause a service request. + service_request_enabled = Register('*SRE?', '*SRE {}') + + #: Define which bits contribute to the event status in the status byte. + event_status_enabled = Register('*ESE?', '*ESE {}') + + @RegisterAction(('operation_complete', 'request_control', 'query_error', + 'device_dependent_error', 'execution_error', + 'command_error', 'user_request', 'power_on',)) + def read_event_status_register(self) -> int: + """Read and clear the event register. + + """ + return int(self.visa_resource.query('*ESR?')) + + +# ============================================================================= +# --- Internal operations ----------------------------------------------------- +# ============================================================================= + +class IEEEIdentity(VisaMessageDriver): + """Class implementing the identification command. + + The identity susbsytem feature values are extracted by default from the + answer to the *IDN? command. Its format can be specified by overriding + the idn_format of the subsystem. + + """ + identity = subsystem(Identity) + + with identity as i: + + #: Format string specifying the format of the IDN query answer and + #: allowing to extract the following information: + #: - manufacturer: name of the instrument manufacturer + #: - model: name of the instrument model + #: - serial: serial number of the instrument + #: - firmware: firmware revision + #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} + i.IEEE_IDN_FORMAT = '' + + i.manufacturer = set_feat(getter='*IDN?') + i.model = set_feat(getter='*IDN?') + i.serial = set_feat(getter='*IDN?') + i.firmware = set_feat(getter='*IDN?') + + def _post_getter(feat, driver, value): + """Get the identity info from the *IDN?. + + """ + infos = Parser(self.IEEE_IDN_FORMAT)(value) + driver._cache.update(infos) + return infos.get(feat.name, '') + + for f in ('manufacturer', 'model', 'serial', 'firmware'): + setattr(i, '_post_get_' + f, + customize(f, 'post_get')(_post_getter)) + + def is_connected(self) -> bool: + try: + self.visa_resource.query('*IDN?') + except Exception: + return False + + return True + + +class IEEESelfTest(VisaMessageDriver): + """Class implementing the self-test command. + + """ + #: Meaning of the self test result. + IEEE_SELF_TEST: ClassVar[Dict[int, str]] = {0: 'Normal completion'} + + @Action() + def perform_self_test(self) -> str: + """Run the self test routine. + + """ + return self.IEEE_SELF_TEST.get(int(self.visa_resource.query('*TST?')), + 'Unknown error') + + +class IEEEReset(VisaMessageDriver): + """Class implementing the reset command. + + """ + IEEE_RESET_WAIT: ClassVar[int] = 1 + + @Action() + def reset(self) -> None: + """Initialize the instrument settings. + + After running this you might need to wait a bit before sending new + commands to the instrument. + + """ + self.visa_resource.write('*RST') + self.clear_cache() + sleep(self.IEEE_RESET_WAIT) + + +class IEEEInternalOperations(IEEEReset, IEEESelfTest, IEEEIdentity): + """Class implementing all the internal operations. + + """ + pass + + +# ============================================================================= +# --- Synchronization --------------------------------------------------------- +# ============================================================================= + +class IEEEOperationComplete(VisaMessageDriver): + """A mixin class implementing the operation complete commands. + + * `*OPC` - See IEC 60488-2:2004(E) section 10.18 + * `*OPC?` - See IEC 60488-2:2004(E) section 10.19 + + """ + + @Action() + def complete_operation(self) -> None: + """Sets the operation complete bit high of the event status byte. + + """ + self.visa_resource.write('*OPC') + + @Action() + def is_operation_completed(self) -> bool: + """Check whether or not the instrument has completed all pending + operations. + + """ + return bool(int(self.visa_resource.query('*OPC?'))) + + +class IEEEWaitToContinue(VisaMessageDriver): + """A mixin class implementing the wait command. + + * `*WAI` - See IEC 60488-2:2004(E) section 10.39 + + """ + @Action() + def wait_to_continue(self) -> None: + """Prevents the device from executing any further commands or queries + until the no operation flag is `True`. + + Notes + ----- + In devices implementing only sequential commands, the no-operation + flag is always True. + + """ + self.visa_resource.write('*WAI') + + +class IEEESynchronisation(IEEEWaitToContinue, IEEEOperationComplete): + """A mixin class implementing all synchronization methods. + + """ + pass + + +# ============================================================================= +# --- Power on ---------------------------------------------------------------- +# ============================================================================= + +class IEEEPowerOn(VisaMessageDriver): + """A mixin class, implementing the optional power-on common commands. + + The IEC 60488-2:2004(E) defines the following optional power-on common + commands: + + * `*PSC` - See IEC 60488-2:2004(E) section 10.25 + * `*PSC?` - See IEC 60488-2:2004(E) section 10.26 + + """ + #: Represents the power-on status clear flag. If it is `False` the event + #: status enable, service request enable and serial poll enable registers + #: will retain their status when power is restored to the device and will + #: be cleared if it is set to `True`. + poweron_status_clear = Bool('*PSC?', '*PSC {}', + mapping={True: '1', False: '0'}) + + +# ============================================================================= +# --- Resource description ---------------------------------------------------- +# ============================================================================= + +class IEEEResourceDescription(VisaMessageDriver): + """A class implementing the resource description common commands. + + * `*RDT` - See IEC 60488-2:2004(E) section 10.30 + * `*RDT?` - See IEC 60488-2:2004(E) section 10.31 + + """ + #: Description of the resource. The formatting is not checked. + resource_description = Str('*RDT?', '*RDT {}') + + +# ============================================================================= +# --- Protected user data ----------------------------------------------------- +# ============================================================================= + +class IEEEProtectedUserData(VisaMessageDriver): + """A class implementing the protected user data common commands. + + * `*RDT` - See IEC 60488-2:2004(E) section 10.30 + * `*RDT?` - See IEC 60488-2:2004(E) section 10.31 + + """ + #: Protected user data. The validity of the passed string is not checked. + protected_user_data = Str('*PUD?', '*PUD {}') + + +# ============================================================================= +# --- Calibration ------------------------------------------------------------- +# ============================================================================= + +class IEEECalibration(object): + """A class implementing the optional calibration command. + + * `*CAL?` - See IEC 60488-2:2004(E) section 10.2 + + """ + CALIBRATION: ClassVar[Dict[int, str]] = {0: 'Calibration completed'} + + @Action() + def calibrate(self) -> str: + """Performs a internal self-calibration. + + """ + return self.CALIBRATION.get(int(self.visa_resource.query('*CAL?')), + 'Unknown error') + + +# ============================================================================= +# --- Triggering -------------------------------------------------------------- +# ============================================================================= + +class IEEETrigger(VisaMessageDriver): + """A class implementing the optional trigger command. + + * `*TRG` - See IEC 60488-2:2004(E) section 10.37 + + It is mandatory for devices implementing the DT1 subset. + + """ + + @Action() + def fire_trigger(self) -> None: + """Creates a trigger event. + + """ + self.visa_resource.write('*TRG') + + +# ============================================================================= +# --- Macro trigger ----------------------------------------------------------- +# ============================================================================= + +class IEEETriggerMacro(IEEETrigger): + """A class implementing the optional trigger macro commands. + + * `*DDT` - See IEC 60488-2:2004(E) section 10.4 + * `*DDT?` - See IEC 60488-2:2004(E) section 10.5 + + """ + #: Sequence of commands to execute when receiving a trigger. + trigger_macro = Str('*DDT?', 'DDT {}') + + +# ============================================================================= +# --- Option identification --------------------------------------------------- +# ============================================================================= + +class IEEEOptionsIdentification(VisaMessageDriver): + """A class implementing the option identification command. + + * `*OPT?` - See IEC 60488-2:2004(E) section 10.20 + + """ + #: Mapping between the value returned by the instrument (as a comma + #: separated list) and the names presented to the user. + #: When writing a driver this class variable should be updated and used + #: to generate the names of the feature using for example. + #: instr_options = set_default(names=dict.fromkeys(INSTR_OPTIONS_MAP, + #: bool)) + INSTR_OPTIONS_MAP: ClassVar[Dict[str, str]] = {} + + instr_options = Options('*OPT?', names={'example': bool}) + + @customize('instr_options', 'post_get') + def _convert_options(feat, driver, value): + """Split the returned value and identify the options. + + """ + options = dict.fromkeys(feat.names, False) + options.update({driver.INSTR_OPTIONS_MAP[k]: True + for k in value.split(',')}) + return options + + +# ============================================================================= +# --- Stored settings --------------------------------------------------------- +# ============================================================================= + +class IEEEStoredSettings(VisaMessageDriver): + """A class implementing the stored setting commands. + + * `*RCL` - See IEC 60488-2:2004(E) section 10.29 + * `*SAV` - See IEC 60488-2:2004(E) section 10.33 + + """ + @Action() + def recall(self, idx) -> None: + """Restores the current settings from a copy stored in local memory. + + Parameters + --------- + idx : int + Specifies the memory slot. + + """ + self.visa_resource.write('*RCL {}'.format(idx)) + self.clear_cache() + + @Action() + def save(self, idx) -> None: + """Stores the current settings of a device in local memory. + + Parameters + ---------- + idx : int + Specifies the memory slot. + + """ + self.visa_resource.write('*SAV {}'.format(idx)) diff --git a/i3py/drivers/common/rs232.py b/i3py/drivers/common/rs232.py new file mode 100644 index 0000000..9473f76 --- /dev/null +++ b/i3py/drivers/common/rs232.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Base class for driver supporting the VISA RS232 communication protocol. + +This class ensures that the instrument is always in remote mode before +sending any other command. + +""" +from types import MethodType + +from i3py.backends.visa import VisaMessageDriver +from pyvisa.resources.serial import SerialInstrument + + +class VisaRS232(VisaMessageDriver): + """Base class for all instruments supporting the RS232 interface. + + The specifity of the RS232 interface is that the device need to be switched + to remote mode before sending any command. This class wrapps the low-level + write method of the ressource when the connection is opened in RS232 mode + and prepend the RS232_HEADER string to the message. + + """ + #: Header to add to the message to switch the instrument in remote mode + #: This HAVE TO BE A BYTE STRING and should include the character + #: separating the two messages. + RS232_HEADER = b'' + + def initialize(self): + """Initialize the driver and if pertinent wrap low level so that + RS232_HEADER is prepended to messages. + + """ + super(VisaRS232, self).initialize() + if isinstance(self._resource, SerialInstrument) and self.RS232_HEADER: + write_raw = self._resource.write_raw.__func__ + + def new_write(self, message): + return write_raw(self, self.RS232_HEADER + message) + self._resource.write_raw = MethodType(new_write, self._resource) diff --git a/i3py/drivers/common/scpi/error_reading.py b/i3py/drivers/common/scpi/error_reading.py new file mode 100644 index 0000000..353c2b3 --- /dev/null +++ b/i3py/drivers/common/scpi/error_reading.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Base driver for instrument implementing SCPI error reporting commands. + +""" +from typing import Tuple + +from i3py.core.actions import Action +from i3py.backends.visa import VisaMessageDriver + + +class SCPIErrorReading(VisaMessageDriver): + """Base class for all instruments implementing 'SYST:ERR?'. + + """ + + @Action() + def read_error(self) -> Tuple[int, str]: + """Read the first error in the error queue. + + If an unhandle error occurs, the error queue should be polled till it + is empty. + + """ + code, msg = self.query('SYST:ERR?').split(',') # type: ignore + return int(code), msg + + def default_check_operation(self, feat, value, i_value, response): + """Check if an error is present in the error queue. + + """ + code, msg = self.read_error() + return not bool(code), msg diff --git a/i3py/drivers/common/scpi/rs232.py b/i3py/drivers/common/scpi/rs232.py new file mode 100644 index 0000000..971752f --- /dev/null +++ b/i3py/drivers/common/scpi/rs232.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Base class for SCPI instruments supporting RS232-based communication. + +""" +from ..rs232 import VisaRS232 + + +class SCPIRS232(VisaRS232): + """Base class for SCPI compliant instruments supporting the RS232 protocol. + + """ + + RS232_HEADER = b'SYST:REM:;' From cd3138847e3c8e71a1325081796f002568961ac6 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Sat, 14 Apr 2018 20:50:31 +0200 Subject: [PATCH 04/29] drivers.itest: wip on BILT system basic support --- i3py/drivers/itest/__init__.py | 3 +- i3py/drivers/itest/modules/__init__.py | 1 + i3py/drivers/itest/modules/be21xx.py | 240 +++++++++++++++++++++++++ i3py/drivers/itest/modules/common.py | 69 +++++++ i3py/drivers/itest/racks.py | 77 ++++++++ tests/drivers/itest/test_be2101.py | 36 ++++ 6 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 i3py/drivers/itest/modules/be21xx.py create mode 100644 i3py/drivers/itest/modules/common.py create mode 100644 i3py/drivers/itest/racks.py create mode 100644 tests/drivers/itest/test_be2101.py diff --git a/i3py/drivers/itest/__init__.py b/i3py/drivers/itest/__init__.py index b9aa581..0edaf6b 100644 --- a/i3py/drivers/itest/__init__.py +++ b/i3py/drivers/itest/__init__.py @@ -12,6 +12,7 @@ import sys from i3py.core.lazy_package import LazyPackage -DRIVERS = {'BN100': 'bn100.BN100'} +DRIVERS = {'BN100': 'racks.BN100', 'BN101': 'racks.BN101', + 'BN103': 'racks.BN103', 'BN105': 'racks.BN105'} sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/itest/modules/__init__.py b/i3py/drivers/itest/modules/__init__.py index 84aa2bb..4b598c8 100644 --- a/i3py/drivers/itest/modules/__init__.py +++ b/i3py/drivers/itest/modules/__init__.py @@ -11,6 +11,7 @@ """ import sys from i3py.core.lazy_package import LazyPackage +from .common import make_card_detector DRIVERS = {} diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py new file mode 100644 index 0000000..9836f21 --- /dev/null +++ b/i3py/drivers/itest/modules/be21xx.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Package for the Itest BE21xx voltage source card. + +""" +from typing import Optional, Callable + +from i3py.core import FloatLimitsValidator, channel, set_feat, subsystem +from i3py.core.actions import Action +from i3py.core.features import Float, Str, constant +from i3py.core.job import InstrJob +from i3py.core.unit import to_float, FLOAT_QUANTITY + +from ...common.ieee488 import IEEEIdentity +from ...base.dc_sources import (DCPowerSourceWithMeasure, + DCSourceTriggerSubsystem) +from .common import BiltModule + + +class BE21xx(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + __version__ = '0.1.0' + + # Identity support. + identity = subsystem() + with identity as i: + i.IEEE_IDN_FORMAT = ('{_:d}, "{manufacturer:s} {model:s}BB/{_:s}' + '\\{serial:s} LC{_:s} VL{firmware:s}\\{_:d}') + + # DC outputs + output = channel((0,)) + + with output as o: + o.enabled = set_feat(getter='OUT?', setter='OUT {}') + + o.voltage = set_feat(getter='VOLT?', setter='VOLT {:E}', + limits='voltage') + + o.voltage_range = set_feat(getter='VOLT:RANG?', setter='VOLT:RANG {}', + values=(1.2, 12), extract='{},{_}', + checks=(None, 'not driver.output'), + discard={'features': ('voltage',), + 'limits': ('voltage',)}) + + #: Specify stricter voltage limitations than the ones linked to the + #: range. + o.voltage_saturation = subsystem() + with o.voltage_saturation as vs: + #: Lowest allowed voltage. + vs.low = Float('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}', unit='V', + limits=(-12, 0), discard={'limits': ('voltage',)}) + + #: Highest allowed voltage. + vs.high = Float('VOLT:SAT:POS?', 'VOLT:SAT:POS {}', unit='V', + limits=(0, 12), discard={'limits': ('voltage',)}) + + o.current = set_feat(getter=constant(0.2)) + + o.current_range = set_feat(getter=constant(0.2)) + + #: Subsystem handling triggering and reaction to triggering. + o.trigger = subsystem(DCSourceTriggerSubsystem) + with o.trigger as tr: + #: Type of response to triggering : + #: - disabled : immediate update of voltage every time the voltage + #: feature is updated. + #: - slope : update after receiving a trigger based on the slope + #: value. + #: - stair : update after receiving a trigger using step_amplitude + #: and step_width. + #: - step : increment by one step_amplitude till target value for + #: each triggering. + #: - auto : update after receiving a trigger by steps but + #: determining when to move to next step based on voltage + #: sensing. + tr.mode = Str('TRIG:IN?', 'TRIG:IN {}', + mapping={'disabled': '0', 'slope': '1', + 'stair': '2', 'step': '4', 'auto': '5'}) + + #: The only valid source for the trigger is software trigger. + tr.source = Str(constant('software')) + + #: Delay to wait after receiving a trigger event before reacting. + tr.delay = set_feat(getter='TRIG:IN:DEL?', setter='TRIG:IN:DEL {}', + unit='ms', limits=(0, 60000, 1)) + + #: Voltage slope to use in slope mode. + tr.slope = Float('VOLT:SLOP?', 'VOLT:SLOP {}', unit='V/ms', + limits=(1.2e-6, 1)) + + #: High of each update in stair and step mode. + tr.step_amplitude = Float('VOLT:ST:AMPL?', 'VOLT:ST:AMPL {}', + unit='V', limits='voltage') + + #: Width of each step in stair mode. + tr.step_width = Float('VOLT:ST:WID?', 'VOLT:ST:WID {}', unit='ms', + limits=(100, 60000, 1)) + + #: Absolute threshold value of the settling tracking comparator. + tr.ready_amplitude = Float('TRIG:READY:AMPL?', + 'TRIG:READY:AMPL {}', + unit='V', limits='voltage') + + @Action() + def fire(self): + """Send a software trigger. + + """ + self.root.visa_resource.write(f'I {self.ch_id};TRIG:IN:INIT') + + # XXX todo + @o + @Action() + def read_output_status(self) -> str: + """Determine the current status of the output. + + """ + answer = self.root.visa_resource.query('LIM:FAIL?') + + @o + @Action() + def read_voltage_status(self) -> str: + """Progression of the current voltage update. + + Returns + ------- + progression : int + Progression of the voltage update. The value is between 0 + and 1. + + """ + msg = f'I {self.parent.ch_id};VOLT:STAT?' + if int(self.root.visa_resource.query(msg)) == 1: + return 'settled' + else: + return 'changing' + + @o + @Action(unit=((None, None, None), 'V')) + def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: + """Measure the output voltage. + + """ + if kind != 'voltage': + raise ValueError('') + else: + return float(self.query(f'I{self.parent.ch_id};MEAS:VOLT?')) + + @o + @Action() + def wait_for_settling(self, + break_condition_callable: + Optional[Callable[[], bool]]=None, + timeout: float=15, + refresh_time: float=1) -> bool: + """Wait for the output to settle. + + Parameters + ---------- + break_condition_callable : Callable, optional + Callable indicating that we should stop waiting. + + timeout : float, optional + Time to wait in seconds in addition to the expected condition + time before breaking. + + refresh_time : float, optional + Time interval at which to check the break condition. + + Returns + ------- + result : bool + Boolean indicating if the wait succeeded of was interrupted. + + """ + job = InstrJob(lambda: self.read_voltage_status() == 'settled', 1) + return job.wait_for_completion(break_condition_callable, + timeout, refresh_time) + + # ===================================================================== + # --- Private API ----------------------------------------------------- + # ===================================================================== + + @o + def _limits_voltage(self): + """Compute the voltage limits based on range and saturation. + + """ + rng = to_float(self.voltage_range) + low = max(-rng, float(self.voltage_saturation.low)) + high = min(rng, float(self.voltage_saturation.high)) + + step = 1.2e-6 if rng == 1.2 else 1.2e-5 + + return FloatLimitsValidator(low, high, step, 'V') + + +class BE210x(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + __version__ = '0.1.0' + + output = channel((0,)) + + with output as o: + #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms + o.voltage_filter = Str('VOLT:FILT?', 'VOLT:FILT {}', + mapping={'Slow': 0, 'Fast': 1}) + + +class BE2101(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + __version__ = '0.1.0' + + +class BE214x(BE21xx): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + __version__ = '0.1.0' + + output = channel((0, 1, 2, 3)) + + +class BE2141(BE214x): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + __version__ = '0.1.0' diff --git a/i3py/drivers/itest/modules/common.py b/i3py/drivers/itest/modules/common.py new file mode 100644 index 0000000..f5e92a5 --- /dev/null +++ b/i3py/drivers/itest/modules/common.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Common tools for the Bilt module instruments. + +""" +from typing import Any, Callable, List, Union + +from i3py.core import Channel, subsystem + +from ...base.identity import Identity + + +def make_card_detector(model_id: Union[str, List[str]] + ) -> Callable[[Any], List[str]]: + """Create a function listing the available card of a given model. + + Parameters + ---------- + model_id : str or list of str + Id or ids of the model. ex BE2101 + + """ + if not isinstance(model_id, list): + model_id = [model_id] + + # We strip the leading BE + model_id = set(m.strip('BE') for m in model_id) + + def list_channel(driver): + """Query all the cards fitted on the rack and filter based on the model + + """ + cards = {int(i): id + for i, id in [card.split(',') + for card in driver.query('I:L?').split(';')]} + + return [index for index in cards if cards[index] in model_id] + + return list_channel + + +class BiltModule(Channel): + """Base driver for module used with the Bilt chassis. + + """ + identity = subsystem(Identity) + + with identity as i: + pass + + def default_get_feature(self, feat, cmd, *args, **kwargs): + """Prepend module selection to command. + + """ + cmd = 'I{ch_id};'+cmd + super().default_get_feature(feat, cmd, *args, **kwargs) + + def default_set_feature(self, feat, cmd, *args, **kwargs): + """Prepend module selection to command. + + """ + cmd = 'I{ch_id};'+cmd + super().default_set_feature(feat, cmd, *args, **kwargs) diff --git a/i3py/drivers/itest/racks.py b/i3py/drivers/itest/racks.py new file mode 100644 index 0000000..e7dce7d --- /dev/null +++ b/i3py/drivers/itest/racks.py @@ -0,0 +1,77 @@ +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Driver for the Itest rack instruments + +""" +from i3py.core import channel + +from ..common.ieee488 import IEEEReset +from ..common.scpi.error_reading import SCPIErrorReading +from .modules.be21xx import BE2101, BE2141 +from .modules import make_card_detector + + +class BiltMainframe(IEEEReset, SCPIErrorReading): + """Driver for the Itest BN100 chassis. + + """ + PROTOCOLS = {'TCPIP': {'resource_class': 'SOCKET', + 'port': '5025'}, + 'GPIB': {'resource_class': 'INSTR'}, + 'ASRL': {'resource_class': 'INSTR'} + } + + DEFAULTS = {'COMMON': {'read_termination': '\n', + 'write_termination': '\n'} + } + + IEEE_RESET_WAIT = 4 + + #: Support for the BE2101 card + be2101 = channel('_list_be2101', BE2101) + + #: Support for the BE2141 card + be2141 = channel('_list_be2141', BE2141) + + def initialize(self): + """Make sure the communication parameters are correctly sets. + + """ + super().initialize() + self.visa_resource.write('SYST:VERB 0') + + _list_be2101 = make_card_detector(['BE2101']) + _list_be2141 = make_card_detector(['BE2141']) + + +class BN100(BiltMainframe): + """Driver for the BN100 Bilt rack. + + """ + __version__ = '0.1.0' + + +class BN101(BiltMainframe): + """Driver for the BN100 Bilt rack. + + """ + __version__ = '0.1.0' + + +class BN103(BiltMainframe): + """Driver for the BN100 Bilt rack. + + """ + __version__ = '0.1.0' + + +class BN105(BiltMainframe): + """Driver for the BN100 Bilt rack. + + """ + __version__ = '0.1.0' diff --git a/tests/drivers/itest/test_be2101.py b/tests/drivers/itest/test_be2101.py new file mode 100644 index 0000000..42cdd06 --- /dev/null +++ b/tests/drivers/itest/test_be2101.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""This file is meant to check the working of the driver for the BE2101. + +The rack is expected to have a BE2101 in one slot, whose output can be safely +switched on and off and whose output value can vary (and has a large impedance) + +""" +# Visa connection info +VISA_RESOURCE_NAME = 'TCPIP::192.168.0.10::5025::SOCKET' + +# Index of the slot in which the BE2101 can be found (starting from 1) +MODULE_INDEX = 1 + +from i3py.drivers.itest import BN100 + +with BN100(VISA_RESOURCE_NAME) as rack: + + print(rack.be2101.available) + + module = rack.be2101[MODULE_INDEX] + print(module.manufacturer) + + output = module.output[0] + for f_name in output.__feats__: + print(getattr(output, f_name)) + + for sub in output.__subsystems__: + for f_name in sub.__feats__: + print(getattr(output, f_name)) From a404fc227260c5aa3ca849bee0f38c0a7a60efc0 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 16 Apr 2018 10:19:44 +0200 Subject: [PATCH 05/29] drivers: fixes to ieee and scpi base commands --- i3py/drivers/common/ieee488.py | 2 +- i3py/drivers/common/scpi/error_reading.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i3py/drivers/common/ieee488.py b/i3py/drivers/common/ieee488.py index 0e5f5d3..fa3fc70 100644 --- a/i3py/drivers/common/ieee488.py +++ b/i3py/drivers/common/ieee488.py @@ -207,7 +207,7 @@ def _post_getter(feat, driver, value): """Get the identity info from the *IDN?. """ - infos = Parser(self.IEEE_IDN_FORMAT)(value) + infos = Parser(driver.IEEE_IDN_FORMAT)(value) driver._cache.update(infos) return infos.get(feat.name, '') diff --git a/i3py/drivers/common/scpi/error_reading.py b/i3py/drivers/common/scpi/error_reading.py index 353c2b3..23a7bef 100644 --- a/i3py/drivers/common/scpi/error_reading.py +++ b/i3py/drivers/common/scpi/error_reading.py @@ -24,11 +24,11 @@ class SCPIErrorReading(VisaMessageDriver): def read_error(self) -> Tuple[int, str]: """Read the first error in the error queue. - If an unhandle error occurs, the error queue should be polled till it + If an unhandled error occurs, the error queue should be polled till it is empty. """ - code, msg = self.query('SYST:ERR?').split(',') # type: ignore + code, msg = self.visa_resource.query('SYST:ERR?').split(',') return int(code), msg def default_check_operation(self, feat, value, i_value, response): From 68ea660651e5e434a7af0b327ba719aad0982089 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 16 Apr 2018 10:20:07 +0200 Subject: [PATCH 06/29] drivers.itest: get all tests for be2101 to pass --- i3py/drivers/itest/modules/be21xx.py | 76 ++++++++++++++++++++++------ i3py/drivers/itest/modules/common.py | 8 +-- i3py/drivers/itest/racks.py | 2 + tests/drivers/itest/test_be2101.py | 10 ++-- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index 9836f21..ea072ce 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -9,37 +9,68 @@ """Package for the Itest BE21xx voltage source card. """ -from typing import Optional, Callable +from typing import Callable, Optional -from i3py.core import FloatLimitsValidator, channel, set_feat, subsystem +from i3py.core import (FloatLimitsValidator, channel, customize, set_feat, + subsystem) from i3py.core.actions import Action from i3py.core.features import Float, Str, constant from i3py.core.job import InstrJob -from i3py.core.unit import to_float, FLOAT_QUANTITY +from i3py.core.unit import FLOAT_QUANTITY, to_float +from stringparser import Parser -from ...common.ieee488 import IEEEIdentity from ...base.dc_sources import (DCPowerSourceWithMeasure, DCSourceTriggerSubsystem) +from ...base.identity import Identity from .common import BiltModule -class BE21xx(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): +class BE21xx(BiltModule, DCPowerSourceWithMeasure): """Driver for the Bilt BE2100 high precision dc voltage source. """ __version__ = '0.1.0' - # Identity support. - identity = subsystem() + #: Identity support (we do not use IEEEIdentity because we are not on the + #: root level). + identity = subsystem(Identity) + with identity as i: - i.IEEE_IDN_FORMAT = ('{_:d}, "{manufacturer:s} {model:s}BB/{_:s}' - '\\{serial:s} LC{_:s} VL{firmware:s}\\{_:d}') - # DC outputs + #: Format string specifying the format of the IDN query answer and + #: allowing to extract the following information: + #: - manufacturer: name of the instrument manufacturer + #: - model: name of the instrument model + #: - serial: serial number of the instrument + #: - firmware: firmware revision + #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} + i.IEEE_IDN_FORMAT = ('{_:d},"{manufacturer:s} {model:s}BB/{_:s}' + '/SN{serial:s}\\{_:s} LC{_:s} VL{firmware:s}' + '\\{_:d}"') + + i.manufacturer = set_feat(getter='*IDN?') + i.model = set_feat(getter='*IDN?') + i.serial = set_feat(getter='*IDN?') + i.firmware = set_feat(getter='*IDN?') + + def _post_getter(feat, driver, value): + """Get the identity info from the *IDN?. + + """ + infos = Parser(driver.IEEE_IDN_FORMAT)(value) + driver._cache.update(infos) + return infos.get(feat.name, '') + + for f in ('manufacturer', 'model', 'serial', 'firmware'): + setattr(i, '_post_get_' + f, + customize(f, 'post_get')(_post_getter)) + + #: DC outputs output = channel((0,)) with output as o: - o.enabled = set_feat(getter='OUT?', setter='OUT {}') + o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', + mapping={False: '0', True: '1'}) o.voltage = set_feat(getter='VOLT?', setter='VOLT {:E}', limits='voltage') @@ -54,6 +85,7 @@ class BE21xx(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): #: range. o.voltage_saturation = subsystem() with o.voltage_saturation as vs: + #: Lowest allowed voltage. vs.low = Float('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}', unit='V', limits=(-12, 0), discard={'limits': ('voltage',)}) @@ -62,6 +94,20 @@ class BE21xx(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): vs.high = Float('VOLT:SAT:POS?', 'VOLT:SAT:POS {}', unit='V', limits=(0, 12), discard={'limits': ('voltage',)}) + @vs + @customize('low', 'post_get', ('prepend',)) + def _convert_min(feat, driver, value): + if value == 'MIN': + value = '-12' + return value + + @vs + @customize('high', 'post_get', ('prepend',)) + def _convert_min(feat, driver, value): + if value == 'MAX': + value = '12' + return value + o.current = set_feat(getter=constant(0.2)) o.current_range = set_feat(getter=constant(0.2)) @@ -203,7 +249,7 @@ def _limits_voltage(self): return FloatLimitsValidator(low, high, step, 'V') -class BE210x(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): +class BE210x(BE21xx): """Driver for the Bilt BE2100 high precision dc voltage source. """ @@ -213,11 +259,11 @@ class BE210x(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): with output as o: #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms - o.voltage_filter = Str('VOLT:FILT?', 'VOLT:FILT {}', - mapping={'Slow': 0, 'Fast': 1}) + o.voltage_filter = Str('VOLT:FIL?', 'VOLT:FIL {}', + mapping={'Slow': '0', 'Fast': '1'}) -class BE2101(BiltModule, DCPowerSourceWithMeasure, IEEEIdentity): +class BE2101(BE210x): """Driver for the Bilt BE2100 high precision dc voltage source. """ diff --git a/i3py/drivers/itest/modules/common.py b/i3py/drivers/itest/modules/common.py index f5e92a5..fedbfe1 100644 --- a/i3py/drivers/itest/modules/common.py +++ b/i3py/drivers/itest/modules/common.py @@ -36,9 +36,9 @@ def list_channel(driver): """Query all the cards fitted on the rack and filter based on the model """ + card_list = driver.visa_resource.query('I:L?').split(';') cards = {int(i): id - for i, id in [card.split(',') - for card in driver.query('I:L?').split(';')]} + for i, id in [card.split(',') for card in card_list]} return [index for index in cards if cards[index] in model_id] @@ -59,11 +59,11 @@ def default_get_feature(self, feat, cmd, *args, **kwargs): """ cmd = 'I{ch_id};'+cmd - super().default_get_feature(feat, cmd, *args, **kwargs) + return super().default_get_feature(feat, cmd, *args, **kwargs) def default_set_feature(self, feat, cmd, *args, **kwargs): """Prepend module selection to command. """ cmd = 'I{ch_id};'+cmd - super().default_set_feature(feat, cmd, *args, **kwargs) + return super().default_set_feature(feat, cmd, *args, **kwargs) diff --git a/i3py/drivers/itest/racks.py b/i3py/drivers/itest/racks.py index e7dce7d..f1265e4 100644 --- a/i3py/drivers/itest/racks.py +++ b/i3py/drivers/itest/racks.py @@ -44,6 +44,8 @@ def initialize(self): """ super().initialize() self.visa_resource.write('SYST:VERB 0') + while self.read_error()[0]: + pass _list_be2101 = make_card_detector(['BE2101']) _list_be2141 = make_card_detector(['BE2141']) diff --git a/tests/drivers/itest/test_be2101.py b/tests/drivers/itest/test_be2101.py index 42cdd06..e199667 100644 --- a/tests/drivers/itest/test_be2101.py +++ b/tests/drivers/itest/test_be2101.py @@ -25,12 +25,14 @@ print(rack.be2101.available) module = rack.be2101[MODULE_INDEX] - print(module.manufacturer) + print(module.identity.manufacturer) output = module.output[0] for f_name in output.__feats__: - print(getattr(output, f_name)) + print(f_name, getattr(output, f_name)) - for sub in output.__subsystems__: + for sub_name in output.__subsystems__: + print('testing ', sub_name) + sub = getattr(output, sub_name) for f_name in sub.__feats__: - print(getattr(output, f_name)) + print(getattr(sub, f_name)) From cfbb3cf5add1d3a37164c13137d104b35d9a6796 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 16 Apr 2018 19:05:16 +0200 Subject: [PATCH 07/29] drivers.itest: wip --- i3py/drivers/itest/modules/be21xx.py | 84 +++++++++++++++++-- i3py/drivers/itest/modules/common.py | 10 ++- tests/drivers/__init__.py | 0 .../itest/{test_be2101.py => t_be2101.py} | 5 +- 4 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 tests/drivers/__init__.py rename tests/drivers/itest/{test_be2101.py => t_be2101.py} (90%) diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index ea072ce..2679af5 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -66,11 +66,14 @@ def _post_getter(feat, driver, value): customize(f, 'post_get')(_post_getter)) #: DC outputs - output = channel((0,)) + output = channel((1,)) with output as o: o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', - mapping={False: '0', True: '1'}) + mapping={False: '0', True: '1'}, + checks=(None, + 'driver.read_output_status() == "normal"') + ) o.voltage = set_feat(getter='VOLT?', setter='VOLT {:E}', limits='voltage') @@ -103,7 +106,7 @@ def _convert_min(feat, driver, value): @vs @customize('high', 'post_get', ('prepend',)) - def _convert_min(feat, driver, value): + def _convert_max(feat, driver, value): if value == 'MAX': value = '12' return value @@ -117,7 +120,7 @@ def _convert_min(feat, driver, value): with o.trigger as tr: #: Type of response to triggering : #: - disabled : immediate update of voltage every time the voltage - #: feature is updated. + #: feature is updated. The update respect the slope. #: - slope : update after receiving a trigger based on the slope #: value. #: - stair : update after receiving a trigger using step_amplitude @@ -155,21 +158,47 @@ def _convert_min(feat, driver, value): 'TRIG:READY:AMPL {}', unit='V', limits='voltage') + @tr @Action() def fire(self): """Send a software trigger. """ - self.root.visa_resource.write(f'I {self.ch_id};TRIG:IN:INIT') + msg = f'I {self.parent.parent.id};TRIG:IN:INIT' + self.root.visa_resource.write(msg) + + #: Status of the output. Save for the first one, they are all related + #: to dire issues that lead to switch off the output. + o.OUTPUT_STATES = {0: 'normal', + 5: 'main failure', + 6: 'system failure', + 7: 'temperature failure', + 8: 'regulation issue'} - # XXX todo @o @Action() def read_output_status(self) -> str: """Determine the current status of the output. """ - answer = self.root.visa_resource.query('LIM:FAIL?') + msg = self._header_() + 'LIM:FAIL?' + answer = int(self.root.visa_resource.query(msg)) + if answer != 0: + del self.enabled + return self.OUTPUT_STATES.get(answer, f'unknown({answer})') + + @o + @Action() + def clear_output_status(self) -> None: + """Clear the error condition of the output. + + This must be called after a failure before switching the output + back on + + """ + self.root.write.query(self._header_() + 'LIM:CLE') + if not self.read_output_status() == 'normal': + raise RuntimeError('Failed to clear output status.') @o @Action() @@ -183,7 +212,7 @@ def read_voltage_status(self) -> str: and 1. """ - msg = f'I {self.parent.ch_id};VOLT:STAT?' + msg = self._header_() + 'VOLT:STAT?' if int(self.root.visa_resource.query(msg)) == 1: return 'settled' else: @@ -198,7 +227,8 @@ def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: if kind != 'voltage': raise ValueError('') else: - return float(self.query(f'I{self.parent.ch_id};MEAS:VOLT?')) + msg = self._header_() + 'MEAS:VOLT?' + return float(self.visa_resource.query(msg)) @o @Action() @@ -248,6 +278,10 @@ def _limits_voltage(self): return FloatLimitsValidator(low, high, step, 'V') + @o + def _header_(self): + return f'I{self.parent.id};' + class BE210x(BE21xx): """Driver for the Bilt BE2100 high precision dc voltage source. @@ -278,6 +312,38 @@ class BE214x(BE21xx): output = channel((0, 1, 2, 3)) + with output as o: + + o.OUTPUT_STATES = {0: 'normal', + 11: 'main failure', + 12: 'system failure', + 17: 'regulation issue', + 18: 'over-current'} + + def default_get_feature(self, feat, cmd, *args, **kwargs): + """Prepend module selection to command. + + """ + cmd = f'C{self.id};' + cmd + return self.parent.default_get_feature(feat, cmd, *args, **kwargs) + + def default_set_feature(self, feat, cmd, *args, **kwargs): + """Prepend module selection to command. + + """ + cmd = f'C{self.id};' + cmd + return self.parent.default_set_feature(feat, cmd, *args, **kwargs) + + o.trigger = subsystem() + with o.trigger as tr: + #: The BE2141 requires the triggering to be disabled before + #: changing the triggering mode when the output is enabled. + tr.mode = set_feat(setter='TRIG:IN 0;TRIG:IN {}') + + @o + def _header_(self): + return f'I{self.parent.id};C{self.id};' + class BE2141(BE214x): """Driver for the Bilt BE2100 high precision dc voltage source. diff --git a/i3py/drivers/itest/modules/common.py b/i3py/drivers/itest/modules/common.py index fedbfe1..6b0035f 100644 --- a/i3py/drivers/itest/modules/common.py +++ b/i3py/drivers/itest/modules/common.py @@ -49,6 +49,8 @@ class BiltModule(Channel): """Base driver for module used with the Bilt chassis. """ + CHANNEL_ID = 'module_id' + identity = subsystem(Identity) with identity as i: @@ -58,12 +60,12 @@ def default_get_feature(self, feat, cmd, *args, **kwargs): """Prepend module selection to command. """ - cmd = 'I{ch_id};'+cmd - return super().default_get_feature(feat, cmd, *args, **kwargs) + cmd = f'I{self.id};' + cmd + return self.parent.default_get_feature(feat, cmd, *args, **kwargs) def default_set_feature(self, feat, cmd, *args, **kwargs): """Prepend module selection to command. """ - cmd = 'I{ch_id};'+cmd - return super().default_set_feature(feat, cmd, *args, **kwargs) + cmd = f'I{self.id};' + cmd + return self.parent.default_set_feature(feat, cmd, *args, **kwargs) diff --git a/tests/drivers/__init__.py b/tests/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/drivers/itest/test_be2101.py b/tests/drivers/itest/t_be2101.py similarity index 90% rename from tests/drivers/itest/test_be2101.py rename to tests/drivers/itest/t_be2101.py index e199667..0ec04e0 100644 --- a/tests/drivers/itest/test_be2101.py +++ b/tests/drivers/itest/t_be2101.py @@ -26,8 +26,11 @@ module = rack.be2101[MODULE_INDEX] print(module.identity.manufacturer) + print(module.identity.model) + print(module.identity.serial) + print(module.identity.firmware) - output = module.output[0] + output = module.output[1] for f_name in output.__feats__: print(f_name, getattr(output, f_name)) From 9e09f695b0255ffb962f86f25cc2f91f178660f3 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 18 Apr 2018 13:46:32 +0200 Subject: [PATCH 08/29] drivers.itest: wip --- i3py/drivers/base/dc_sources.py | 12 +-- i3py/drivers/itest/modules/be21xx.py | 116 +++++++++++++++++++++------ tests/drivers/itest/t_be2101.py | 26 ++++-- tests/drivers/itest/t_be2141.py | 51 ++++++++++++ 4 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 tests/drivers/itest/t_be2141.py diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py index 7c29f2f..195b0af 100644 --- a/i3py/drivers/base/dc_sources.py +++ b/i3py/drivers/base/dc_sources.py @@ -21,7 +21,7 @@ class DCPowerSource(HasFeatures): """ #: Outputs of the source. By default we declare a single output on index 0. - output = channel((0,)) + output = channel((1,)) with output as o: @@ -68,16 +68,16 @@ def read_output_status(self) -> str: Returns ------- - status : {'constant_voltage', 'constant_current', - 'over_voltage', 'over_current', 'unregulated'} + status : {'constant-voltage', 'constant-current', + 'over-voltage', 'over-current', 'unregulated'} The possible values for the output status are the following. - 'constant_voltage': the target voltage was reached before the target current and voltage_limit_behavior is 'regulate'. - - 'constant_current': the target current was reached before the + - 'constant-current': the target current was reached before the target voltage and current_limit_behavior is 'regulate'. - - 'over_voltage': the output tripped after reaching the + - 'over-voltage': the output tripped after reaching the voltage limit. - - 'over_current': the output tripped after reaching the + - 'over-current': the output tripped after reaching the current limit. - 'unregulated': The output of the instrument is not stable. diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index 2679af5..9913402 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -12,9 +12,9 @@ from typing import Callable, Optional from i3py.core import (FloatLimitsValidator, channel, customize, set_feat, - subsystem) + subsystem, limit) from i3py.core.actions import Action -from i3py.core.features import Float, Str, constant +from i3py.core.features import Float, Str, Bool, constant from i3py.core.job import InstrJob from i3py.core.unit import FLOAT_QUANTITY, to_float from stringparser import Parser @@ -84,11 +84,45 @@ def _post_getter(feat, driver, value): discard={'features': ('voltage',), 'limits': ('voltage',)}) + o.current_limit_behavior = set_feat(getter=constant('trip')) + #: Specify stricter voltage limitations than the ones linked to the #: range. o.voltage_saturation = subsystem() with o.voltage_saturation as vs: + #: Is the low voltage limit enabled. + vs.low_enabled = Bool('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}') + + @vs + @customize('low_enabled', 'post_get', ('prepend',)) + def _convert_low_answer(feat, driver, value): + return not value == 'MIN' + + @vs + @customize('low_enabled', 'pre_set', ('prepend',)) + def _prepare_low_answer(feat, driver, value): + if value: + return driver.low + else: + return 'MIN' + + #: Is the high voltage limit enabled. + vs.high_enabled = Bool('VOLT:SAT:POS?', 'VOLT:SAT:POS {}') + + @vs + @customize('high_enabled', 'post_get', ('prepend',)) + def _convert_high_answer(feat, driver, value): + return not value == 'MAX' + + @vs + @customize('high_enabled', 'pre_set', ('prepend',)) + def _prepare_high_answer(feat, driver, value): + if value: + return driver.high + else: + return 'MAX' + #: Lowest allowed voltage. vs.low = Float('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}', unit='V', limits=(-12, 0), discard={'limits': ('voltage',)}) @@ -159,7 +193,7 @@ def _convert_max(feat, driver, value): unit='V', limits='voltage') @tr - @Action() + @Action(retries=1) def fire(self): """Send a software trigger. @@ -168,15 +202,15 @@ def fire(self): self.root.visa_resource.write(msg) #: Status of the output. Save for the first one, they are all related - #: to dire issues that lead to switch off the output. - o.OUTPUT_STATES = {0: 'normal', - 5: 'main failure', - 6: 'system failure', - 7: 'temperature failure', - 8: 'regulation issue'} + #: to dire issues that lead to switching off the output. + o.OUTPUT_STATES = {0: 'constant-voltage', + 5: 'main-failure', + 6: 'system-failure', + 7: 'temperature-failure', + 8: 'unregulated'} @o - @Action() + @Action(retries=1) def read_output_status(self) -> str: """Determine the current status of the output. @@ -188,7 +222,7 @@ def read_output_status(self) -> str: return self.OUTPUT_STATES.get(answer, f'unknown({answer})') @o - @Action() + @Action(retries=1) def clear_output_status(self) -> None: """Clear the error condition of the output. @@ -196,30 +230,32 @@ def clear_output_status(self) -> None: back on """ - self.root.write.query(self._header_() + 'LIM:CLE') + self.root.visa_resource.write(self._header_() + 'LIM:CLEAR') if not self.read_output_status() == 'normal': raise RuntimeError('Failed to clear output status.') @o - @Action() + @Action(retries=1) def read_voltage_status(self) -> str: """Progression of the current voltage update. Returns ------- - progression : int - Progression of the voltage update. The value is between 0 - and 1. + status: {'waiting', 'settled', 'changing'} + Status of the output voltage. """ msg = self._header_() + 'VOLT:STAT?' - if int(self.root.visa_resource.query(msg)) == 1: + status = float(self.root.visa_resource.query(msg)) + if status == 1: return 'settled' + elif status == 0: + return 'waiting' else: return 'changing' @o - @Action(unit=((None, None, None), 'V')) + @Action(unit=((None, None, None), 'V'), retries=1) def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: """Measure the output voltage. @@ -228,11 +264,12 @@ def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: raise ValueError('') else: msg = self._header_() + 'MEAS:VOLT?' - return float(self.visa_resource.query(msg)) + return float(self.root.visa_resource.query(msg)) @o @Action() def wait_for_settling(self, + stop_on_break: bool=True, break_condition_callable: Optional[Callable[[], bool]]=None, timeout: float=15, @@ -241,6 +278,8 @@ def wait_for_settling(self, Parameters ---------- + cancel_on_break : bool, optional + break_condition_callable : Callable, optional Callable indicating that we should stop waiting. @@ -257,15 +296,25 @@ def wait_for_settling(self, Boolean indicating if the wait succeeded of was interrupted. """ - job = InstrJob(lambda: self.read_voltage_status() == 'settled', 1) - return job.wait_for_completion(break_condition_callable, - timeout, refresh_time) + def stop_ramp(): + # We round to ensure that we never get any range issue + self.voltage = round(self.measure('voltage'), 4) + self.trigger.fire() + + job = InstrJob(lambda: self.read_voltage_status() == 'settled', 1, + cancel=stop_ramp) + result = job.wait_for_completion(break_condition_callable, + timeout, refresh_time) + if not result and stop_on_break: + job.cancel() + return result # ===================================================================== # --- Private API ----------------------------------------------------- # ===================================================================== @o + @limit('voltage') def _limits_voltage(self): """Compute the voltage limits based on range and saturation. @@ -289,7 +338,7 @@ class BE210x(BE21xx): """ __version__ = '0.1.0' - output = channel((0,)) + output = channel((1,)) with output as o: #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms @@ -310,14 +359,31 @@ class BE214x(BE21xx): """ __version__ = '0.1.0' - output = channel((0, 1, 2, 3)) + #: Identity support (we do not use IEEEIdentity because we are not on the + #: root level). + identity = subsystem() + + with identity as i: + + #: Format string specifying the format of the IDN query answer and + #: allowing to extract the following information: + #: - manufacturer: name of the instrument manufacturer + #: - model: name of the instrument model + #: - serial: serial number of the instrument + #: - firmware: firmware revision + #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} + i.IEEE_IDN_FORMAT = ('{_:d},"{manufacturer:s} {model:s}B/{_:s}' + '/SN{serial:s} LC{_:s} VL{firmware:s}' + '\\{_:d}"') + + output = channel((1, 2, 3, 4)) with output as o: o.OUTPUT_STATES = {0: 'normal', 11: 'main failure', 12: 'system failure', - 17: 'regulation issue', + 17: 'unregulated', 18: 'over-current'} def default_get_feature(self, feat, cmd, *args, **kwargs): diff --git a/tests/drivers/itest/t_be2101.py b/tests/drivers/itest/t_be2101.py index 0ec04e0..90c1ce0 100644 --- a/tests/drivers/itest/t_be2101.py +++ b/tests/drivers/itest/t_be2101.py @@ -22,20 +22,30 @@ with BN100(VISA_RESOURCE_NAME) as rack: - print(rack.be2101.available) + # Test reading all features + print('Available modules', rack.be2101.available) module = rack.be2101[MODULE_INDEX] - print(module.identity.manufacturer) - print(module.identity.model) - print(module.identity.serial) - print(module.identity.firmware) + print('Manufacturer', module.identity.manufacturer) + print('Model', module.identity.model) + print('Serial number', module.identity.serial) + print('Firmware', module.identity.firmware) + print('Testing output') output = module.output[1] for f_name in output.__feats__: - print(f_name, getattr(output, f_name)) + print(' ', f_name, getattr(output, f_name)) for sub_name in output.__subsystems__: - print('testing ', sub_name) + print(' Testing ', sub_name) sub = getattr(output, sub_name) for f_name in sub.__feats__: - print(getattr(sub, f_name)) + print(' ', f_name, getattr(sub, f_name)) + + # Test action reading basic status + print('Output status', output.read_output_status()) + output.clear_output_status() + print('Voltage status', output.read_voltage_status()) + print('Measured output voltage', output.measure('voltage')) + + # Test settings and general behavior diff --git a/tests/drivers/itest/t_be2141.py b/tests/drivers/itest/t_be2141.py new file mode 100644 index 0000000..5827536 --- /dev/null +++ b/tests/drivers/itest/t_be2141.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""This file is meant to check the working of the driver for the BE2101. + +The rack is expected to have a BE2101 in one slot, whose output can be safely +switched on and off and whose output value can vary (and has a large impedance) + +""" +# Visa connection info +VISA_RESOURCE_NAME = 'TCPIP::192.168.0.10::5025::SOCKET' + +# Index of the slot in which the BE2101 can be found (starting from 1) +MODULE_INDEX = 1 + +from i3py.drivers.itest import BN100 + +with BN100(VISA_RESOURCE_NAME) as rack: + + # Test reading all features + print('Available modules', rack.be2141.available) + + module = rack.be2141[MODULE_INDEX] + print('Manufacturer', module.identity.manufacturer) + print('Model', module.identity.model) + print('Serial number', module.identity.serial) + print('Firmware', module.identity.firmware) + + print('Testing output') + output = module.output[1] + for f_name in output.__feats__: + print(' ', f_name, getattr(output, f_name)) + + for sub_name in output.__subsystems__: + print(' Testing ', sub_name) + sub = getattr(output, sub_name) + for f_name in sub.__feats__: + print(' ', f_name, getattr(sub, f_name)) + + # Test action reading basic status + print('Output status', output.read_output_status()) + output.clear_output_status() + print('Voltage status', output.read_voltage_status()) + print('Measured output voltage', output.measure('voltage')) + + # Test settings and general behavior From 5c3e8107881fe27b89b31e15dc371889a3d3f1ac Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 18 Apr 2018 18:54:35 +0200 Subject: [PATCH 09/29] drivers.itest: wip --- i3py/drivers/itest/modules/be21xx.py | 80 ++++++++++++++++++++-------- tests/drivers/itest/t_be2141.py | 36 ++++++++++++- 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index 9913402..5f61863 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -72,7 +72,8 @@ def _post_getter(feat, driver, value): o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', mapping={False: '0', True: '1'}, checks=(None, - 'driver.read_output_status() == "normal"') + 'driver.read_output_status() == ' + '"constant-voltage"') ) o.voltage = set_feat(getter='VOLT?', setter='VOLT {:E}', @@ -80,7 +81,7 @@ def _post_getter(feat, driver, value): o.voltage_range = set_feat(getter='VOLT:RANG?', setter='VOLT:RANG {}', values=(1.2, 12), extract='{},{_}', - checks=(None, 'not driver.output'), + checks=(None, 'not driver.enabled'), discard={'features': ('voltage',), 'limits': ('voltage',)}) @@ -91,8 +92,12 @@ def _post_getter(feat, driver, value): o.voltage_saturation = subsystem() with o.voltage_saturation as vs: - #: Is the low voltage limit enabled. - vs.low_enabled = Bool('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}') + #: Is the low voltage limit enabled. If this conflict with the + #: current voltage, the voltage will clipped to th smallest allowed + #: value. + vs.low_enabled = Bool('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}', + discard={'features': ('.voltage',), + 'limits': ('.voltage',)}) @vs @customize('low_enabled', 'post_get', ('prepend',)) @@ -100,15 +105,19 @@ def _convert_low_answer(feat, driver, value): return not value == 'MIN' @vs - @customize('low_enabled', 'pre_set', ('prepend',)) + @customize('low_enabled', 'pre_set', ('append',)) def _prepare_low_answer(feat, driver, value): if value: return driver.low else: return 'MIN' - #: Is the high voltage limit enabled. - vs.high_enabled = Bool('VOLT:SAT:POS?', 'VOLT:SAT:POS {}') + #: Is the high voltage limit enabled. If this conflict with the + #: current voltage, the voltage will clipped to th largest allowed + #: value. + vs.high_enabled = Bool('VOLT:SAT:POS?', 'VOLT:SAT:POS {}', + discard={'features': ('.voltage',), + 'limits': ('.voltage',)}) @vs @customize('high_enabled', 'post_get', ('prepend',)) @@ -116,20 +125,28 @@ def _convert_high_answer(feat, driver, value): return not value == 'MAX' @vs - @customize('high_enabled', 'pre_set', ('prepend',)) + @customize('high_enabled', 'pre_set', ('append',)) def _prepare_high_answer(feat, driver, value): if value: return driver.high else: return 'MAX' - #: Lowest allowed voltage. + #: Lowest allowed voltage. If this conflict with the current + #: voltage, the voltage will clipped to th smallest allowed + #: value. vs.low = Float('VOLT:SAT:NEG?', 'VOLT:SAT:NEG {}', unit='V', - limits=(-12, 0), discard={'limits': ('voltage',)}) + limits=(-12, 0), + discard={'features': ('.voltage',), + 'limits': ('.voltage',)}) - #: Highest allowed voltage. + #: Highest allowed voltage. If this conflict with the current + #: voltage, the voltage will clipped to th smallest allowed + #: value. vs.high = Float('VOLT:SAT:POS?', 'VOLT:SAT:POS {}', unit='V', - limits=(0, 12), discard={'limits': ('voltage',)}) + limits=(0, 12), + discard={'features': ('.voltage',), + 'limits': ('.voltage',)}) @vs @customize('low', 'post_get', ('prepend',)) @@ -190,7 +207,7 @@ def _convert_max(feat, driver, value): #: Absolute threshold value of the settling tracking comparator. tr.ready_amplitude = Float('TRIG:READY:AMPL?', 'TRIG:READY:AMPL {}', - unit='V', limits='voltage') + unit='V', limits=(1.2e-6, 1)) @tr @Action(retries=1) @@ -198,9 +215,19 @@ def fire(self): """Send a software trigger. """ - msg = f'I {self.parent.parent.id};TRIG:IN:INIT' + msg = self.parent._header_() + 'TRIG:IN:INIT' self.root.visa_resource.write(msg) + @tr + @Action(retries=1) + def is_trigger_ready(self) -> bool: + """Check if the output is within ready_amplitude of the target + value. + + """ + msg = self.parent._header_() + 'TRIG:READY?' + return bool(int(self.root.visa_resource.query(msg))) + #: Status of the output. Save for the first one, they are all related #: to dire issues that lead to switching off the output. o.OUTPUT_STATES = {0: 'constant-voltage', @@ -235,10 +262,13 @@ def clear_output_status(self) -> None: raise RuntimeError('Failed to clear output status.') @o - @Action(retries=1) + @Action(retries=1, checks='driver.trigger.mode != "disabled"') def read_voltage_status(self) -> str: """Progression of the current voltage update. + This action return meaningful values if we use a triggered setting + of the output. + Returns ------- status: {'waiting', 'settled', 'changing'} @@ -267,7 +297,7 @@ def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: return float(self.root.visa_resource.query(msg)) @o - @Action() + @Action(checks='driver.trigger.mode != "disabled"') def wait_for_settling(self, stop_on_break: bool=True, break_condition_callable: @@ -276,7 +306,10 @@ def wait_for_settling(self, refresh_time: float=1) -> bool: """Wait for the output to settle. - Parameters + This action should only be used in conjunction with a triggered + setting. + + Parameters ---------- cancel_on_break : bool, optional @@ -320,12 +353,13 @@ def _limits_voltage(self): """ rng = to_float(self.voltage_range) - low = max(-rng, float(self.voltage_saturation.low)) - high = min(rng, float(self.voltage_saturation.high)) - - step = 1.2e-6 if rng == 1.2 else 1.2e-5 + low = max(-rng, + float(self.voltage_saturation.low) + if self.voltage_saturation.low_enabled else -15) + high = min(rng, float(self.voltage_saturation.high) + if self.voltage_saturation.high_enabled else 15) - return FloatLimitsValidator(low, high, step, 'V') + return FloatLimitsValidator(low, high, unit='V') @o def _header_(self): @@ -404,7 +438,7 @@ def default_set_feature(self, feat, cmd, *args, **kwargs): with o.trigger as tr: #: The BE2141 requires the triggering to be disabled before #: changing the triggering mode when the output is enabled. - tr.mode = set_feat(setter='TRIG:IN 0;TRIG:IN {}') + tr.mode = set_feat(setter='TRIG:IN 0\nTRIG:IN {}') @o def _header_(self): diff --git a/tests/drivers/itest/t_be2141.py b/tests/drivers/itest/t_be2141.py index 5827536..779a8e9 100644 --- a/tests/drivers/itest/t_be2141.py +++ b/tests/drivers/itest/t_be2141.py @@ -18,6 +18,7 @@ # Index of the slot in which the BE2101 can be found (starting from 1) MODULE_INDEX = 1 +from i3py.core.errors import I3pyFailedCall, I3pyFailedSet from i3py.drivers.itest import BN100 with BN100(VISA_RESOURCE_NAME) as rack: @@ -45,7 +46,40 @@ # Test action reading basic status print('Output status', output.read_output_status()) output.clear_output_status() - print('Voltage status', output.read_voltage_status()) print('Measured output voltage', output.measure('voltage')) # Test settings and general behavior + print('Setting ouputs') + output.voltage_saturation.low_enabled = False + output.voltage_saturation.high_enabled = False + print('Known limits', output.declared_limits) + output.trigger.mode = 'disabled' + output.voltage_range = 1.2 + output.enabled = True + output.voltage = 1.0 + try: + output.read_voltage_status() + except I3pyFailedCall: + print('Cannot read voltage status in non-triggered mode') + try: + output.wait_for_settling() + except I3pyFailedCall: + print('Cannot wait for settling in non-triggered mode') + + # XXX test the other trigger modes + output.trigger.mode = 'slope' + output.trigger.slope = 0.01 + output.voltage = 0.5 + output.trigger.fire() + output.wait_for_settling() + print(output.measure('voltage')) + + output.voltage_saturation.low_enabled = True + output.voltage_saturation.high_enabled = True + output.voltage_saturation.low = -0.5 + output.voltage_saturation.high = 0.4 + print('New voltage', output.voltage) + try: + output.voltage = -0.6 + except I3pyFailedSet: + print('New restriction on the gate voltage') From 439bbaa59c9ebe4b2b2863f20785fe9bfa1d8b17 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Thu, 10 May 2018 08:31:43 +0200 Subject: [PATCH 10/29] drivers.base: update DC sources standard --- i3py/drivers/base/dc_sources.py | 75 ++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py index 195b0af..a442409 100644 --- a/i3py/drivers/base/dc_sources.py +++ b/i3py/drivers/base/dc_sources.py @@ -21,11 +21,14 @@ class DCPowerSource(HasFeatures): """ #: Outputs of the source. By default we declare a single output on index 0. - output = channel((1,)) + output = channel((0,)) with output as o: - #: Is the output on or off. Note that this value does not + #: Is the output on or off. + #: Care should be taken that this value may not be up to date if a + #: failure occured. To know the current status of the output use + #: read_output_status, this feature only store the target setting. o.enabled = Bool(aliases={True: ['On', 'ON', 'On'], False: ['Off', 'OFF', 'off']}) @@ -66,25 +69,32 @@ class DCPowerSource(HasFeatures): def read_output_status(self) -> str: """Determine the status of the output. + The generic format of the status is status:reason, if the reason is + not known used 'unknown'. The following values correspond to usual + situations. + Returns ------- - status : {'constant-voltage', 'constant-current', - 'over-voltage', 'over-current', 'unregulated'} + status : {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-current', + 'tripped:over-voltage', + 'tripped:over-current', + 'unregulated'} The possible values for the output status are the following. - - 'constant_voltage': the target voltage was reached before the - target current and voltage_limit_behavior is 'regulate'. - - 'constant-current': the target current was reached before the - target voltage and current_limit_behavior is 'regulate'. - - 'over-voltage': the output tripped after reaching the + - 'disabled': the output is currently disabled + - 'enabled:constant-voltage': the target voltage was reached + before the target current and voltage_limit_behavior is + 'regulate'. + - 'enabled:constant-current': the target current was reached + before the target voltage and current_limit_behavior is + 'regulate'. + - 'tripped:over-voltage': the output tripped after reaching the voltage limit. - - 'over-current': the output tripped after reaching the + - 'tripped:over-current': the output tripped after reaching the current limit. - 'unregulated': The output of the instrument is not stable. - Notes - ----- - Calling this function is meaningful only if the output is enabled. - """ raise NotImplementedError() @@ -122,7 +132,6 @@ def measure(self, quantity: str, **kwargs) -> FLOAT_QUANTITY: raise NotImplementedError() -# TODO complete once we have a real use for it class DCSourceTriggerSubsystem(SubSystem): """Subsystem handing the usual triggering mechanism of DC sources. @@ -146,3 +155,39 @@ def arm(self): """ pass + + +class DCSourceProtectionSubsystem(SubSystem): + """Interface for DC source protection. + + """ + #: Is the protection enabled. + enabled = Bool(aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + #: How the output behaves when the low/limit is reached. + behavior = Str(constant('trip')) + + #: Lower limit below which the setting is not allowed to go. + low_level = Float() + + #: Higher limit above which the setting is not allowed to go. + high_level = Float() + + @Action() + def read_status(self) -> str: + """Read the current status of the protection. + + Returns + ------- + status : {'working', 'tripped'} + + """ + pass + + @Action() + def reset(self) -> None: + """Reset the protection after an issue. + + """ + pass From 6ea0d66b006e3f8484c4d2651cb6b93a018741ed Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Thu, 10 May 2018 08:32:07 +0200 Subject: [PATCH 11/29] drivers.itest: complete Bilt drivers --- i3py/drivers/itest/modules/be21xx.py | 115 ++++++++++++++++++--------- i3py/drivers/itest/racks.py | 6 +- 2 files changed, 81 insertions(+), 40 deletions(-) diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index 5f61863..6f82ad1 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -66,9 +66,10 @@ def _post_getter(feat, driver, value): customize(f, 'post_get')(_post_getter)) #: DC outputs - output = channel((1,)) + output = channel((0,)) with output as o: + o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', mapping={False: '0', True: '1'}, checks=(None, @@ -85,8 +86,6 @@ def _post_getter(feat, driver, value): discard={'features': ('voltage',), 'limits': ('voltage',)}) - o.current_limit_behavior = set_feat(getter=constant('trip')) - #: Specify stricter voltage limitations than the ones linked to the #: range. o.voltage_saturation = subsystem() @@ -146,7 +145,7 @@ def _prepare_high_answer(feat, driver, value): vs.high = Float('VOLT:SAT:POS?', 'VOLT:SAT:POS {}', unit='V', limits=(0, 12), discard={'features': ('.voltage',), - 'limits': ('.voltage',)}) + 'limits': ('.voltage',)}) @vs @customize('low', 'post_get', ('prepend',)) @@ -171,7 +170,7 @@ def _convert_max(feat, driver, value): with o.trigger as tr: #: Type of response to triggering : #: - disabled : immediate update of voltage every time the voltage - #: feature is updated. The update respect the slope. + #: feature is updated. #: - slope : update after receiving a trigger based on the slope #: value. #: - stair : update after receiving a trigger using step_amplitude @@ -230,10 +229,10 @@ def is_trigger_ready(self) -> bool: #: Status of the output. Save for the first one, they are all related #: to dire issues that lead to switching off the output. - o.OUTPUT_STATES = {0: 'constant-voltage', - 5: 'main-failure', - 6: 'system-failure', - 7: 'temperature-failure', + o.OUTPUT_STATES = {0: 'enabled:constant-voltage', + 5: 'tripped:main-failure', + 6: 'tripped:system-failure', + 7: 'tripped:temperature-failure', 8: 'unregulated'} @o @@ -242,10 +241,14 @@ def read_output_status(self) -> str: """Determine the current status of the output. """ + if not self.enabled: + return 'disabled' msg = self._header_() + 'LIM:FAIL?' answer = int(self.root.visa_resource.query(msg)) + # If a failure occured the whole card switches off. if answer != 0: - del self.enabled + for o in self.parent.output: + del self.enabled return self.OUTPUT_STATES.get(answer, f'unknown({answer})') @o @@ -285,33 +288,45 @@ def read_voltage_status(self) -> str: return 'changing' @o - @Action(unit=((None, None, None), 'V'), retries=1) - def measure(self, kind, **kwargs) -> FLOAT_QUANTITY: + @Action(unit=((None, None, None), 'V'), + values={'quantity': ('voltage',)}, retries=1) + def measure(self, quantity, **kwargs) -> FLOAT_QUANTITY: """Measure the output voltage. """ - if kind != 'voltage': - raise ValueError('') - else: - msg = self._header_() + 'MEAS:VOLT?' - return float(self.root.visa_resource.query(msg)) + msg = self._header_() + 'MEAS:VOLT?' + return float(self.root.visa_resource.query(msg)) @o - @Action(checks='driver.trigger.mode != "disabled"') + @Action(checks=('not (method == "voltage_status" and' + ' self.trigger.mode == "disabled")'), + values={'method': ('measure', 'trigger_ready', + 'voltage_status')} + ) def wait_for_settling(self, + method: str='measure', stop_on_break: bool=True, break_condition_callable: Optional[Callable[[], bool]]=None, timeout: float=15, - refresh_time: float=1) -> bool: + refresh_time: float=1, + tolerance: float=1e-5) -> bool: """Wait for the output to settle. - This action should only be used in conjunction with a triggered - setting. - Parameters ---------- - cancel_on_break : bool, optional + method : {'measure', 'trigger_ready', 'voltage_status'} + Method used to estimate that the target voltage was reached. + - 'measure': measure the output voltage and compare to target + within tolerance (see tolerance) + - 'trigger_ready': rely on the trigger ready status. + - 'voltage_status': rely on the voltage status reading, this + does work only for triggered settings. + + stop_on_break : bool, optional + Should the ramp be stopped if the break condition is met. This + is achieved through a quick measurement of the output followed + by a setting and a trigger. break_condition_callable : Callable, optional Callable indicating that we should stop waiting. @@ -323,6 +338,10 @@ def wait_for_settling(self, refresh_time : float, optional Time interval at which to check the break condition. + tolerance : float, optional + Tolerance used to determine that the target was reached when + using the measure method. + Returns ------- result : bool @@ -334,8 +353,23 @@ def stop_ramp(): self.voltage = round(self.measure('voltage'), 4) self.trigger.fire() - job = InstrJob(lambda: self.read_voltage_status() == 'settled', 1, - cancel=stop_ramp) + if method == "measure": + def has_reached_target(): + if 'tripped' in self.read_output_status(): + raise RuntimeError(f'Output {self.ch_id} tripped') + return abs(self.voltage - self.measure('voltage')) + elif method == "trigger_ready": + def has_reached_target(): + if 'tripped' in self.read_output_status(): + raise RuntimeError(f'Output {self.ch_id} tripped') + return self.trigger.is_trigger_ready() + else: + def has_reached_target(): + if 'tripped' in self.read_output_status(): + raise RuntimeError(f'Output {self.ch_id} tripped') + return self.read_voltage_status() == 'settled' + + job = InstrJob(has_reached_target, 1, cancel=stop_ramp) result = job.wait_for_completion(break_condition_callable, timeout, refresh_time) if not result and stop_on_break: @@ -372,13 +406,18 @@ class BE210x(BE21xx): """ __version__ = '0.1.0' - output = channel((1,)) + output = channel((0,)) with output as o: #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms o.voltage_filter = Str('VOLT:FIL?', 'VOLT:FIL {}', mapping={'Slow': '0', 'Fast': '1'}) + #: Is the remote sensing of the voltage enabled. + o.remote_sensing = Bool('VOLT:REM?', 'VOLT:REM {:d}', + aliases={True: ('ON', 'On', 'on'), + False: ('OFF', 'Off', 'off')}) + class BE2101(BE210x): """Driver for the Bilt BE2100 high precision dc voltage source. @@ -410,34 +449,36 @@ class BE214x(BE21xx): '/SN{serial:s} LC{_:s} VL{firmware:s}' '\\{_:d}"') - output = channel((1, 2, 3, 4)) + output = channel((0, 1, 2, 3)) with output as o: - o.OUTPUT_STATES = {0: 'normal', - 11: 'main failure', - 12: 'system failure', + o.OUTPUT_STATES = {0: 'enabled', + 11: 'tripped:main-failure', + 12: 'tripped:system-failure', 17: 'unregulated', - 18: 'over-current'} + 18: 'tripped:over-current'} + + o.current_limit_behavior = set_feat(getter=constant('trip')) def default_get_feature(self, feat, cmd, *args, **kwargs): - """Prepend module selection to command. + """Prepend output selection to command. """ - cmd = f'C{self.id};' + cmd + cmd = f'C{self.id + 1};' + cmd return self.parent.default_get_feature(feat, cmd, *args, **kwargs) def default_set_feature(self, feat, cmd, *args, **kwargs): - """Prepend module selection to command. + """Prepend output selection to command. """ - cmd = f'C{self.id};' + cmd + cmd = f'C{self.id + 1};' + cmd return self.parent.default_set_feature(feat, cmd, *args, **kwargs) o.trigger = subsystem() with o.trigger as tr: - #: The BE2141 requires the triggering to be disabled before - #: changing the triggering mode when the output is enabled. + # HINT The BE2141 requires the triggering to be disabled before + # changing the triggering mode when the output is enabled. tr.mode = set_feat(setter='TRIG:IN 0\nTRIG:IN {}') @o diff --git a/i3py/drivers/itest/racks.py b/i3py/drivers/itest/racks.py index f1265e4..63bae40 100644 --- a/i3py/drivers/itest/racks.py +++ b/i3py/drivers/itest/racks.py @@ -59,21 +59,21 @@ class BN100(BiltMainframe): class BN101(BiltMainframe): - """Driver for the BN100 Bilt rack. + """Driver for the BN101 Bilt rack. """ __version__ = '0.1.0' class BN103(BiltMainframe): - """Driver for the BN100 Bilt rack. + """Driver for the BN103 Bilt rack. """ __version__ = '0.1.0' class BN105(BiltMainframe): - """Driver for the BN100 Bilt rack. + """Driver for the BN105 Bilt rack. """ __version__ = '0.1.0' From 31e40656164ec297fb252c68717ec8bd989b9f72 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Thu, 10 May 2018 08:32:51 +0200 Subject: [PATCH 12/29] drivers.yokogawa: add GS200 and 7651 drivers --- i3py/drivers/yokogawa/gs200.py | 208 ++++++++++++++++++ i3py/drivers/yokogawa/model_7651.py | 328 ++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+) create mode 100644 i3py/drivers/yokogawa/gs200.py create mode 100644 i3py/drivers/yokogawa/model_7651.py diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py new file mode 100644 index 0000000..6bd66d1 --- /dev/null +++ b/i3py/drivers/yokogawa/gs200.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Driver for the Yokogawa GS200 DC power source. + +""" +from i3py.core import (set_feat, channel, limit, customize, + FloatLimitsValidator) +from i3py.core.actions import Action +from i3py.core.features import Str, conditional, constant +from i3py.core.unit import to_float + +from ..base.dc_sources import DCPowerSource +from ..common.ieee488 import (IEEEInternalOperations, IEEEStatusReporting, + IEEEOperationComplete, IEEEOptionsIdentification, + IEEEStoredSettings) +from ..common.scpi.error_reading import SCPIErrorReading + +VOLTAGE_RESOLUTION = {10e-3: 1e-7, + 100e-3: 1e-6, + 1.0: 1e-5, + 10: 1e-4, + 30: 1e-3} + +CURRENT_RESOLUTION = {1e-3: 1e-8, + 10e-3: 1e-7, + 100e-3: 1e-6, + 200e-3: 1e-6} + + +class GS200(DCPowerSource, IEEEInternalOperations, + IEEEStatusReporting, IEEEOperationComplete, + IEEEOptionsIdentification, IEEEStoredSettings, + SCPIErrorReading): + """Driver for the Yokogawa GS200 DC power source. + + Notes + ----- + - the measurement option is not yet supported. + - add support for programs + - add RS232 support + + XXX add motivation for use of limits (basically always enabled and behave + just like a target value) + + """ + __version__ = '0.1.0' + + PROTOCOLS = {'GPIB': [{'resource_class': 'INSTR'}], + 'USB': [{'resource_class': 'INSTR', + 'manufacturer_id': '0xB21', + 'model_code': '0x39'}], + 'TCPIP': [{'reource_class': 'INSTR'}] + } + + DEFAULTS = {'COMMON': {'read_termination': '\n', + 'write_termination': '\n'}} + + output = channel((0,)) + + with output as o: + #: Preferential working mode for the source. In voltage mode, the + #: source tries to work as a voltage source, the current settings is + #: simply used to protect the sample. In current mode it is the + #: opposite. Changing the mode cause the output to be disabled. + o.mode = Str(getter=':SOUR:FUNC?', + setter=':SOUR:FUNC {}', + mapping={'voltage': 'VOLT', 'current': 'CURR'}, + discard={'feature': ('enabled', + 'voltage', 'voltage_range', + 'current', 'current_range'), + 'limits': ('voltage', 'current')}) + + o.voltage = set_feat( + getter=conditional('":SOUR:LEV?" if driver.mode == "voltage" ' + 'else ":SOUR:PROT:VOLT?"', default=True), + setter=conditional('":SOUR:LEV {}" if self.mode == "voltage" ' + 'else ":SOUR:PROT:VOLT {}"', default=True), + limits='voltage') + + o.voltage_range = set_feat(getter=True, + setter=':SOUR:RANG {}', + checks=(None, 'driver.mode == "voltage"'), + values=(10e-3, 100e-3, 1.0, 10.0, 30.0), + discard={'features': ('ocp.enabled', + 'ocp.high_level'), + 'limits': ('voltage',)}) + + o.voltage_limit_behavior = set_feat(getter=constant("regulate")) + + o.current = set_feat(getter=True, + setter=True, + limits='current') + + o.current_range = set_feat(getter=True, + setter=':SOUR:RANG {}', + values=(1e-3, 10e-3, 100e-3, 200e-3), + discard={'limits': 'current'}) + + o.voltage_limit_behavior = set_feat(getter=constant("regulate")) + + @o + @Action() + def read_output_status(self): + """Determine the status of the output. + + Returns + ------- + status : str, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-voltage', + 'tripped:unknown', 'unregulated'} + + """ + if not self.enabled: + return 'disabled' + event = int(self.root.visa_resource.query(':STAT:EVENT?:')) + if event & 2**12: + del self.enabled + return 'tripped:unknown' + elif (event & 2**11) or (event & 2**10): + if self.mode == 'voltage': + return 'constant current' + else: + return 'constant voltage' + else: + if self.mode == 'voltage': + return 'constant voltage' + else: + return 'constant current' + + # TODO add support for options and measuring subsystem (change + # inheritance) + + # ===================================================================== + # --- Private API ----------------------------------------------------- + # ===================================================================== + + @o + @customize('current', 'get') + def _get_current(self, feat): + """Get the target/limit current. + + """ + if self.mode != 'current': + if to_float(self.voltage_range) in (10e-3, 100e-3): + return 0.2 + else: + return self.default_get_feature(':SOUR:PROT:CURR?') + return self.default_get_feature(':SOUR:LEV?') + + @o + @customize('current', 'set') + def _set_current(self, feat, value): + """Set the target/limit current. + + In voltage mode this is only possible if the range is 1V or greater + + """ + if self.mode != 'current': + if to_float(self.voltage_range) in (10e-3, 100e-3): + raise ValueError('Cannot set the current limit for ranges ' + '10mV and 100mV') + else: + return self.default_set_feature(':SOUR:PROT:CURR {}', + value) + return self.default_set_feature(':SOUR:LEV {}', value) + + @o + @limit('voltage') + def _limits_voltage(self): + """Determine the voltage limits based on the currently selected + range. + + """ + if self.mode == 'voltage': + ran = to_float(self.voltage_range) + res = VOLTAGE_RESOLUTION[ran] + if ran != 30.0: + ran *= 1.2 + else: + ran = 32.0 + return FloatLimitsValidator(-ran, ran, res, 'V') + else: + return FloatLimitsValidator(1, 30, 1, 'V') + + @o + @limit('current') + def _limits_current(self): + """Determine the current limits based on the currently selected + range. + + """ + if self.mode == 'current': + ran = to_float(self.current_range) # Casting handling Quantity + res = CURRENT_RESOLUTION[ran] + if ran != 200e-3: + ran *= 1.2 + else: + ran = 220e-3 + return FloatLimitsValidator(-ran, ran, res, 'A') + else: + return FloatLimitsValidator(1e-3, 0.2, 1e-3, 'A') diff --git a/i3py/drivers/yokogawa/model_7651.py b/i3py/drivers/yokogawa/model_7651.py new file mode 100644 index 0000000..9ad5337 --- /dev/null +++ b/i3py/drivers/yokogawa/model_7651.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Driver for the Yokogawa 7651 DC power source. + +""" +from functools import partial + +from i3py.core import (set_feat, set_action, channel, subsystem, + limit, customize, FloatLimitsValidator) +from i3py.core.features import conditional +from i3py.core.unit import to_float +from i3py.core.actions import Action, RegisterAction +from i3py.backends.visa import VisaMessageDriver +from stringparser import Parser + +from ..base.dc_sources import DCPowerSource +from ..base.identity import Identity + + +VOLTAGE_RESOLUTION = {10e-3: 1e-7, + 100e-3: 1e-6, + 1.0: 1e-5, + 10: 1e-4, + 30: 1e-3} + +CURRENT_RESOLUTION = {1e-3: 1e-8, + 10e-3: 1e-7, + 100e-3: 1e-6} + + +class Model7651(VisaMessageDriver, DCPowerSource): + """Driver for the Yokogawa 7651 DC power source. + + This driver can also be used on Yokogawa GS200 used in compatibility mode. + + XXX add motivation for use of limits + + Notes + ----- + - we should check the mode on startup + - ideally we should not keep the VisaSession opened on GPIB + - add support for programs + - add RS232 support + + """ + __version__ = '0.1.0' + + PROTOCOLS = {'GPIB': [{'resource_class': 'INSTR'}]} + + DEFAULTS = {'COMMON': {'read_termination': '\r\n'}, + 'ASRL': {'write_termination': '\r\n'}} + + def initialize(self): + """Set the data termination. + + """ + super().initialize() + self.visa_resource.write('DL0') # Choose the termination character + self.visa_resource.write('MS31') # Unmask the status byte by default. + + @RegisterAction(('Program setting', # Program is currently edited + 'Program execution', # Program under execution + 'Error', # Previous command error + 'Output unustable', + 'Output on', + 'Calibration mode', + 'IC memory card', + 'CAL switch')) + def read_status_code(self): # Should this live in a subsystem ? + """Read the status code. + + """ + return int(self.visa_resource.query('OC')) + + read_status_byte = set_action(names=('End of output change', + 'SRQ key on', + 'Syntax error', + 'Limit error', + 'Program end', + 'Error', + 'Request', + 7)) + + def is_connected(self): + """Check whether or not the connection is opened. + + """ + try: + self.visa.resource.query('OC') + except Exception: + return False + + return True + + identity = subsystem(Identity) + + with identity as i: + + i.model = set_feat(getter=True) + + i.firmware = set_feat(getter=True) + + def _get_from_os(index, self): + """Read the requested info from OS command. + + """ + visa_rsc = self.parent.visa_resource + mes = visa_rsc.query('OS') + visa_rsc.read() + visa_rsc.read() + visa_rsc.read() + visa_rsc.read() + return mes.split(',')[index] + + i._get_model = customize('model', 'get')(partial(0, i._get_from_os)) + + i._get_firmware = customize('model', 'get')(partial(1, i._get_from_os)) + + output = channel((1,)) + + with output as o: + o.function = set_feat(getter='OD', + setter='F{}E', + mapping=({'Voltage': '1', 'Current': '5'}, + {'V': 'Voltage', 'A': 'Current'}), + extract='{_}DC{}{_:+E}') + + o.enabled = set_feat(getter=True, + setter='O{}E', + mapping={True: 1, False: 0}) + + o.voltage = set_feat( + getter=True, + setter=conditional('"S{+E}E" if driver.mode == "voltage" ' + 'else "LV{}E"', default=True), + limits='voltage') + + o.voltage_range = set_feat(getter=True, + setter='R{}E', + extract='F1R{}S{_}', + mapping={10e-3: 2, 100e-3: 3, 1.0: 4, + 10.0: 5, 30.0: 6}, + discard={'features': ('current'), + 'limits': ('voltage',)}) + + o.current = set_feat(getter=True, + setter=True, + limits='current') + + o.current_range = set_feat(getter=True, + setter='R{}E', + extract='F5R{}S{_}', + mapping={1e-3: 4, 10e-3: 5, 100e-3: 6}, + discard={'limits': ('current',)}) + + @o + @Action() + def read_output_status(self): + """Determine the status of the output. + + Returns + ------- + status : unicode, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-voltage', + 'tripped:unknown', 'unregulated'} + + """ + if not self.enabled: + return 'disabled' + if 'Output on' not in self.parent.read_status_code(): + return 'tripped:unknown' + if self.parent.query('OD')[0] == 'E': + if self.mode == 'voltage': + return 'enabled:constant-current' + else: + return 'enabled:constant-voltage' + if self.mode == 'voltage': + return 'enabled:constant-voltage' + else: + return 'enabled:constant-current' + + # ===================================================================== + # --- Private API ----------------------------------------------------- + # ===================================================================== + + @o + def default_check_operation(self, feat, value, i_value, state=None): + """Check that the operation did not result in any error. + + """ + stb = self.parent.visa_resource.read_status_byte() + if stb['Syntax error']: + msg = 'Syntax error' if stb['Limit error'] else 'Overload' + return False, msg + + return True, None + + @o + @limit('voltage') + def _limits_voltage(self): + """Determine the voltage limits based on the currently selected + range. + + """ + if self.mode == 'voltage': + ran = to_float(self.voltage_range) + res = VOLTAGE_RESOLUTION[ran] + if ran != 30.0: + ran *= 1.2 + else: + ran = 32.0 + return FloatLimitsValidator(-ran, ran, res, 'V') + else: + return FloatLimitsValidator(1, 30, 1, 'V') + + @o + @limit('current') + def _limits_current(self): + """Determine the current limits based on the currently selected + range. + + """ + if self.mode == 'voltage': + ran = float(self.current_range) # Casting handling Quantity + res = CURRENT_RESOLUTION[ran] + if ran != 200e-3: + ran *= 1.2 + else: + ran = 220e-3 + return FloatLimitsValidator(-ran, ran, res, 'A') + else: + return FloatLimitsValidator(5e-3, 120e-3, 1e-3, 'A') + + @o + @customize('enabled', 'get') + def _get_enabled(self): + """Read the output current status byte and extract the output state + + """ + return 'Output' in self.parent.read_status_code() + + o._OD_PARSER = Parser('{_}DC{_}{:E+}') + + o._VOLT_LIM_PARSER = Parser('LV{}LA{_}') + + o._CURR_LIM_PARSER = Parser('LV{_}LA{}') + + @o + @customize('voltage', 'get') + def _get_voltage(self, feat): + """Get the voltage in voltage mode and return the maximum voltage + in current mode. + + """ + if self.mode != 'voltage': + return self._VOLT_LIM_PARSER(self._get_limiter_value()) + return self._OD_PARSER(self.default_get_feature('OD')) + + @o + @customize('current', 'get') + def _get_current(self, feat): + """Get the current in current mode and return the maximum current + in voltage mode. + + """ + if self.mode != 'voltage': + if to_float(self.voltage_range) in (10e-3, 100e-3): + return 0.12 + return self._CURR_LIM_PARSER(self._get_limiter_value())*1e3 + return self._OD_PARSER(self.default_get_feature('OD')) + + @o + @customize('current', 'set') + def _set_current(self, feat, value): + """Set the target/limit current. + + In voltage mode this is only possible if the range is 1V or greater + + """ + if self.mode != 'current': + if to_float(self.voltage_range) in (10e-3, 100e-3): + raise ValueError('Cannot set the current limit for ranges ' + '10mV and 100mV') + else: + return self.default_set_feature('LA{}E', value) + return self.default_set_feature('S{+E}E', value) + + @o + def _get_limiter_value(self): + """Helper function reading the limiter value. + + Used to read the voltage/current target. + + """ + visa_rsc = self.parent.visa_resource + visa_rsc.write('OS') + visa_rsc.read() # Model and software version + visa_rsc.read() # Function, range, output data + visa_rsc.read() # Program parameters + return visa_rsc.read() # Limits + + def _get_range(kind, self): + """Read the range. + + """ + visa_rsc = self.parent.visa_resource + if self.mode == kind: + visa_rsc.write('OS') + visa_rsc.read() # Model and software version + msg = visa_rsc.read() # Function, range, output data + visa_rsc.read() # Program parameters + visa_rsc.read() # Limits + return msg + else: + return 'F{}R6S1E+0'.format(1 if kind == 'voltage' else 5) + + o._get_voltage_range = customize('voltage_range', + 'get')(partial(_get_range, 'voltage')) + + o._get_current_range = customize('current_range', + 'get')(partial(_get_range, 'current')) From 3d4e830b53ce3489b13bc1e16fe4c1c3aef966d5 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Thu, 10 May 2018 08:33:32 +0200 Subject: [PATCH 13/29] drivers.keysight: add E3631A, E3633A, E3634A drivers --- i3py/drivers/keysight/E363XA.py | 546 ++++++++++++++++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 i3py/drivers/keysight/E363XA.py diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py new file mode 100644 index 0000000..96cc87f --- /dev/null +++ b/i3py/drivers/keysight/E363XA.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""Driver for the keysight E3633A and E3634A DC power source. + +""" +from i3py.core import (FloatLimitsValidator, I3pyError, channel, customize, + limit, set_feat, subsystem) +from i3py.core.actions import Action +from i3py.core.features import Alias, Bool, Feature, conditional +from i3py.core.unit import to_float, to_quantity + +from ..base.dc_sources import (DCPowerSourceWithMeasure, + DCSourceTriggerSubsystem, + DCSourceProtectionSubsystem) +from ..common.ieee488 import (IEEEInternalOperations, IEEEOperationComplete, + IEEEOptionsIdentification, IEEEPowerOn, + IEEEStatusReporting, IEEEStoredSettings, + IEEESynchronisation, IEEETrigger) +from ..common.scpi.error_reading import SCPIErrorReading +from ..common.scpi.rs232 import SCPIRS232 + + +class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, + IEEEStatusReporting, IEEEOperationComplete, + IEEEOptionsIdentification, IEEEStoredSettings, + IEEETrigger, IEEESynchronisation, IEEEPowerOn, + SCPIErrorReading, SCPIRS232): + """Driver for the Keysight E3631A DC power source. + + """ + __version__ = '0.1.0' + + PROTOCOLS = {'GPIB': [{'resource_class': 'INSTR'}], + 'ASRL': [{'resource_class': 'INSTR'}] + } + + DEFAULTS = {'COMMON': {'write_termination': '\n', + 'read_termination': '\n'}} + + output = channel((0,)) + + with output as o: + + o.enabled = set_feat(getter='OUTP?', setter='OUTP {:d}') + + o.voltage = set_feat( + getter=conditional(('"VOLT?" if driver.trigger.mode != "enabled"' + ' else "VOLT:TRIG?"'), default=True), + setter=conditional(('"VOLT {}" if driver.trigger.mode != "enabled"' + ' else "VOLT:TRIG {}"'), default=True), + limits='voltage') + + o.voltage_range = set_feat(getter='VOLT:RANGE?', + setter='VOLT:RANGE {}') + + o.current = set_feat( + getter=conditional(('"CURR?" if driver.trigger.mode != "enabled"' + ' else "CURR:TRIG?"'), default=True), + setter=conditional(('"CURR {}" if driver.trigger.mode != "enabled"' + ' else "CURR:TRIG {}"'), default=True), + limits='current') + + o.current_range = set_feat(getter='CURR:RANGE?', + setter='CURR:RANGE {}') + + @o + @Action(values={'quantity': ("voltage", "current")}) + def measure(self, quantity, **kwargs): + """Measure the output voltage/current. + + Parameters + ---------- + quantity : unicode, {'voltage', 'current'} + Quantity to measure. + + **kwargs : + This instrument recognize no optional parameters. + + Returns + ------- + value : float or pint.Quantity + Measured value. If units are supported the value is a Quantity + object. + + """ + cmd = 'MEAS:' + ('VOLT' if quantity != 'current' else 'CURR') + value = float(self.parent.visa_resource.query(cmd)) + value = to_quantity(value, 'V' if quantity != 'current' else 'A') + + return value + + @o + @Action(unit=(None, (None, 'V', 'A')), + limits={'voltage': 'voltage', 'current': 'current'}) + def apply(self, voltage, current): + """Set both the voltage and current limit. + + """ + with self.lock: + self.parent.visa_resource.write(f'APPLY {voltage}, {current}') + res, msg = self.parent.read_error() + if res != 0: + err = 'Failed to apply {}V, {}A to output {} :\n{}' + raise I3pyError(err.format(voltage, current, self.id, msg)) + + o.trigger = subsystem(DCSourceTriggerSubsystem) + + with o.trigger as t: + + # HINT this is a soft feature !!! + t.mode = set_feat(getter=True, setter=True, + values=('disabled', 'enabled')) + + t.source = set_feat('TRIG:SOUR?', 'TRIG:SOUR {}', + mapping={'immediate': 'IMM', 'bus': 'BUS'}) + + t.delay = set_feat('TRIG:DEL?', 'TRIG:DEL {}', + limits=(1, 3600, 1)) + + @o + @Action() + def arm(self): + """Prepare the channel to receive a trigger. + + If the trigger mode is immediate the update occurs as soon as + the command is processed. + + """ + with self.lock: + self.write('INIT') + res, msg = self.root.read_error() + if res: + err = 'Failed to arm the trigger for output {}:\n{}' + raise I3pyError(err.format(self.id, msg)) + + # HINT mode is a "soft" feature meaning it has no reality for the + # the instrument. As a consequence having a default value is enough + # the caching does the rest for us. + @t + @customize('mode', 'get') + def _get_mode(feat, driver): + return 'disabled' + + @t + @customize('mode', 'set') + def _set_mode(feat, driver, value): + vrsc = driver.root.visa_resource + vrsc.write(f'VOLT:TRIG {driver.parent.voltage}') + vrsc.write(f'CURR:TRIG {driver.parent.current}') + res, msg = self.root.read_error() + if res: + err = ('Failed to set the triggered values for voltage ' + 'and current {}:\n{}') + raise I3pyError(err.format(self.id, msg)) + + +VOLTAGE_RANGES = {'P6V': 6, 'P25V': 25, 'N25V': -25} + +CURRENT_RANGES = {'P6V': 5, 'P25V': 1, 'N25V': 1} + + +class KeysightE3631A(KeysightE363xA): + """Driver for the Keysight E3631A DC power source. + + """ + PROTOCOLS = {'GPIB': 'INSTR', 'ASRL': 'INSTR'} + + DEFAULTS = {'COMMON': {'write_termination': '\n', + 'read_termination': '\n'}} + + #: In this model, outputs are always enabled together. + outputs_enabled = Bool('OUTP?', 'OUTP {:d}', + aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + #: Whether to couple together teh output triggers, causing a trigger + #: received on one to update the other values. + coupled_triggers = Feature(getter=True, setter=True, + checks=(None, ('value is False or ' + 'not driver.outputs_tracking')) + ) + + #: Activate tracking between the P25V and the N25V output. In tracking + #: one have P25V.voltage = - N25V + outputs_tracking = Bool('OUTP:TRAC?', + 'OUTP:TRAC {}', + aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}, + checks=(None, + ('value is False or' + 'driver.coupled_triggers is None or ' + '1 not in driver.coupled_triggers or ' + '2 not in driver.coupled_triggers'))) + + output = channel((0, 1, 2), + aliases={'P6V': 0, 'P25V': 1, 'N25V': 2}) + + with output as o: + + o.enabled = Alias('.outputs_enabled') # should this be settable ? + + o.voltage_range = set_feat(getter=True) + + o.current_range = set_feat(getter=True) + + @o + @Action() + def measure(self, quantity, **kwargs): + """Measure the output voltage/current. + + Parameters + ---------- + quantity : unicode, {'voltage', 'current'} + Quantity to measure. + + **kwargs : + This instrument recognize no optional parameters. + + Returns + ------- + value : float or pint.Quantity + Measured value. If units are supported the value is a Quantity + object. + + """ + with self.lock: + self.parent.write('INSTR:SELECT %s' % self.id) + super().measure(quantity, **kwargs) + + @o + @Action() + def apply(self, voltage, current): + """Set both the voltage and current limit. + + """ + with self.lock: + self.parent.write('INSTR:SELECT %s' % self.id) + super().apply(voltage, current) + + @o + @Action() + def read_output_status(self): + """Read the status of the output. + + Returns + ------- + status : unicode, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-current', + 'tripped:over-voltage', + 'tripped:over-current', + 'unregulated'} + + """ + if not self.enabled: + return 'disabled' + status = int(self.parent.visa_resource( + f'STAT:QUES:INST:ISUM{self.id + 1}?')) + if status & 1: + return 'enabled:constant-voltage' + if status & 2: + return 'enabled:constant-current' + return 'unregulated' + + o.trigger = subsystem(DCSourceTriggerSubsystem) + + with o.trigger as t: + + @o + @Action() + def arm(self): + """Prepare the channel to receive a trigger. + + If the trigger mode is immediate the update occurs as soon as + the command is processed. + + """ + with self.lock: + self.root.visa_resource.write(f'INSTR:NSEL {self.id + 1}') + super().arm() + + @o + def default_get_feature(self, feat, cmd, *args, **kwargs): + """Always select the channel before getting. + + """ + cmd = f'INSTR:NSEL {self.id + 1};' + cmd + return super().default_get_feature(feat, cmd, *args, **kwargs) + + @o + def default_set_feature(self, feat, cmd, *args, **kwargs): + """Always select the channel before getting. + + """ + cmd = f'INSTR:NSEL {self.id + 1};' + cmd + return super().default_set_feature(feat, cmd, *args, **kwargs) + + @o + @customize('voltage', 'post_set', ('append',)) + def _post_setattr_voltage(self, feat, value, i_value, state=None): + """Make sure that in tracking mode the voltage cache is correct. + + """ + if self.id != 0: + del self.parent.output[1].voltage + del self.parent.output[2].voltage + + @o + @customize('voltage_range', 'get') + def _get_voltage_range(self, feat): + """Get the voltage range. + + """ + return VOLTAGE_RANGES[self.id] + + @o + @customize('current_range', 'get') + def _get_current_range(self, feat): + """Get the current range. + + """ + return CURRENT_RANGES[self.id] + + @o + @limit('voltage') + def _limits_voltage(self): + """Build the voltage limits matching the output. + + """ + if self.id == 'P6V': + return FloatLimitsValidator(0, 6.18, 1e-3, unit='V') + elif self.id == 'P25V': + return FloatLimitsValidator(0, 25.75, 1e-2, unit='V') + else: + return FloatLimitsValidator(-25.75, 0, 1e-2, unit='V') + + @o + @limit('current') + def _limits_current(self): + """Build the current limits matching the output. + + """ + if self.id == 'P6V': + return FloatLimitsValidator(0, 5.15, 1e-3, unit='A') + elif self.id == 'P25V': + return FloatLimitsValidator(0, 1.03, 1e-3, unit='A') + else: + return FloatLimitsValidator(0, 1.03, 1e-3, unit='A') + + +class KeysightE3633A(KeysightE363xA): + """Driver for the Keysight E3633A DC power source. + + """ + __version__ = '0.1.0' + + output = channel((0,)) + + with output as o: + + o.voltage_range = set_feat(values=(8, 20)) + + o.current_range = set_feat(values=(20, 10)) + + o.over_voltage_protection = subsystem(DCSourceProtectionSubsystem) + + with o.over_voltage_protection as ovp: + + ovp.enabled = set_feat(getter='VOLT:PROC:STAT?', + setter='VOLT:PROC:STAT {:d}') + + ovp.high_level = set_feat(getter='VOLT:PROT:LEV?', + setter='VOLT:PROT:LEV {}') + + ovp.low_level = set_feat(getter=True, setter=True) + + @ovp + @Action() + def read_status(self) -> str: + """Read the status of the voltage protection + + """ + return ('tripped' + if self.root.visa_resource.query('VOLT:PROT:TRIP?') + else 'working') + + @ovp + @Action() + def clear(self) -> None: + """Clear the voltage protection status. + + """ + root = self.root + root.visa_resource.write('VOLT:PROT:CLEAR') + res, msg = root.read_error() + if res: + raise I3pyError( + f'Failed to clear voltage protection: {msg}') + + @ovp + @customize('low_level', 'get') + def _get_low_level(feat, driver): + return - driver.high_level + + @ovp + @customize('low_level', 'get') + def _set_low_level(feat, driver, value): + driver.high_level = - value + + o.over_current_protection = subsystem(DCSourceProtectionSubsystem) + + with o.over_current_protection as ocp: + + ovp.enabled = set_feat(getter='CURR:PROC:STAT?', + setter='CURR:PROC:STAT {:d}') + + ovp.high_level = set_feat(getter='CURR:PROT:LEV?', + setter='CURR:PROT:LEV {}') + + ovp.low_level = set_feat(getter=True, setter=True) + + @ovp + @Action() + def read_status(self) -> str: + """Read the status of the voltage protection + + """ + return ('tripped' + if self.root.visa_resource.query('CURR:PROT:TRIP?') + else 'working') + + @ovp + @Action() + def clear(self) -> None: + """Clear the voltage protection status. + + """ + root = self.root + root.visa_resource.write('CURR:PROT:CLEAR') + res, msg = root.read_error() + if res: + raise I3pyError( + f'Failed to clear voltage protection: {msg}') + + @ovp + @customize('low_level', 'get') + def _get_low_level(feat, driver): + return - driver.high_level + + @ovp + @customize('low_level', 'get') + def _set_low_level(feat, driver, value): + driver.high_level = - value + + @o + @Action() + def read_output_status(self): + """Read the status of the output. + + Returns + ------- + status : unicode, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-current', + 'tripped:over-voltage', + 'tripped:over-current', + 'unregulated'} + + """ + status = self.parent.visa_resource.query('STAT:QUES:COND?') + if status == '0': + return 'disabled' if not self.enabled else 'unregulated' + elif status == '1': + return 'enabled:constant-voltage' + elif status == '2': + return 'enabled:constant-current' + else: + if self.over_voltage_protection.read_status() == 'tripped': + return 'tripped:over-voltage' + else: + return 'tripped:over-current' + + @o + @limit('voltage') + def _limits_voltage(self): + """Build the voltage limits. + + """ + if to_float(self.voltage_range) == 8: + return FloatLimitsValidator(0, 8.24, 1e-3, unit='V') + else: + return FloatLimitsValidator(0, 20.6, 1e-2, unit='V') + + @o + @limit('current') + def _limits_current(self): + """Build the current limits. + + """ + if to_float(self.current_range) == 20: + return FloatLimitsValidator(0, 20.60, 1e-3, unit='A') + else: + return FloatLimitsValidator(0, 10.3, 1e-3, unit='A') + + +class KeysightE3634A(KeysightE3633A): + """Driver for the Keysight E3634A DC power source. + + """ + __version__ = '0.1.0' + + output = channel((0,)) + + with output as o: + + o.voltage_range = set_feat(values=(25, 50)) + + o.current_range = set_feat(values=(7, 4)) + + @o + @limit('voltage') + def _limits_voltage(self): + """Build the voltage limits based on the range. + + """ + if to_float(self.voltage_range) == 25: + return FloatLimitsValidator(0, 25.75, 1e-3, unit='V') + else: + return FloatLimitsValidator(0, 51.5, 1e-3, unit='V') + + @o + @limit('current') + def _limits_current(self): + """Build the current limits based on the range. + + """ + if to_float(self.current_range) == 7: + return FloatLimitsValidator(0, 7.21, 1e-3, unit='A') + else: + return FloatLimitsValidator(0, 4.12, 1e-3, unit='A') From bcd2bde1012eb6df0d9c73bff37a8351c2239859 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Thu, 10 May 2018 08:38:03 +0200 Subject: [PATCH 14/29] drivers.keysight: fix MRO of E363XA --- i3py/drivers/keysight/E363XA.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py index 96cc87f..c9155b6 100644 --- a/i3py/drivers/keysight/E363XA.py +++ b/i3py/drivers/keysight/E363XA.py @@ -18,7 +18,7 @@ from ..base.dc_sources import (DCPowerSourceWithMeasure, DCSourceTriggerSubsystem, DCSourceProtectionSubsystem) -from ..common.ieee488 import (IEEEInternalOperations, IEEEOperationComplete, +from ..common.ieee488 import (IEEEInternalOperations, IEEEOptionsIdentification, IEEEPowerOn, IEEEStatusReporting, IEEEStoredSettings, IEEESynchronisation, IEEETrigger) @@ -27,10 +27,9 @@ class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, - IEEEStatusReporting, IEEEOperationComplete, - IEEEOptionsIdentification, IEEEStoredSettings, - IEEETrigger, IEEESynchronisation, IEEEPowerOn, - SCPIErrorReading, SCPIRS232): + IEEEStatusReporting, IEEEOptionsIdentification, + IEEEStoredSettings, IEEETrigger, IEEESynchronisation, + IEEEPowerOn, SCPIErrorReading, SCPIRS232): """Driver for the Keysight E3631A DC power source. """ From 57c437ddc4456f7a87d6f865045be22ad6634065 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Fri, 11 May 2018 20:57:27 +0200 Subject: [PATCH 15/29] tests: add basic tests for all DC voltage sources They would be worth to expand ! --- i3py/drivers/keysight/E363XA.py | 17 ++++++- i3py/drivers/keysight/__init__.py | 3 +- i3py/drivers/yokogawa/__init__.py | 2 +- i3py/drivers/yokogawa/gs200.py | 22 +++++++-- tests/drivers/itest/t_be2141.py | 4 +- tests/drivers/keysight/__init__.py | 0 tests/drivers/keysight/t_E3631A.py | 46 +++++++++++++++++++ tests/drivers/yokogawa/__init__.py | 0 tests/drivers/yokogawa/t_gs200.py | 65 +++++++++++++++++++++++++++ tests/drivers/yokogawa/t_model7651.py | 65 +++++++++++++++++++++++++++ 10 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 tests/drivers/keysight/__init__.py create mode 100644 tests/drivers/keysight/t_E3631A.py create mode 100644 tests/drivers/yokogawa/__init__.py create mode 100644 tests/drivers/yokogawa/t_gs200.py create mode 100644 tests/drivers/yokogawa/t_model7651.py diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py index c9155b6..f9b5cab 100644 --- a/i3py/drivers/keysight/E363XA.py +++ b/i3py/drivers/keysight/E363XA.py @@ -32,6 +32,8 @@ class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, IEEEPowerOn, SCPIErrorReading, SCPIRS232): """Driver for the Keysight E3631A DC power source. + XXX proper format for IDN + """ __version__ = '0.1.0' @@ -42,6 +44,19 @@ class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, DEFAULTS = {'COMMON': {'write_termination': '\n', 'read_termination': '\n'}} + identity = subsystem() + + with identity as i: + + #: Format string specifying the format of the IDN query answer and + #: allowing to extract the following information: + #: - manufacturer: name of the instrument manufacturer + #: - model: name of the instrument model + #: - serial: serial number of the instrument + #: - firmware: firmware revision + #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} + i.IEEE_IDN_FORMAT = '' + output = channel((0,)) with output as o: @@ -178,7 +193,7 @@ class KeysightE3631A(KeysightE363xA): aliases={True: ['On', 'ON', 'On'], False: ['Off', 'OFF', 'off']}) - #: Whether to couple together teh output triggers, causing a trigger + #: Whether to couple together the output triggers, causing a trigger #: received on one to update the other values. coupled_triggers = Feature(getter=True, setter=True, checks=(None, ('value is False or ' diff --git a/i3py/drivers/keysight/__init__.py b/i3py/drivers/keysight/__init__.py index ad1b9ec..c28f606 100644 --- a/i3py/drivers/keysight/__init__.py +++ b/i3py/drivers/keysight/__init__.py @@ -12,6 +12,7 @@ import sys from i3py.core.lazy_package import LazyPackage -DRIVERS = {} +DRIVERS = {'E3631A': 'E363XA.E3631A', 'E3633A': 'E363XA.E3633A', + 'E3634A': 'E363XA.E3634A'} sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/yokogawa/__init__.py b/i3py/drivers/yokogawa/__init__.py index 27a44e5..3703e71 100644 --- a/i3py/drivers/yokogawa/__init__.py +++ b/i3py/drivers/yokogawa/__init__.py @@ -12,6 +12,6 @@ import sys from i3py.core.lazy_package import LazyPackage -DRIVERS = {} +DRIVERS = {'GS200': 'gs200.GS200', 'Model7651': 'model_7651.Model7651'} sys.modules[__name__] = LazyPackage(DRIVERS, __name__, __doc__, locals()) diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index 6bd66d1..cb16750 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -47,6 +47,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, XXX add motivation for use of limits (basically always enabled and behave just like a target value) + XXX proper format for IDN """ __version__ = '0.1.0' @@ -61,6 +62,19 @@ class GS200(DCPowerSource, IEEEInternalOperations, DEFAULTS = {'COMMON': {'read_termination': '\n', 'write_termination': '\n'}} + identity = subsystem() + + with identity as i: + + #: Format string specifying the format of the IDN query answer and + #: allowing to extract the following information: + #: - manufacturer: name of the instrument manufacturer + #: - model: name of the instrument model + #: - serial: serial number of the instrument + #: - firmware: firmware revision + #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} + i.IEEE_IDN_FORMAT = '' + output = channel((0,)) with output as o: @@ -125,14 +139,14 @@ def read_output_status(self): return 'tripped:unknown' elif (event & 2**11) or (event & 2**10): if self.mode == 'voltage': - return 'constant current' + return 'enabled:constant-current' else: - return 'constant voltage' + return 'enabled:constant-voltage' else: if self.mode == 'voltage': - return 'constant voltage' + return 'enabled:constant-voltage' else: - return 'constant current' + return 'enabled:constant-current' # TODO add support for options and measuring subsystem (change # inheritance) diff --git a/tests/drivers/itest/t_be2141.py b/tests/drivers/itest/t_be2141.py index 779a8e9..de5fba1 100644 --- a/tests/drivers/itest/t_be2141.py +++ b/tests/drivers/itest/t_be2141.py @@ -6,7 +6,7 @@ # # The full license is in the file LICENCE, distributed with this software. # ----------------------------------------------------------------------------- -"""This file is meant to check the working of the driver for the BE2101. +"""This file is meant to check the working of the driver for the BE2141. The rack is expected to have a BE2101 in one slot, whose output can be safely switched on and off and whose output value can vary (and has a large impedance) @@ -33,7 +33,7 @@ print('Firmware', module.identity.firmware) print('Testing output') - output = module.output[1] + output = module.output[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) diff --git a/tests/drivers/keysight/__init__.py b/tests/drivers/keysight/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/drivers/keysight/t_E3631A.py b/tests/drivers/keysight/t_E3631A.py new file mode 100644 index 0000000..9975e7e --- /dev/null +++ b/tests/drivers/keysight/t_E3631A.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""This file is meant to check the working of the driver for the E3631A. + +The instrument is expected to be in a situation where output can be safely +switched on and off and whose output value can vary (and has a large impedance) + +""" +# Visa connection info +VISA_RESOURCE_NAME = 'GPIB::10::INSTR' + +from i3py.core.errors import I3pyFailedCall, I3pyFailedSet +from i3py.drivers.keysight import E3631A + +with E3631A(VISA_RESOURCE_NAME) as driver: + + # Test reading all features + print('Manufacturer', driver.identity.manufacturer) + print('Model', driver.identity.model) + print('Serial number', driver.identity.serial) + print('Firmware', driver.identity.firmware) + + print('Testing output') + for output in driver.output: + for f_name in output.__feats__: + print(' ', f_name, getattr(output, f_name)) + + for sub_name in output.__subsystems__: + print(' Testing ', sub_name) + sub = getattr(output, sub_name) + for f_name in sub.__feats__: + print(' ', f_name, getattr(sub, f_name)) + + # Test action reading basic status + print('Output status', output.read_output_status()) + output.clear_output_status() + print('Measured output voltage', output.measure('voltage')) + print('Measured output current', output.measure('current')) + + # XXX add more comprehensive tests diff --git a/tests/drivers/yokogawa/__init__.py b/tests/drivers/yokogawa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/drivers/yokogawa/t_gs200.py b/tests/drivers/yokogawa/t_gs200.py new file mode 100644 index 0000000..bb5db52 --- /dev/null +++ b/tests/drivers/yokogawa/t_gs200.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""This file is meant to check the working of the driver for the GS200. + +The instrument is expected to be in a situation where output can be safely +switched on and off and whose output value can vary (and has a large impedance) + +""" +# Visa connection info +VISA_RESOURCE_NAME = 'USB::::INSTR' + +from i3py.core.errors import I3pyFailedCall, I3pyFailedSet +from i3py.drivers.yokogawa import GS200 + +with GS200(VISA_RESOURCE_NAME) as driver: + + # Test reading all features + print('Manufacturer', driver.identity.manufacturer) + print('Model', driver.identity.model) + print('Serial number', driver.identity.serial) + print('Firmware', driver.identity.firmware) + + print('Testing output') + output = driver.output[0] + for f_name in output.__feats__: + print(' ', f_name, getattr(output, f_name)) + + for sub_name in output.__subsystems__: + print(' Testing ', sub_name) + sub = getattr(output, sub_name) + for f_name in sub.__feats__: + print(' ', f_name, getattr(sub, f_name)) + + # Test action reading basic status + print('Output status', output.read_output_status()) + output.clear_output_status() + print('Measured output voltage', output.measure('voltage')) + print('Measured output current', output.measure('current')) + + # Test voltage mode + print('Voltage mode') + output.enabled = False + output.mode = 'voltage' + print('Known limits', output.declared_limits) + output.voltage_range = 1 + output.enabled = True + output.voltage = 1.0 + output.current = 0.2 + assert output.read_output_status() == 'enabled:constant-voltage' + + # Test current mode + print('Current mode') + output.mode = 'current' + assert not output.enabled + output.enabled = True + output.current_range = 0.2 + output.voltage = 10 + output.current = 0.1 + assert output.read_output_status() == 'enabled:constant-voltage' diff --git a/tests/drivers/yokogawa/t_model7651.py b/tests/drivers/yokogawa/t_model7651.py new file mode 100644 index 0000000..61c05f8 --- /dev/null +++ b/tests/drivers/yokogawa/t_model7651.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright 2018 by I3py Authors, see AUTHORS for more details. +# +# Distributed under the terms of the BSD license. +# +# The full license is in the file LICENCE, distributed with this software. +# ----------------------------------------------------------------------------- +"""This file is meant to check the working of the driver for the 7651. + +The instrument is expected to be in a situation where output can be safely +switched on and off and whose output value can vary (and has a large impedance) + +""" +# Visa connection info +VISA_RESOURCE_NAME = 'GPIB::10::INSTR' + +from i3py.core.errors import I3pyFailedCall, I3pyFailedSet +from i3py.drivers.yokogawa import Model7651 + +with Model7651(VISA_RESOURCE_NAME) as driver: + + # Test reading all features + print('Manufacturer', driver.identity.manufacturer) + print('Model', driver.identity.model) + print('Serial number', driver.identity.serial) + print('Firmware', driver.identity.firmware) + + print('Testing output') + output = driver.output[0] + for f_name in output.__feats__: + print(' ', f_name, getattr(output, f_name)) + + for sub_name in output.__subsystems__: + print(' Testing ', sub_name) + sub = getattr(output, sub_name) + for f_name in sub.__feats__: + print(' ', f_name, getattr(sub, f_name)) + + # Test action reading basic status + print('Output status', output.read_output_status()) + output.clear_output_status() + print('Measured output voltage', output.measure('voltage')) + print('Measured output current', output.measure('current')) + + # Test voltage mode + print('Voltage mode') + output.enabled = False + output.mode = 'voltage' + print('Known limits', output.declared_limits) + output.voltage_range = 1 + output.enabled = True + output.voltage = 1.0 + output.current = 0.2 + assert output.read_output_status() == 'enabled:constant-voltage' + + # Test current mode + print('Current mode') + output.mode = 'current' + assert not output.enabled + output.enabled = True + output.current_range = 0.2 + output.voltage = 10 + output.current = 0.1 + assert output.read_output_status() == 'enabled:constant-voltage' From 34d3a9c7ebf16dedadd7ca92cad99d2573c62dc6 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Sat, 12 May 2018 10:14:46 +0200 Subject: [PATCH 16/29] drivers.keysight: E3631A use lock=True in action rather than manual locking --- i3py/drivers/keysight/E363XA.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py index f9b5cab..bc56417 100644 --- a/i3py/drivers/keysight/E363XA.py +++ b/i3py/drivers/keysight/E363XA.py @@ -183,11 +183,6 @@ class KeysightE3631A(KeysightE363xA): """Driver for the Keysight E3631A DC power source. """ - PROTOCOLS = {'GPIB': 'INSTR', 'ASRL': 'INSTR'} - - DEFAULTS = {'COMMON': {'write_termination': '\n', - 'read_termination': '\n'}} - #: In this model, outputs are always enabled together. outputs_enabled = Bool('OUTP?', 'OUTP {:d}', aliases={True: ['On', 'ON', 'On'], @@ -224,7 +219,7 @@ class KeysightE3631A(KeysightE363xA): o.current_range = set_feat(getter=True) @o - @Action() + @Action(lock=True, values={'quantity': ('voltage', 'current')}) def measure(self, quantity, **kwargs): """Measure the output voltage/current. @@ -243,19 +238,17 @@ def measure(self, quantity, **kwargs): object. """ - with self.lock: - self.parent.write('INSTR:SELECT %s' % self.id) - super().measure(quantity, **kwargs) + self.parent.write('INSTR:SELECT %s' % self.id) + super().measure(quantity, **kwargs) @o - @Action() + @Action(lock=True) def apply(self, voltage, current): """Set both the voltage and current limit. """ - with self.lock: - self.parent.write('INSTR:SELECT %s' % self.id) - super().apply(voltage, current) + self.parent.write('INSTR:SELECT %s' % self.id) + super().apply(voltage, current) @o @Action() @@ -287,7 +280,7 @@ def read_output_status(self): with o.trigger as t: @o - @Action() + @Action(lock=True) def arm(self): """Prepare the channel to receive a trigger. @@ -295,9 +288,8 @@ def arm(self): the command is processed. """ - with self.lock: - self.root.visa_resource.write(f'INSTR:NSEL {self.id + 1}') - super().arm() + self.root.visa_resource.write(f'INSTR:NSEL {self.id + 1}') + super().arm() @o def default_get_feature(self, feat, cmd, *args, **kwargs): From c3717db24f358a86cd0209b4f180363302e66ceb Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 14 May 2018 17:36:27 +0200 Subject: [PATCH 17/29] drivers.base: fix issue in SCPI error reading --- i3py/drivers/common/scpi/error_reading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3py/drivers/common/scpi/error_reading.py b/i3py/drivers/common/scpi/error_reading.py index 23a7bef..44b2151 100644 --- a/i3py/drivers/common/scpi/error_reading.py +++ b/i3py/drivers/common/scpi/error_reading.py @@ -28,7 +28,7 @@ def read_error(self) -> Tuple[int, str]: is empty. """ - code, msg = self.visa_resource.query('SYST:ERR?').split(',') + code, msg = self.visa_resource.query('SYST:ERR?').split(',', 1) return int(code), msg def default_check_operation(self, feat, value, i_value, response): From ba00e8525f7a766355c36a892ca815a82b4d8ea4 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 14 May 2018 17:36:56 +0200 Subject: [PATCH 18/29] drivers.itest: final fixes to the BE drivers --- i3py/drivers/itest/modules/be21xx.py | 31 +++++++++-------- tests/drivers/itest/t_be2101.py | 50 ++++++++++++++++++++++++++-- tests/drivers/itest/t_be2141.py | 14 ++++---- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index 6f82ad1..a83a2ab 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -71,10 +71,7 @@ def _post_getter(feat, driver, value): with output as o: o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', - mapping={False: '0', True: '1'}, - checks=(None, - 'driver.read_output_status() == ' - '"constant-voltage"') + mapping={False: '0', True: '1'} ) o.voltage = set_feat(getter='VOLT?', setter='VOLT {:E}', @@ -245,7 +242,7 @@ def read_output_status(self) -> str: return 'disabled' msg = self._header_() + 'LIM:FAIL?' answer = int(self.root.visa_resource.query(msg)) - # If a failure occured the whole card switches off. + # If a failure occurred the whole card switches off. if answer != 0: for o in self.parent.output: del self.enabled @@ -261,8 +258,12 @@ def clear_output_status(self) -> None: """ self.root.visa_resource.write(self._header_() + 'LIM:CLEAR') - if not self.read_output_status() == 'normal': - raise RuntimeError('Failed to clear output status.') + new_status = self.read_output_status() + if 'tripped' in new_status or 'unregulated' in new_status: + _, err = self.root.read_error() + raise RuntimeError('Failed to clear output status. ' + f'Current status is {new_status}, ' + f'the error message is {err}.') @o @Action(retries=1, checks='driver.trigger.mode != "disabled"') @@ -356,17 +357,17 @@ def stop_ramp(): if method == "measure": def has_reached_target(): if 'tripped' in self.read_output_status(): - raise RuntimeError(f'Output {self.ch_id} tripped') + raise RuntimeError(f'Output {self.id} tripped') return abs(self.voltage - self.measure('voltage')) elif method == "trigger_ready": def has_reached_target(): if 'tripped' in self.read_output_status(): - raise RuntimeError(f'Output {self.ch_id} tripped') + raise RuntimeError(f'Output {self.id} tripped') return self.trigger.is_trigger_ready() else: def has_reached_target(): if 'tripped' in self.read_output_status(): - raise RuntimeError(f'Output {self.ch_id} tripped') + raise RuntimeError(f'Output {self.id} tripped') return self.read_voltage_status() == 'settled' job = InstrJob(has_reached_target, 1, cancel=stop_ramp) @@ -411,10 +412,12 @@ class BE210x(BE21xx): with output as o: #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms o.voltage_filter = Str('VOLT:FIL?', 'VOLT:FIL {}', - mapping={'Slow': '0', 'Fast': '1'}) + mapping={'Slow': '0', 'Fast': '1'}, + checks=(None, 'driver.enabled == False')) #: Is the remote sensing of the voltage enabled. - o.remote_sensing = Bool('VOLT:REM?', 'VOLT:REM {:d}', + o.remote_sensing = Bool('VOLT:REM?', 'VOLT:REM {}', + mapping={True: '1', False: '0'}, aliases={True: ('ON', 'On', 'on'), False: ('OFF', 'Off', 'off')}) @@ -453,7 +456,7 @@ class BE214x(BE21xx): with output as o: - o.OUTPUT_STATES = {0: 'enabled', + o.OUTPUT_STATES = {0: 'enabled:constant-voltage', 11: 'tripped:main-failure', 12: 'tripped:system-failure', 17: 'unregulated', @@ -483,7 +486,7 @@ def default_set_feature(self, feat, cmd, *args, **kwargs): @o def _header_(self): - return f'I{self.parent.id};C{self.id};' + return f'I{self.parent.id};C{self.id + 1};' class BE2141(BE214x): diff --git a/tests/drivers/itest/t_be2101.py b/tests/drivers/itest/t_be2101.py index 90c1ce0..bb7cd95 100644 --- a/tests/drivers/itest/t_be2101.py +++ b/tests/drivers/itest/t_be2101.py @@ -18,11 +18,12 @@ # Index of the slot in which the BE2101 can be found (starting from 1) MODULE_INDEX = 1 +from i3py.core.errors import I3pyFailedCall, I3pyFailedSet from i3py.drivers.itest import BN100 with BN100(VISA_RESOURCE_NAME) as rack: - # Test reading all features + # Test reading all features print('Available modules', rack.be2101.available) module = rack.be2101[MODULE_INDEX] @@ -32,20 +33,63 @@ print('Firmware', module.identity.firmware) print('Testing output') - output = module.output[1] + output = module.output[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) + delattr(output, f_name) for sub_name in output.__subsystems__: print(' Testing ', sub_name) sub = getattr(output, sub_name) for f_name in sub.__feats__: print(' ', f_name, getattr(sub, f_name)) + delattr(sub, f_name) # Test action reading basic status print('Output status', output.read_output_status()) output.clear_output_status() - print('Voltage status', output.read_voltage_status()) print('Measured output voltage', output.measure('voltage')) # Test settings and general behavior + print('Setting outputs') + output.enabled = False + output.trigger.mode = 'disabled' + output.voltage = 0 + output.wait_for_settling() + output.voltage_saturation.low_enabled = False + output.voltage_saturation.high_enabled = False + print('Known limits', output.declared_limits) + output.voltage_range = 1.2 + output.voltage_filter = ('Slow' if output.voltage_filter == 'Fast' else + 'Fast') + output.remote_sensing = True + del output.remote_sensing + print('Remote sensing is ', output.remote_sensing) + output.remote_sensing = False + output.enabled = True + + output.voltage = 1.0 + try: + output.read_voltage_status() + except I3pyFailedCall: + print('Cannot read voltage status in non-triggered mode') + output.wait_for_settling() + + # TODO test the other trigger modes and other sync methods + output.trigger.mode = 'slope' + output.trigger.slope = 0.01 + output.voltage = 0.5 + output.trigger.fire() + output.wait_for_settling() + print(f'Measured output is {output.measure("voltage")}, ' + f'target is {output.voltage}') + + output.voltage_saturation.low_enabled = True + output.voltage_saturation.high_enabled = True + output.voltage_saturation.low = -0.5 + output.voltage_saturation.high = 0.4 + print('New voltage', output.voltage) + try: + output.voltage = -0.6 + except I3pyFailedSet: + print('New restriction on the gate voltage') diff --git a/tests/drivers/itest/t_be2141.py b/tests/drivers/itest/t_be2141.py index de5fba1..32b7c9a 100644 --- a/tests/drivers/itest/t_be2141.py +++ b/tests/drivers/itest/t_be2141.py @@ -36,12 +36,14 @@ output = module.output[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) + delattr(output, f_name) for sub_name in output.__subsystems__: print(' Testing ', sub_name) sub = getattr(output, sub_name) for f_name in sub.__feats__: print(' ', f_name, getattr(sub, f_name)) + delattr(sub, f_name) # Test action reading basic status print('Output status', output.read_output_status()) @@ -49,7 +51,7 @@ print('Measured output voltage', output.measure('voltage')) # Test settings and general behavior - print('Setting ouputs') + print('Setting outputs') output.voltage_saturation.low_enabled = False output.voltage_saturation.high_enabled = False print('Known limits', output.declared_limits) @@ -61,18 +63,16 @@ output.read_voltage_status() except I3pyFailedCall: print('Cannot read voltage status in non-triggered mode') - try: - output.wait_for_settling() - except I3pyFailedCall: - print('Cannot wait for settling in non-triggered mode') + output.wait_for_settling() - # XXX test the other trigger modes + # TODO test the other trigger modes and other sync methods output.trigger.mode = 'slope' output.trigger.slope = 0.01 output.voltage = 0.5 output.trigger.fire() output.wait_for_settling() - print(output.measure('voltage')) + print(f'Measured output is {output.measure("voltage")}, ' + f'target is {output.voltage}') output.voltage_saturation.low_enabled = True output.voltage_saturation.high_enabled = True From ee10a05c35871e2b4f94fe8a8fe70820415df7c6 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Mon, 14 May 2018 17:37:20 +0200 Subject: [PATCH 19/29] drivers.yokogawa: fix GS200 driver --- i3py/drivers/yokogawa/gs200.py | 69 ++++++++++++++++++++----------- tests/drivers/yokogawa/t_gs200.py | 11 +++-- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index cb16750..0b4f990 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -9,7 +9,7 @@ """Driver for the Yokogawa GS200 DC power source. """ -from i3py.core import (set_feat, channel, limit, customize, +from i3py.core import (set_feat, subsystem, channel, limit, customize, FloatLimitsValidator) from i3py.core.actions import Action from i3py.core.features import Str, conditional, constant @@ -47,7 +47,6 @@ class GS200(DCPowerSource, IEEEInternalOperations, XXX add motivation for use of limits (basically always enabled and behave just like a target value) - XXX proper format for IDN """ __version__ = '0.1.0' @@ -56,7 +55,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, 'USB': [{'resource_class': 'INSTR', 'manufacturer_id': '0xB21', 'model_code': '0x39'}], - 'TCPIP': [{'reource_class': 'INSTR'}] + 'TCPIP': [{'resource_class': 'INSTR'}] } DEFAULTS = {'COMMON': {'read_termination': '\n', @@ -73,7 +72,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, #: - serial: serial number of the instrument #: - firmware: firmware revision #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} - i.IEEE_IDN_FORMAT = '' + i.IEEE_IDN_FORMAT = '{manufacturer},{model},{serial},{firmware}' output = channel((0,)) @@ -85,15 +84,18 @@ class GS200(DCPowerSource, IEEEInternalOperations, o.mode = Str(getter=':SOUR:FUNC?', setter=':SOUR:FUNC {}', mapping={'voltage': 'VOLT', 'current': 'CURR'}, - discard={'feature': ('enabled', - 'voltage', 'voltage_range', - 'current', 'current_range'), + discard={'features': ('enabled', + 'voltage', 'voltage_range', + 'current', 'current_range'), 'limits': ('voltage', 'current')}) + o.enabled = set_feat(getter=':OUTP?', setter=':OUTP {}', + mapping={False: '0', True: '1'}) + o.voltage = set_feat( getter=conditional('":SOUR:LEV?" if driver.mode == "voltage" ' 'else ":SOUR:PROT:VOLT?"', default=True), - setter=conditional('":SOUR:LEV {}" if self.mode == "voltage" ' + setter=conditional('":SOUR:LEV {}" if driver.mode == "voltage" ' 'else ":SOUR:PROT:VOLT {}"', default=True), limits='voltage') @@ -101,11 +103,9 @@ class GS200(DCPowerSource, IEEEInternalOperations, setter=':SOUR:RANG {}', checks=(None, 'driver.mode == "voltage"'), values=(10e-3, 100e-3, 1.0, 10.0, 30.0), - discard={'features': ('ocp.enabled', - 'ocp.high_level'), - 'limits': ('voltage',)}) + discard={'limits': ('voltage',)}) - o.voltage_limit_behavior = set_feat(getter=constant("regulate")) + o.current_limit_behavior = set_feat(getter=constant("regulate")) o.current = set_feat(getter=True, setter=True, @@ -133,7 +133,7 @@ def read_output_status(self): """ if not self.enabled: return 'disabled' - event = int(self.root.visa_resource.query(':STAT:EVENT?:')) + event = int(self.root.visa_resource.query(':STAT:EVENT?')) if event & 2**12: del self.enabled return 'tripped:unknown' @@ -157,33 +157,54 @@ def read_output_status(self): @o @customize('current', 'get') - def _get_current(self, feat): + def _get_current(feat, driver): """Get the target/limit current. """ - if self.mode != 'current': - if to_float(self.voltage_range) in (10e-3, 100e-3): + if driver.mode != 'current': + if to_float(driver.voltage_range) in (10e-3, 100e-3): return 0.2 else: - return self.default_get_feature(':SOUR:PROT:CURR?') - return self.default_get_feature(':SOUR:LEV?') + return driver.default_get_feature(feat, ':SOUR:PROT:CURR?') + return driver.default_get_feature(feat, ':SOUR:LEV?') @o @customize('current', 'set') - def _set_current(self, feat, value): + def _set_current(feat, driver, value): """Set the target/limit current. In voltage mode this is only possible if the range is 1V or greater """ - if self.mode != 'current': - if to_float(self.voltage_range) in (10e-3, 100e-3): + if driver.mode != 'current': + if to_float(driver.voltage_range) in (10e-3, 100e-3): raise ValueError('Cannot set the current limit for ranges ' '10mV and 100mV') else: - return self.default_set_feature(':SOUR:PROT:CURR {}', - value) - return self.default_set_feature(':SOUR:LEV {}', value) + return driver.default_set_feature(feat, + ':SOUR:PROT:CURR {}', + value) + return driver.default_set_feature(feat, ':SOUR:LEV {}', value) + + @o + @customize('voltage_range', 'get') + def _get_voltage_range(feat, driver): + """Get the voltage range depending on the mode. + + """ + if driver.mode == 'voltage': + return driver.default_get_feature(feat, ':SOUR:RANG?') + return '30' + + @o + @customize('current_range', 'get') + def _get_current_range(feat, driver): + """Get the current range depending on the mode. + + """ + if driver.mode == 'current': + return driver.default_get_feature(feat, ':SOUR:RANG?') + return '0.2' @o @limit('voltage') diff --git a/tests/drivers/yokogawa/t_gs200.py b/tests/drivers/yokogawa/t_gs200.py index bb5db52..52b330b 100644 --- a/tests/drivers/yokogawa/t_gs200.py +++ b/tests/drivers/yokogawa/t_gs200.py @@ -13,7 +13,7 @@ """ # Visa connection info -VISA_RESOURCE_NAME = 'USB::::INSTR' +VISA_RESOURCE_NAME = 'USB::0x0B21::0x0039::91N326145::INSTR' from i3py.core.errors import I3pyFailedCall, I3pyFailedSet from i3py.drivers.yokogawa import GS200 @@ -39,14 +39,12 @@ # Test action reading basic status print('Output status', output.read_output_status()) - output.clear_output_status() - print('Measured output voltage', output.measure('voltage')) - print('Measured output current', output.measure('current')) # Test voltage mode print('Voltage mode') output.enabled = False output.mode = 'voltage' + print(output.check_cache()) print('Known limits', output.declared_limits) output.voltage_range = 1 output.enabled = True @@ -57,9 +55,14 @@ # Test current mode print('Current mode') output.mode = 'current' + print(output.check_cache()) + # del output.enabled assert not output.enabled output.enabled = True output.current_range = 0.2 output.voltage = 10 output.current = 0.1 assert output.read_output_status() == 'enabled:constant-voltage' + + driver.visa_resource.write('x_x') + driver.read_error() From fcfbaceb9e1c00c361c480dbb48c703c627bcf02 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Tue, 15 May 2018 18:38:43 +0200 Subject: [PATCH 20/29] drivers.ieee488: meaningful default format for idn --- i3py/drivers/common/ieee488.py | 2 +- i3py/drivers/yokogawa/gs200.py | 20 +++----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/i3py/drivers/common/ieee488.py b/i3py/drivers/common/ieee488.py index fa3fc70..9a75b21 100644 --- a/i3py/drivers/common/ieee488.py +++ b/i3py/drivers/common/ieee488.py @@ -196,7 +196,7 @@ class IEEEIdentity(VisaMessageDriver): #: - serial: serial number of the instrument #: - firmware: firmware revision #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} - i.IEEE_IDN_FORMAT = '' + i.IEEE_IDN_FORMAT = '{manufacturer},{model},{serial},{firmware}' i.manufacturer = set_feat(getter='*IDN?') i.model = set_feat(getter='*IDN?') diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index 0b4f990..b7011de 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -9,15 +9,14 @@ """Driver for the Yokogawa GS200 DC power source. """ -from i3py.core import (set_feat, subsystem, channel, limit, customize, - FloatLimitsValidator) +from i3py.core import FloatLimitsValidator, channel, customize, limit, set_feat from i3py.core.actions import Action from i3py.core.features import Str, conditional, constant from i3py.core.unit import to_float from ..base.dc_sources import DCPowerSource -from ..common.ieee488 import (IEEEInternalOperations, IEEEStatusReporting, - IEEEOperationComplete, IEEEOptionsIdentification, +from ..common.ieee488 import (IEEEInternalOperations, IEEEOperationComplete, + IEEEOptionsIdentification, IEEEStatusReporting, IEEEStoredSettings) from ..common.scpi.error_reading import SCPIErrorReading @@ -61,19 +60,6 @@ class GS200(DCPowerSource, IEEEInternalOperations, DEFAULTS = {'COMMON': {'read_termination': '\n', 'write_termination': '\n'}} - identity = subsystem() - - with identity as i: - - #: Format string specifying the format of the IDN query answer and - #: allowing to extract the following information: - #: - manufacturer: name of the instrument manufacturer - #: - model: name of the instrument model - #: - serial: serial number of the instrument - #: - firmware: firmware revision - #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} - i.IEEE_IDN_FORMAT = '{manufacturer},{model},{serial},{firmware}' - output = channel((0,)) with output as o: From 1f09a0374f18c4846535d8dbe47b545babcb6114 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Tue, 15 May 2018 18:40:51 +0200 Subject: [PATCH 21/29] drivers.yokogawa: fix 7651 driver --- i3py/drivers/yokogawa/model_7651.py | 194 +++++++++++++++----------- tests/drivers/yokogawa/t_model7651.py | 18 ++- 2 files changed, 126 insertions(+), 86 deletions(-) diff --git a/i3py/drivers/yokogawa/model_7651.py b/i3py/drivers/yokogawa/model_7651.py index 9ad5337..ef13f66 100644 --- a/i3py/drivers/yokogawa/model_7651.py +++ b/i3py/drivers/yokogawa/model_7651.py @@ -9,11 +9,9 @@ """Driver for the Yokogawa 7651 DC power source. """ -from functools import partial - from i3py.core import (set_feat, set_action, channel, subsystem, - limit, customize, FloatLimitsValidator) -from i3py.core.features import conditional + limit, customize, FloatLimitsValidator, I3pyError) +from i3py.core.features import Str, conditional, constant from i3py.core.unit import to_float from i3py.core.actions import Action, RegisterAction from i3py.backends.visa import VisaMessageDriver @@ -43,7 +41,7 @@ class Model7651(VisaMessageDriver, DCPowerSource): Notes ----- - - we should check the mode on startup + - we should check the mode on startup (is it only possible ?) - ideally we should not keep the VisaSession opened on GPIB - add support for programs - add RS232 support @@ -60,39 +58,50 @@ def initialize(self): """Set the data termination. """ - super().initialize() - self.visa_resource.write('DL0') # Choose the termination character - self.visa_resource.write('MS31') # Unmask the status byte by default. - - @RegisterAction(('Program setting', # Program is currently edited - 'Program execution', # Program under execution - 'Error', # Previous command error - 'Output unustable', - 'Output on', - 'Calibration mode', - 'IC memory card', - 'CAL switch')) + print(self.resource_name) + try: + super().initialize() + # Choose the termination character + self.visa_resource.write('DL0') + # Unmask the status byte by default. + self.visa_resource.write('MS31') + # Clear the status byte + self.read_status_byte() + except Exception as e: + raise I3pyError('Connection failed to open. One possible reason ' + 'is because the instrument is configured to write ' + 'on the memory card.') from e + + @RegisterAction(('program_setting', # Program is currently edited + 'program_execution', # Program under execution + 'error', # Previous command error + 'output_unstable', + 'output_on', + 'calibration mode', + 'ic_memory_card', + 'cal_switch')) def read_status_code(self): # Should this live in a subsystem ? """Read the status code. """ - return int(self.visa_resource.query('OC')) - - read_status_byte = set_action(names=('End of output change', - 'SRQ key on', - 'Syntax error', - 'Limit error', - 'Program end', - 'Error', - 'Request', - 7)) + # The return format is STS1={value} + return int(self.visa_resource.query('OC')[5:]) + + read_status_byte = set_action(names=('end_of_output_change', + 'srq_key_on', + 'syntax_error', + 'limit_error', + 'program_end', + 'error', + 'request', + None)) def is_connected(self): """Check whether or not the connection is opened. """ try: - self.visa.resource.query('OC') + self.visa_resource.query('OC') except Exception: return False @@ -102,51 +111,66 @@ def is_connected(self): with identity as i: + i.manufacturer = set_feat(getter=constant('Yokogawa')) + i.model = set_feat(getter=True) + i.serial = set_feat(getter=constant('xxx')) + i.firmware = set_feat(getter=True) - def _get_from_os(index, self): + @i + def _get_from_os(driver, index): """Read the requested info from OS command. """ - visa_rsc = self.parent.visa_resource + parser = Parser('MDL{}REV{}') + visa_rsc = driver.parent.visa_resource mes = visa_rsc.query('OS') visa_rsc.read() visa_rsc.read() visa_rsc.read() visa_rsc.read() - return mes.split(',')[index] + return parser(mes)[index] - i._get_model = customize('model', 'get')(partial(0, i._get_from_os)) + @i + @customize('model', 'get') + def _get_model(feat, driver): + return driver._get_from_os(0) - i._get_firmware = customize('model', 'get')(partial(1, i._get_from_os)) + @i + @customize('firmware', 'get') + def _get_firmware(feat, driver): + return driver._get_from_os(1) - output = channel((1,)) + output = channel((0,)) with output as o: - o.function = set_feat(getter='OD', - setter='F{}E', - mapping=({'Voltage': '1', 'Current': '5'}, - {'V': 'Voltage', 'A': 'Current'}), - extract='{_}DC{}{_:+E}') + o.mode = Str('OD', 'F{}E', + mapping=({'voltage': '1', 'current': '5'}, + {'V': 'voltage', 'A': 'current'}), + extract='{_}DC{}{_:+E}', + discard={'features': ('enabled', 'voltage', 'current', + 'voltage_range', 'current_range'), + 'limits': ('current', 'voltage')}) o.enabled = set_feat(getter=True, setter='O{}E', - mapping={True: 1, False: 0}) + mapping=({True: 1, False: 0}, None)) o.voltage = set_feat( getter=True, - setter=conditional('"S{+E}E" if driver.mode == "voltage" ' - 'else "LV{}E"', default=True), + setter=conditional('"S{:+E}E" if driver.mode == "voltage" ' + 'else "LV{:.0f}"', default=True), limits='voltage') o.voltage_range = set_feat(getter=True, setter='R{}E', - extract='F1R{}S{_}', + extract='F1R{:d}S{_}', + checks=(None, 'driver.mode == "voltage"'), mapping={10e-3: 2, 100e-3: 3, 1.0: 4, 10.0: 5, 30.0: 6}, - discard={'features': ('current'), + discard={'features': ('current',), 'limits': ('voltage',)}) o.current = set_feat(getter=True, @@ -155,7 +179,8 @@ def _get_from_os(index, self): o.current_range = set_feat(getter=True, setter='R{}E', - extract='F5R{}S{_}', + extract='F5R{:d}S{_}', + checks=(None, 'driver.mode == "current"'), mapping={1e-3: 4, 10e-3: 5, 100e-3: 6}, discard={'limits': ('current',)}) @@ -166,17 +191,20 @@ def read_output_status(self): Returns ------- - status : unicode, {'disabled', - 'enabled:constant-voltage', - 'enabled:constant-voltage', - 'tripped:unknown', 'unregulated'} + status : str, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-voltage', + 'tripped:unknown', 'unregulated'} """ if not self.enabled: return 'disabled' - if 'Output on' not in self.parent.read_status_code(): + sc = self.parent.read_status_code() + if sc & sc.output_unstable: + return 'unregulated' + elif not (sc & sc.output_on): return 'tripped:unknown' - if self.parent.query('OD')[0] == 'E': + if self.parent.visa_resource.query('OD')[0] == 'E': if self.mode == 'voltage': return 'enabled:constant-current' else: @@ -195,10 +223,10 @@ def default_check_operation(self, feat, value, i_value, state=None): """Check that the operation did not result in any error. """ - stb = self.parent.visa_resource.read_status_byte() - if stb['Syntax error']: - msg = 'Syntax error' if stb['Limit error'] else 'Overload' - return False, msg + stb = self.parent.read_status_byte() + if stb & stb.error: + return False, ('Syntax error' if stb & stb.syntax_error else + 'Limit error') return True, None @@ -227,7 +255,7 @@ def _limits_current(self): range. """ - if self.mode == 'voltage': + if self.mode == 'current': ran = float(self.current_range) # Casting handling Quantity res = CURRENT_RESOLUTION[ran] if ran != 200e-3: @@ -240,11 +268,12 @@ def _limits_current(self): @o @customize('enabled', 'get') - def _get_enabled(self): + def _get_enabled(feat, driver): """Read the output current status byte and extract the output state """ - return 'Output' in self.parent.read_status_code() + sc = driver.parent.read_status_code() + return bool(sc & sc.output_on) o._OD_PARSER = Parser('{_}DC{_}{:E+}') @@ -254,43 +283,45 @@ def _get_enabled(self): @o @customize('voltage', 'get') - def _get_voltage(self, feat): + def _get_voltage(feat, driver): """Get the voltage in voltage mode and return the maximum voltage in current mode. """ - if self.mode != 'voltage': - return self._VOLT_LIM_PARSER(self._get_limiter_value()) - return self._OD_PARSER(self.default_get_feature('OD')) + if driver.mode != 'voltage': + return driver._VOLT_LIM_PARSER(driver._get_limiter_value()) + return driver._OD_PARSER(driver.default_get_feature(feat, 'OD')) @o @customize('current', 'get') - def _get_current(self, feat): + def _get_current(feat, driver): """Get the current in current mode and return the maximum current in voltage mode. """ - if self.mode != 'voltage': - if to_float(self.voltage_range) in (10e-3, 100e-3): + if driver.mode != 'voltage': + if to_float(driver.voltage_range) in (10e-3, 100e-3): return 0.12 - return self._CURR_LIM_PARSER(self._get_limiter_value())*1e3 - return self._OD_PARSER(self.default_get_feature('OD')) + answer = driver._get_limiter_value() + return float(driver._CURR_LIM_PARSER(answer))*1e3 + return driver._OD_PARSER(driver.default_get_feature(feat, 'OD')) @o @customize('current', 'set') - def _set_current(self, feat, value): + def _set_current(feat, driver, value): """Set the target/limit current. In voltage mode this is only possible if the range is 1V or greater """ - if self.mode != 'current': - if to_float(self.voltage_range) in (10e-3, 100e-3): + if driver.mode != 'current': + if to_float(driver.voltage_range) in (10e-3, 100e-3): raise ValueError('Cannot set the current limit for ranges ' '10mV and 100mV') else: - return self.default_set_feature('LA{}E', value) - return self.default_set_feature('S{+E}E', value) + return driver.default_set_feature(feat, 'LA{:d}', + int(round(value*1e3))) + return driver.default_set_feature(feat, 'S{:+E}E', value) @o def _get_limiter_value(self): @@ -306,12 +337,13 @@ def _get_limiter_value(self): visa_rsc.read() # Program parameters return visa_rsc.read() # Limits - def _get_range(kind, self): + @o + def _get_range(driver, kind): """Read the range. """ - visa_rsc = self.parent.visa_resource - if self.mode == kind: + visa_rsc = driver.parent.visa_resource + if driver.mode == kind: visa_rsc.write('OS') visa_rsc.read() # Model and software version msg = visa_rsc.read() # Function, range, output data @@ -321,8 +353,12 @@ def _get_range(kind, self): else: return 'F{}R6S1E+0'.format(1 if kind == 'voltage' else 5) - o._get_voltage_range = customize('voltage_range', - 'get')(partial(_get_range, 'voltage')) + @o + @customize('voltage_range', 'get') + def _get_voltage_range(feat, driver): + return driver._get_range('voltage') - o._get_current_range = customize('current_range', - 'get')(partial(_get_range, 'current')) + @o + @customize('current_range', 'get') + def _get_current_range(feat, driver): + return driver._get_range('current') diff --git a/tests/drivers/yokogawa/t_model7651.py b/tests/drivers/yokogawa/t_model7651.py index 61c05f8..dd26b50 100644 --- a/tests/drivers/yokogawa/t_model7651.py +++ b/tests/drivers/yokogawa/t_model7651.py @@ -13,14 +13,16 @@ """ # Visa connection info -VISA_RESOURCE_NAME = 'GPIB::10::INSTR' +VISA_RESOURCE_NAME = 'GPIB0::5::INSTR' +from time import sleep from i3py.core.errors import I3pyFailedCall, I3pyFailedSet from i3py.drivers.yokogawa import Model7651 with Model7651(VISA_RESOURCE_NAME) as driver: # Test reading all features + assert driver.is_connected() print('Manufacturer', driver.identity.manufacturer) print('Model', driver.identity.model) print('Serial number', driver.identity.serial) @@ -39,27 +41,29 @@ # Test action reading basic status print('Output status', output.read_output_status()) - output.clear_output_status() - print('Measured output voltage', output.measure('voltage')) - print('Measured output current', output.measure('current')) # Test voltage mode print('Voltage mode') output.enabled = False output.mode = 'voltage' + print(output.check_cache()) print('Known limits', output.declared_limits) output.voltage_range = 1 output.enabled = True output.voltage = 1.0 - output.current = 0.2 + output.current = 0.1 + sleep(1) assert output.read_output_status() == 'enabled:constant-voltage' # Test current mode print('Current mode') output.mode = 'current' + print(output.check_cache()) assert not output.enabled + output.current = 0 output.enabled = True - output.current_range = 0.2 + output.current_range = 0.1 output.voltage = 10 - output.current = 0.1 + output.current = 0.05 + sleep(1) assert output.read_output_status() == 'enabled:constant-voltage' From 7f7bbb7e7bef3be594b5b150bc9d9dbe05d4e1cb Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 16 May 2018 18:42:00 +0200 Subject: [PATCH 22/29] drivers: output -> outputs to mark it is a channel --- i3py/drivers/base/dc_sources.py | 10 +++++----- i3py/drivers/itest/modules/be21xx.py | 12 ++++++------ i3py/drivers/yokogawa/gs200.py | 4 ++-- i3py/drivers/yokogawa/model_7651.py | 4 ++-- tests/drivers/itest/t_be2101.py | 2 +- tests/drivers/itest/t_be2141.py | 2 +- tests/drivers/yokogawa/t_gs200.py | 2 +- tests/drivers/yokogawa/t_model7651.py | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py index a442409..d5da84f 100644 --- a/i3py/drivers/base/dc_sources.py +++ b/i3py/drivers/base/dc_sources.py @@ -21,13 +21,13 @@ class DCPowerSource(HasFeatures): """ #: Outputs of the source. By default we declare a single output on index 0. - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: #: Is the output on or off. #: Care should be taken that this value may not be up to date if a - #: failure occured. To know the current status of the output use + #: failure occurred. To know the current status of the output use #: read_output_status, this feature only store the target setting. o.enabled = Bool(aliases={True: ['On', 'ON', 'On'], False: ['Off', 'OFF', 'off']}) @@ -104,9 +104,9 @@ class DCPowerSourceWithMeasure(DCPowerSource): """ #: Outputs of the source. By default we declare a single output on index 0. - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: @o @Action() diff --git a/i3py/drivers/itest/modules/be21xx.py b/i3py/drivers/itest/modules/be21xx.py index a83a2ab..cccf575 100644 --- a/i3py/drivers/itest/modules/be21xx.py +++ b/i3py/drivers/itest/modules/be21xx.py @@ -66,9 +66,9 @@ def _post_getter(feat, driver, value): customize(f, 'post_get')(_post_getter)) #: DC outputs - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', mapping={False: '0', True: '1'} @@ -407,9 +407,9 @@ class BE210x(BE21xx): """ __version__ = '0.1.0' - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms o.voltage_filter = Str('VOLT:FIL?', 'VOLT:FIL {}', mapping={'Slow': '0', 'Fast': '1'}, @@ -452,9 +452,9 @@ class BE214x(BE21xx): '/SN{serial:s} LC{_:s} VL{firmware:s}' '\\{_:d}"') - output = channel((0, 1, 2, 3)) + outputs = channel((0, 1, 2, 3)) - with output as o: + with outputs as o: o.OUTPUT_STATES = {0: 'enabled:constant-voltage', 11: 'tripped:main-failure', diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index b7011de..e507948 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -60,9 +60,9 @@ class GS200(DCPowerSource, IEEEInternalOperations, DEFAULTS = {'COMMON': {'read_termination': '\n', 'write_termination': '\n'}} - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: #: Preferential working mode for the source. In voltage mode, the #: source tries to work as a voltage source, the current settings is #: simply used to protect the sample. In current mode it is the diff --git a/i3py/drivers/yokogawa/model_7651.py b/i3py/drivers/yokogawa/model_7651.py index ef13f66..f3b5e58 100644 --- a/i3py/drivers/yokogawa/model_7651.py +++ b/i3py/drivers/yokogawa/model_7651.py @@ -143,9 +143,9 @@ def _get_model(feat, driver): def _get_firmware(feat, driver): return driver._get_from_os(1) - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: o.mode = Str('OD', 'F{}E', mapping=({'voltage': '1', 'current': '5'}, {'V': 'voltage', 'A': 'current'}), diff --git a/tests/drivers/itest/t_be2101.py b/tests/drivers/itest/t_be2101.py index bb7cd95..73c2bdf 100644 --- a/tests/drivers/itest/t_be2101.py +++ b/tests/drivers/itest/t_be2101.py @@ -33,7 +33,7 @@ print('Firmware', module.identity.firmware) print('Testing output') - output = module.output[0] + output = module.outputs[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) delattr(output, f_name) diff --git a/tests/drivers/itest/t_be2141.py b/tests/drivers/itest/t_be2141.py index 32b7c9a..6ac8f4d 100644 --- a/tests/drivers/itest/t_be2141.py +++ b/tests/drivers/itest/t_be2141.py @@ -33,7 +33,7 @@ print('Firmware', module.identity.firmware) print('Testing output') - output = module.output[0] + output = module.outputs[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) delattr(output, f_name) diff --git a/tests/drivers/yokogawa/t_gs200.py b/tests/drivers/yokogawa/t_gs200.py index 52b330b..616ba68 100644 --- a/tests/drivers/yokogawa/t_gs200.py +++ b/tests/drivers/yokogawa/t_gs200.py @@ -27,7 +27,7 @@ print('Firmware', driver.identity.firmware) print('Testing output') - output = driver.output[0] + output = driver.outputs[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) diff --git a/tests/drivers/yokogawa/t_model7651.py b/tests/drivers/yokogawa/t_model7651.py index dd26b50..03546c3 100644 --- a/tests/drivers/yokogawa/t_model7651.py +++ b/tests/drivers/yokogawa/t_model7651.py @@ -29,7 +29,7 @@ print('Firmware', driver.identity.firmware) print('Testing output') - output = driver.output[0] + output = driver.outputs[0] for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) From d2afda8b99b95b6eaed3bd72cc5be45c651fedd0 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 16 May 2018 18:42:30 +0200 Subject: [PATCH 23/29] drivers.keysight: basic debugging of the E3631A --- i3py/drivers/keysight/E363XA.py | 202 +++++++++++++++++------------ tests/drivers/keysight/t_E3631A.py | 11 +- 2 files changed, 123 insertions(+), 90 deletions(-) diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py index bc56417..f7fcc20 100644 --- a/i3py/drivers/keysight/E363XA.py +++ b/i3py/drivers/keysight/E363XA.py @@ -6,7 +6,7 @@ # # The full license is in the file LICENCE, distributed with this software. # ----------------------------------------------------------------------------- -"""Driver for the keysight E3633A and E3634A DC power source. +"""Driver for the Keysight E3631A, E3633A and E3634A DC power source. """ from i3py.core import (FloatLimitsValidator, I3pyError, channel, customize, @@ -16,24 +16,19 @@ from i3py.core.unit import to_float, to_quantity from ..base.dc_sources import (DCPowerSourceWithMeasure, - DCSourceTriggerSubsystem, - DCSourceProtectionSubsystem) -from ..common.ieee488 import (IEEEInternalOperations, - IEEEOptionsIdentification, IEEEPowerOn, + DCSourceProtectionSubsystem, + DCSourceTriggerSubsystem) +from ..common.ieee488 import (IEEEInternalOperations, IEEEPowerOn, IEEEStatusReporting, IEEEStoredSettings, IEEESynchronisation, IEEETrigger) from ..common.scpi.error_reading import SCPIErrorReading -from ..common.scpi.rs232 import SCPIRS232 -class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, - IEEEStatusReporting, IEEEOptionsIdentification, - IEEEStoredSettings, IEEETrigger, IEEESynchronisation, - IEEEPowerOn, SCPIErrorReading, SCPIRS232): +class E363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, + IEEEStatusReporting, IEEEStoredSettings, IEEETrigger, + IEEESynchronisation, IEEEPowerOn, SCPIErrorReading): """Driver for the Keysight E3631A DC power source. - XXX proper format for IDN - """ __version__ = '0.1.0' @@ -44,22 +39,9 @@ class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, DEFAULTS = {'COMMON': {'write_termination': '\n', 'read_termination': '\n'}} - identity = subsystem() - - with identity as i: - - #: Format string specifying the format of the IDN query answer and - #: allowing to extract the following information: - #: - manufacturer: name of the instrument manufacturer - #: - model: name of the instrument model - #: - serial: serial number of the instrument - #: - firmware: firmware revision - #: ex {manufacturer},<{model}>,SN{serial}, Firmware revision {firmware} - i.IEEE_IDN_FORMAT = '' + outputs = channel((0,)) - output = channel((0,)) - - with output as o: + with outputs as o: o.enabled = set_feat(getter='OUTP?', setter='OUTP {:d}') @@ -84,13 +66,14 @@ class KeysightE363xA(DCPowerSourceWithMeasure, IEEEInternalOperations, setter='CURR:RANGE {}') @o - @Action(values={'quantity': ("voltage", "current")}) + @Action(values={'quantity': ("voltage", "current")}, + lock=True) def measure(self, quantity, **kwargs): """Measure the output voltage/current. Parameters ---------- - quantity : unicode, {'voltage', 'current'} + quantity : str, {'voltage', 'current'} Quantity to measure. **kwargs : @@ -103,7 +86,7 @@ def measure(self, quantity, **kwargs): object. """ - cmd = 'MEAS:' + ('VOLT' if quantity != 'current' else 'CURR') + cmd = 'MEAS:' + ('VOLT?' if quantity != 'current' else 'CURR?') value = float(self.parent.visa_resource.query(cmd)) value = to_quantity(value, 'V' if quantity != 'current' else 'A') @@ -111,14 +94,15 @@ def measure(self, quantity, **kwargs): @o @Action(unit=(None, (None, 'V', 'A')), - limits={'voltage': 'voltage', 'current': 'current'}) + limits={'voltage': 'voltage', 'current': 'current'}, + discard=('voltage', 'current'), + lock=True) def apply(self, voltage, current): """Set both the voltage and current limit. """ - with self.lock: - self.parent.visa_resource.write(f'APPLY {voltage}, {current}') - res, msg = self.parent.read_error() + self.parent.visa_resource.write(f'APPLY {voltage}, {current}') + res, msg = self.parent.read_error() if res != 0: err = 'Failed to apply {}V, {}A to output {} :\n{}' raise I3pyError(err.format(voltage, current, self.id, msg)) @@ -131,10 +115,10 @@ def apply(self, voltage, current): t.mode = set_feat(getter=True, setter=True, values=('disabled', 'enabled')) - t.source = set_feat('TRIG:SOUR?', 'TRIG:SOUR {}', + t.source = set_feat(getter='TRIG:SOUR?', setter='TRIG:SOUR {}', mapping={'immediate': 'IMM', 'bus': 'BUS'}) - t.delay = set_feat('TRIG:DEL?', 'TRIG:DEL {}', + t.delay = set_feat(getter='TRIG:DEL?', setter='TRIG:DEL {}', limits=(1, 3600, 1)) @o @@ -167,50 +151,91 @@ def _set_mode(feat, driver, value): vrsc = driver.root.visa_resource vrsc.write(f'VOLT:TRIG {driver.parent.voltage}') vrsc.write(f'CURR:TRIG {driver.parent.current}') - res, msg = self.root.read_error() + res, msg = driver.root.read_error() if res: err = ('Failed to set the triggered values for voltage ' 'and current {}:\n{}') - raise I3pyError(err.format(self.id, msg)) + raise I3pyError(err.format(driver.id, msg)) -VOLTAGE_RANGES = {'P6V': 6, 'P25V': 25, 'N25V': -25} +VOLTAGE_RANGES = {0: 6, 1: 25, 2: -25} -CURRENT_RANGES = {'P6V': 5, 'P25V': 1, 'N25V': 1} +CURRENT_RANGES = {0: 5, 1: 1, 2: 1} -class KeysightE3631A(KeysightE363xA): +class E3631A(E363xA): """Driver for the Keysight E3631A DC power source. """ + __version__ = '0.1.0' + #: In this model, outputs are always enabled together. - outputs_enabled = Bool('OUTP?', 'OUTP {:d}', + outputs_enabled = Bool('OUTP?', 'OUTP {}', + mapping={True: '1', False: '0'}, aliases={True: ['On', 'ON', 'On'], False: ['Off', 'OFF', 'off']}) #: Whether to couple together the output triggers, causing a trigger #: received on one to update the other values. - coupled_triggers = Feature(getter=True, setter=True, + #: The value is a tuple containing the indexes of the outputs for which the + #: triggers are coupled. + coupled_triggers = Feature('INST:COUP?', 'INST:COUP {}', checks=(None, ('value is False or ' 'not driver.outputs_tracking')) ) + @customize('coupled_triggers', 'post_get', ('append',)) + def _post_get_coupled_triggers(feat, driver, value): + """Get the currently coupled triggers. + + """ + if value == 'NONE': + return () + elif value == 'ALL': + return (0, 1, 2) + else: + return tuple(i for i, id in enumerate(('P6V', 'P25V', 'N25V')) + if id in value) + + @customize('coupled_triggers', 'pre_set', ('append',)) + def _pre_set_coupled_triggers(feat, driver, value): + """Properly format the value for setting the coupled triggers. + + """ + aliases = driver.outputs.aliases + names = [] + if len(value) != len(set(value)): + raise ValueError('Impossible to couple to identical outputs ' + f'({value})') + for index in value: + if index not in aliases: + raise ValueError(f'Invalid output index: {index}') + names.append(aliases[index]) + + if not names: + return 'NONE' + elif len(names) == 3: + return 'ALL' + else: + return ','.join(names) + #: Activate tracking between the P25V and the N25V output. In tracking #: one have P25V.voltage = - N25V outputs_tracking = Bool('OUTP:TRAC?', 'OUTP:TRAC {}', + mapping={True: '1', False: '0'}, aliases={True: ['On', 'ON', 'On'], False: ['Off', 'OFF', 'off']}, checks=(None, - ('value is False or' + ('value is False or ' 'driver.coupled_triggers is None or ' '1 not in driver.coupled_triggers or ' '2 not in driver.coupled_triggers'))) - output = channel((0, 1, 2), - aliases={'P6V': 0, 'P25V': 1, 'N25V': 2}) + outputs = channel((0, 1, 2), + aliases={0: 'P6V', 1: 'P25V', 2: 'N25V'}) - with output as o: + with outputs as o: o.enabled = Alias('.outputs_enabled') # should this be settable ? @@ -225,7 +250,7 @@ def measure(self, quantity, **kwargs): Parameters ---------- - quantity : unicode, {'voltage', 'current'} + quantity : str, {'voltage', 'current'} Quantity to measure. **kwargs : @@ -238,17 +263,20 @@ def measure(self, quantity, **kwargs): object. """ - self.parent.write('INSTR:SELECT %s' % self.id) - super().measure(quantity, **kwargs) + self.parent.visa_resource.write(f'INST:NSEL {self.id + 1}') + return super(E3631A.outputs, self).measure(quantity, **kwargs) @o - @Action(lock=True) + @Action(unit=(None, (None, 'V', 'A')), + limits={'voltage': 'voltage', 'current': 'current'}, + discard=('voltage', 'current'), + lock=True) def apply(self, voltage, current): """Set both the voltage and current limit. """ - self.parent.write('INSTR:SELECT %s' % self.id) - super().apply(voltage, current) + self.parent.visa_resource.write(f'INST:NSEL {self.id + 1}') + super(E3631A.outputs, self).apply(voltage, current) @o @Action() @@ -257,12 +285,12 @@ def read_output_status(self): Returns ------- - status : unicode, {'disabled', - 'enabled:constant-voltage', - 'enabled:constant-current', - 'tripped:over-voltage', - 'tripped:over-current', - 'unregulated'} + status : str, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-current', + 'tripped:over-voltage', + 'tripped:over-current', + 'unregulated'} """ if not self.enabled: @@ -289,49 +317,51 @@ def arm(self): """ self.root.visa_resource.write(f'INSTR:NSEL {self.id + 1}') - super().arm() + super(E3631A.outputs.trigger).arm() @o def default_get_feature(self, feat, cmd, *args, **kwargs): """Always select the channel before getting. """ - cmd = f'INSTR:NSEL {self.id + 1};' + cmd - return super().default_get_feature(feat, cmd, *args, **kwargs) + self.root.visa_resource.write(f'INST:NSEL {self.id + 1}') + return super(E3631A.outputs, + self).default_get_feature(feat, cmd, *args, **kwargs) @o def default_set_feature(self, feat, cmd, *args, **kwargs): """Always select the channel before getting. """ - cmd = f'INSTR:NSEL {self.id + 1};' + cmd - return super().default_set_feature(feat, cmd, *args, **kwargs) + self.root.visa_resource.write(f'INST:NSEL {self.id + 1}') + return super(E3631A.outputs, + self).default_set_feature(feat, cmd, *args, **kwargs) @o @customize('voltage', 'post_set', ('append',)) - def _post_setattr_voltage(self, feat, value, i_value, state=None): + def _post_setattr_voltage(feat, driver, value, i_value, response): """Make sure that in tracking mode the voltage cache is correct. """ - if self.id != 0: - del self.parent.output[1].voltage - del self.parent.output[2].voltage + if driver.id != 0: + del driver.parent.outputs[1].voltage + del driver.parent.outputs[2].voltage @o @customize('voltage_range', 'get') - def _get_voltage_range(self, feat): + def _get_voltage_range(feat, driver): """Get the voltage range. """ - return VOLTAGE_RANGES[self.id] + return VOLTAGE_RANGES[driver.id] @o @customize('current_range', 'get') - def _get_current_range(self, feat): + def _get_current_range(feat, driver): """Get the current range. """ - return CURRENT_RANGES[self.id] + return CURRENT_RANGES[driver.id] @o @limit('voltage') @@ -360,15 +390,15 @@ def _limits_current(self): return FloatLimitsValidator(0, 1.03, 1e-3, unit='A') -class KeysightE3633A(KeysightE363xA): +class E3633A(E363xA): """Driver for the Keysight E3633A DC power source. """ __version__ = '0.1.0' - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: o.voltage_range = set_feat(values=(8, 20)) @@ -415,7 +445,7 @@ def _get_low_level(feat, driver): return - driver.high_level @ovp - @customize('low_level', 'get') + @customize('low_level', 'set') def _set_low_level(feat, driver, value): driver.high_level = - value @@ -460,7 +490,7 @@ def _get_low_level(feat, driver): return - driver.high_level @ovp - @customize('low_level', 'get') + @customize('low_level', 'set') def _set_low_level(feat, driver, value): driver.high_level = - value @@ -471,12 +501,12 @@ def read_output_status(self): Returns ------- - status : unicode, {'disabled', - 'enabled:constant-voltage', - 'enabled:constant-current', - 'tripped:over-voltage', - 'tripped:over-current', - 'unregulated'} + status : str, {'disabled', + 'enabled:constant-voltage', + 'enabled:constant-current', + 'tripped:over-voltage', + 'tripped:over-current', + 'unregulated'} """ status = self.parent.visa_resource.query('STAT:QUES:COND?') @@ -515,15 +545,15 @@ def _limits_current(self): return FloatLimitsValidator(0, 10.3, 1e-3, unit='A') -class KeysightE3634A(KeysightE3633A): +class E3634A(E3633A): """Driver for the Keysight E3634A DC power source. """ __version__ = '0.1.0' - output = channel((0,)) + outputs = channel((0,)) - with output as o: + with outputs as o: o.voltage_range = set_feat(values=(25, 50)) diff --git a/tests/drivers/keysight/t_E3631A.py b/tests/drivers/keysight/t_E3631A.py index 9975e7e..641b0fc 100644 --- a/tests/drivers/keysight/t_E3631A.py +++ b/tests/drivers/keysight/t_E3631A.py @@ -13,7 +13,7 @@ """ # Visa connection info -VISA_RESOURCE_NAME = 'GPIB::10::INSTR' +VISA_RESOURCE_NAME = 'GPIB::2::INSTR' from i3py.core.errors import I3pyFailedCall, I3pyFailedSet from i3py.drivers.keysight import E3631A @@ -26,8 +26,12 @@ print('Serial number', driver.identity.serial) print('Firmware', driver.identity.firmware) + print('Outputs enabled', driver.outputs_enabled) + print('Coupled triggers', driver.coupled_triggers) + print('Outputs tracking', driver.outputs_tracking) + print('Testing output') - for output in driver.output: + for output in driver.outputs: for f_name in output.__feats__: print(' ', f_name, getattr(output, f_name)) @@ -39,8 +43,7 @@ # Test action reading basic status print('Output status', output.read_output_status()) - output.clear_output_status() print('Measured output voltage', output.measure('voltage')) print('Measured output current', output.measure('current')) - # XXX add more comprehensive tests + # TODO add more comprehensive tests From cc8790ca21251f37e45c2b9968f5b93cfd7c5c1b Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 16 May 2018 18:49:51 +0200 Subject: [PATCH 24/29] drivers: add some additional comments --- i3py/drivers/keysight/E363XA.py | 6 ++++++ i3py/drivers/yokogawa/gs200.py | 8 +++++--- i3py/drivers/yokogawa/model_7651.py | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/i3py/drivers/keysight/E363XA.py b/i3py/drivers/keysight/E363XA.py index f7fcc20..83f26b0 100644 --- a/i3py/drivers/keysight/E363XA.py +++ b/i3py/drivers/keysight/E363XA.py @@ -8,6 +8,12 @@ # ----------------------------------------------------------------------------- """Driver for the Keysight E3631A, E3633A and E3634A DC power source. +Notes +----- + +WARNING : The drivers implemented in this module have been only very lightly +tested. + """ from i3py.core import (FloatLimitsValidator, I3pyError, channel, customize, limit, set_feat, subsystem) diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index e507948..5152ea4 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -38,15 +38,17 @@ class GS200(DCPowerSource, IEEEInternalOperations, SCPIErrorReading): """Driver for the Yokogawa GS200 DC power source. + Because the over-voltage (current) protection is always enabled in + current (voltage) mode, they basically act as limits and fully the same + role as target voltage (current) for a power source lacking mode selection. + As a consequence they are implemented in the same way. + Notes ----- - the measurement option is not yet supported. - add support for programs - add RS232 support - XXX add motivation for use of limits (basically always enabled and behave - just like a target value) - """ __version__ = '0.1.0' diff --git a/i3py/drivers/yokogawa/model_7651.py b/i3py/drivers/yokogawa/model_7651.py index f3b5e58..9982ded 100644 --- a/i3py/drivers/yokogawa/model_7651.py +++ b/i3py/drivers/yokogawa/model_7651.py @@ -37,7 +37,10 @@ class Model7651(VisaMessageDriver, DCPowerSource): This driver can also be used on Yokogawa GS200 used in compatibility mode. - XXX add motivation for use of limits + Because the over-voltage (current) protection is always enabled in + current (voltage) mode, they basically act as limits and fully the same + role as target voltage (current) for a power source lacking mode selection. + As a consequence they are implemented in the same way. Notes ----- From 363916dea407b3cd7a68f558b2b1cc3b89fe5eb8 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 23 May 2018 14:45:01 +0200 Subject: [PATCH 25/29] drivers: address @lcontami review comments --- i3py/drivers/base/dc_sources.py | 6 +++--- i3py/drivers/yokogawa/gs200.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py index d5da84f..bce96a8 100644 --- a/i3py/drivers/base/dc_sources.py +++ b/i3py/drivers/base/dc_sources.py @@ -29,7 +29,7 @@ class DCPowerSource(HasFeatures): #: Care should be taken that this value may not be up to date if a #: failure occurred. To know the current status of the output use #: read_output_status, this feature only store the target setting. - o.enabled = Bool(aliases={True: ['On', 'ON', 'On'], + o.enabled = Bool(aliases={True: ['On', 'ON', 'on'], False: ['Off', 'OFF', 'off']}) #: Target voltage for the output. If the source is a "current" source @@ -40,7 +40,7 @@ class DCPowerSource(HasFeatures): o.voltage_range = Float(unit='V') #: How does the source behave if it cannot reach the target voltage - #: because its reached the target current first. + #: because it reached the target current first. #: - regulate: we stop at the reached voltage when the target current #: is reached. #: - trip: the output is disabled if the current reaches or gets @@ -56,7 +56,7 @@ class DCPowerSource(HasFeatures): o.current_range = Float(unit='A') #: How does the source behave if it cannot reach the target voltage - #: because its reached the target current first. + #: because it reached the target current first. #: - regulate: we stop at the reached voltage when the target current #: is reached. #: - trip: the output is disabled if the voltage reaches or gets diff --git a/i3py/drivers/yokogawa/gs200.py b/i3py/drivers/yokogawa/gs200.py index 5152ea4..2600075 100644 --- a/i3py/drivers/yokogawa/gs200.py +++ b/i3py/drivers/yokogawa/gs200.py @@ -39,7 +39,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, """Driver for the Yokogawa GS200 DC power source. Because the over-voltage (current) protection is always enabled in - current (voltage) mode, they basically act as limits and fully the same + current (voltage) mode, they basically act as limits and fullfill the same role as target voltage (current) for a power source lacking mode selection. As a consequence they are implemented in the same way. @@ -90,7 +90,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, o.voltage_range = set_feat(getter=True, setter=':SOUR:RANG {}', checks=(None, 'driver.mode == "voltage"'), - values=(10e-3, 100e-3, 1.0, 10.0, 30.0), + values=tuple(VOLTAGE_RESOLUTION.keys()), discard={'limits': ('voltage',)}) o.current_limit_behavior = set_feat(getter=constant("regulate")) @@ -101,7 +101,7 @@ class GS200(DCPowerSource, IEEEInternalOperations, o.current_range = set_feat(getter=True, setter=':SOUR:RANG {}', - values=(1e-3, 10e-3, 100e-3, 200e-3), + values=tuple(CURRENT_RESOLUTION.keys()), discard={'limits': 'current'}) o.voltage_limit_behavior = set_feat(getter=constant("regulate")) From 7752a6467db5dc0db4db895604153860e5755b2b Mon Sep 17 00:00:00 2001 From: galactikvoyager Date: Wed, 13 Jun 2018 15:00:07 +0200 Subject: [PATCH 26/29] Core: * typos in docs. * typos in class name InstrumentSingleton in base_driver.py --- i3py/core/base_driver.py | 6 +++--- i3py/core/base_subsystem.py | 2 +- i3py/drivers/base/dc_sources.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i3py/core/base_driver.py b/i3py/core/base_driver.py index 0e0e1d2..119777f 100644 --- a/i3py/core/base_driver.py +++ b/i3py/core/base_driver.py @@ -19,7 +19,7 @@ from .has_features import HasFeatures -class InstrumentSigleton(type): +class InstrumentSingleton(type): """Metaclass ensuring that a single driver is created per instrument. """ @@ -54,7 +54,7 @@ def __call__(cls, *args, **kwargs) -> 'BaseDriver': cache = cls._instances_cache[cls] driver_id = cls.compute_id(args, kwargs) # type: ignore if driver_id not in cache: - dr = super(InstrumentSigleton, cls).__call__(*args, **kwargs) + dr = super(InstrumentSingleton, cls).__call__(*args, **kwargs) cache[driver_id] = dr else: @@ -64,7 +64,7 @@ def __call__(cls, *args, **kwargs) -> 'BaseDriver': return dr -class BaseDriver(HasFeatures, metaclass=InstrumentSigleton): +class BaseDriver(HasFeatures, metaclass=InstrumentSingleton): """ Base class of all instrument drivers in I3py. This class defines the common interface drivers are expected to implement diff --git a/i3py/core/base_subsystem.py b/i3py/core/base_subsystem.py index 07389c5..0e5c756 100644 --- a/i3py/core/base_subsystem.py +++ b/i3py/core/base_subsystem.py @@ -19,7 +19,7 @@ class SubSystem(HasFeatures): - """SubSystem allow to split the implementation of a driver into multiple + """SubSystem allows one to split the implementation of a driver into multiple parts. This mechanism allow to avoid crowding the instrument namespace with very diff --git a/i3py/drivers/base/dc_sources.py b/i3py/drivers/base/dc_sources.py index bce96a8..7670cc9 100644 --- a/i3py/drivers/base/dc_sources.py +++ b/i3py/drivers/base/dc_sources.py @@ -55,8 +55,8 @@ class DCPowerSource(HasFeatures): #: Range in which the current can be set. o.current_range = Float(unit='A') - #: How does the source behave if it cannot reach the target voltage - #: because it reached the target current first. + #: How does the source behave if it cannot reach the target current + #: because it reached the target voltage first. #: - regulate: we stop at the reached voltage when the target current #: is reached. #: - trip: the output is disabled if the voltage reaches or gets From 3204aaec9973da34e3d5521f47cae74b658d14c0 Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 4 Jul 2018 18:14:37 +0200 Subject: [PATCH 27/29] drivers.itest: let the superclass clean the error queue --- i3py/drivers/itest/racks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/i3py/drivers/itest/racks.py b/i3py/drivers/itest/racks.py index 63bae40..9ddac30 100644 --- a/i3py/drivers/itest/racks.py +++ b/i3py/drivers/itest/racks.py @@ -44,8 +44,6 @@ def initialize(self): """ super().initialize() self.visa_resource.write('SYST:VERB 0') - while self.read_error()[0]: - pass _list_be2101 = make_card_detector(['BE2101']) _list_be2141 = make_card_detector(['BE2141']) From d3c468b604a9f5ff7e4238efac1b524aca784d8c Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 4 Jul 2018 18:38:38 +0200 Subject: [PATCH 28/29] coverage: omit coverage in drivers --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index fea3cf0..ba51f82 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,6 +2,7 @@ omit = *i3py/version.py */__init__.py + *i3py/drivers/ [report] # Regexes for lines to exclude from consideration From b730745c1575de1e1f28c18bc13df01b5014346d Mon Sep 17 00:00:00 2001 From: Matthieu Dartiailh Date: Wed, 4 Jul 2018 18:46:20 +0200 Subject: [PATCH 29/29] coverage: omit coverage in drivers --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index ba51f82..ffb4b96 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,7 +2,7 @@ omit = *i3py/version.py */__init__.py - *i3py/drivers/ + *i3py/drivers/* [report] # Regexes for lines to exclude from consideration