Example #1
0
        def __init__(self, parent_instrument, command_prefix="POWer"):
            super().__init__(parent_instrument)
            self.cmd_prefix = command_prefix

            # setting properties requiring methods of class
            # properties need to be set as attributes of class not of instance
            setattr(self.__class__, 'auto_range', Instrument.control(
                self._cmd("RANGe:Auto?"), self._cmd("RANGe:Auto %d"),
                "Auto Range Setting",
                set_process=lambda v: bool(v), get_process=lambda v: bool(v)
                ))
            setattr(self.__class__, 'range_min', Instrument.measurement(
                self._cmd("RANGe? MINimum"), "Minimum settable range"))
            setattr(self.__class__, 'range_max', Instrument.measurement(
                self._cmd("RANGe? MAXimum"), "Maximum settable range"))
            setattr(self.__class__, 'reference_min', Instrument.measurement(
                self._cmd("REFerence? MINimum"),
                "Minimum settable reference value"))
            setattr(self.__class__, 'reference_state', Instrument.control(
                self._cmd("REFerence:STATe?"), self._cmd("REFerence:STATe %d"),
                "Switch to delta mode",
                set_process=lambda v: bool(v)))
            setattr(self.__class__, 'reference_max', Instrument.measurement(
                self._cmd("REFerence? MAXimum"),
                "Maximum settable reference value"))
            setattr(self.__class__, 'reference_default',
                    Instrument.measurement(self._cmd("REFerence? DEFault"),
                                           "Default reference value"))
Example #2
0
class Agilent33521A(Agilent33500):
    """Represents the Agilent 33521A Function/Arbitrary Waveform Generator.
    This documentation page shows only methods different from the parent class :doc:`Agilent33500 <agilent33500>`.

    """
    def __init__(self, adapter, **kwargs):
        super(Agilent33521A, self).__init__(adapter, **kwargs)

    frequency = Instrument.control(
        "FREQ?",
        "FREQ %f",
        """ A floating point property that controls the frequency of the output
        waveform in Hz, from 1 uHz to 30 MHz, depending on the specified function.
        Can be set. """,
        validator=strict_range,
        values=[1e-6, 30e+6],
    )

    arb_srate = Instrument.control(
        "FUNC:ARB:SRAT?",
        "FUNC:ARB:SRAT %f",
        """ An floating point property that sets the sample rate of the currently selected 
        arbitrary signal. Valid values are 1 µSa/s to 250 MSa/s. This can be set. """,
        validator=strict_range,
        values=[1e-6, 250e6],
    )
Example #3
0
class SR510(Instrument):
    TIME_CONSTANTS = {1e-3: 1, 3e-3: 2, 10e-3: 3, 30e-3: 4, 100e-3: 5,
                      300e-3: 6, 1: 7, 3: 8, 10: 9, 30: 10, 100: 11, }

    SENSITIVITIES = {10e-9: 1, 20e-9: 2, 50e-9: 3, 100e-9: 4, 200e-9: 5, 500e-9: 6,
                     1e-6: 7, 2e-6: 8, 5e-6: 9, 10e-6: 10, 20e-6: 11, 50e-6: 12, 100e-6: 13,
                     200e-6: 14, 500e-6: 15, 1e-3: 16, 2e-3: 17, 5e-3: 18, 10e-3: 19, 20e-3: 20,
                     50e-3: 21, 100e-3: 22, 200e-3: 23, 500e-3: 24, }

    phase = Instrument.control("P", "P %g",
                               """A float property that represents the SR510 reference to input
                               phase offset in degrees. Queries return values between -180 and
                               180 degrees. This property can be set with a range of values
                               between -999 to 999 degrees. Set values are mapped internal in the
                               lockin to -180 and 180 degrees.""",
                               validator=truncated_range,
                               values=[-999, 999],
                               )

    time_constant = Instrument.control("T1", "T1,%d",
                                       """A float property that represents the SR510 PRE filter time constant.
                                          This property can be set.""",
                                       validator=truncated_discrete_set,
                                       values=TIME_CONSTANTS,
                                       map_values=True,
                                       )

    sensitivity = Instrument.control("G", "G%d",
                                     """A float property that represents the SR510 sensitivity value.
                                        This property can be set.""",
                                     validator=truncated_discrete_set,
                                     values=SENSITIVITIES,
                                     map_values=True,
                                     )

    frequency = Instrument.measurement("F",
                                       """A float property representing the SR510 input reference
                                       frequency""",
                                       )

    status = Instrument.measurement("Y",
                                    """A string property representing the bits set within the SR510
                                    status byte""",
                                    get_process=lambda s: bin(int(s))[2:],
                                    )

    output = Instrument.measurement("Q",
                                    """A float property that represents the SR510 output voltage in
                                    Volts.""",
                                    )

    def __init__(self, resourceName, **kwargs):
        kwargs.setdefault('write_termination', '\r')
        super(SR510, self).__init__(
            resourceName,
            "Stanford Research Systems SR510 Lock-in amplifier",
            includeSCPI=False,
            **kwargs,
        )
Example #4
0
class AnritsuMG3692C(Instrument):
    """ Represents the Anritsu MG3692C Signal Generator
    """
    power = Instrument.control(
        ":POWER?;", ":POWER %g dBm;",
        """ A floating point property that represents the output power
        in dBm. This property can be set. """
    )
    frequency = Instrument.control(
        ":FREQUENCY?;", ":FREQUENCY %e Hz;",
        """ A floating point property that represents the output frequency
        in Hz. This property can be set. """
    )

    def __init__(self, resourceName, **kwargs):
        super().__init__(
            resourceName,
            "Anritsu MG3692C Signal Generator",
            **kwargs
        )

    @property
    def output(self):
        """ A boolean property that represents the signal output state.
        This property can be set to control the output.
        """
        return int(self.ask(":OUTPUT?")) == 1

    @output.setter
    def output(self, value):
        if value:
            self.write(":OUTPUT ON;")
        else:
            self.write(":OUTPUT OFF;")

    def enable(self):
        """ Enables the signal output.
        """
        self.output = True

    def disable(self):
        """ Disables the signal output.
        """
        self.output = False

    def shutdown(self):
        """ Shuts down the instrument, putting it in a safe state.
        """
        # TODO: Implement modulation
        self.modulation = False
        self.disable()
