diff --git a/lantz_drivers/base/__init__.py b/lantz_drivers/base/__init__.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/lantz_drivers/base/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/lantz_drivers/base/dc_sources.py b/lantz_drivers/base/dc_sources.py new file mode 100644 index 0000000..016ef5c --- /dev/null +++ b/lantz_drivers/base/dc_sources.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.base.dc_sources + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Definition of the standard expected from DC sources. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) +from lantz_core.has_features import (HasFeatures, channel, Subsystem, Action) +from lantz_core.features import Bool, Float, Unicode, constant + + +class DCPowerSource(HasFeatures): + """Standard interface expected from all DC Power sources. + + """ + + #: + output = channel((0,)) + + with output as o: + + #: + o.enabled = Bool(aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + #: + o.voltage = Float(unit='V') + + #: + o.voltage_range = Float(unit='V') + + #: + o.voltage_limit_behavior = Unicode(constant('regulate'), + values=('irrelevant', 'trip', + 'regulate')) + + #: + o.current = Float(unit='A') + + #: + o.current_range = Float(unit='A') + + #: + o.current_limit_behavior = Unicode(constant('regulate'), + values=('irrelevant', 'trip', + 'regulate')) + + @o + @Action() + def read_output_status(self): + """ + """ + pass + + +class DCPowerSourceWithMeasure(Subsystem): + """ + """ + #: + output = channel((0,)) + + with output as o: + + @o + @Action() + def measure(self, quantity, **kwargs): + """Measure the output voltage/current. + + Parameters + ---------- + quantity : unicode, {'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. + + """ + pass + + +class DCSourceTriggerSubsystem(Subsystem): + """ + """ + #: + mode = Unicode(values=('disabled', 'enabled')) + + #: + source = Unicode(values=('immediate', 'bus')) # Will extend later + + #: + delay = Float(unit='s') + + @Action() + def arm(self): + """ + """ + pass + + +class DCSourceProtectionSubsystem(Subsystem): + """ + """ + #: + enabled = Bool(aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + #: + behavior = Unicode(constant('trip')) + + #: + low_level = Float() + + #: + high_level = Float() + + @Action() + def read_status(self): + """ + """ + pass + + @Action() + def reset(self): + """ + """ + pass diff --git a/lantz_drivers/base/identity.py b/lantz_drivers/base/identity.py new file mode 100644 index 0000000..27da583 --- /dev/null +++ b/lantz_drivers/base/identity.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.base.identity + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Definition of the standard identity subsystem. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) +from lantz_core.has_features import Subsystem +from lantz_core.features import Unicode + + +class Identity(Subsystem): + """Standard subsystem defining the expected identity infos. + + This should be used as a base class for the identity subsystem of + instruments providing identity informations. + + Notes + ----- + Somes of those infos might not be available for a given instrument. In such + a case the Feature should return ''. + + """ + #: Manufacturer as returned by the instrument. + manufacturer = Unicode() + + #: Model name as returned by the instrument. + model = Unicode() + + #: Instrument serial number. + serial = Unicode() + + #: Version of the installed firmware. + firmware = Unicode() diff --git a/lantz_drivers/bilt/__init__.py b/lantz_drivers/bilt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lantz_drivers/bilt/bn100.py b/lantz_drivers/bilt/bn100.py new file mode 100644 index 0000000..be8547d --- /dev/null +++ b/lantz_drivers/bilt/bn100.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.bilt.bn100 + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the Bilt BN100 chassis. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. + +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from lantz_core.has_features import channel + +from ..common.scpi.error_reading import SCPIErrorReading +from ..common.ieee488 import IEEEReset +from .cards.be2100 import BE2100, detect_be2100 + + +class BN100(IEEEReset, SCPIErrorReading): + """Driver for the Bilt BN100 chassis. + + """ + + PROTOCOLS = {'TCPIP': '5025::SOCKET'} + + DEFAULTS = {'COMMON': {'read_termination': '\n', + 'write_termination': '\n'} + } + + be2100 = channel('_list_be2100', BE2100) + + IEEE_RESET_WAIT = 4 + + def initialize(self): + """Make sure the communication parameters are correctly sets. + + """ + super(BN100, self).initialize() + self.write('SYST:VERB 0') + + _list_be2100 = detect_be2100 diff --git a/lantz_drivers/bilt/cards/__init__.py b/lantz_drivers/bilt/cards/__init__.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/lantz_drivers/bilt/cards/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/lantz_drivers/bilt/cards/be2100.py b/lantz_drivers/bilt/cards/be2100.py new file mode 100644 index 0000000..e78076b --- /dev/null +++ b/lantz_drivers/bilt/cards/be2100.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.bilt.cards.be2100 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the Bilt BE2100 card : high stability DC voltage source. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. + +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from lantz_core import set_feat, subsystem, channel, Action +from lantz_core.features import Float, Unicode, constant +from lantz_core.limits import FloatLimitsValidator +from lantz_core.unit import to_float + +from .common import BN100Card, make_card_detector +from ...base.dc_sources import (DCPowerSourceWithMeasure, + DCSourceTriggerSubsystem) + + +detect_be2100 = make_card_detector(['BE2101', 'BE2102', 'BE2103']) + + +# Add identity parsing +class BE2100(BN100Card, DCPowerSourceWithMeasure): + """Driver for the Bilt BE2100 high precision dc voltage source. + + """ + output = channel() + + 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',)}) + + o.voltage_limit_behavior = set_feat(getter=constant('irrelevant')) + + #: Set the voltage settling filter. Slow 100 ms, Fast 10 ms + o.voltage_filter = Unicode('VOLT:FILT?', 'VOLT:FILT {}', + mapping={'Slow': 0, 'Fast': 1}) + + #: 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=(-12, 0), discard={'limits': ('voltage',)}) + + o.current = set_feat(getter=constant(0.2)) + + o.current_range = set_feat(getter=constant(0.2)) + + o.current_limit_behavior = set_feat(getter=constant('regulate')) + + #: 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 everytime 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 = Unicode('TRIG:IN?', 'TRIG:IN {}', + mapping={'disabled': '0', 'slope': '1', + 'stair': '2', 'step': '4', 'auto': '5'}) + + #: 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') + +# XXXX + @o + @Action() + def read_output_status(self): + """ + """ + pass + + @o + @Action() + def read_voltage_status(self): + """Progression of the current voltage update. + + Returns + ------- + progression : int + Progression of the voltage update. The value is between 0 + and 1. + + """ + return int(self.query('I {};VOLT:STAT?'.format(self.ch_id))) + + # XXXX + @o + @Action(unit=()) + def measure(self, kind, **kwargs): + """ + """ + if kind != 'voltage': + raise ValueError('') + + # ===================================================================== + # --- 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') + + @Action + def fire_trigger(self): + """Send a software trigger. + + """ + self.write('I {};TRIG:IN:INIT'.format(self.ch_id)) diff --git a/lantz_drivers/bilt/cards/common.py b/lantz_drivers/bilt/cards/common.py new file mode 100644 index 0000000..8b59ffb --- /dev/null +++ b/lantz_drivers/bilt/cards/common.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.bilt.common + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Common driver for Bilt cards. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. + +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from stringparser import Parser +from lantz_core import Channel, subsystem +from ...base.identity import Identity + + +def make_card_detector(model_id): + """Create a function listing the available card of a given model. + + Parameters + ---------- + model_id : unicode or list of unicode + Id or ids of the model. ex BE2100 + + """ + if isinstance(model_id, list): + model_id = [m[2:] for m in model_id] + else: + model_id = [model_id[2:]] + + def list_channel(driver): + """Query all the cards fitted on the rack and filter based on the model + + """ + cards = {id: int(i) + for card in driver.query('I:L?').split(';') + for i, id, _ in card.split(',')} + + return [cards[id] for id in cards if id in model_id] + + +class BN100Card(Channel): + """Base driver for cards used with the Bilt BN100 chassis. + + """ + identity = subsystem(Identity) + + with identity as i: + + i.idn_format = '' + + @i + def _getter(self, feat): + """Get the identity infos from the *IDN?. + + """ + idn = self.query('*IDN?') + infos = Parser(self.idn_format)(idn) + self._cache.update(infos) + return infos.get(feat.name, '') + + i._get_manufacturer = _getter + i._get_model = _getter + i._get_serial = _getter + i._get_firmware = _getter + + def default_get_feature(self, feat, cmd, *args, **kwargs): + """Prepend module selection to command. + + """ + cmd = 'I{ch_id};'+cmd + super(BN100Card, self).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(BN100Card, self).default_set_feature(feat, cmd, *args, **kwargs) diff --git a/lantz_drivers/common/__init__.py b/lantz_drivers/common/__init__.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/lantz_drivers/common/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/lantz_drivers/common/ieee488.py b/lantz_drivers/common/ieee488.py new file mode 100644 index 0000000..59edc1a --- /dev/null +++ b/lantz_drivers/common/ieee488.py @@ -0,0 +1,481 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.common.ieee488 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + This module implements base classes for instruments supporting standards + command such *IDN?. + + A lot of those class are heavily insopired from the Slave package. + + The standards specifies that if one command of a group is implmented 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 + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from time import sleep + +from strinparser import Parser +from lantz_core import Action, subsystem +from lantz_core.features import Bool, Register, Unicode +from lantz_core.utils import byte_to_dict +from lantz_core.backends.visa import VisaMessageDriver + +from ..base 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 + + """ + #: Meaning of the event register. + EVENT_STATUS_REGISTER = ( + 'operation complete', + 'request control', + 'query error', + 'device dependent error', + 'execution error', + 'command error', + 'user request', + 'power on', + ) + + #: 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 {}', ) + + @Action() + def read_event_status_register(self): + """Read and clear the event register. + + """ + return byte_to_dict(int(self.query('*ESR?')), + self.EVENT_STATUS_REGISTER) + + +# ============================================================================= +# --- Internal operations ----------------------------------------------------- +# ============================================================================= + +class IEEEIdentify(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: + + i.IEEE_IDN_FORMAT = '' + + @i + def _getter(self, feat): + """Get the identity infos from the *IDN?. + + """ + idn = self.query('*IDN?') + infos = Parser(self.idn_format)(idn) + self._cache.update(infos) + return infos.get(feat.name, '') + + i._get_manufacturer = _getter + i._get_model = _getter + i._get_serial = _getter + i._get_firmware = _getter + + @property + def connected(self): + try: + self.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 = {0: 'Normal completion'} + + @Action() + def perform_self_test(self): + """Run the self test routine. + + """ + return self.IEEE_SELF_TEST.get(int(self.query('*TST?')), + 'Unknown error') + + +class IEEEReset(VisaMessageDriver): + """Class implemnting the reset command. + + """ + IEEE_RESET_WAIT = 1 + + @Action() + def reset(self): + """Initialize the instrument settings. + + After running this you might need to wait a bit before sending new + commands to the instrument. + + """ + self.write('*RST') + self.clear_cache() + sleep(self.IEEE_RESET_WAITE_RESET_WAIT) + + +class IEEEInternalOperations(IEEEReset, IEEESelfTest, IEEEIdentify): + """Class implementing all the internal operations. + + """ + pass + + +# ============================================================================= +# --- Synchronisation --------------------------------------------------------- +# ============================================================================= + +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): + """Sets the operation complete bit high of the event status byte. + + """ + self.write('*OPC') + + @Action() + def is_operation_completed(self): + """Check whether or not the instrument has completed all pending + operations. + + """ + return bool(int(self.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): + """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.write('*WAI') + + +class IEEESynchronisation(IEEEWaitToContinue, IEEEOperationComplete): + """A mixin class implementing all synchronisation 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 + + """ + #: Descrption of the resource. The formatting is not checked. + resource_description = Unicode('*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 validaty of the passed string is not checked. + protected_user_data = Unicode('*PUD?', '*PUD {}') + + +# ============================================================================= +# --- Calibration ------------------------------------------------------------- +# ============================================================================= + +class IEEECalibration(object): + """A class implementing the optional calibration command. + + * `*CAL?` - See IEC 60488-2:2004(E) section 10.2 + + """ + CALIBRATION = {0: 'Calibration completed'} + + def calibrate(self): + """Performs a internal self-calibration. + + """ + return self.CALIBRATION.get(int(self.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. + + """ + def trigger(self): + """Creates a trigger event. + + """ + self.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 = Unicode('*DDT?', 'DDT {}') + + +# ============================================================================= +# --- Option identification --------------------------------------------------- +# ============================================================================= + +class IEEEOptionsIdentification(VisaMessageDriver): + """A class implementing the option identification command. + + * `*OPT?` - See IEC 60488-2:2004(E) section 10.20 + + """ + instr_options = Unicode('*OPT?') + + +# ============================================================================= +# --- 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): + """Restores the current settings from a copy stored in local memory. + + Paremters + --------- + idx : int + Specifies the memory slot. + + """ + self.write('*RCL {}'.format(idx)) + self.clear_cache() + + @Action() + def save(self, idx): + """Stores the current settings of a device in local memory. + + Parameters + ---------- + idx : int + Specifies the memory slot. + + """ + self.write('*SAV {}'.format(idx)) diff --git a/lantz_drivers/common/rs232.py b/lantz_drivers/common/rs232.py new file mode 100644 index 0000000..f179389 --- /dev/null +++ b/lantz_drivers/common/rs232.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.common.rs232 + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 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. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from types import MethodType + +from lantz_core.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. + + """ + + 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 = self._resource.write.__func__ + + def new_write(self, message, termination=None, encoding=None): + return write(self, self.RS232_HEADER+message, termination, + encoding) + self._resource.write = MethodType(new_write, self._resource) + + # XXX should do the same for write_ascii_values ? + # XXX should do the same for write_binary_values ? diff --git a/lantz_drivers/common/scpi/__init__.py b/lantz_drivers/common/scpi/__init__.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/lantz_drivers/common/scpi/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/lantz_drivers/common/scpi/error_reading.py b/lantz_drivers/common/scpi/error_reading.py new file mode 100644 index 0000000..b4b7c39 --- /dev/null +++ b/lantz_drivers/common/scpi/error_reading.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.common.scpi.error_reading + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Base driver for instrument implementing SCPI error reporting commands. + + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from lantz_core import Action +from lantz_core.backends.visa import VisaMessageDriver + + +class SCPIErrorReading(VisaMessageDriver): + """Base class for all instruments implementing 'SYST:ERR?'. + + """ + + @Action() + def read_error(self): + """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(',') + 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 bool(code), msg diff --git a/lantz_drivers/common/scpi/rs232.py b/lantz_drivers/common/scpi/rs232.py new file mode 100644 index 0000000..253a069 --- /dev/null +++ b/lantz_drivers/common/scpi/rs232.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.common.scpi.rs232 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the keysight E3631A DC power source. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from ..rs232 import VisaRS232 + + +class SCPIRS232(VisaRS232): + """Base class for SCPI compliant instruments supporting the RS232 protocol. + + """ + + RS232_HEADER = 'SYST:REM;:' diff --git a/lantz_drivers/keysight/__init__.py b/lantz_drivers/keysight/__init__.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/lantz_drivers/keysight/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/lantz_drivers/keysight/model_E363XA.py b/lantz_drivers/keysight/model_E363XA.py new file mode 100644 index 0000000..a9c5939 --- /dev/null +++ b/lantz_drivers/keysight/model_E363XA.py @@ -0,0 +1,407 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.keysight.model_E363XA + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the keysight E3633A and E3634A DC power source. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from lantz_core import set_feat, channel, subsystem, Action +from lantz_core.errors import LantzError +from lantz_core.limits import FloatLimitsValidator +from lantz_core.unit import to_float, to_quantity +from lantz_core.features import Feature, Bool, Alias, conditional +from lantz_core.features.util import append + +from ..base.dc_sources import (DCPowerSourceWithMeasure, + DCSourceTriggerSubsystem) +from ..common.ieee488 import (IEEEInternalOperations, IEEEStatusReporting, + IEEEOperationComplete, IEEEOptionsIdentification, + IEEEStoredSettings, IEEETrigger, + IEEESynchronisation, IEEEPowerOn) +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. + + """ + PROTOCOLS = {'GPIB': 'INSTR', 'ASRL': 'INSTR'} + + DEFAULTS = {'COMMON': {'write_termination': '\n', + 'read_termination': '\n'}} + + output = channel() + + with output as o: + + o.enabled = set_feat(getter='OUTP?', setter='OUTP {}', + aliases={True: ['On', 'ON', 'On'], + False: ['Off', 'OFF', 'off']}) + + 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(checks={'quantity': 'value in ("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:' + cmd += 'VOLT' if quantity != 'current' else 'CURR' + value = float(self.parent.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.write('APPLY {}, {}'.format(self.id, voltage, + current)) + res, msg = self.parent.read_error() + if res: + err = 'Failed to apply {}V, {}A to output {} :\n{}' + raise LantzError(err.format(voltage, current, self.id, msg)) +# XXXX + @o + @Action() + def read_output_status(self): + """ + """ + pass + + o.trigger = subsystem(DCSourceTriggerSubsystem) + + with o.trigger as t: + + #: + t.mode = set_feat(getter=True, setter=True) + + 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.parent.parent.read_error() + if res: + err = 'Failed to arm the trigger for output {}:\n{}' + raise LantzError(err.format(self.id, msg)) + + # Actually the caching do the rest for us. + @t + def _get_mode(self, feat): + return 'disabled' + + @t + def _set_mode(self, feat, value): + pass + + +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 {}', + 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(('P6V', 'P25V', 'N25V'), + aliases={0: 'P6V', 1: 'P25V', 2: 'N25V'}) + + 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(KeysightE3631A.output, self).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(KeysightE3631A.output, self).apply(voltage, current) +# XXXX + @o + @Action() + def read_output_status(self): + """Read the status of the output. + + Returns + ------- + status : unicode, {'disabled', + 'constant voltage', 'constant voltage', + 'tripped, voltage', 'tripped, current', + 'unregulated'} + + """ + pass + + 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.parent.parent.write('INSTR:SELECT %s' % self.id) + super(KeysightE3631A.output.trigger, self).arm() + + @o + def default_get_feature(self, feat, cmd, *args, **kwargs): + """Always select the channel before getting. + + """ + cmd = 'INSTR:SELECT {ch_id};' + cmd + kwargs['ch_id'] = self.id + return super(type(self), 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 = 'INSTR:SELECT {ch_id};' + cmd + kwargs['ch_id'] = self.id + return super(type(self), self).default_set_feature(feat, cmd, + *args, **kwargs) + + @o + @append + def _post_setattr_voltage(self, feat, value, i_value, state=None): + """Make sure that in tracking mode teh voltage cache is correct. + + """ + if self.id != 'P6V': + self.parent.output['P25V'].clear_cache(features=('voltage',)) + self.parent.output['N25V'].clear_cache(features=('voltage',)) + + @o + def _get_voltage_range(self, feat): + """Get the voltage range. + + """ + return VOLTAGE_RANGES[self.id] + + @o + def _get_current_range(self, feat): + """Get the current range. + + """ + return CURRENT_RANGES[self.id] + + @o + 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 + 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. + + """ + output = channel() + + with output as o: + + o.voltage_range = set_feat(values=(8, 20)) + + o.current_range = set_feat(values=(20, 10)) + + @o + 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 + 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(_KeysightE363XA): + """Driver for the Keysight E3634A DC power source. + + """ + output = channel() + + with output as o: + + o.voltage_range = set_feat(values=(25, 50)) + + o.current_range = set_feat(values=(7, 4)) + + @o + 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 + 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') diff --git a/lantz_drivers/yokogawa/__init__.py b/lantz_drivers/yokogawa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lantz_drivers/yokogawa/model_7651.py b/lantz_drivers/yokogawa/model_7651.py new file mode 100644 index 0000000..e84499c --- /dev/null +++ b/lantz_drivers/yokogawa/model_7651.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.yokogawa.model_7651 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the Yokogawa 7651 DC power source. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. + +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from functools import partial + +from lantz_core import (set_feat, channel, conditional, Action, subsystem) +from lantz_core.limits import FloatLimitsValidator +from lantz_core.utils import byte_to_dict +from lantz_core.unit import to_float +from lantz_core.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 Yokogawa7651(VisaMessageDriver, DCPowerSource): + """Driver for the Yokogawa 7651 DC power source. + + This driver can also be used on Yokogawa GS200 used in compatibility mode. + + """ + PROTOCOLS = {'GPIB': 'INSTR'} + + DEFAULTS = {'COMMON': {'read_termination': '\r\n'}, + 'ASRL': {'write_termination': '\r\n'}} + + STATUS_BYTE = ('End of output change', + 'SRQ key on', + 'Syntax error', + 'Limit error', + 'Program end', + 'Error', + 'Request', + 7) + + def initialize(self): + """Set the data termination. + + """ + super(Yokogawa7651, self).initialize() + self.write('DL0') # Choose the termination character + self.write('MS31') # Unmask the status byte by default. + + @Action() + def read_status_code(self): # Should this live in a subsystem ? + """Read the status code. + + """ + return byte_to_dict(self.query('OC'), + ('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')) + + @property + def connected(self): + """Check whether or not the connection is opened. + + """ + try: + self.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) + + @i + def _get_from_os(index, self): + """Read the requested info from OS command. + + """ + mes = self.parent.query('OS') + self.parent.read() + self.parent.read() + self.parent.read() + self.parent.read() + return mes.split(',')[index] + + i._get_model = partial(0, i._get_from_os) + + i._get_firmware = partial(1, i._get_from_os) + + output = channel() + + 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', + 'constant voltage', 'constant voltage', + 'tripped', 'unregulated'} + + """ + if not self.enabled: + return 'disabled' + if 'Output on' not in self.read_status_code(): + return 'tripped' + if self.parent.query('OD')[0] == 'E': + if self.mode == 'voltage': + return 'constant current' + else: + return 'constant voltage' + if self.mode == 'voltage': + return 'constant voltage' + else: + return '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.read_status_byte() + if stb['Syntax error']: + msg = 'Syntax error' if stb['Limit error'] else 'Overload' + return False, msg + + return True, None + + @o + 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 + 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 + 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 + 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 + 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 + 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): + """Read the limiter value. + + """ + self.write('OS') + self.read() # Model and software version + self.read() # Function, range, output data + self.read() # Program parameters + return self.read() # Limits + + @o + def _get_range(kind, self): + """Read the range. + + """ + if self.mode == kind: + self.write('OS') + self.read() # Model and software version + msg = self.read() # Function, range, output data + self.read() # Program parameters + self.read() # Limits + return msg + else: + return 'F{}R6S1E+0'.format(1 if kind == 'voltage' else 5) + + o._get_voltage_range = partial(o._get_range, 'voltage') + + o._get_current_range = partial(o._get_range, 'current') diff --git a/lantz_drivers/yokogawa/model_7651.pyc b/lantz_drivers/yokogawa/model_7651.pyc new file mode 100644 index 0000000..b8f9980 Binary files /dev/null and b/lantz_drivers/yokogawa/model_7651.pyc differ diff --git a/lantz_drivers/yokogawa/model_gs200.py b/lantz_drivers/yokogawa/model_gs200.py new file mode 100644 index 0000000..263b4c7 --- /dev/null +++ b/lantz_drivers/yokogawa/model_gs200.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +""" + lantz_drivers.yokogawa.model_gs200 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Driver for the Yokogawa GS200 DC power source. + + :copyright: 2015 by The Lantz Authors + :license: BSD, see LICENSE for more details. +""" +from __future__ import (division, unicode_literals, print_function, + absolute_import) + +from lantz_core import (set_feat, channel, Action) +from lantz_core.limits import FloatLimitsValidator +from lantz_core.features import Unicode, conditional +from lantz_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 YokogawaGS200(DCPowerSource, IEEEInternalOperations, + IEEEStatusReporting, IEEEOperationComplete, + IEEEOptionsIdentification, IEEEStoredSettings, + SCPIErrorReading): + """Driver for the Yokogawa GS200 DC power source. + + """ + PROTOCOLS = {'GPIB': 'INSTR', 'USB': 'INSTR', 'TCPIP': 'INSTR'} + + DEFAULTS = {'COMMON': {'read_termination': '\n', + 'write_termination': '\n'}} + + MANUFACTURER_ID = 0xB21 + + MODEL_CODE = 0x39 + + output = channel() + + 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 = Unicode(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=conditional( + '"regulate" if self.mode=="current" else "irrelevant"')) + + 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=conditional( + '"irrelevant" if self.mode=="current" else "regulate"')) + + @o + @Action() + def read_output_status(self): + """Determine the status of the output. + + Returns + ------- + status : unicode, {'disabled', + 'constant voltage', 'constant voltage', + 'tripped', 'unregulated'} + + """ + if not self.enabled: + return 'disabled' + event = int(self.query(':STAT:EVENT?:')) + if event & 2**12: + self.clear_cache(features=('enabled')) + return 'tripped' + 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' + + # ===================================================================== + # --- Private API ----------------------------------------------------- + # ===================================================================== + + @o + 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 + 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 + 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 + 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')