Example #5
0
class APSIN12G(Instrument):
    """ Represents the Anapico APSIN12G Signal Generator with option 9K,
    HP and GPIB. """
    FREQ_LIMIT = [9e3, 12e9]
    POW_LIMIT = [-30, 27]

    power = Instrument.control(
        "SOUR:POW:LEV:IMM:AMPL?;", "SOUR:POW:LEV:IMM:AMPL %gdBm;",
        """ A floating point property that represents the output power
        in dBm. This property can be set. """,
        validator=strict_range,
        values=POW_LIMIT
    )
    frequency = Instrument.control(
        "SOUR:FREQ:CW?;", "SOUR:FREQ:CW %eHz;",
        """ A floating point property that represents the output frequency
        in Hz. This property can be set. """,
        validator=strict_range,
        values=FREQ_LIMIT
    )
    blanking = Instrument.control(
        ":OUTP:BLAN:STAT?", ":OUTP:BLAN:STAT %s",
        """ A string property that represents the blanking of output power
        when frequency is changed. ON makes the output to be blanked (off) while
        changing frequency. This property can be set. """,
        validator=strict_discrete_set,
        values=['ON', 'OFF']
    )
    reference_output = Instrument.control(
        "SOUR:ROSC:OUTP:STAT?", "SOUR:ROSC:OUTP:STAT %s",
        """ A string property that represents the 10MHz reference output from
        the synth. This property can be set. """,
        validator=strict_discrete_set,
        values=['ON', 'OFF']
    )

    def __init__(self, resourceName, **kwargs):
        super(APSIN12G, self).__init__(
            resourceName,
            "Anapico APSIN12G Signal Generator",
            **kwargs
        )

    def enable_rf(self):
        """ Enables the RF output. """
        self.write("OUTP:STAT 1")

    def disable_rf(self):
        """ Disables the RF output. """
        self.write("OUTP:STAT 0")
Example #6
0
class Fluke7341(Instrument):
    """ Represents the compact constant temperature bath from Fluke
    """

    set_point = Instrument.control(
        "s",
        "s=%g",
        """ A `float` property to set the bath temperature set-point.
                                   Valid values are  in the range -40 to 150 °C.
                                   The unit is as defined in property :attr:`~.unit`. This property
                                   can be read
                                   """,
        validator=strict_range,
        values=(-40, 150),
    )

    unit = Instrument.control(
        "u",
        "u=%s",
        """ A string property that controls the temperature
                              unit. Possible values are `c` for Celsius and `f` for Fahrenheit`.""",
        validator=strict_discrete_set,
        values=('c', 'f'),
    )

    temperature = Instrument.measurement(
        "t",
        """ Read the current bath temperature.
                                         The unit is as defined in property :attr:`unit`.""",
    )

    id = Instrument.measurement(
        "*ver",
        """ Read the instrument model """,
        preprocess_reply=lambda x: x,
        get_process=lambda x: "Fluke,{},NA,{}".format(x[0][4:], x[1]))

    def __init__(self, resource_name, **kwargs):
        super().__init__(resource_name,
                         "Fluke 7341",
                         timeout=2000,
                         write_termination='\r\n',
                         preprocess_reply=lambda x: x.split()[1],
                         includeSCPI=False,
                         **kwargs)

        if isinstance(self.adapter, VISAAdapter):
            self.adapter.connection.baud_rate = 2400
Example #7
0
class GenericInstrument(FakeInstrument):
    #  Use truncated_range as this easily lets us test for the range boundaries
    fake_ctrl = Instrument.control(
        "",
        "%d",
        "docs",
        validator=truncated_range,
        values=(1, 10),
        dynamic=True,
    )
    fake_setting = Instrument.setting(
        "%d",
        "docs",
        validator=truncated_range,
        values=(1, 10),
        dynamic=True,
    )
    fake_measurement = Instrument.measurement(
        "",
        "docs",
        values={
            'X': 1,
            'Y': 2,
            'Z': 3
        },
        map_values=True,
        dynamic=True,
    )
Example #8
0
 class Fake(FakeInstrument):
     x = Instrument.control(
         "",
         "%d,%d",
         "",
         dynamic=dynamic,
     )
Example #9
0
 def __init__(self, instrument):
     super().__init__(instrument, command_prefix="POWer")
     self.unit = Instrument.control(
         self._cmd("UNIT?"), self._cmd("UNIT %s"),
         "Power Unit setting, W or dBm",
         validator=lambda v: strict_discrete_set(v, ("W", "dBm", "dbm"))
         )
Example #10
0
class VAR1(VARX):
    """ Class to handle all the specific definitions needed for VAR1.
    Most common methods are inherited from base class.
    """

    def __init__(self, resourceName, **kwargs):
        super().__init__(
            resourceName,
            "VAR1",
            **kwargs
        )

    spacing = Instrument.control(
        ":PAGE:MEAS:VAR1:SPAC?",
        ":PAGE:MEAS:VAR1:SPAC %s",
        """
        Selects the sweep type of VAR1.

        - Values: :code:`LINEAR`, :code:`LOG10`, :code:`LOG25`, :code:`LOG50`.
        """,
        validator=strict_discrete_set,
        values={'LINEAR': 'LIN', 'LOG10': 'L10',
                'LOG25': 'L25', 'LOG50': 'L50'},
        map_values=True,
        check_set_errors=True,
        check_get_errors=True
    )
Example #11
0
class VAR2(VARX):
    """ Class to handle all the specific definitions needed for VAR2.
    Common methods are imported from base class.
    """

    def __init__(self, resourceName, **kwargs):
        super().__init__(
            resourceName,
            "VAR2",
            **kwargs
        )

    points = Instrument.control(
        ":PAGE:MEAS:VAR2:POINTS?",
        ":PAGE:MEAS:VAR2:POINTS %g",
        """
        Sets the number of sweep steps of VAR2.
        You use this command only if there is an SMU or VSU
        whose function (FCTN) is VAR2.

        .. code-block:: python

            instr.var2.points = 10
        """,
        validator=strict_discrete_set,
        values=range(1, 128),
        check_set_errors=True,
        check_get_errors=True
    )
Example #12
0
 class Fake(FakeInstrument):
     x = Instrument.control("",
                            "%d",
                            "",
                            validator=strict_discrete_set,
                            values=range(10),
                            dynamic=dynamic)
Example #13
0
class BKPrecision9130B(Instrument):
    """ Represents the BK Precision 9130B DC Power Supply interface for interacting with
    the instrument. """

    current = Instrument.control(
        'MEASure:SCALar:CURRent:DC?',
        'SOURce:CURRent:LEVel:IMMediate:AMPLitude %g',
        """Floating point property used to control current of the selected channel.""",
        validator=truncated_range,
        values=[0, 3]
    )

    source_enabled = Instrument.control(
        'SOURce:CHANnel:OUTPut:STATe?',
        'SOURce:CHANnel:OUTPut:STATe %d',
        """A boolean property that controls whether the source is enabled, takes values
        True or False. """,
        validator=strict_discrete_set,
        values={True: 1, False: 0},
        map_values=True
    )

    channel = Instrument.control(
        'INSTrument:SELect?',
        'INSTrument:SELect CH%d',
        f"""An integer property used to control which channel is selected. Can only take
        values {CHANNEL_NUMS}.""",
        validator=strict_discrete_set,
        values=CHANNEL_NUMS,
        get_process=lambda x: int(x[2])
    )

    def __init__(self, adapter, **kwargs):
        super(BKPrecision9130B, self).__init__(
            adapter, "BK Precision 9130B Source", **kwargs
        )

    @property
    def voltage(self):
        """Floating point property used to control voltage of the selected channel."""
        return float(self.ask("MEASure:SCALar:VOLTage:DC?"))

    @voltage.setter
    def voltage(self, level):
        voltage_range = [0, 5] if self.channel == 3 else [0, 30]
        new_level = truncated_range(level, voltage_range)
        self.write("SOURce:VOLTage:LEVel:IMMediate:AMPLitude %g" % new_level)
Example #14
0
 class Fake(FakeInstrument):
     x = Instrument.control("",
                            "%d",
                            "",
                            validator=strict_discrete_set,
                            values=[4, 5, 6, 7],
                            map_values=True,
                            dynamic=dynamic)
Example #15
0
 def _boolean_control(identifier, state_index, docs, inverted=False, **kwargs):
     return Instrument.control(
         'CST', identifier + '%d', docs,
         validator=strict_discrete_set,
         values=[True, False],
         get_process=lambda x: inverted ^ bool(int(x[state_index][1])),
         set_process=lambda x: int(inverted ^ x),
         **kwargs
     )
Example #16
0
 class Fake(FakeInstrument):
     x = Instrument.control(
         "",
         "JUNK%d",
         "",
         preprocess_reply=lambda v: v.replace('JUNK', ''),
         dynamic=dynamic,
         cast=int,
     )
Example #17
0
class E364A(Instrument):
    """
    Agilent E3648A Dual Output DC Power Supply
    """
    ##############
    # Properties #
    ##############
    voltage = Instrument.control(":VOLT?",
                                 ":VOLT %0.2f",
                                 """ Voltage output of the power supply """,
                                 validator=strict_range,
                                 values=[0, 20.0])
    current = Instrument.control(":CURR?",
                                 ":CURR %0.2f",
                                 """ Current output of the power supply """,
                                 validator=strict_range,
                                 values=[0, 5.0])
    output = Instrument.control(
        "INST:SEL?",
        "INST:SEL $s",
        """ Which power supply output is to be used (OUTP1 or OUTP2) """,
        validator=strict_discrete_set,
        values=["OUTP1", "OUTP2"])
    enabled = Instrument.control("OUTP?",
                                 "OUTP %s",
                                 """ Enable or disable power supply output """,
                                 validator=strict_discrete_set,
                                 values=["ON", "OFF"])

    ###########
    # Methods #
    ###########
    def __init__(self, adapter, **kwargs):
        super(E364A, self).__init__(adapter, "Agilent E3648", **kwargs)

    def reset(self):
        self.write("*RST")

    def apply(self, voltage, current):
        self.write("APPL {:0.2f}, {:0.2f}".format(voltage, current))

    def trigger(self):
        self.write("TRIG:SOUR IMM")
        self.write("INIT")
Example #18
0
 class Fake(FakeInstrument):
     x = Instrument.control(
         "",
         "JUNK%d",
         "",
         validator=strict_range,
         values=[0, 10],
         get_process=lambda v: int(v.replace('JUNK', '')),
         dynamic=dynamic,
     )
Example #19
0
class AdvantestR3767CG(Instrument):
    """ Represents the Advantest R3767CG VNA. Implements controls to change the analysis
        range and to retreve the data for the trace.
    """

    id = Instrument.measurement("*IDN?",
                                """ Reads the instrument identification """)

    center_frequency = Instrument.control(":FREQ:CENT?",
                                          ":FREQ:CENT %d",
                                          """Center Frequency in Hz""",
                                          validator=strict_range,
                                          values=[300000, 8000000000])

    span_frequency = Instrument.control(":FREQ:SPAN?",
                                        ":FREQ:SPAN %d",
                                        """Span Frequency in Hz""",
                                        validator=strict_range,
                                        values=[1, 8000000000])

    start_frequency = Instrument.control(":FREQ:STAR?",
                                         ":FREQ:STAR %d",
                                         """ Starting frequency in Hz """,
                                         validator=strict_range,
                                         values=[1, 8000000000])

    stop_frequency = Instrument.control(":FREQ:STOP?",
                                        ":FREQ:STOP %d",
                                        """ Stoping frequency in Hz """,
                                        validator=strict_range,
                                        values=[1, 8000000000])

    trace_1 = Instrument.measurement(
        "TRAC:DATA? FDAT1",
        """ Reads the Data array from trace 1 after formatting """)

    def __init__(self, resourceName, **kwargs):
        super(AdvantestR3767CG, self).__init__(resourceName,
                                               "Advantest R3767CG", **kwargs)

        # Tell unit to operate in IEEE488.2-1987 command mode.
        self.write("OLDC OFF")
Example #20
0
 class Fake(FakeInstrument):
     x = Instrument.control(
         "",
         "%d",
         "",
         validator=strict_range,
         values=[5e-3, 120e-3],
         get_process=lambda v: v * 1e-3,
         set_process=lambda v: v * 1e3,
         dynamic=dynamic,
     )
Example #21
0
 class Fake(FakeInstrument):
     x = Instrument.control("",
                            "%d",
                            "",
                            validator=strict_discrete_set,
                            values={
                                5: 1,
                                10: 2,
                                20: 3
                            },
                            map_values=True,
                            dynamic=dynamic)
Example #22
0
class ThorlabsPro8000(Instrument):
    """Represents Thorlabs Pro 8000 modular laser driver"""
    SLOTS = range(1, 9)
    LDC_POLARITIES = ['AG', 'CG']
    STATUS = ['ON', 'OFF']

    def __init__(self, resourceName, **kwargs):
        super(ThorlabsPro8000, self).__init__(resourceName,
                                              "Thorlabs Pro 8000", **kwargs)
        self.write(':SYST:ANSW VALUE')

    # Code for general purpose commands (mother board related)
    slot = Instrument.control(":SLOT?",
                              ":SLOT %d",
                              "Slot selection. Allowed values are: {}"
                              "".format(SLOTS),
                              validator=strict_discrete_set,
                              values=SLOTS,
                              map_values=False)

    # Code for LDC-xxxx daughter boards (laser driver)
    LDCCurrent = Instrument.control(":ILD:SET?", ":ILD:SET %g",
                                    """Laser current.""")
    LDCCurrentLimit = Instrument.control(
        ":LIMC:SET?", ":LIMC:SET %g",
        """Set Software current Limit (value must be lower than hardware current limit)."""
    )
    LDCPolarity = Instrument.control(
        ":LIMC:SET?",
        ":LIMC:SET %s",
        """Set laser diode polarity. Allowed values are: {}""".format(
            LDC_POLARITIES),
        validator=strict_discrete_set,
        values=LDC_POLARITIES,
        map_values=False)
    LDCStatus = Instrument.control(
        ":LASER?",
        ":LASER %s",
        """Set laser diode status. Allowed values are: {}""".format(STATUS),
        validator=strict_discrete_set,
        values=STATUS,
        map_values=False)

    # Code for TED-xxxx daughter boards (TEC driver)
    TEDStatus = Instrument.control(
        ":TEC?",
        ":TEC %s",
        """Set TEC status. Allowed values are: {}""".format(STATUS),
        validator=strict_discrete_set,
        values=STATUS,
        map_values=False)
    TEDSetTemperature = Instrument.control(":TEMP:SET?", ":TEMP:SET %g",
                                           """Set TEC temperature""")
Example #23
0
 class Fake(FakeInstrument):
     x = Instrument.control(
         "",
         "%d",
         "",
         validator=strict_discrete_set,
         values={
             'X': 1,
             'Y': 2,
             'Z': 3
         },
         map_values=True,
         dynamic=dynamic,
     )
Example #24
0
class AH2700A(AH2500A):
    """ Andeen Hagerling 2700A Precision Capacitance Bridge implementation
    """
    def __init__(self,
                 adapter,
                 name="Andeen Hagerling 2700A Precision Capacitance Bridge",
                 timeout=5000,
                 **kwargs):
        super(AH2700A, self).__init__(adapter,
                                      name=name,
                                      timeout=timeout,
                                      **kwargs)

    id = Instrument.measurement("*IDN?",
                                """ Reads the instrument identification """)

    config = Instrument.measurement(
        "SHOW ALL",
        """ Read out configuration """,
    )

    frequency = Instrument.control(
        "SH FR",
        "FR %.1f",
        """test frequency used for the measurements. Allowed are values between
        50 and 20000 Hz. The device selects the closest possible frequency to
        the given value.""",
        validator=strict_range,
        values=[50, 20000],
        # typical reply: "FREQUENCY      1200.0 Hz"
        get_process=lambda v: float(AH2500A._renumeric.search(v).group(0)),
    )

    def reset(self):
        """ Resets the instrument. """
        self.write("*RST")

    def trigger(self):
        """
        Triggers a new measurement without blocking and waiting for the return
        value.
        """
        self.write("*TRG")
        self._triggered = True
Example #25
0
    def control(get_command,
                set_command,
                docs,
                validator=lambda v, vs: v,
                values=(),
                map_values=False,
                get_process=lambda v: v,
                set_process=lambda v: v,
                check_set_errors=False,
                check_get_errors=False,
                **kwargs):
        """Fake Instrument.control.

        Strip commands and only store and return values indicated by
        format strings to mimic many simple commands.
        This is analogous how the tests in test_instrument are handled.
        """

        # Regex search to find first format specifier in the command
        fmt_spec_pattern = r'(%[\w.#-+ *]*[diouxXeEfFgGcrsa%])'
        match = re.findall(fmt_spec_pattern, set_command)
        if match:
            # format_specifier = match.group(0)
            format_specifier = ','.join(match)
        else:
            format_specifier = ''
        # To preserve as much functionality as possible, call the real
        # control method with modified get_command and set_command.
        return Instrument.control(get_command="",
                                  set_command=format_specifier,
                                  docs=docs,
                                  validator=validator,
                                  values=values,
                                  map_values=map_values,
                                  get_process=get_process,
                                  set_process=set_process,
                                  check_set_errors=check_set_errors,
                                  check_get_errors=check_get_errors,
                                  **kwargs)
Example #26
0
class Step():
    """ Implementation of a Keithley 2306 step. """

    trigger_level = Instrument.control(
        ":SENS:PCUR:STEP:TLEV<step>?", ":SENS:PCUR:STEP:TLEV<step> %g",
        """A floating point property that controls the pulse current step trigger
        level range in amps. Takes values from 0 up to the range set via
        pulse_current_step_range.""",
        validator=truncated_range,
        values=[0, 5],
    )

    def __init__(self, instrument, number):
        self.instrument = instrument
        self.number = number

    def values(self, command, **kwargs):
        return self.instrument.values(command.replace("<step>", str(self.number)),
                                      **kwargs)

    def write(self, command):
        self.instrument.write(command.replace("<step>", str(self.number)))
Example #27
0
class Relay():
    """ Implementation of a Keithley 2306 relay. """

    closed = Instrument.control(
        "?", " %s",
        """A boolean property that controls whether the relay is closed (True)
        or open (False). """,
        validator=strict_discrete_set,
        values={True: 'ONE', False: 'ZERO'},
        map_values=True
    )

    def __init__(self, instrument, number):
        self.instrument = instrument
        self.number = number

    def values(self, command, **kwargs):
        return self.instrument.values(":OUTP:REL%d%s" % (
            self.number, command), **kwargs)

    def write(self, command):
        self.instrument.write(":OUTP:REL%d%s" % (self.number, command))
Example #28
0
class DSP7265(Instrument):
    """This is the class for the DSP 7265 lockin amplifier"""

    SENSITIVITIES = [
        np.nan, 2.0e-9, 5.0e-9, 10.0e-9, 20.0e-9, 50.0e-9, 100.0e-9,
        200.0e-9, 500.0e-9, 1.0e-6, 2.0e-6, 5.0e-6, 10.0e-6,
        20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6, 1.0e-3,
        2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3,
        200.0e-3, 500.0e-3, 1.0
    ]
    SEN_MULTIPLIER = [1, 1e-6, 1e-8]

    TIME_CONSTANTS = [
        10.0e-6, 20.0e-6, 40.0e-6, 80.0e-6, 160.0e-6, 320.0e-6,
        640.0e-6, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3,
        200.0e-3, 500.0e-3, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0,
        100.0, 200.0, 500.0, 1.0e3, 2.0e3, 5.0e3, 10.0e3,
        20.0e3, 50.0e3
    ]
    REFERENCES = ['internal', 'external rear', 'external front']
    IMODES = ['voltage mode', 'current mode', 'low noise current mode']

    CURVE_BITS = ['x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1',
                  'adc2', 'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio',
                  'event', 'frequency part 1', 'frequency part 2',
                  # Dual modes
                  'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2']

    def __init__(self, resourceName, **kwargs):
        super().__init__(
            resourceName,
            "Signal Recovery DSP 7265",
            includeSCPI=False,
            # Remove extra unicode character
            preprocess_reply=lambda r: r.replace('\x00', ''),
            **kwargs
        )

    voltage = Instrument.control(
        "OA.", "OA. %g",
        """ A floating point property that represents the voltage
        in Volts. This property can be set. """,
        validator=truncated_range,
        values=[0, 5]
    )
    frequency = Instrument.control(
        "OF.", "OF. %g",
        """ A floating point property that represents the lock-in
        frequency in Hz. This property can be set. """,
        validator=truncated_range,
        values=[0, 2.5e5]
    )
    dac1 = Instrument.control(
        "DAC. 1", "DAC. 1 %g",
        """ A floating point property that represents the output
        value on DAC1 in Volts. This property can be set. """,
        validator=truncated_range,
        values=[-12, 12]
    )
    dac2 = Instrument.control(
        "DAC. 2", "DAC. 2 %g",
        """ A floating point property that represents the output
        value on DAC2 in Volts. This property can be set. """,
        validator=truncated_range,
        values=[-12, 12]
    )
    dac3 = Instrument.control(
        "DAC. 3", "DAC. 3 %g",
        """ A floating point property that represents the output
        value on DAC3 in Volts. This property can be set. """,
        validator=truncated_range,
        values=[-12, 12]
    )
    dac4 = Instrument.control(
        "DAC. 4", "DAC. 4 %g",
        """ A floating point property that represents the output
        value on DAC4 in Volts. This property can be set. """,
        validator=truncated_range,
        values=[-12, 12]
    )
    harmonic = Instrument.control(
        "REFN", "REFN %d",
        """ An integer property that represents the reference
        harmonic mode control, taking values from 1 to 65535.
        This property can be set. """,
        validator=truncated_discrete_set,
        values=list(range(65535))
    )
    reference_phase = Instrument.control(
        "REFP.", "REFP. %g",
        """ A floating point property that represents the reference
        harmonic phase in degrees. This property can be set. """,
        validator=modular_range_bidirectional,
        values=[0, 360]
    )
    x = Instrument.measurement("X.",
                               """ Reads the X value in Volts """
                               )
    y = Instrument.measurement("Y.",
                               """ Reads the Y value in Volts """
                               )
    xy = Instrument.measurement("XY.",
                                """ Reads both the X and Y values in Volts """
                                )
    mag = Instrument.measurement("MAG.",
                                 """ Reads the magnitude in Volts """
                                 )
    phase = Instrument.measurement("PHA.",
                                   """ Reads the phase in degrees """
                                   )
    adc1 = Instrument.measurement("ADC. 1",
                                  """ Reads the input value of ADC1 in Volts """
                                  )
    adc2 = Instrument.measurement("ADC. 2",
                                  """ Reads the input value of ADC2 in Volts """
                                  )
    id = Instrument.measurement("ID",
                                """ Reads the instrument identification """
                                )
    ratio = Instrument.measurement("RT.",
                                   """ Reads the ratio output, defined as X/ADC1 """
                                   )
    log_ratio = Instrument.measurement("LR.",
                                       """ Reads the log ratio output, defined as log(X/ADC1) """
                                       )
    reference = Instrument.control(
        "IE", "IE %d",
        """Controls the oscillator reference. Can be "internal",
        "external rear" or "external front" """,
        validator=strict_discrete_set,
        values=REFERENCES,
        map_values=True
    )

    @property
    def sensitivity(self):
        """ A floating point property that controls the sensitivity
        range in Volts (for voltage mode) or Amps (for current modes).
        When in Volts it takes discrete values from 2 nV to 1 V. When in
        Amps it takes discrete values from 2 fA to 1 µA (for normal current
        mode) or up to 10 nA (for low noise current mode).
        This property can be set. """
        return self.values("SEN.")[0]

    @sensitivity.setter
    def sensitivity(self, value):
        # get the voltage/current mode:
        imode = self.IMODES.index(self.imode)

        # Scale the sensitivities to the correct range for voltage/current mode
        sensitivities = [s * self.SEN_MULTIPLIER[imode] for s in self.SENSITIVITIES]
        if imode == 2:
            sensitivities[0:7] = [np.nan] * 7

        # Check and map the value
        value = truncated_discrete_set(value, sensitivities)
        print(value)
        value = sensitivities.index(value)

        # Set sensitivity
        self.write("SEN %d" % value)

    imode = Instrument.control(
        "IMODE", "IMODE %d",
        """ Property that controls the voltage/current mode. can be
        'voltage mode', 'current mode', or 'low noise current mode' """,
        validator=strict_discrete_set,
        values=IMODES,
        map_values=True
    )
    slope = Instrument.control(
        "SLOPE", "SLOPE %d",
        """ A integer property that controls the filter slope in
        dB/octave, which can take the values 6, 12, 18, or 24 dB/octave.
        This property can be set. """,
        validator=truncated_discrete_set,
        values=[6, 12, 18, 24],
        map_values=True
    )
    time_constant = Instrument.control(
        "TC", "TC %d",
        """ A floating point property that controls the time constant
        in seconds, which takes values from 10 microseconds to 50,000
        seconds. This property can be set. """,
        validator=truncated_discrete_set,
        values=TIME_CONSTANTS,
        map_values=True
    )

    def set_voltage_mode(self):
        self.write("IMODE 0")

    def setDifferentialMode(self, lineFiltering=True):
        """Sets lockin to differential mode, measuring A-B"""
        self.write("VMODE 3")
        self.write("LF %d 0" % 3 if lineFiltering else 0)

    def setChannelAMode(self):
        self.write("VMODE 1")

    @property
    def adc3(self):
        # 50,000 for 1V signal over 1 s
        integral = self.values("ADC 3")[0]
        return integral / (50000.0 * self.adc3_time)

    @property
    def adc3_time(self):
        # Returns time in seconds
        return self.values("ADC3TIME")[0] / 1000.0

    @adc3_time.setter
    def adc3_time(self, value):
        # Takes time in seconds
        self.write("ADC3TIME %g" % int(1000 * value))
        sleep(value * 1.2)

    @property
    def auto_gain(self):
        return (int(self.values("AUTOMATIC")) == 1)

    @auto_gain.setter
    def auto_gain(self, value):
        if value:
            self.write("AUTOMATIC 1")
        else:
            self.write("AUTOMATIC 0")

    def auto_sensitivity(self):
        self.write("AS")

    def auto_phase(self):
        self.write("AQN")

    @property
    def gain(self):
        return self.values("ACGAIN")

    @gain.setter
    def gain(self, value):
        self.write("ACGAIN %d" % int(value / 10.0))

    curve_buffer_bits = Instrument.control(
        "CBD", "CBD %d",
        """ An integer property that controls which data outputs are stored
        in the curve buffer. Valid values are values between 1 and 65,535 (or
        2,097,151 in dual reference mode). """,
        values=[1, 2097151],
        validator=truncated_range,
        cast=int,
    )

    curve_buffer_length = Instrument.control(
        "LEN", "LEN %d",
        """ An integer property that controls the length of the curve buffer.
        Valid values are values between 1 and 32,768, but the actual maximum
        amount of points is determined by the amount of curves that are stored,
        as set via the curve_buffer_bits property (32,768 / n) """,
        values=[1, 32768],
        validator=truncated_range,
        cast=int,
    )

    curve_buffer_interval = Instrument.control(
        "STR", "STR %d",
        """ An integer property that controls Sets the time interval between
        successive points being acquired in the curve buffer. The time interval
        is specified in ms with a resolution of 5 ms; input values are rounded
        up to a multiple of 5. Valid values are values between 0 and 1,000,000,000
        (corresponding to 12 days).
        The interval may be set to 0, which sets the rate of data storage to the
        curve buffer to 1.25 ms/point (800 Hz). However this only allows storage
        of the X and Y channel outputs. There is no need to issue a CBD 3 command
        to set this up since it happens automatically when acquisition starts.
        """,
        values=[1, 1000000000],
        validator=truncated_range,
        cast=int,
    )

    curve_buffer_status = Instrument.measurement(
        "M",
        """ A property that represents the status of the curve buffer acquisition
        with four values:
        the first value represents the status with 5 possibilities (0: no activity,
        1: acquisition via TD command running, 2: acquisition bya TDC command running,
        5: acquisition via TD command halted, 6: acquisition bia TDC command halted);
        the second value is the number of sweeps that is acquired;
        the third value is the decimal representation of the status byte (the same
        response as the ST command;
        the fourth value is the number of points acquired in the curve buffer.
        """,
        cast=int,
    )

    def init_curve_buffer(self):
        """ Initializes the curve storage memory and status variables. All record
        of previously taken curves is removed.
        """
        self.write("NC")

    def set_buffer(self, points, quantities=None, interval=10.0e-3):
        """ Method that prepares the curve buffer for a measurement.

        :param int points:
            Number of points to be recorded in the curve buffer

        :param list quantities:
            List containing the quantities (strings) that are to be
            recorded in the curve buffer, can be any of:
            'x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1',
            'adc2', 'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio',
            'event', 'frequency' (or 'frequency part 1' and 'frequency part 2');
            for both dual modes, additional options are:
            'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2'.
            Default is 'x' and 'y'.

        :param float interval:
            The interval between two subsequent points stored in the
            curve buffer in s. Default is 10 ms.
        """
        if quantities is None:
            quantities = ["x", "y"]

        if "frequency" in quantities:
            quantities.remove("frequency")
            quantities.extend([
                "frequency part 1",
                "frequency part 2"
            ])

        # remove all possible duplicates
        quantities = list({q.lower() for q in quantities})

        bits = 0
        for q in quantities:
            bits += 2**self.CURVE_BITS.index(q)

        self.curve_buffer_bits = bits
        self.curve_buffer_length = points

        self.curve_buffer_interval = int(interval * 1000)
        self.init_curve_buffer()

    def start_buffer(self):
        """ Initiates data acquisition. Acquisition starts at the current position
        in the curve buffer and continues at the rate set by the STR command until
        the buffer is full.
        """
        self.write("TD")

    def wait_for_buffer(self, timeout=None, delay=0.1):
        """ Method that waits until the curve buffer is filled
        """
        start = time()
        while self.curve_buffer_status[0] == 1:
            sleep(delay)
            if timeout is not None and time() < start + timeout:
                break

    def get_buffer(self, quantity=None, convert_to_float=True, wait_for_buffer=True):
        """ Method that retrieves the buffer after it has been filled. The data
        retrieved from the lock-in is in a fixed-point format, which requires
        translation before it can be interpreted as meaningful data. When
        `convert_to_float` is True the conversion is performed (if possible)
        before returning the data.

        :param str quantity:
            If provided, names the quantity that is to be retrieved from the
            curve buffer; can be any of:
            'x', 'y', 'magnitude', 'phase', 'sensitivity', 'adc1', 'adc2',
            'adc3', 'dac1', 'dac2', 'noise', 'ratio', 'log ratio', 'event',
            'frequency part 1' and 'frequency part 2';
            for both dual modes, additional options are:
            'x2', 'y2', 'magnitude2', 'phase2', 'sensitivity2'.
            If no quantity is provided, all available data is retrieved.

        :param bool convert_to_float:
            Bool that determines whether to convert the fixed-point buffer-data to
            meaningful floating point values via the `buffer_to_float` method. If True,
            this method tries to convert all the available data to meaningful values;
            if this is not possible, an exception will be raised. If False, this
            conversion is not performed and the raw buffer-data is returned.

        :param bool wait_for_buffer:
            Bool that determines whether to wait for the data acquisition to finished
            if this method is called before the acquisition is finished. If True, the
            method waits until the buffer is filled before continuing; if False, the
            method raises an exception if the acquisition is not finished when the
            method is called.
        """
        # Check if buffer is finished
        if self.curve_buffer_status[0] != 0:
            if wait_for_buffer:
                self.wait_for_buffer()
            else:
                raise RuntimeError("Buffer acquisition is not yet finished.")

        # Check which quantities are recorded in the buffer
        bits = format(self.curve_buffer_bits, '021b')[::-1]
        quantity_enums = [e for e, b in enumerate(bits) if b == "1"]

        # Check if the provided quantity (if any) is indeed recorded
        if quantity is not None:
            if self.CURVE_BITS.index(quantity) in quantity_enums:
                quantity_enums = [self.CURVE_BITS.index(quantity)]
            else:
                raise KeyError("The selected quantity '%s' is not recorded;"
                               "quantity should be one of: %s" % (
                                   quantity, ", ".join(
                                       [self.CURVE_BITS[q] for q in quantity_enums]
                                   )))

        # Retrieve the data
        data = {}
        for enum in quantity_enums:
            self.write("DC %d" % enum)
            q_data = []

            while True:
                stb = format(self.adapter.connection.read_stb(), '08b')[::-1]

                if bool(int(stb[2])):
                    raise ValueError("Status byte reports command parameter error.")

                if bool(int(stb[0])):
                    break

                if bool(int(stb[7])):
                    q_data.append(int(self.read().strip()))

            data[self.CURVE_BITS[enum]] = np.array(q_data)

        if convert_to_float:
            data = self.buffer_to_float(data)

        if quantity is not None:
            data = data[quantity]

        return data

    def buffer_to_float(self, buffer_data, sensitivity=None, sensitivity2=None, raise_error=True):
        """Method that converts fixed-point buffer data to floating point data.

        The provided data is converted as much as possible, but there are some requirements to the
        data if all provided columns are to be converted; if a key in the provided data cannot be
        converted it will be omitted in the returned data or an exception will be raised,
        depending on the value of raise_error.

        The requirements for converting the data are as follows:

        - Converting X, Y, magnitude and noise requires sensitivity data, which can either be part
          of the provided data or can be provided via the sensitivity argument
        - The same holds for X2, Y2 and magnitude2 with sensitivity2.
        - Converting the frequency requires both 'frequency part 1' and 'frequency part 2'.

        :param dict buffer_data:
            The data to be converted. Must be in the format as returned by the
            `get_buffer` method: a dict of numpy arrays.

        :param sensitivity:
            If provided, the sensitivity used to convert X, Y, magnitude and noise.
            Can be provided as a float or as an array that matches the length of elements in
            `buffer_data`. If both a sensitivity is provided and present in the buffer_data, the
            provided value is used for the conversion, but the sensitivity in the buffer_data is
            stored in the returned dict.

        :param sensitivity2:
            Same as the first sensitivity argument, but for X2, Y2, magnitude2 and noise2.

        :param bool raise_error:
            Determines whether an exception is raised in case not all keys provided in buffer_data
            can be converted. If False, the columns that cannot be converted are omitted in the
            returned dict.

        :return: Floating-point buffer data
        :rtype: dict
        """
        data = {}

        def maybe_raise(message):
            if raise_error:
                raise ValueError(message)

        def convert_if_present(keys, multiply_by=1):
            """Copy any available entries from buffer_data to data, scale with multiply_by.
            """
            for key in keys:
                if key in buffer_data:
                    data[key] = buffer_data[key] * multiply_by

        # Sensitivity (for both single and dual modes)
        for key in ["sensitivity", "sensitivity2"]:
            if key in buffer_data:
                data[key] = np.array([
                    self.SENSITIVITIES[v % 32] * self.SEN_MULTIPLIER[v // 32]
                    for v in buffer_data[key]
                ])
        # Try to set sensitivity values from arg or data
        sensitivity = sensitivity or data.get('sensitivity', None)
        sensitivity2 = sensitivity2 or data.get('sensitivity2', None)

        if any(["x" in buffer_data,
                "y" in buffer_data,
                "magnitude" in buffer_data,
                "noise" in buffer_data, ]):
            if sensitivity is None:
                maybe_raise("X, Y, magnitude and noise cannot be converted as no "
                            "sensitivity is provided, neither as argument nor as "
                            "part of the buffer_data. ")
            else:
                convert_if_present(["x", "y", "magnitude", "noise"], sensitivity / 10000)

        # phase data (for both single and dual modes)
        convert_if_present(["phase", "phase2"], 1 / 100)

        # frequency data from frequency part 1 and 2
        if "frequency part 1" in buffer_data or "frequency part 2" in buffer_data:
            if "frequency part 1" in buffer_data and "frequency part 2" in buffer_data:
                data["frequency"] = np.array([
                    int(format(v2, "016b") + format(v1, "016b"), 2) / 1000 for
                    v1, v2 in zip(buffer_data["frequency part 1"], buffer_data["frequency part 2"])
                ])
            else:
                maybe_raise("Can calculate the frequency only when both"
                            "frequency part 1 and 2 are provided.")

        # conversion for, adc1, adc2, dac1, dac2, ratio, and log ratio
        convert_if_present(["adc1", "adc2", "dac1", "dac2", "ratio", "log ratio"], 1 / 1000)

        # adc3 (integrating converter); requires a call to adc3_time
        if "adc3" in buffer_data:
            data["adc3"] = buffer_data["adc3"] / (50000 * self.adc3_time)

        # event does not require a conversion
        convert_if_present(["event"])

        # X, Y, and magnitude data for both dual modes
        if any(["x2" in buffer_data,
                "y2" in buffer_data,
                "magnitude2" in buffer_data, ]):
            if sensitivity2 is None:
                maybe_raise("X2, Y2 and magnitude2 cannot be converted as no "
                            "sensitivity2 is provided, neither as argument nor as "
                            "part of the buffer_data. ")
            else:
                convert_if_present(["x2", "y2", "magnitude2"], sensitivity2 / 10000)

        return data

    def shutdown(self):
        log.info("Shutting down %s." % self.name)
        self.voltage = 0.
        self.isShutdown = True
Example #29
0
class YokogawaGS200(Instrument):
    """ Represents the Yokogawa GS200 source and provides a high-level interface for interacting
    with the instrument. """

    source_enabled = Instrument.control(
        "OUTPut:STATe?",
        "OUTPut:STATe %d",
        """A boolean property that controls whether the source is enabled, takes values
        True or False. """,
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    source_mode = Instrument.control(
        ":SOURce:FUNCtion?",
        ":SOURce:FUNCtion %s",
        """String property that controls the source mode. Can be either 'current' or 'voltage'.""",
        validator=strict_discrete_set,
        values={
            'current': 'CURR',
            'voltage': 'VOLT'
        },
        get_process=lambda s: s.strip())

    source_range = Instrument.control(
        ":SOURce:RANGe?",
        "SOURce:RANGe %g",
        """Floating point number that controls the range (either in voltage or current)
        of the output. "Range" refers to the maximum source level. """,
        validator=truncated_discrete_set,
        values=[1e-3, 10e-3, 100e-3, 200e-3, 1, 10, 30])

    voltage_limit = Instrument.control(
        "SOURce:PROTection:VOLTage?",
        "SOURce:PROTection:VOLTage %g",
        """Floating point number that controls the voltage limit. "Limit" refers to maximum
        value of the electrical value that is conjugate to the mode (current is conjugate to
        voltage, and vice versa). Thus, voltage limit is only applicable when in 'current' mode""",
        validator=truncated_range,
        values=[1, 30])

    current_limit = Instrument.control(
        "SOURce:PROTection:CURRent?",
        "SOURce:PROTection:CURRent %g",
        """Floating point number that controls the current limit. "Limit" refers to maximum value
        of the electrical value that is conjugate to the mode (current is conjugate to voltage,
        and vice versa). Thus, current limit is only applicable when in 'voltage' mode""",
        validator=truncated_range,
        values=[1e-3, 200e-3])

    def __init__(self, adapter, **kwargs):
        super().__init__(adapter, "Yokogawa GS200 Source", **kwargs)

    @property
    def source_level(self):
        """ Floating point number that controls the output level, either a voltage or a current,
        depending on the source mode.
        """
        return float(self.ask(":SOURce:LEVel?"))

    @source_level.setter
    def source_level(self, level):
        if level > self.source_range * 1.2:
            raise ValueError(
                "Level must be within 1.2 * source_range, otherwise the Yokogawa will produce an "
                "error.")
        else:
            self.write("SOURce:LEVel %g" % level)

    def trigger_ramp_to_level(self, level, ramp_time):
        """
        Ramp the output level from its current value to "level" in time "ramp_time". This method
        will NOT wait until the ramp is finished (thus, it will not block further code evaluation).

        :param float level: final output level
        :param float ramp_time: time in seconds to ramp
        :return: None
        """
        if not self.source_enabled:
            raise ValueError(
                "YokogawaGS200 source must be enabled in order to ramp to a specified level. "
                "Otherwise, the Yokogawa will reject the ramp.")
        if ramp_time < MIN_RAMP_TIME:
            log.warning(
                f"Ramp time of {ramp_time}s is below the minimum ramp time of {MIN_RAMP_TIME}s, "
                f"so the Yokogawa will instead be instantaneously set to the desired level."
            )
            self.source_level = level
        else:
            # Use the Yokogawa's "program" mode to create the ramp
            ramp_program = (f":program:edit:start;"
                            f":source:level {level};"
                            f":program:edit:end;")
            # set "interval time" equal to "slope time" to make a continuous ramp
            ramp_program += (f":program:interval {ramp_time};"
                             f":program:slope {ramp_time};")
            # run it once
            ramp_program += (":program:repeat 0;" ":program:run")
            self.write(ramp_program)
Example #30
0
class LakeShore331(Instrument):
    """ Represents the Lake Shore 331 Temperature Controller and provides
    a high-level interface for interacting with the instrument.

    .. code-block:: python

        controller = LakeShore331("GPIB::1")

        print(controller.setpoint_1)        # Print the current setpoint for loop 1
        controller.setpoint_1 = 50          # Change the setpoint to 50 K
        controller.heater_range = 'low'     # Change the heater range to Low
        controller.wait_for_temperature()   # Wait for the temperature to stabilize
        print(controller.temperature_A)     # Print the temperature at sensor A

    """

    temperature_A = Instrument.measurement(
        "KRDG? A", """ Reads the temperature of the sensor A in Kelvin. """)
    temperature_B = Instrument.measurement(
        "KRDG? B", """ Reads the temperature of the sensor B in Kelvin. """)
    setpoint_1 = Instrument.control(
        "SETP? 1", "SETP 1, %g",
        """ A floating point property that controls the setpoint temperature
        in Kelvin for Loop 1. """)
    setpoint_2 = Instrument.control(
        "SETP? 2", "SETP 2, %g",
        """ A floating point property that controls the setpoint temperature
        in Kelvin for Loop 2. """)
    heater_range = Instrument.control(
        "RANGE?",
        "RANGE %d",
        """ A string property that controls the heater range, which
        can take the values: off, low, medium, and high. These values
        correlate to 0, 0.5, 5 and 50 W respectively. """,
        validator=strict_discrete_set,
        values={
            'off': 0,
            'low': 1,
            'medium': 2,
            'high': 3
        },
        map_values=True)

    def __init__(self, adapter, **kwargs):
        super(LakeShore331,
              self).__init__(adapter, "Lake Shore 331 Temperature Controller",
                             **kwargs)

    def disable_heater(self):
        """ Turns the :attr:`~.heater_range` to :code:`off` to disable the heater. """
        self.heater_range = 'off'

    def wait_for_temperature(self,
                             accuracy=0.1,
                             interval=0.1,
                             sensor='A',
                             setpoint=1,
                             timeout=360,
                             should_stop=lambda: False):
        """ Blocks the program, waiting for the temperature to reach the setpoint
        within the accuracy (%), checking this each interval time in seconds.

        :param accuracy: An acceptable percentage deviation between the
                         setpoint and temperature
        :param interval: A time in seconds that controls the refresh rate
        :param sensor: The desired sensor to read, either A or B
        :param setpoint: The desired setpoint loop to read, either 1 or 2
        :param timeout: A timeout in seconds after which an exception is raised
        :param should_stop: A function that returns True if waiting should stop, by
                            default this always returns False
        """
        temperature_name = 'temperature_%s' % sensor
        setpoint_name = 'setpoint_%d' % setpoint
        # Only get the setpoint once, assuming it does not change
        setpoint_value = getattr(self, setpoint_name)

        def percent_difference(temperature):
            return abs(100 * (temperature - setpoint_value) / setpoint_value)

        t = time()
        while percent_difference(getattr(self, temperature_name)) > accuracy:
            sleep(interval)
            if (time() - t) > timeout:
                raise Exception(
                    ("Timeout occurred after waiting %g seconds for "
                     "the LakeShore 331 temperature to reach %g K.") %
                    (timeout, setpoint))
            if should_stop():
                return