Exemple #1
0
 class Fake(FakeInstrument):
     x = Instrument.setting(
         "OUT %d",
         "",
         set_process=lambda v: int(bool(v)),
         dynamic=dynamic,
     )
Exemple #2
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,
    )
Exemple #3
0
class Nxds(Instrument):
    """ Represents the Edwards nXDS (10i) Vacuum Pump
    and provides a low-level interaction with the instrument.
    This could potentially work with Edwards pump that has a RS232 interface. 
    This instrument is constructed to only start and stop pump.
    """

    enable = Instrument.setting(
        "!C802 %d",
        """ Starts/stops pump with default settings.""",
        validator=strict_discrete_set,
        values=(0, 1),
    )

    def __init__(self, resourceName, **kwargs):
        super(Nxds, self).__init__(resourceName,
                                   "Edwards NXDS Vacuum Pump",
                                   includeSCPI=False,
                                   **kwargs)
Exemple #4
0
class ITC503(Instrument):
    """Represents the Oxford Intelligent Temperature Controller 503.

    .. code-block:: python

        itc = ITC503("GPIB::24")        # Default channel for the ITC503

        itc.control_mode = "RU"         # Set the control mode to remote
        itc.heater_gas_mode = "AUTO"    # Turn on auto heater and flow
        itc.auto_pid = True             # Turn on auto-pid

        print(itc.temperature_setpoint) # Print the current set-point
        itc.temperature_setpoint = 300  # Change the set-point to 300 K
        itc.wait_for_temperature()      # Wait for the temperature to stabilize
        print(itc.temperature_1)        # Print the temperature at sensor 1

    """

    control_mode = Instrument.control(
        "X",
        "$C%d",
        """ A string property that sets the ITC in LOCAL or REMOTE and LOCKES,
        or UNLOCKES, the LOC/REM button. Allowed values are:
        LL: LOCAL & LOCKED
        RL: REMOTE & LOCKED
        LU: LOCAL & UNLOCKED
        RU: REMOTE & UNLOCKED. """,
        get_process=lambda v: int(v[5:6]),
        validator=strict_discrete_set,
        values={
            "LL": 0,
            "RL": 1,
            "LU": 2,
            "RU": 3
        },
        map_values=True,
    )

    heater_gas_mode = Instrument.control(
        "X",
        "$A%d",
        """ A string property that sets the heater and gas flow control to
        AUTO or MANUAL. Allowed values are:
        MANUAL: HEATER MANUAL, GAS MANUAL
        AM: HEATER AUTO, GAS MANUAL
        MA: HEATER MANUAL, GAS AUTO
        AUTO: HEATER AUTO, GAS AUTO. """,
        get_process=lambda v: int(v[3:4]),
        validator=strict_discrete_set,
        values={
            "MANUAL": 0,
            "AM": 1,
            "MA": 2,
            "AUTO": 3
        },
        map_values=True,
    )

    auto_pid = Instrument.control(
        "X",
        "$L%d",
        """ A boolean property that sets the Auto-PID mode on (True) or off (False).
        """,
        get_process=lambda v: int(v[12:13]),
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True,
    )

    sweep_status = Instrument.control(
        "X",
        "$S%d",
        """ An integer property that sets the sweep status. Values are:
        0: Sweep not running
        1: Start sweep / sweeping to first set-point
        2P - 1: Sweeping to set-point P
        2P: Holding at set-point P. """,
        get_process=lambda v: int(v[7:9]),
        validator=strict_range,
        values=[0, 32])

    temperature_setpoint = Instrument.control(
        "R0",
        "$T%f",
        """ A floating point property that controls the temperature set-point of
        the ITC in kelvin. """,
        get_process=lambda v: float(v[1:]),
        validator=truncated_range,
        values=[0, 301])

    temperature_1 = Instrument.measurement(
        "R1",
        """ Reads the temperature of the sensor 1 in Kelvin. """,
        get_process=lambda v: float(v[1:]),
    )

    temperature_2 = Instrument.measurement(
        "R2",
        """ Reads the temperature of the sensor 2 in Kelvin. """,
        get_process=lambda v: float(v[1:]),
    )

    temperature_2 = Instrument.measurement(
        "R3",
        """ Reads the temperature of the sensor 3 in Kelvin. """,
        get_process=lambda v: float(v[1:]),
    )

    temperature_error = Instrument.measurement(
        "R4",
        """ Reads the difference between the set-point and the measured
        temperature in Kelvin. Positive when set-point is larger than
        measured. """,
        get_process=lambda v: float(v[1:]),
    )

    xpointer = Instrument.setting(
        "$x%d",
        """ An integer property to set pointers into tables for loading and
        examining values in the table. For programming the sweep table values
        from 1 to 16 are allowed, corresponding to the maximum number of steps.
        """,
        validator=strict_range,
        values=[0, 128])

    ypointer = Instrument.setting(
        "$y%d",
        """ An integer property to set pointers into tables for loading and
        examining values in the table. For programming the sweep table the
        allowed values are:
        1: Setpoint temperature,
        2: Sweep-time to set-point,
        3: Hold-time at set-point. """,
        validator=strict_range,
        values=[0, 128])

    sweep_table = Instrument.control(
        "r",
        "$s%f",
        """ A property that sets values in the sweep table. Relies on the
        xpointer and ypointer to point at the location in the table that
        is to be set. """,
        get_process=lambda v: float(v[1:]),
    )

    def __init__(self, resourceName, clear_buffer=True, **kwargs):
        super(ITC503, self).__init__(resourceName,
                                     "Oxford ITC503",
                                     includeSCPI=False,
                                     send_end=True,
                                     read_termination="\r",
                                     **kwargs)

        # Clear the buffer in order to prevent communication problems
        if clear_buffer:
            self.adapter.connection.clear()

    def wait_for_temperature(self,
                             error=0.01,
                             timeout=3600,
                             check_interval=0.5,
                             stability_interval=10,
                             thermalize_interval=300,
                             should_stop=lambda: False):
        """
        Wait for the ITC to reach the set-point temperature.

        :param error: The maximum error in Kelvin under which the temperature
                      is considered at set-point
        :param timeout: The maximum time the waiting is allowed to take. If
                        timeout is exceeded, a TimeoutError is raised. If
                        timeout is set to zero, no timeout will be used.
        :param check_interval: The time between temperature queries to the ITC.
        :param stability_interval: The time over which the temperature_error is
                                   to be below error to be considered stable.
        :param thermalize_interval: The time to wait after stabilizing for the
                                    system to thermalize.
        :param should_stop: Optional function (returning a bool) to allow the
                            waiting to be stopped before its end.
        """

        number_of_intervals = int(stability_interval / check_interval)
        stable_intervals = 0
        attempt = 0

        t0 = time()
        while True:

            if abs(self.temperature_error) < error:
                stable_intervals += 1
            else:
                stable_intervals = 0
                attempt += 1

            if stable_intervals >= number_of_intervals:
                break

            if timeout >= 0 and (time() - t0) > timeout:
                raise TimeoutError(
                    "Timeout expired while waiting for the Oxford ITC305 to \
                    reach the set-point temperature")

            if should_stop():
                return

            sleep(check_interval)

        if attempt == 0:
            return

        t1 = time() + thermalize_interval
        while time() < t1:
            sleep(check_interval)
            if should_stop():
                return

        return

    def program_sweep(self, temperatures, sweep_time, hold_time, steps=None):
        """
        Program a temperature sweep in the controller. Stops any running sweep.
        After programming the sweep, it can be started using
        OxfordITC503.sweep_status = 1.

        :param temperatures: An array containing the temperatures for the sweep
        :param sweep_time: The time (or an array of times) to sweep to a
                           set-point in minutes (between 0 and 1339.9).
        :param hold_time: The time (or an array of times) to hold at a
                          set-point in minutes (between 0 and 1339.9).
        :param steps: The number of steps in the sweep, if given, the
                      temperatures, sweep_time and hold_time will be
                      interpolated into (approximately) equal segments
        """
        # Check if in remote control
        if not self.control_mode.startswith("R"):
            raise AttributeError("Oxford ITC503 not in remote control mode")

        # Stop sweep if running to be able to write the program
        self.sweep_status = 0

        # Convert input np.ndarrays
        temperatures = numpy.array(temperatures, ndmin=1)
        sweep_time = numpy.array(sweep_time, ndmin=1)
        hold_time = numpy.array(hold_time, ndmin=1)

        # Make steps array
        if steps is None:
            steps = temperatures.size
        steps = numpy.linspace(1, steps, steps)

        # Create interpolated arrays
        interpolator = numpy.round(
            numpy.linspace(1, steps.size, temperatures.size))
        temperatures = numpy.interp(steps, interpolator, temperatures)

        interpolator = numpy.round(
            numpy.linspace(1, steps.size, sweep_time.size))
        sweep_time = numpy.interp(steps, interpolator, sweep_time)

        interpolator = numpy.round(
            numpy.linspace(1, steps.size, hold_time.size))
        hold_time = numpy.interp(steps, interpolator, hold_time)

        # Pad with zeros to wipe unused steps (total 16) of the sweep program
        padding = 16 - temperatures.size
        temperatures = numpy.pad(temperatures, (0, padding),
                                 'constant',
                                 constant_values=temperatures[-1])
        sweep_time = numpy.pad(sweep_time, (0, padding), 'constant')
        hold_time = numpy.pad(hold_time, (0, padding), 'constant')

        # Setting the arrays to the controller
        for line, (setpoint, sweep, hold) in \
                enumerate(zip(temperatures, sweep_time, hold_time), 1):
            self.xpointer = line

            self.ypointer = 1
            self.sweep_table = setpoint

            self.ypointer = 2
            self.sweep_table = sweep

            self.ypointer = 3
            self.sweep_table = hold
Exemple #5
0
class Agilent33500(Instrument):
    """Represents the Agilent 33500 Function/Arbitrary Waveform Generator family.
    Individual devices are represented by subclasses.

    .. code-block:: python

        generator = Agilent33500("GPIB::1")

        generator.shape = 'SIN'                 # Sets the output signal shape to sine
        generator.frequency = 1e3               # Sets the output frequency to 1 kHz
        generator.amplitude = 5                 # Sets the output amplitude to 5 Vpp
        generator.output = 'on'                 # Enables the output

        generator.shape = 'ARB'                 # Set shape to arbitrary
        generator.arb_srate = 1e6               # Set sample rate to 1MSa/s

        generator.data_volatile_clear()         # Clear volatile internal memory
        generator.data_arb(                     # Send data points of arbitrary waveform
            'test',
            range(-10000, 10000, +20),          # In this case a simple ramp
            data_format='DAC'                   # Data format is set to 'DAC'
        )
        generator.arb_file = 'test'             # Select the transmitted waveform 'test'

    """

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

    def __init__(self, adapter, **kwargs):
        super().__init__(
            adapter,
            "Agilent 33500 Function/Arbitrary Waveform generator family",
            **kwargs)

    def beep(self):
        """ Causes a system beep. """
        self.write("SYST:BEEP")

    shape = Instrument.control(
        "FUNC?",
        "FUNC %s",
        """ A string property that controls the output waveform. Can be set to:
        SIN<USOID>, SQU<ARE>, TRI<ANGLE>, RAMP, PULS<E>, PRBS,  NOIS<E>, ARB, DC. """,
        validator=strict_discrete_set,
        values=[
            "SIN", "SQU", "TRI", "RAMP", "PULS", "PRBS", "NOIS", "ARB", "DC"
        ],
    )

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

    amplitude = Instrument.control(
        "VOLT?",
        "VOLT %f",
        """ A floating point property that controls the voltage amplitude of the
        output waveform in V, from 10e-3 V to 10 V. Depends on the output
        impedance. Can be set. """,
        validator=strict_range,
        values=[10e-3, 10],
    )

    amplitude_unit = Instrument.control(
        "VOLT:UNIT?",
        "VOLT:UNIT %s",
        """ A string property that controls the units of the amplitude. Valid
        values are VPP (default), VRMS, and DBM. Can be set. """,
        validator=strict_discrete_set,
        values=["VPP", "VRMS", "DBM"],
    )

    offset = Instrument.control(
        "VOLT:OFFS?",
        "VOLT:OFFS %f",
        """ A floating point property that controls the voltage offset of the
        output waveform in V, from 0 V to 4.995 V, depending on the set
        voltage amplitude (maximum offset = (Vmax - voltage) / 2). Can be set.
        """,
        validator=strict_range,
        values=[-4.995, +4.995],
    )

    voltage_high = Instrument.control(
        "VOLT:HIGH?",
        "VOLT:HIGH %f",
        """ A floating point property that controls the upper voltage of the
        output waveform in V, from -4.990 V to 5 V (must be higher than low
        voltage by at least 1 mV). Can be set. """,
        validator=strict_range,
        values=[-4.99, 5],
    )

    voltage_low = Instrument.control(
        "VOLT:LOW?",
        "VOLT:LOW %f",
        """ A floating point property that controls the lower voltage of the
        output waveform in V, from -5 V to 4.990 V (must be lower than high
        voltage by at least 1 mV). Can be set. """,
        validator=strict_range,
        values=[-5, 4.99],
    )

    phase = Instrument.control(
        "PHAS?",
        "PHAS %f",
        """ A floating point property that controls the phase of the output
        waveform in degrees, from -360 degrees to 360 degrees. Not available
        for arbitrary waveforms or noise. Can be set. """,
        validator=strict_range,
        values=[-360, 360],
    )

    square_dutycycle = Instrument.control(
        "FUNC:SQU:DCYC?",
        "FUNC:SQU:DCYC %f",
        """ A floating point property that controls the duty cycle of a square
        waveform function in percent, from 0.01% to 99.98%.
        The duty cycle is limited by the frequency and the minimal pulse width of
        16 ns. See manual for more details. Can be set. """,
        validator=strict_range,
        values=[0.01, 99.98],
    )

    ramp_symmetry = Instrument.control(
        "FUNC:RAMP:SYMM?",
        "FUNC:RAMP:SYMM %f",
        """ A floating point property that controls the symmetry percentage
        for the ramp waveform, from 0.0% to 100.0% Can be set. """,
        validator=strict_range,
        values=[0, 100],
    )

    pulse_period = Instrument.control(
        "FUNC:PULS:PER?",
        "FUNC:PULS:PER %e",
        """ A floating point property that controls the period of a pulse
        waveform function in seconds, ranging from 33 ns to 1e6 s. Can be set
        and overwrites the frequency for *all* waveforms. If the period is
        shorter than the pulse width + the edge time, the edge time and pulse
        width will be adjusted accordingly. """,
        validator=strict_range,
        values=[33e-9, 1e6],
    )

    pulse_hold = Instrument.control(
        "FUNC:PULS:HOLD?",
        "FUNC:PULS:HOLD %s",
        """ A string property that controls if either the pulse width or the
        duty cycle is retained when changing the period or frequency of the
        waveform. Can be set to: WIDT<H> or DCYC<LE>. """,
        validator=strict_discrete_set,
        values=["WIDT", "WIDTH", "DCYC", "DCYCLE"],
    )

    pulse_width = Instrument.control(
        "FUNC:PULS:WIDT?",
        "FUNC:PULS:WIDT %e",
        """ A floating point property that controls the width of a pulse
        waveform function in seconds, ranging from 16 ns to 1e6 s, within a
        set of restrictions depending on the period. Can be set. """,
        validator=strict_range,
        values=[16e-9, 1e6],
    )

    pulse_dutycycle = Instrument.control(
        "FUNC:PULS:DCYC?",
        "FUNC:PULS:DCYC %f",
        """ A floating point property that controls the duty cycle of a pulse
        waveform function in percent, from 0% to 100%. Can be set. """,
        validator=strict_range,
        values=[0, 100],
    )

    pulse_transition = Instrument.control(
        "FUNC:PULS:TRAN?",
        "FUNC:PULS:TRAN:BOTH %e",
        """ A floating point property that controls the edge time in
        seconds for both the rising and falling edges. It is defined as the
        time between the 10% and 90% thresholds of the edge.
        Valid values are between 8.4 ns to 1 µs. Can be set. """,
        validator=strict_range,
        values=[8.4e-9, 1e-6],
    )

    output = Instrument.control(
        "OUTP?",
        "OUTP %d",
        """ A boolean property that turns on (True, 'on') or off (False, 'off')
        the output of the function generator. Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            'on': 1,
            'ON': 1,
            False: 0,
            'off': 0,
            'OFF': 0
        },
    )

    output_load = Instrument.control(
        "OUTP:LOAD?",
        "OUTP:LOAD %s",
        """ Sets the expected load resistance (should be the load impedance connected
        to the output. The output impedance is always 50 Ohm, this setting can be used
        to correct the displayed voltage for loads unmatched to 50 Ohm.
        Valid values are between 1 and 10 kOhm or INF for high impedance.
        No validator is used since both numeric and string inputs are accepted,
        thus a value outside the range will not return an error.
        Can be set. """,
    )

    burst_state = Instrument.control(
        "BURS:STAT?",
        "BURS:STAT %d",
        """ A boolean property that controls whether the burst mode is on
        (True) or off (False). Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )

    burst_mode = Instrument.control(
        "BURS:MODE?",
        "BURS:MODE %s",
        """ A string property that controls the burst mode. Valid values
        are: TRIG<GERED>, GAT<ED>. This setting can be set. """,
        validator=strict_discrete_set,
        values=["TRIG", "TRIGGERED", "GAT", "GATED"],
    )

    burst_period = Instrument.control(
        "BURS:INT:PER?",
        "BURS:INT:PER %e",
        """ A floating point property that controls the period of subsequent bursts.
        Has to follow the equation burst_period > (burst_ncycles / frequency) + 1 µs.
        Valid values are 1 µs to 8000 s. Can be set. """,
        validator=strict_range,
        values=[1e-6, 8000],
    )

    burst_ncycles = Instrument.control(
        "BURS:NCYC?",
        "BURS:NCYC %d",
        """ An integer property that sets the number of cycles to be output
        when a burst is triggered. Valid values are 1 to 100000. This can be
        set. """,
        validator=strict_range,
        values=range(1, 100000),
    )

    arb_file = Instrument.control(
        "FUNC:ARB?", "FUNC:ARB %s",
        """ A string property that selects the arbitrary signal from the volatile
        memory of the device. String has to match an existing arb signal in volatile
        memore (set by data_arb()). Can be set. """)

    arb_advance = Instrument.control(
        "FUNC:ARB:ADV?",
        "FUNC:ARB:ADV %s",
        """ A string property that selects how the device advances from data point
        to data point. Can be set to 'TRIG<GER>' or 'SRAT<E>' (default). """,
        validator=strict_discrete_set,
        values=["TRIG", "TRIGGER", "SRAT", "SRATE"],
    )

    arb_filter = Instrument.control(
        "FUNC:ARB:FILT?",
        "FUNC:ARB:FILT %s",
        """ A string property that selects the filter setting for arbitrary signals.
        Can be set to 'NORM<AL>', 'STEP' and 'OFF'. """,
        validator=strict_discrete_set,
        values=["NORM", "NORMAL", "STEP", "OFF"],
    )

    # TODO: This implementation is currently not working. Do not know why.
    # arb_period = Instrument.control(
    #     "FUNC:ARB:PER?", "FUNC:ARB:PER %e",
    #     """ A floating point property that controls the period of the arbitrary signal.
    #     Limited by number of signal points. Check for instrument errors when setting
    #     this property. Can be set. """,
    #     validator=strict_range,
    #     values=[33e-9, 1e6],
    # )
    #
    # arb_frequency = Instrument.control(
    #     "FUNC:ARB:FREQ?", "FUNC:ARB:FREQ %f",
    #     """ A floating point property that controls the frequency of the arbitrary signal.
    #     Limited by number of signal points. Check for instrument
    #     errors when setting this property. Can be set. """,
    #     validator=strict_range,
    #     values=[1e-6, 30e+6],
    # )
    #
    # arb_npoints = Instrument.measurement(
    #     "FUNC:ARB:POIN?",
    #     """ Returns the number of points in the currently selected arbitrary trace. """
    # )
    #
    # arb_voltage = Instrument.control(
    #     "FUNC:ARB:PTP?", "FUNC:ARB:PTP %f",
    #     """ An floating point property that sets the peak-to-peak voltage for the
    #     currently selected arbitrary signal. Valid values are 1 mV to 10 V. This can be
    #     set. """,
    #     validator=strict_range,
    #     values=[0.001, 10],
    # )

    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 (maximum range, can be lower
        depending on your device). This can be set. """,
        validator=strict_range,
        values=[1e-6, 250e6],
    )

    def data_volatile_clear(self):
        """
        Clear all arbitrary signals from the volatile memory. This should be done if the same name
        is used continuously to load different arbitrary signals into the memory, since an error
        will occur if a trace is loaded which already exists in the memory.
        """
        self.write("DATA:VOL:CLE")

    def data_arb(self, arb_name, data_points, data_format='DAC'):
        """
        Uploads an arbitrary trace into the volatile memory of the device. The data_points can be
        given as comma separated 16 bit DAC values (ranging from -32767 to +32767), as comma
        separated floating point values (ranging from -1.0 to +1.0) or as a binary data stream.
        Check the manual for more information. The storage depends on the device type and ranges
        from 8 Sa to 16 MSa (maximum).
        TODO: *Binary is not yet implemented*

        :param arb_name: The name of the trace in the volatile memory. This is used to access the
                         trace.
        :param data_points: Individual points of the trace. The format depends on the format
                            parameter.

                            format = 'DAC' (default): Accepts list of integer values ranging from
                            -32767 to +32767. Minimum of 8 a maximum of 65536 points.

                            format = 'float': Accepts list of floating point values ranging from
                            -1.0 to +1.0. Minimum of 8 a maximum of 65536 points.

                            format = 'binary': Accepts a binary stream of 8 bit data.
        :param data_format: Defines the format of data_points. Can be 'DAC' (default), 'float' or
                            'binary'. See documentation on parameter data_points above.
        """
        if data_format == 'DAC':
            separator = ', '
            data_points_str = [str(item) for item in data_points
                               ]  # Turn list entries into strings
            data_string = separator.join(
                data_points_str)  # Join strings with separator
            print(f"DATA:ARB:DAC {arb_name}, {data_string}")
            self.write(f"DATA:ARB:DAC {arb_name}, {data_string}")
            return
        elif data_format == 'float':
            separator = ', '
            data_points_str = [str(item) for item in data_points
                               ]  # Turn list entries into strings
            data_string = separator.join(
                data_points_str)  # Join strings with separator
            print(f"DATA:ARB {arb_name}, {data_string}")
            self.write(f"DATA:ARB {arb_name}, {data_string}")
            return
        elif data_format == 'binary':
            raise NotImplementedError(
                'The binary format has not yet been implemented. Use "DAC" or "float" instead.'
            )
        else:
            raise ValueError(
                'Undefined format keyword was used. Valid entries are "DAC", "float" and "binary"'
            )

    display = Instrument.setting(
        "DISP:TEXT '%s'",
        """ A string property which is displayed on the front panel of
        the device. Can be set. """,
    )

    def clear_display(self):
        """ Removes a text message from the display. """
        self.write("DISP:TEXT:CLE")

    def trigger(self):
        """ Send a trigger signal to the function generator. """
        self.write("*TRG;*WAI")

    def wait_for_trigger(self, timeout=3600, should_stop=lambda: False):
        """ Wait until the triggering has finished or timeout is reached.

        :param timeout: The maximum time the waiting is allowed to take. If
                        timeout is exceeded, a TimeoutError is raised. If
                        timeout is set to zero, no timeout will be used.
        :param should_stop: Optional function (returning a bool) to allow the
                            waiting to be stopped before its end.

        """
        self.write("*OPC?")

        t0 = time()
        while True:
            try:
                ready = bool(self.read())
            except VisaIOError:
                ready = False

            if ready:
                return

            if timeout != 0 and time() - t0 > timeout:
                raise TimeoutError(
                    "Timeout expired while waiting for the Agilent 33220A" +
                    " to finish the triggering.")

            if should_stop:
                return

    trigger_source = Instrument.control(
        "TRIG:SOUR?",
        "TRIG:SOUR %s",
        """ A string property that controls the trigger source. Valid values
        are: IMM<EDIATE> (internal), EXT<ERNAL> (rear input), BUS (via trigger
        command). This setting can be set. """,
        validator=strict_discrete_set,
        values=["IMM", "IMMEDIATE", "EXT", "EXTERNAL", "BUS"],
    )

    ext_trig_out = Instrument.control(
        "OUTP:TRIG?",
        "OUTP:TRIG %d",
        """ A boolean property that controls whether the trigger out signal is
        active (True) or not (False). This signal is output from the Ext Trig
        connector on the rear panel in Burst and Wobbel mode. Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )
Exemple #6
0
class ITC503(Instrument):
    """Represents the Oxford Intelligent Temperature Controller 503.

    .. code-block:: python

        itc = ITC503("GPIB::24")        # Default channel for the ITC503

        itc.control_mode = "RU"         # Set the control mode to remote
        itc.heater_gas_mode = "AUTO"    # Turn on auto heater and flow
        itc.auto_pid = True             # Turn on auto-pid

        print(itc.temperature_setpoint) # Print the current set-point
        itc.temperature_setpoint = 300  # Change the set-point to 300 K
        itc.wait_for_temperature()      # Wait for the temperature to stabilize
        print(itc.temperature_1)        # Print the temperature at sensor 1

    """
    def __init__(self,
                 adapter,
                 name="Oxford ITC503",
                 clear_buffer=True,
                 min_temperature=0,
                 max_temperature=1677.7,
                 **kwargs):

        if isinstance(adapter, (int, str)):
            kwargs.setdefault('read_termination', '\r')
            kwargs.setdefault('send_end', True)
            adapter = OxfordInstrumentsAdapter(
                adapter,
                asrl={
                    'baud_rate': 9600,
                    'data_bits': 8,
                    'parity': 0,
                    'stop_bits': 20,
                },
                preprocess_reply=lambda v: v[1:],
                **kwargs,
            )

        super().__init__(
            adapter=adapter,
            name=name,
            includeSCPI=False,
        )

        # Clear the buffer in order to prevent communication problems
        if clear_buffer:
            self.adapter.connection.clear()

        self.temperature_setpoint_values = [min_temperature, max_temperature]

    class FLOW_CONTROL_STATUS(IntFlag):
        """ IntFlag class for decoding the flow control status. Contains the following
        flags:

        === ======================  ==============================================
        bit flag                    meaning
        === ======================  ==============================================
        4   HEATER_ERROR_SIGN       Sign of heater-error; True means negative
        3   TEMPERATURE_ERROR_SIGN  Sign of temperature-error; True means negative
        2   SLOW_VALVE_ACTION       Slow valve action occurring
        1   COOLDOWN_TERMINATION    Cooldown-termination occurring
        0   FAST_COOLDOWN           Fast-cooldown occurring
        === ======================  ==============================================

        """
        HEATER_ERROR_SIGN = 16
        TEMPERATURE_ERROR_SIGN = 8
        SLOW_VALVE_ACTION = 4
        COOLDOWN_TERMINATION = 2
        FAST_COOLDOWN = 1

    version = Instrument.measurement(
        "V",
        """ A string property that returns the version of the IPS. """,
        preprocess_reply=lambda v: v,
    )

    control_mode = Instrument.control(
        "X",
        "C%d",
        """ A string property that sets the ITC in `local` or `remote` and `locked`
        or `unlocked`, locking the LOC/REM button. Allowed values are:

        =====   =================
        value   state
        =====   =================
        LL      local & locked
        RL      remote & locked
        LU      local & unlocked
        RU      remote & unlocked
        =====   =================
        """,
        preprocess_reply=lambda v: v[5:6],
        cast=int,
        validator=strict_discrete_set,
        values={
            "LL": 0,
            "RL": 1,
            "LU": 2,
            "RU": 3
        },
        map_values=True,
    )

    heater_gas_mode = Instrument.control(
        "X",
        "A%d",
        """ A string property that sets the heater and gas flow control to
        `auto` or `manual`. Allowed values are:

        ======   =======================
        value    state
        ======   =======================
        MANUAL   heater & gas manual
        AM       heater auto, gas manual
        MA       heater manual, gas auto
        AUTO     heater & gas auto
        ======   =======================
        """,
        preprocess_reply=lambda v: v[3:4],
        cast=int,
        validator=strict_discrete_set,
        values={
            "MANUAL": 0,
            "AM": 1,
            "MA": 2,
            "AUTO": 3
        },
        map_values=True,
    )

    heater = Instrument.control(
        "R5",
        "O%f",
        """ A floating point property that represents the heater output power
        as a percentage of the maximum voltage. Can be set if the heater is in
        manual mode. Valid values are in range 0 [off] to 99.9 [%]. """,
        validator=truncated_range,
        values=[0, 99.9])

    heater_voltage = Instrument.measurement(
        "R6",
        """ A floating point property that represents the heater output power
        in volts. For controlling the heater, use the :class:`ITC503.heater`
        property. """,
    )

    gasflow = Instrument.control(
        "R7",
        "G%f",
        """ A floating point property that controls gas flow when in manual
        mode. The value is expressed as a percentage of the maximum gas flow.
        Valid values are in range 0 [off] to 99.9 [%]. """,
        validator=truncated_range,
        values=[0, 99.9])

    proportional_band = Instrument.control(
        "R8",
        "P%f",
        """ A floating point property that controls the proportional band
        for the PID controller in Kelvin. Can be set if the PID controller
        is in manual mode. Valid values are 0 [K] to 1677.7 [K]. """,
        validator=truncated_range,
        values=[0, 1677.7])

    integral_action_time = Instrument.control(
        "R9",
        "I%f",
        """ A floating point property that controls the integral action time
        for the PID controller in minutes. Can be set if the PID controller
        is in manual mode. Valid values are 0 [min.] to 140 [min.]. """,
        validator=truncated_range,
        values=[0, 140])

    derivative_action_time = Instrument.control(
        "R10",
        "D%f",
        """ A floating point property that controls the derivative action time
        for the PID controller in minutes. Can be set if the PID controller
        is in manual mode. Valid values are 0 [min.] to 273 [min.]. """,
        validator=truncated_range,
        values=[0, 273])

    auto_pid = Instrument.control(
        "X",
        "L%d",
        """ A boolean property that sets the Auto-PID mode on (True) or off (False).
        """,
        preprocess_reply=lambda v: v[12:13],
        cast=int,
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True,
    )

    sweep_status = Instrument.control(
        "X",
        "S%d",
        """ An integer property that sets the sweep status. Values are:

        =========   =========================================
        value       meaning
        =========   =========================================
        0           Sweep not running
        1           Start sweep / sweeping to first set-point
        2P - 1      Sweeping to set-point P
        2P          Holding at set-point P
        =========   =========================================
        """,
        preprocess_reply=lambda v: v[7:9],
        cast=int,
        validator=strict_range,
        values=[0, 32])

    temperature_setpoint = Instrument.control(
        "R0",
        "T%f",
        """ A floating point property that controls the temperature set-point of
        the ITC in kelvin. """,
        validator=truncated_range,
        values=[
            0, 1677.7
        ],  # Kelvin, 0 - 1677.7K is the maximum range of the instrument
        dynamic=True,
    )

    temperature_1 = Instrument.measurement(
        "R1",
        """ Reads the temperature of the sensor 1 in Kelvin. """,
    )

    temperature_2 = Instrument.measurement(
        "R2",
        """ Reads the temperature of the sensor 2 in Kelvin. """,
    )

    temperature_3 = Instrument.measurement(
        "R3",
        """ Reads the temperature of the sensor 3 in Kelvin. """,
    )

    temperature_error = Instrument.measurement(
        "R4",
        """ Reads the difference between the set-point and the measured
        temperature in Kelvin. Positive when set-point is larger than
        measured. """,
    )

    front_panel_display = Instrument.setting(
        "F%d",
        """ A string property that controls what value is displayed on
        the front panel of the ITC. Valid values are:
        'temperature setpoint', 'temperature 1', 'temperature 2',
        'temperature 3', 'temperature error', 'heater', 'heater voltage',
        'gasflow', 'proportional band', 'integral action time',
        'derivative action time', 'channel 1 freq/4', 'channel 2 freq/4',
        'channel 3 freq/4'.
        """,
        validator=strict_discrete_set,
        map=True,
        values={
            "temperature setpoint": 0,
            "temperature 1": 1,
            "temperature 2": 2,
            "temperature 3": 3,
            "temperature error": 4,
            "heater": 5,
            "heater voltage": 6,
            "gasflow": 7,
            "proportional band": 8,
            "integral action time": 9,
            "derivative action time": 10,
            "channel 1 freq/4": 11,
            "channel 2 freq/4": 12,
            "channel 3 freq/4": 13,
        },
    )

    x_pointer = Instrument.setting(
        "x%d",
        """ An integer property to set pointers into tables for loading and
        examining values in the table. The significance and valid values for
        the pointer depends on what property is to be read or set. """,
        validator=strict_range,
        values=[0, 128])

    y_pointer = Instrument.setting(
        "y%d",
        """ An integer property to set pointers into tables for loading and
        examining values in the table. The significance and valid values for
        the pointer depends on what property is to be read or set. """,
        validator=strict_range,
        values=[0, 128])

    pointer = Instrument.setting(
        "$x%d\r$y%d",
        """ A tuple property to set pointers into tables for loading and
        examining values in the table, of format (x, y). The significance
        and valid values for the pointer depends on what property is to be
        read or set. The value for x and y can be in the range 0 to 128. """,
        validator=pointer_validator,
        values=[0, 128])

    sweep_table = Instrument.control(
        "r",
        "s%f",
        """ A property that controls values in the sweep table. Relies on
        :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or
        :class:`ITC503.pointer`) to point at the location in the table that is
        to be set or read.

        The x-pointer selects the step of the sweep (1 to 16); the y-pointer
        selects the parameter:

        =========   =======================
        y-pointer   parameter
        =========   =======================
        1           set-point temperature
        2           sweep-time to set-point
        3           hold-time at set-point
        =========   =======================
        """,
    )

    auto_pid_table = Instrument.control(
        "q",
        "p%f",
        """ A property that controls values in the auto-pid table. Relies on
        :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or
        :class:`ITC503.pointer`) to point at the location in the table that
        is to be set or read.

        The x-pointer selects the table entry (1 to 16); the y-pointer
        selects the parameter:

        =========   =======================
        y-pointer   parameter
        =========   =======================
        1           upper temperature limit
        2           proportional band
        3           integral action time
        4           derivative action time
        =========   =======================
        """,
    )

    target_voltage_table = Instrument.control(
        "t",
        "v%f",
        """ A property that controls values in the target heater voltage table.
        Relies on the :class:`ITC503.x_pointer` to select the entry in the table
        that is to be set or read (1 to 64).
        """,
    )

    gasflow_configuration_parameter = Instrument.control(
        "d",
        "c%f",
        """ A property that controls the gas flow configuration parameters.
        Relies on the :class:`ITC503.x_pointer` to select which parameter
        is set or read:

        =========   =====================================
        x-pointer   parameter
        =========   =====================================
        1           valve gearing
        2           target table & features configuration
        3           gas flow scaling
        4           temperature error sensitivity
        5           heater voltage error sensitivity
        6           minimum gas valve in auto
        =========   =====================================
        """,
    )

    gasflow_control_status = Instrument.measurement(
        "m",
        """ A property that reads the gas-flow control status. Returns
        the status in the form of a :class:`ITC503.FLOW_CONTROL_STATUS`
        IntFlag. """,
        cast=int,
        get_process=lambda v: ITC503.FLOW_CONTROL_STATUS(v),
    )

    target_voltage = Instrument.measurement(
        "n",
        """ A float property that reads the current heater target voltage
        with which the actual heater voltage is being compared. Only valid
        if gas-flow in auto mode. """,
    )

    valve_scaling = Instrument.measurement(
        "o",
        """ A float property that reads the valve scaling parameter. Only
        valid if gas-flow in auto mode. """,
    )

    def wait_for_temperature(
        self,
        error=0.01,
        timeout=3600,
        check_interval=0.5,
        stability_interval=10,
        thermalize_interval=300,
        should_stop=lambda: False,
    ):
        """
        Wait for the ITC to reach the set-point temperature.

        :param error: The maximum error in Kelvin under which the temperature
                      is considered at set-point
        :param timeout: The maximum time the waiting is allowed to take. If
                        timeout is exceeded, a TimeoutError is raised. If
                        timeout is None, no timeout will be used.
        :param check_interval: The time between temperature queries to the ITC.
        :param stability_interval: The time over which the temperature_error is
                                   to be below error to be considered stable.
        :param thermalize_interval: The time to wait after stabilizing for the
                                    system to thermalize.
        :param should_stop: Optional function (returning a bool) to allow the
                            waiting to be stopped before its end.
        """

        number_of_intervals = int(stability_interval / check_interval)
        stable_intervals = 0
        attempt = 0

        t0 = time()
        while True:
            temp_error = self.temperature_error
            if abs(temp_error) < error:
                stable_intervals += 1
            else:
                stable_intervals = 0
                attempt += 1

            if stable_intervals >= number_of_intervals:
                break

            if timeout is not None and (time() - t0) > timeout:
                raise TimeoutError(
                    "Timeout expired while waiting for the Oxford ITC305 to "
                    "reach the set-point temperature")

            if should_stop():
                return

            sleep(check_interval)

        if attempt == 0:
            return

        t1 = time() + thermalize_interval
        while time() < t1:
            sleep(check_interval)
            if should_stop():
                return

        return

    def program_sweep(self, temperatures, sweep_time, hold_time, steps=None):
        """
        Program a temperature sweep in the controller. Stops any running sweep.
        After programming the sweep, it can be started using
        OxfordITC503.sweep_status = 1.

        :param temperatures: An array containing the temperatures for the sweep
        :param sweep_time: The time (or an array of times) to sweep to a
                           set-point in minutes (between 0 and 1339.9).
        :param hold_time: The time (or an array of times) to hold at a
                          set-point in minutes (between 0 and 1339.9).
        :param steps: The number of steps in the sweep, if given, the
                      temperatures, sweep_time and hold_time will be
                      interpolated into (approximately) equal segments
        """
        # Check if in remote control
        if not self.control_mode.startswith("R"):
            raise AttributeError("Oxford ITC503 not in remote control mode")

        # Stop sweep if running to be able to write the program
        self.sweep_status = 0

        # Convert input np.ndarrays
        temperatures = numpy.array(temperatures, ndmin=1)
        sweep_time = numpy.array(sweep_time, ndmin=1)
        hold_time = numpy.array(hold_time, ndmin=1)

        # Make steps array
        if steps is None:
            steps = temperatures.size
        steps = numpy.linspace(1, steps, steps)

        # Create interpolated arrays
        interpolator = numpy.round(
            numpy.linspace(1, steps.size, temperatures.size))
        temperatures = numpy.interp(steps, interpolator, temperatures)

        interpolator = numpy.round(
            numpy.linspace(1, steps.size, sweep_time.size))
        sweep_time = numpy.interp(steps, interpolator, sweep_time)

        interpolator = numpy.round(
            numpy.linspace(1, steps.size, hold_time.size))
        hold_time = numpy.interp(steps, interpolator, hold_time)

        # Pad with zeros to wipe unused steps (total 16) of the sweep program
        padding = 16 - temperatures.size
        temperatures = numpy.pad(temperatures, (0, padding),
                                 'constant',
                                 constant_values=temperatures[-1])
        sweep_time = numpy.pad(sweep_time, (0, padding), 'constant')
        hold_time = numpy.pad(hold_time, (0, padding), 'constant')

        # Setting the arrays to the controller
        for line, (setpoint, sweep, hold) in \
                enumerate(zip(temperatures, sweep_time, hold_time), 1):
            self.pointer = (line, 1)
            self.sweep_table = setpoint

            self.pointer = (line, 2)
            self.sweep_table = sweep

            self.pointer = (line, 3)
            self.sweep_table = hold

    def wipe_sweep_table(self):
        """ Wipe the currently programmed sweep table. """
        self.write("w")
Exemple #7
0
class Agilent33220A(Instrument):
    """Represents the Agilent 33220A Arbitrary Waveform Generator.

    .. code-block:: python

        # Default channel for the Agilent 33220A
        wfg = Agilent33220A("GPIB::10")

        wfg.shape = "SINUSOID"          # Sets a sine waveform
        wfg.frequency = 4.7e3           # Sets the frequency to 4.7 kHz
        wfg.amplitude = 1               # Set amplitude of 1 V
        wfg.offset = 0                  # Set the amplitude to 0 V

        wfg.burst = True                # Enable burst mode
        wfg.burst_ncycles = 10e3        # A burst will consist of 10 cycles
        wfg.burst_mode = "TRIGGERED"    # A burst will be applied on a trigger
        wfg.trigger_source = "BUS"      # A burst will be triggered on TRG*

        wfg.output = True               # Enable output of waveform generator
        wfg.trigger()                   # Trigger a burst
        wfg.wait_for_trigger()          # Wait until the triggering is finished
        wfg.beep()                      # "beep"

        wfg.check_errors()              # Get the error queue

    """
    def __init__(self, adapter, **kwargs):
        super(Agilent33220A,
              self).__init__(adapter,
                             "Agilent 33220A Arbitrary Waveform generator",
                             **kwargs)

    shape = Instrument.control(
        "FUNC?",
        "FUNC %s",
        """ A string property that controls the output waveform. Can be set to:
        SIN<USOID>, SQU<ARE>, RAMP, PULS<E>, NOIS<E>, DC, USER. """,
        validator=string_validator,
        values=[
            "SINUSOID", "SIN", "SQUARE", "SQU", "RAMP", "PULSE", "PULS",
            "NOISE", "NOIS", "DC", "USER"
        ],
    )

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

    amplitude = Instrument.control(
        "VOLT?",
        "VOLT %f",
        """ A floating point property that controls the voltage amplitude of the
        output waveform in V, from 10e-3 V to 10 V. Can be set. """,
        validator=strict_range,
        values=[10e-3, 10],
    )

    amplitude_unit = Instrument.control(
        "VOLT:UNIT?",
        "VOLT:UNIT %s",
        """ A string property that controls the units of the amplitude. Valid
        values are Vpp (default), Vrms, and dBm. Can be set. """,
        validator=string_validator,
        values=["VPP", "VRMS", "DBM"],
    )

    offset = Instrument.control(
        "VOLT:OFFS?",
        "VOLT:OFFS %f",
        """ A floating point property that controls the voltage offset of the
        output waveform in V, from 0 V to 4.995 V, depending on the set
        voltage amplitude (maximum offset = (10 - voltage) / 2). Can be set.
        """,
        validator=strict_range,
        values=[-4.995, +4.995],
    )

    voltage_high = Instrument.control(
        "VOLT:HIGH?",
        "VOLT:HIGH %f",
        """ A floating point property that controls the upper voltage of the
        output waveform in V, from -4.990 V to 5 V (must be higher than low
        voltage). Can be set. """,
        validator=strict_range,
        values=[-4.99, 5],
    )

    voltage_low = Instrument.control(
        "VOLT:LOW?",
        "VOLT:LOW %f",
        """ A floating point property that controls the lower voltage of the
        output waveform in V, from -5 V to 4.990 V (must be lower than high
        voltage). Can be set. """,
        validator=strict_range,
        values=[-5, 4.99],
    )

    square_dutycycle = Instrument.control(
        "FUNC:SQU:DCYC?",
        "FUNC:SQU:DCYC %f",
        """ A floating point property that controls the duty cycle of a square
        waveform function in percent. Can be set. """,
        validator=strict_range,
        values=[20, 80],
    )

    ramp_symmetry = Instrument.control(
        "FUNC:RAMP:SYMM?",
        "FUNC:RAMP:SYMM %f",
        """ A floating point property that controls the symmetry percentage
        for the ramp waveform. Can be set. """,
        validator=strict_range,
        values=[0, 100],
    )

    pulse_period = Instrument.control(
        "PULS:PER?",
        "PULS:PER %f",
        """ A floating point property that controls the period of a pulse
        waveform function in seconds, ranging from 200 ns to 2000 s. Can be set
        and overwrites the frequency for *all* waveforms. If the period is
        shorter than the pulse width + the edge time, the edge time and pulse
        width will be adjusted accordingly. """,
        validator=strict_range,
        values=[200e-9, 2e3],
    )

    pulse_hold = Instrument.control(
        "FUNC:PULS:HOLD?",
        "FUNC:PULS:HOLD %s",
        """ A string property that controls if either the pulse width or the
        duty cycle is retained when changing the period or frequency of the
        waveform. Can be set to: WIDT<H> or DCYC<LE>. """,
        validator=string_validator,
        values=["WIDT", "WIDTH", "DCYC", "DCYCLE"],
    )

    pulse_width = Instrument.control(
        "FUNC:PULS:WIDT?",
        "FUNC:PULS:WIDT %f",
        """ A floating point property that controls the width of a pulse
        waveform function in seconds, ranging from 20 ns to 2000 s, within a
        set of restrictions depending on the period. Can be set. """,
        validator=strict_range,
        values=[20e-9, 2e3],
    )

    pulse_dutycycle = Instrument.control(
        "FUNC:PULS:DCYC?",
        "FUNC:PULS:DCYC %f",
        """ A floating point property that controls the duty cycle of a pulse
        waveform function in percent. Can be set. """,
        validator=strict_range,
        values=[0, 100],
    )

    pulse_transition = Instrument.control(
        "FUNC:PULS:TRAN?",
        "FUNC:PULS:TRAN %f",
        """ A floating point property that controls the the edge time in
        seconds for both the rising and falling edges. It is defined as the
        time between 0.1 and 0.9 of the threshold. Valid values are between
        5 ns to 100 ns. Can be set. """,
        validator=strict_range,
        values=[5e-9, 100e-9],
    )

    output = Instrument.control(
        "OUTP?",
        "OUTP %d",
        """ A boolean property that turns on (True) or off (False) the output
        of the function generator. Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )

    burst_state = Instrument.control(
        "BURS:STAT?",
        "BURS:STAT %d",
        """ A boolean property that controls whether the burst mode is on
        (True) or off (False). Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )

    burst_mode = Instrument.control(
        "BURS:MODE?",
        "BURS:MODE %s",
        """ A string property that controls the burst mode. Valid values
        are: TRIG<GERED>, GAT<ED>. This setting can be set. """,
        validator=string_validator,
        values=["TRIG", "TRIGGERED", "GAT", "GATED"],
    )

    burst_ncycles = Instrument.control(
        "BURS:NCYC?",
        "BURS:NCYC %d",
        """ An integer property that sets the number of cycles to be output
        when a burst is triggered. Valid values are 1 to 50000. This can be
        set. """,
        validator=strict_discrete_set,
        values=range(1, 50001),
    )

    def trigger(self):
        """ Send a trigger signal to the function generator. """
        self.write("*TRG;*WAI")

    def wait_for_trigger(self, timeout=3600, should_stop=lambda: False):
        """ Wait until the triggering has finished or timeout is reached.

        :param timeout: The maximum time the waiting is allowed to take. If
                        timeout is exceeded, a TimeoutError is raised. If
                        timeout is set to zero, no timeout will be used.
        :param should_stop: Optional function (returning a bool) to allow the
                            waiting to be stopped before its end.

        """
        self.write("*OPC?")

        t0 = time()
        while True:
            try:
                ready = bool(self.read())
            except VisaIOError:
                ready = False

            if ready:
                return

            if timeout != 0 and time() - t0 > timeout:
                raise TimeoutError(
                    "Timeout expired while waiting for the Agilent 33220A" +
                    " to finish the triggering.")

            if should_stop:
                return

    trigger_source = Instrument.control(
        "TRIG:SOUR?",
        "TRIG:SOUR %s",
        """ A string property that controls the trigger source. Valid values
        are: IMM<EDIATE> (internal), EXT<ERNAL> (rear input), BUS (via trigger
        command). This setting can be set. """,
        validator=string_validator,
        values=["IMM", "IMMEDIATE", "EXT", "EXTERNAL", "BUS"],
    )

    trigger_state = Instrument.control(
        "OUTP:TRIG?",
        "OUTP:TRIG %d",
        """ A boolean property that controls whether the output is triggered
        (True) or not (False). Can be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )

    remote_local_state = Instrument.setting(
        "SYST:COMM:RLST %s",
        """ A string property that controls the remote/local state of the
        function generator. Valid values are: LOC<AL>, REM<OTE>, RWL<OCK>.
        This setting can only be set. """,
        validator=string_validator,
        values=["LOC", "LOCAL", "REM", "REMOTE", "RWL", "RWLOCK"],
    )

    def check_errors(self):
        """ Read all errors from the instrument. """

        errors = []
        while True:
            err = self.values("SYST:ERR?")
            if int(err[0]) != 0:
                errmsg = "Agilent 33220A: %s: %s" % (err[0], err[1])
                log.error(errmsg + '\n')
                errors.append(errmsg)
            else:
                break

        return errors

    beeper_state = Instrument.control(
        "SYST:BEEP:STAT?",
        "SYST:BEEP:STAT %d",
        """ A boolean property that controls the state of the beeper. Can
        be set. """,
        validator=strict_discrete_set,
        map_values=True,
        values={
            True: 1,
            False: 0
        },
    )

    def beep(self):
        """ Causes a system beep. """
        self.write("SYST:BEEP")
Exemple #8
0
class HP34401A(Instrument):
    """ Represents the HP 34401A instrument.
    """
    
    #############
    #  Mappings #
    #############
    ONOFF = ["ON", "OFF"]
    ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'}

    ##################
    #  Configuration #
    ##################
    
    display = Instrument.setting(
        "DISP:ENABLE %s", "Instrument display (ON/OFF)",
        validator=strict_discrete_set,
        map_values=True,
        values=ONOFF_MAPPING
    )

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

    voltage_dc = Instrument.measurement("MEAS:VOLT:DC? DEF,DEF", "DC voltage, in Volts")
    
    voltage_ac = Instrument.measurement("MEAS:VOLT:AC? DEF,DEF", "AC voltage, in Volts")
    
    current_dc = Instrument.measurement("MEAS:CURR:DC? DEF,DEF", "DC current, in Amps")
    
    current_ac = Instrument.measurement("MEAS:CURR:AC? DEF,DEF", "AC current, in Amps")
    
    resistance = Instrument.measurement("MEAS:RES? DEF,DEF", "Resistance, in Ohms")
    
    resistance_4w = Instrument.measurement("MEAS:FRES? DEF,DEF", "Four-wires (remote sensing) resistance, in Ohms")

    def __init__(self, resourceName, **kwargs):
        super(HP34401A, self).__init__(
            resourceName,
            "HP 34401A",
            **kwargs
        )

    def display_text(self, text):
        """ Write string on display """
        self.write("DISP:TEXT '%s'" % text)

    def display_text_clear(self):
        """ Clear string from display """
        self.write("DISP:TEXT:CLE")

    def reset(self):
        """ Resets the instrument and clears the queue. """
        self.write("*RST;*CLS;*SRE 0;*ESE 0;:STAT:PRES;")

    def check_errors(self):
        """ Read all errors from the instrument. """

        errors = []
        while True:
            err = self.values("SYST:ERR?")
            if int(err[0]) != 0:
                errmsg = "Agilent 34401A: {0}: {1}".format(err[0], err[1])
                log.error(errmsg + '\n')
                errors.append(errmsg)
            else:
                break

        return errors
Exemple #9
0
class Keithley2306(Instrument):
    """ Represents the Keithley 2306 Dual Channel Battery/Charger Simulator.
    """

    def __init__(self, resourceName, **kwargs):
        super().__init__(
            resourceName,
            "Keithley 2306",
            **kwargs
        )
        self.ch1 = BatteryChannel(self, 1)
        self.ch2 = Channel(self, 2)
        self.relay1 = Relay(self, 1)
        self.relay2 = Relay(self, 2)
        self.relay3 = Relay(self, 3)
        self.relay4 = Relay(self, 4)

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

    display_brightness = Instrument.control(
        ":DISP:BRIG?", ":DISP:BRIG %g",
        """A floating point property that controls the display brightness,
        takes values beteween 0.0 and 1.0. A blank display is 0.0,
        1/4 brightness is for values less or equal to 0.25, otherwise 1/2
        brightness for values less than or equal to 0.5, otherwise 3/4
        brightness for values less than or equal to 0.75, otherwise full
        brightness. """,
        validator=truncated_range,
        values=[0, 1],
    )

    display_channel = Instrument.control(
        ":DISP:CHAN?", ":DISP:CHAN %d",
        """An integer property that controls the display channel, takes
        values 1 or 2. """,
        validator=strict_discrete_set,
        values=[1, 2],
    )

    display_text_data = Instrument.control(
        ":DISP:TEXT:DATA?", ":DISP:TEXT:DATA \"%s\"",
        """A string property that control text to be displayed, takes strings
        up to 32 characters. """,
        get_process=lambda v: v.replace('"', '')
    )

    display_text_enabled = Instrument.control(
        ":DISP:TEXT:STAT?", ":DISP:TEXT:STAT %d",
        """A boolean property that controls whether display text is enabled,
        takes values True or False. """,
        validator=strict_discrete_set,
        values={True: 1, False: 0},
        map_values=True,
    )

    both_channels_enabled = Instrument.setting(
        ":BOTHOUT%s",
        """A boolean setting that controls whether both channel outputs are
        enabled, takes values of True or False. """,
        validator=strict_discrete_set,
        values={True: "ON", False: "OFF"},
        map_values=True,
    )

    def ch(self, channel_number):
        if channel_number == 1:
            return self.ch1
        elif channel_number == 2:
            return self.ch2
        else:
            raise ValueError("Invalid channel number. Must be 1 or 2.")

    def relay(self, relay_number):
        if relay_number == 1:
            return self.relay1
        elif relay_number == 2:
            return self.relay2
        elif relay_number == 3:
            return self.relay3
        elif relay_number == 4:
            return self.relay4
        else:
            raise ValueError("Invalid relay number. Must be 1, 2, 3, or 4")
Exemple #10
0
class AnritsuMS9710C(Instrument):
    """Anritsu MS9710C Optical Spectrum Analyzer."""

    #############
    #  Mappings #
    #############
    ONOFF = ["ON", "OFF"]
    ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'}

    ######################
    #  Status Registers  #
    ######################

    ese2 = Instrument.control(
        "ESE2?", "ESE2 %d",
        "Extended Event Status Enable Register 2",
        get_process=int
    )

    esr2 = Instrument.control(
        "ESR2?", "ESR2 %d",
        "Extended Event Status Register 2",
        get_process=_int_or_neg_one
    )

    ###########
    #  Modes  #
    ###########

    measure_mode = Instrument.measurement(
        "MOD?", "Returns the current Measure Mode the OSA is in.",
        values={None: 0, "SINGLE": 1.0, "AUTO": 2.0, "POWER": 3.0},
        map_values=True
    )

    ####################################
    # Spectrum Parameters - Wavelength #
    ####################################
    wavelength_center = Instrument.control('CNT?', 'CNT %g', "Center Wavelength of Spectrum Scan in nm.")

    wavelength_span = Instrument.control('SPN?', 'SPN %g', "Wavelength Span of Spectrum Scan in nm.")

    wavelength_start = Instrument.control('STA?', 'STA %g', "Wavelength Start of Spectrum Scan in nm.")

    wavelength_stop = Instrument.control('STO?', 'STO %g', "Wavelength Stop of Spectrum Scan in nm.")

    wavelength_marker_value = Instrument.control(
        'MKV?', 'MKV %s',
        "Wavelength Marker Value (wavelength or freq.?)",
        validator=strict_discrete_set,
        values=["WL", "FREQ"]
    )

    wavelength_value_in = Instrument.control(
        'WDP?', 'WDP %s',
        "Wavelength value in Vacuum or Air",
        validator=strict_discrete_set,
        values=["VACUUM", "AIR"]
    )

    level_scale = Instrument.measurement(
        'LVS?', "Current Level Scale",
        values=["LOG", "LIN"]
    )

    level_log = Instrument.control(
        "LOG?", "LOG %f", "Level Log Scale (/div)",
        validator=truncated_range, values=[0.1, 10.0]
    )

    level_lin = Instrument.control(
        "LIN?", "LIN %f", "Level Linear Scale (/div)",
        validator=truncated_range, values=[1e-12, 1]
    )

    level_opt_attn = Instrument.control(
        "ATT?", "ATT %s", "Optical Attenuation Status (ON/OFF)",
        validator=strict_discrete_set,
        values=ONOFF
    )

    resolution = Instrument.control(
        "RES?", "RES %f", "Resolution (nm)",
        validator=truncated_discrete_set,
        values=[0.05, 0.07, 0.1, 0.2, 0.5, 1.0]
    )

    resolution_actual = Instrument.control(
        "ARES?", "ARES %s", "Resolution Actual (ON/OFF)",
        validator=strict_discrete_set,
        values=ONOFF,
        map_values=True

    )

    resolution_vbw = Instrument.control(
        "VBW?", "VBW %s", "Video Bandwidth Resolution",
        validator=strict_discrete_set,
        values=["1MHz", "100kHz", "10kHz", "1kHz", "100Hz", "10Hz"]
    )

    average_point = Instrument.control(
        "AVT?", "AVT %d",
        "Number of averages to take on each point (2-1000), or OFF",
        validator=truncated_range_or_off,
        values=[["OFF"], [2, 1000]]
    )

    average_sweep = Instrument.control(
        "AVS?", "AVS %d",
        "Number of averages to make on a sweep (2-1000) or OFF",
        validator=truncated_range_or_off,
        values=[["OFF"], [2, 1000]]
    )

    sampling_points = Instrument.control(
        "MPT?", "MPT %d", "Number of sampling points",
        validator=truncated_discrete_set,
        values=[51, 101, 251, 501, 1001, 2001, 5001],
        get_process=lambda v: int(v)
    )

    #####################################
    #  Analysis Peak Search Parameters  #
    #####################################

    peak_search = Instrument.control(
        "PKS?", "PKS %s", "Peak Search Mode",
        validator=strict_discrete_set,
        values=["PEAK", "NEXT", "LAST", "LEFT", "RIGHT"]
    )

    dip_search = Instrument.control(
        "DPS?", "DPS %s", "Dip Search Mode",
        validator=strict_discrete_set,
        values=["DIP", "NEXT", "LAST", "LEFT", "RIGHT"]
    )

    analysis = Instrument.control(
        "ANA?", "ANA %s", "Analysis Control"
    )

    analysis_result = Instrument.measurement(
        "ANAR?", "Read back anaysis result from current scan."
    )

    ##########################
    #  Data Memory Commands  #
    ##########################

    data_memory_a_size = Instrument.measurement(
        'DBA?',
        "Returns the number of points sampled in data memory register A."
    )

    data_memory_b_size = Instrument.measurement(
        'DBB?',
        "Returns the number of points sampled in data memory register B."
    )

    data_memory_a_condition = Instrument.measurement(
        "DCA?",
        """Returns the data condition of data memory register A.
        Starting wavelength, and a sampling point (l1, l2, n)."""
    )

    data_memory_b_condition = Instrument.measurement(
        "DCB?",
        """Returns the data condition of data memory register B.
        Starting wavelength, and a sampling point (l1, l2, n)."""
    )

    data_memory_a_values = Instrument.measurement(
        "DMA?",
        "Reads the binary data from memory register A."
    )

    data_memory_b_values = Instrument.measurement(
        "DMA?",
        "Reads the binary data from memory register B."
    )

    data_memory_select = Instrument.control(
        "MSL?", "MSL %s",
        "Memory Data Select.",
        validator=strict_discrete_set,
        values=["A", "B"]
    )

    ###########################
    #  Trace Marker Commands  #
    ###########################

    trace_marker_center = Instrument.setting(
        "TMC %s", "Trace Marker at Center. Set to 1 or True to initiate command",
        map_values=True,
        values={True: ''}
    )

    trace_marker = Instrument.control(
        "TMK?", "TMK %f",
        "Sets the trace marker with a wavelength.  Returns the trace wavelength and power.",
        get_process=_parse_trace_peak
    )

    def __init__(self, adapter, **kwargs):
        """Constructor."""
        self.analysis_mode = None
        super(AnritsuMS9710C, self).__init__(adapter, "Anritsu MS9710C Optical Spectrum Analyzer", **kwargs)

    @property
    def wavelengths(self):
        """Return a numpy array of the current wavelengths of scans."""
        return np.linspace(
            self.wavelength_start,
            self.wavelength_stop,
            self.sampling_points
        )

    def read_memory(self, slot="A"):
        """Read the scan saved in a memory slot."""
        cond_attr = "data_memory_{}_condition".format(slot.lower())
        data_attr = "data_memory_{}_values".format(slot.lower())

        scan = getattr(self, cond_attr)
        wavelengths = np.linspace(scan[0], scan[1], int(scan[2]))
        power = np.fromstring(getattr(self, data_attr), sep="\r\n")

        return wavelengths, power

    def wait(self, n=3, delay=1):
        """Query OPC Command and waits for appropriate response."""
        log.info("Wait for OPC")
        res = self.adapter.ask("*OPC?")
        n_attempts = n
        while(res == ''):
            log.debug("Empty OPC Repsonse. {} remaining".format(n_attempts))
            if n_attempts == 0:
                break
            n_attempts -= 1
            sleep(delay)
            res = self.adapter.read().strip()

        log.debug(res)

    def wait_for_sweep(self, n=20, delay=0.5):
        """Wait for a sweep to stop.

        This is performed by checking bit 1 of the ESR2.
        """
        log.debug("Waiting for spectrum sweep")

        while(self.esr2 != 3 and n > 0):
            log.debug("Wait for sweep [{}]".format(n))
            # log.debug("ESR2: {}".format(esr2))
            sleep(delay)
            n -= 1

        if n <= 0:
            log.warning("Sweep Timeout Occurred ({} s)".format(int(delay * n)))

    def single_sweep(self, **kwargs):
        """Perform a single sweep and wait for completion."""
        log.debug("Performing a Spectrum Sweep")
        self.clear()
        self.write('SSI')
        self.wait_for_sweep(**kwargs)

    def center_at_peak(self, **kwargs):
        """Center the spectrum at the measured peak."""
        self.write("PKC")
        self.wait(**kwargs)

    def measure_peak(self):
        """Measure the peak and return the trace marker."""
        self.peak_search = "PEAK"
        return self.trace_marker
Exemple #11
0
class Agilent5313xA(Instrument):
    """
        Represents the HP/Agilent/Keysight 53131A and 53132A counter.

        Implemented measurements: Frequency
        """

    #############
    #  Mappings #
    #############
    ONOFF = ["ON", "OFF"]
    ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'}

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

    options = Instrument.measurement("*OPT?",
                                     """ Reads the installed options """)

    val = Instrument.measurement(":READ?", "Read current measured value.")

    fetch_frequency = Instrument.measurement("FETCH:FREQ?",
                                             "Read current frequency.")

    fetch_period = Instrument.measurement("FETCH:PERIOD?",
                                          "Read current period.")

    display = Instrument.setting("DISP:ENABLE %s",
                                 "Instrument display (ON/OFF)",
                                 validator=strict_discrete_set,
                                 map_values=True,
                                 values=ONOFF_MAPPING)

    display_menu_off = Instrument.setting(
        ":DISP:MENU %s",
        "Clear active menu item. Set to 1 or True to initiate command",
        map_values=True,
        values={True: 'OFF'})

    hcopy = Instrument.control(
        ":HCOP:CONT?",
        ":HCOP:CONT %d",
        """ Enables or disables printing results.
            This property can be set (True, False).""",
        validator=strict_discrete_set,
        cast=bool,
        values=ONOFF_MAPPING,
    )

    reference = Instrument.setting(
        ":ROSC:SOURCE %s",
        "Control reference oscillator. Can be set to INT / EXT",
        validator=strict_discrete_set,
        values=["INT", "EXT"])

    reference_autocheck_off = Instrument.setting(
        ":ROSC:EXT:CHECK %s",
        "Disable check of external reference source. Set to 1 or True to initiate command",
        map_values=True,
        values={True: 'OFF'})

    cal_interpolator_auto = Instrument.setting(
        ":DIAG:CAL:INT:AUTO %s",
        """ Disable automatic interpolater calibration.
            The most recent calibration values are used in the calculation of frequency. Set to (ON/OFF) """,
        validator=strict_discrete_set,
        map_values=True,
        values=ONOFF_MAPPING)

    format = Instrument.setting(
        ":FORM %s",
        "Sets a data format for transferring numeric information. ASCII or REAL",
        validator=strict_discrete_set,
        values=["ASCII", "REAL"])

    measure_freq = Instrument.setting(":FREQ %d",
                                      "Set channel to measure frequency on.",
                                      validator=strict_discrete_set,
                                      values=[1, 2, 3])

    cont_measurements = Instrument.control(
        ":INIT:CONT?",
        ":INIT:CONT %d",
        """ Sets the enable for continuously initiated measurements.
            This property can be set (True, False).""",
        validator=strict_discrete_set,
        cast=bool,
        values=ONOFF_MAPPING,
    )

    func_frequency = Instrument.setting(":FUNC 'FREQ %d'",
                                        "Set channel to measure frequency on.",
                                        validator=strict_discrete_set,
                                        values=[1, 2, 3])

    func_period = Instrument.setting(":FUNC 'PER %d'",
                                     "Set channel to measure period on.",
                                     validator=strict_discrete_set,
                                     values=[1, 2, 3])

    def __init__(self, adapter, delay=0.02, **kwargs):
        super(Agilent5313xA,
              self).__init__(adapter, "HP/Agilent/Keysight 5313xA Counter",
                             **kwargs)

        self.ch1 = Channel(self, 1)
        self.ch2 = Channel(self, 2)
        self.ch3 = Channel3(self, 3)

    def reset(self):
        """ Resets the instrument and clears the queue. """
        self.write("*RST;*CLS;*SRE 0;*ESE 0;:STAT:PRES;")

    def measure_freq_simple(self, freq, resolution, channel):
        """ Configure measure frequency on channel. """
        if 0 < channel <= 3:
            self.write(":MEASURE:FREQ? {0}Hz {1}, (@{2})".format(
                freq, resolution, channel))

    def configure_freq(self, channel):
        """ Configure measure frequency on channel. """
        if 0 < channel <= 3:
            self.write(":CONF:FREQ (@{0})".format(channel))

    def freq_exp_set(self, frequency):
        """ Set expected frequency. """
        self.write(":FREQ:EXP1 {0}".format(frequency))

    def measure_t_interval(self):
        """ Time Interval from CH1 to CH2. """
        self.write("MEAS:TINT? (@1),(@2)")

    def arming_auto(self):
        """ Enable the time arming mode. """
        self.write(":FREQ:ARM:STAR:SOUR IMM")
        self.write(":FREQ:ARM:STOP:SOUR TIM")

    def arming_time(self, time):
        """ Enable the time arming mode. """
        self.write(":FREQ:ARM:STAR:SOUR IMM")
        self.write(":FREQ:ARM:STOP:SOUR TIM")
        self.write((":FREQ:ARM:STOP:TIM %.1f" % time).lstrip('0'))

    def postproc_disable(self):
        """ Disable all post processing. """
        self.write(":CALC:MATH:STATE OFF")
        self.write(":CALC2:LIM:STATE OFF")
        self.write(":CALC3:AVER:STATE OFF")

    def cal_read(self):
        """ Ask for calibration data. Returned data is binary. """
        self.write(":CAL:DATA?")
        return self.adapter.read_raw()

    def cal_write(self, data):
        """ Write calibration data. Input data is binary. """
        self.adapter.write_raw(":CAL:DATA ".encode('utf-8') + data)

    def trigger_level_set(self, level):
        """ Set trigger level. """
        self.write((":EVENT1:LEVEL %.2f" % level).lstrip('0'))

    def measure_start(self):
        """ Start measurement. """
        self.write("INIT")

    def trigger_set_fetc(self):
        """ Define the Trigger command. This means the command FETC?
            does not need to be sent for every measurement, decreasing the
            number of bytes transferred over the bus  """
        self.write("*DDT #15FETC?")

    def check_errors(self):
        """ Read all errors from the instrument. """

        errors = []
        while True:
            err = self.values("SYST:ERR?")
            if int(err[0]) != 0:
                errmsg = "Agilent 5313xA: {0}: {1}".format(err[0], err[1])
                log.error(errmsg + '\n')
                errors.append(errmsg)
            else:
                break

        return errors
Exemple #12
0
class Keithley2700(Instrument, KeithleyBuffer):
    """ Represents the Keithely 2700 Multimeter/Switch System and provides a
    high-level interface for interacting with the instrument.

    .. code-block:: python

        keithley = Keithley2700("GPIB::1")

    """

    CLIST_VALUES = list(range(101, 300))

    # Routing commands
    closed_channels = Instrument.control(
        "ROUTe:MULTiple:CLOSe?", "ROUTe:MULTiple:CLOSe %s",
        """ Parameter that controls the opened and closed channels.
        All mentioned channels are closed, other channels will be opened.
        """,
        validator=clist_validator,
        values=CLIST_VALUES,
        check_get_errors=True,
        check_set_errors=True,
        separator=None,
        get_process=lambda v: [
            int(vv) for vv in (v.strip(" ()@,").split(",")) if not vv == ""
        ],
    )

    open_channels = Instrument.setting(
        "ROUTe:MULTiple:OPEN %s",
        """ A parameter that opens the specified list of channels. Can only
        be set.
        """,
        validator=clist_validator,
        values=CLIST_VALUES,
        check_set_errors=True
    )

    def get_state_of_channels(self, channels):
        """ Get the open or closed state of the specified channels

        :param channels: a list of channel numbers, or single channel number
        """
        clist = clist_validator(channels, self.CLIST_VALUES)
        state = self.ask("ROUTe:MULTiple:STATe? %s" % clist)

        return state

    def open_all_channels(self):
        """ Open all channels of the Keithley 2700.
        """
        self.write(":ROUTe:OPEN:ALL")

    def __init__(self, adapter, **kwargs):
        super(Keithley2700, self).__init__(
            adapter, "Keithley 2700 MultiMeter/Switch System", **kwargs
        )

        self.check_errors()
        self.determine_valid_channels()

    def determine_valid_channels(self):
        """ Determine what cards are installed into the Keithley 2700
        and from that determine what channels are valid.
        """
        self.CLIST_VALUES.clear()

        self.cards = {slot: card for slot, card in enumerate(self.options, 1)}

        for slot, card in self.cards.items():

            if card == "none":
                continue
            elif card == "7709":
                """The 7709 is a 6(rows) x 8(columns) matrix card, with two
                additional switches (49 & 50) that allow row 1 and 2 to be
                connected to the DMM backplane (input and sense respectively).
                """
                channels = range(1, 51)
            else:
                log.warning(
                    "Card type %s at slot %s is not yet implemented." % (card, slot)
                )
                continue

            channels = [100 * slot + ch for ch in channels]

            self.CLIST_VALUES.extend(channels)

    def close_rows_to_columns(self, rows, columns, slot=None):
        """ Closes (connects) the channels between column(s) and row(s)
        of the 7709 connection matrix.
        Only one of the parameters 'rows' or 'columns' can be "all"

        :param rows: row number or list of numbers; can also be "all"
        :param columns: column number or list of numbers; can also be "all"
        :param slot: slot number (1 or 2) of the 7709 card to be used
        """

        channels = self.channels_from_rows_columns(rows, columns, slot)
        self.closed_channels = channels

    def open_rows_to_columns(self, rows, columns, slot=None):
        """ Opens (disconnects) the channels between column(s) and row(s)
        of the 7709 connection matrix.
        Only one of the parameters 'rows' or 'columns' can be "all"

        :param rows: row number or list of numbers; can also be "all"
        :param columns: column number or list of numbers; can also be "all"
        :param slot: slot number (1 or 2) of the 7709 card to be used
        """

        channels = self.channels_from_rows_columns(rows, columns, slot)
        self.open_channels = channels

    def channels_from_rows_columns(self, rows, columns, slot=None):
        """ Determine the channel numbers between column(s) and row(s) of the
        7709 connection matrix. Returns a list of channel numbers.
        Only one of the parameters 'rows' or 'columns' can be "all"

        :param rows: row number or list of numbers; can also be "all"
        :param columns: column number or list of numbers; can also be "all"
        :param slot: slot number (1 or 2) of the 7709 card to be used

        """

        if slot is not None and self.cards[slot] != "7709":
            raise ValueError("No 7709 card installed in slot %g" % slot)

        if isinstance(rows, str) and isinstance(columns, str):
            raise ValueError("Only one parameter can be 'all'")
        elif isinstance(rows, str) and rows == "all":
            rows = list(range(1, 7))
        elif isinstance(columns, str) and columns == "all":
            columns = list(range(1, 9))

        if isinstance(rows, (list, tuple, np.ndarray)) and \
                isinstance(columns, (list, tuple, np.ndarray)):

            if len(rows) != len(columns):
                raise ValueError("The length of the rows and columns do not match")

            # Flatten (were necessary) the arrays
            new_rows = []
            new_columns = []
            for row, column in zip(rows, columns):
                if isinstance(row, int) and isinstance(column, int):
                    new_rows.append(row)
                    new_columns.append(column)
                elif isinstance(row, (list, tuple, np.ndarray)) and isinstance(column, int):
                    new_columns.extend(len(row) * [column])
                    new_rows.extend(list(row))
                elif isinstance(column, (list, tuple, np.ndarray)) and isinstance(row, int):
                    new_columns.extend(list(column))
                    new_rows.extend(len(column) * [row])

            rows = new_rows
            columns = new_columns

        # Determine channel number from rows and columns number.
        rows = np.array(rows, ndmin=1)
        columns = np.array(columns, ndmin=1)

        channels = (rows - 1) * 8 + columns

        if slot is not None:
            channels += 100 * slot

        return channels

    # system, some taken from Keithley 2400
    def beep(self, frequency, duration):
        """ Sounds a system beep.

        :param frequency: A frequency in Hz between 65 Hz and 2 MHz
        :param duration: A time in seconds between 0 and 7.9 seconds
        """
        self.write(":SYST:BEEP %g, %g" % (frequency, duration))

    def triad(self, base_frequency, duration):
        """ Sounds a musical triad using the system beep.

        :param base_frequency: A frequency in Hz between 65 Hz and 1.3 MHz
        :param duration: A time in seconds between 0 and 7.9 seconds
        """
        self.beep(base_frequency, duration)
        time.sleep(duration)
        self.beep(base_frequency * 5.0 / 4.0, duration)
        time.sleep(duration)
        self.beep(base_frequency * 6.0 / 4.0, duration)

    @property
    def error(self):
        """ Returns a tuple of an error code and message from a
        single error. """
        err = self.values(":system:error?")
        if len(err) < 2:
            err = self.read()  # Try reading again
        code = err[0]
        message = err[1].replace('"', '')
        return (code, message)

    def check_errors(self):
        """ Logs any system errors reported by the instrument.
        """
        code, message = self.error
        while code != 0:
            t = time.time()
            log.info("Keithley 2700 reported error: %d, %s" % (code, message))
            print(code, message)
            code, message = self.error
            if (time.time() - t) > 10:
                log.warning("Timed out for Keithley 2700 error retrieval.")

    def reset(self):
        """ Resets the instrument and clears the queue.  """
        self.write("status:queue:clear;*RST;:stat:pres;:*CLS;")

    options = Instrument.measurement(
        "*OPT?",
        """Property that lists the installed cards in the Keithley 2700.
        Returns a dict with the integer card numbers on the position.""",
        cast=False
    )

    ###########
    # DISPLAY #
    ###########

    text_enabled = Instrument.control(
        "DISP:TEXT:STAT?", "DISP:TEXT:STAT %d",
        """ A boolean property that controls whether a text message can be
        shown on the display of the Keithley 2700.
        """,
        values={True: 1, False: 0},
        map_values=True,
    )
    display_text = Instrument.control(
        "DISP:TEXT:DATA?", "DISP:TEXT:DATA '%s'",
        """ A string property that controls the text shown on the display of
        the Keithley 2700. Text can be up to 12 ASCII characters and must be
        enabled to show.
        """,
        validator=text_length_validator,
        values=12,
        cast=str,
        separator="NO_SEPARATOR",
        get_process=lambda v: v.strip("'\""),
    )

    def display_closed_channels(self):
        """ Show the presently closed channels on the display of the Keithley
        2700.
        """

        # Get the closed channels and make a string of the list
        channels = self.closed_channels
        channel_string = " ".join([
            str(channel % 100) for channel in channels
        ])

        # Prepend "Closed: " or "C: " to the string, depending on the length
        str_length = 12
        if len(channel_string) < str_length - 8:
            channel_string = "Closed: " + channel_string
        elif len(channel_string) < str_length - 3:
            channel_string = "C: " + channel_string

        # enable displaying text-messages
        self.text_enabled = True

        # write the string to the display
        self.display_text = channel_string
Exemple #13
0
class SR570(Instrument):
    def __init__(self, resourceName, **kwargs):
        super(SR570, self).__init__(
            resourceName, "Stanford Research Systems SR570 Lock-in amplifier",
            **kwargs)

    SENSITIVITIES = [
        1e-12, 2e-12, 5e-12, 10e-12, 20e-12, 50e-12, 100e-12, 200e-12, 500e-12,
        1e-9, 2e-9, 5e-9, 10e-9, 20e-9, 50e-9, 100e-9, 200e-9, 500e-9, 1e-6,
        2e-6, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6, 500e-6, 1e-3
    ]

    FREQUENCIES = [
        0.03, 0.1, 0.3, 1, 3, 10, 30, 100, 300, 1e3, 3e3, 1e4, 3e4, 1e5, 3e5,
        1e6
    ]

    FILT_TYPES = [
        '6dB Highpass', '12dB Highpass', '6dB Bandpass', '6dB Lowpass',
        '12dB Lowpass', 'none'
    ]

    BIAS_LIMITS = [-5, 5]

    OFFSET_CURRENTS = [
        1e-12, 2e-12, 5e-12, 10e-12, 20e-12, 50e-12, 100e-12, 200e-12, 500e-12,
        1e-9, 2e-9, 5e-9, 10e-9, 20e-9, 50e-9, 100e-9, 200e-9, 500e-9, 1e-6,
        2e-6, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6, 500e-6, 1e-3, 2e-3,
        5e-3
    ]

    GAIN_MODES = ['Low Noise', 'High Bandwidth', 'Low Drift']

    sensitivity = Instrument.setting(
        "SENS %d",
        """ A floating point value that sets the sensitivity of the 
        amplifier, which takes discrete values in a 1-2-5 sequence. 
        Values are truncated to the closest allowed value if not exact. 
        Allowed values range from 1 pA/V to 1 mA/V.""",
        validators=truncated_discrete_set,
        values=SENSITIVITIES,
        map_values=True)

    filter_type = Instrument.setting("FLTT %d",
                                     """ A string that sets the filter type.
        Allowed values are: {}""".format(FILT_TYPES),
                                     validators=truncated_discrete_set,
                                     values=FILT_TYPES,
                                     map_values=True)

    low_freq = Instrument.setting(
        "LFRQ %d",
        """ A floating point value that sets the lowpass frequency of the 
        amplifier, which takes a discrete value in a 1-3 sequence. 
        Values are truncated to the closest allowed value if not exact. 
        Allowed values range from 0.03 Hz to 1 MHz.""",
        validators=truncated_discrete_set,
        values=FREQUENCIES,
        map_values=True)

    high_freq = Instrument.setting(
        "HFRQ %d",
        """ A floating point value that sets the highpass frequency of the 
        amplifier, which takes a discrete value in a 1-3 sequence. 
        Values are truncated to the closest allowed value if not exact. 
        Allowed values range from 0.03 Hz to 1 MHz.""",
        validators=truncated_discrete_set,
        values=FREQUENCIES,
        map_values=True)

    bias_level = Instrument.setting(
        "BSLV %g",
        """ A floating point value in V that sets the bias voltage level of the 
        amplifier, in the [-5V,+5V] limits. 
        The values are up to 1 mV precision level.""",
        validators=truncated_range,
        values=BIAS_LIMITS,
        set_process=lambda v: int(1000 * v))

    offset_current = Instrument.setting(
        "BSLV %f",
        """ A floating point value in A that sets the absolute value 
        of the offset current of the amplifier, in the [1pA,5mA] limits. 
        The offset current takes discrete values in a 1-2-5 sequence. 
        Values are truncated to the closest allowed value if not exact. """,
        validators=truncated_discrete_set,
        values=OFFSET_CURRENTS,
        map_values=True)

    offset_current_sign = Instrument.setting(
        "IOSN %d",
        """ An string that sets the offset current sign. 
        Allowed values are: 'positive' and 'negative'. """,
        validators=strict_discrete_set,
        values={
            'positive': 1,
            'negative': 0
        },
        map_values=True)

    gain_mode = Instrument.setting("GNMD %d",
                                   """ A string that sets the gain mode.
        Allowed values are: {}""".format(GAIN_MODES),
                                   validators=truncated_discrete_set,
                                   values=GAIN_MODES,
                                   map_values=True)

    invert_signal_sign = Instrument.setting(
        "INVT %d",
        """ An boolean sets the signal invert sense.
        Allowed values are: True (inverted) and False (not inverted). """,
        validators=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    bias_enabled = Instrument.setting(
        "BSON %d",
        """ Boolean that turns the bias on or off.
        Allowed values are: True (bias on) and False (bias off)""",
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    offset_current_enabled = Instrument.setting(
        "IOON %d",
        """ Boolean that turns the offset current on or off.
        Allowed values are: True (current on) and False (current off).""",
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    front_blanked = Instrument.setting(
        "BLNK %d",
        """ Boolean that blanks(True) or un-blanks (False) the front panel""",
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    signal_inverted = Instrument.setting(
        "INVT %d",
        """ Boolean that inverts the signal if True""",
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    ####################
    # Methods        #
    ####################

    def enable_bias(self):
        """Turns the bias voltage on"""
        self.bias_enabled = True

    def disable_bias(self):
        """Turns the bias voltage off"""
        self.bias_enabled = False

    def enable_offset_current(self):
        """"Enables the offset current """
        self.offset_current_enabled = True

    def disable_offset_current(self):
        """"Disables the offset current """
        self.offset_current_enabled = False

    def clear_overload(self):
        """"Reset the filter capacitors to clear an overload condition"""
        self.write("ROLD")

    def blank_front(self):
        """"Blanks the frontend output of the device"""
        self.front_blanked = True

    def unblank_front(self):
        """Un-blanks the frontend output of the device"""
        self.front_blanked = False
Exemple #14
0
class HP3478A(Instrument):
    """ Represents the Hewlett Packard 3748A 5 1/2 digit multimeter
    and provides a high-level interface for interacting
    with the instrument.
    """

    def __init__(self, resourceName, **kwargs):
        kwargs.setdefault('read_termination', '\r\n')
        kwargs.setdefault('send_end', True)
        super(HP3478A, self).__init__(
            resourceName,
            "Hewlett-Packard HP3478A",
            includeSCPI=False,
            **kwargs
        )

    # Definitions for different specifics of this instrument
    MODES = {"DCV": "F1",
             "ACV": "F2",
             "R2W": "F3",
             "R4W": "F4",
             "DCI": "F5",
             "ACI": "F6",
             "Rext": "F7",
             }

    INV_MODES = {v: k for k, v in MODES.items()}

    RANGES = {"DCV": {3E-2: "R-2", 3E-1: "R-1", 3: "R0", 30: "R1", 300: "R2",
                      "auto": "RA"},
              "ACV": {3E-1: "R-1", 3: "R0", 30: "R1", 300: "R2", "auto": "RA"},
              "R2W": {30: "R1", 300: "R2", 3E3: "R3", 3E4: "R4", 3E5: "R5",
                      3E6: "R6", 3E7: "R7", "auto": "RA"},
              "R4W": {30: "R1", 300: "R2", 3E3: "R3", 3E4: "R4", 3E5: "R5",
                      3E6: "R6", 3E7: "R7", "auto": "RA"},
              "DCI": {3E-1: "R-1", 3: "R0", "auto": "RA"},
              "ACI": {3E-1: "R-1", 3: "R0", "auto": "RA"},
              "Rext": {3E7: "R7", "auto": "RA"},
              }

    TRIGGERS = {
        "auto": "T1",
        "internal": "T1",
        "external": "T2",
        "single": "T3",
        "hold": "T4",
        "fast": "T5",
    }

    class ERRORS(IntFlag):
        """Enum element for errror bit decoding
        """
        AD_LINK = 32  # AD link error
        AD_SELFCHK = 16  # AD self check error
        AD_SLOPE = 8  # AD slope error
        ROM = 4  # Control ROM error
        RAM = 2  # RAM selftest failed
        CALIBRATION = 1  # Calibration checksum error or cal range issue
        NO_ERR = 0  # Should be obvious

    class SRQ(IntFlag):
        """Enum element for SRQ mask bit decoding
        """
        Power_on = 128
        Calibration = 32
        Front_panel_button = 16
        Internal_error = 8
        Syntax_error = 4
        Data_ready = 1

    def get_status(self):
        """Method to read the status bytes from the instrument
        :return current_status: a byte array representing the instrument status
        :rtype current_status: bytes
        """
        self.write("B")
        current_status = self.adapter.read_bytes(5)
        return current_status

    # decoder functions
    @classmethod
    def decode_status(cls, status_bytes, field=None):
        """Method to handle the decoding of the status bytes into something meaningfull

        :param status_bytes: list of bytes to be decoded
        :param field: name of field to be returned
        :return ret_val: int status value

        """
        ret_val = Status(Status_bytes(*status_bytes))
        if field is None:
            return ret_val.b
        elif field == "SRQ":
            return cls.SRQ(getattr(ret_val.B, "byte3"))
        else:
            return getattr(ret_val.b, field)

    @classmethod
    def decode_mode(cls, function):
        """Method to decode current mode

        :param function: int indicating the measurement function selected
        :return cur_mode: string with the current measurement mode
        :rtype cur_mode: str

        """
        cur_mode = cls.INV_MODES["F" + str(function)]
        return cur_mode

    @classmethod
    def decode_range(cls, range_undecoded, function):
        """Method to decode current range

        :param range_undecoded: int to be decoded
        :param function: int indicating the measurement function selected
        :return cur_range: float value repesenting the active measurment range
        :rtype cur_range: float

        """
        cur_mode = cls.INV_MODES["F" + str(function)]
        if cur_mode == "DCV":
            correction_factor = 3
        elif cur_mode in ["ACV", "ACI", "DCI"]:
            correction_factor = 2
        else:
            correction_factor = 0
        cur_range = 3 * math.pow(10, range_undecoded - correction_factor)
        return cur_range

    @staticmethod
    def decode_trigger(status_bytes):
        """Method to decode trigger mode

        :param status_bytes: list of bytes to be decoded
        :return trigger_mode: string with the current trigger mode
        :rtype trigger_mode: str

        """
        cur_stat = Status(Status_bytes(*status_bytes))
        i_trig = cur_stat.b.int_trig
        e_trig = cur_stat.b.ext_trig
        if i_trig == 0:
            if e_trig == 0:
                trigger_mode = "hold"
            else:
                trigger_mode = "external"
        else:
            trigger_mode = "internal"
        return trigger_mode

    # commands/properties for instrument control
    @property
    def active_connectors(self):
        """Return selected connectors ("front"/"back"), based on front-panel selector switch
        """
        selection = self.status.front_rear
        if selection == 1:
            return "front"
        else:
            return "back"

    @property
    def auto_range_enabled(self):
        """ Property describing the auto-ranging status

        ======  ============================================
        Value   Status
        ======  ============================================
        True    auto-range function activated
        False   manual range selection / auto-range disabled
        ======  ============================================

        The range can be set with the :py:attr:`range` property
        """
        selection = self.status.auto_range
        return bool(selection)

    @property
    def auto_zero_enabled(self):
        """ Return auto-zero status, this property can be set

        ======  ==================
        Value   Status
        ======  ==================
        True    auto-zero active
        False   auto-zero disabled
        ======  ==================

        """
        selection = self.status.auto_zero
        return bool(selection)

    @auto_zero_enabled.setter
    def auto_zero_enabled(self, value):
        az_set = int(value)
        AZ_str = "Z" + str(int(strict_discrete_set(az_set, [0, 1])))
        self.write(AZ_str)

    @property
    def calibration_enabled(self):
        """Return calibration enable switch setting,
        based on front-panel selector switch

        ======  ===================
        Value   Status
        ======  ===================
        True    calbration possible
        False   calibration locked
        ======  ===================

        """
        selection = self.status.cal_enable
        return bool(selection)

    def check_errors(self):
        """
        Method to read the error status register

        :return error_status: one byte with the error status register content
        :rtype error_status: int
        """
        # Read the error status reigster only one time for this method, as
        # the manual states that reading the error status register also clears it.
        current_errors = self.error_status
        if current_errors != 0:
            log.error("HP3478A error detected: %s", self.ERRORS(current_errors))
        return self.ERRORS(current_errors)

    error_status = Instrument.measurement(
        "E",
        """Checks the error status register

        """,
        cast=int,
    )

    def display_reset(self):
        """ Reset the display of the instrument.

        """
        self.write("D1")

    display_text = Instrument.setting(
        "D2%s",
        """Displays up to 12 upper-case ASCII characters on the display.

        """,
        set_process=(lambda x: str.upper(x[0:12])),
    )

    display_text_no_symbol = Instrument.setting(
        "D3%s",
        """Displays up to 12 upper-case ASCII characters on the display and
        disables all symbols on the display.

        """,
        set_process=(lambda x: str.upper(x[0:12])),
    )

    measure_ACI = Instrument.measurement(
        MODES["ACI"],
        """
        Returns the measured value for AC current as a float in A.

        """,
    )

    measure_ACV = Instrument.measurement(
        MODES["ACV"],
        """
        Returns the measured value for AC Voltage as a float in V.

        """,
    )

    measure_DCI = Instrument.measurement(
        MODES["DCI"],
        """
        Returns the measured value for DC current as a float in A.

        """,
    )

    measure_DCV = Instrument.measurement(
        MODES["DCV"],
        """
        Returns the measured value for DC Voltage as a float in V.

        """,
    )

    measure_R2W = Instrument.measurement(
        MODES["R2W"],
        """
        Returns the measured value for 2-wire resistance as a float in Ohm.

        """,
    )

    measure_R4W = Instrument.measurement(
        MODES["R4W"],
        """
        Returns the measured value for 4-wire resistance as a float in Ohm.

        """,
    )

    measure_Rext = Instrument.measurement(
        MODES["Rext"],
        """
        Returns the measured value for extended resistance mode (>30M, 2-wire)
        resistance as a float in Ohm.
        """,
    )

    @property
    def mode(self):
        """Return current selected measurement mode, this propery can be set.
        Allowed values are

        ====  ==============================================================
        Mode  Function
        ====  ==============================================================
        ACI   AC current
        ACV   AC voltage
        DCI   DC current
        DCV   DC voltage
        R2W   2-wire resistance
        R4W   4-wire resistance
        Rext  extended resistance method (requires additional 10 M resistor)
        ====  ==============================================================
        """
        current_mode = self.decode_mode(self.status.function)
        return current_mode

    @mode.setter
    def mode(self, value):
        mode_set = self.MODES[strict_discrete_set(value, self.MODES)]
        self.write(mode_set)

    @property
    def range(self):
        """Returns the current measurement range, this property can be set.

        Valid values are :

        ====  =======================================
        Mode  Range
        ====  =======================================
        ACI   0.3, 3, auto
        ACV   0.3, 3, 30, 300, auto
        DCI   0.3, 3, auto
        DCV   0.03, 0.3, 3, 30, 300, auto
        R2W   30, 300, 3000, 3E4, 3E5, 3E6, 3E7, auto
        R4W   30, 300, 3000, 3E4, 3E5, 3E6, 3E7, auto
        Rext  3E7, auto
        ====  =======================================

        """
        current_range = self.decode_range(self.status.range, self.status.function)
        return current_range

    @range.setter
    def range(self, value):
        cur_mode = self.mode
        value = strict_discrete_set(value, self.RANGES[cur_mode])
        set_range = self.RANGES[cur_mode][value]
        self.write(set_range)

    @property
    def resolution(self):
        """Returns current selected resolution, this property can be set.

        Possible values are 3,4 or 5 (for 3 1/2, 4 1/2 or 5 1/2 digits of resolution)
        """
        number_of_digit = 6 - self.status.digits
        return number_of_digit

    @resolution.setter
    def resolution(self, value):
        resolution_string = "N" + str(strict_discrete_set(value, [3, 4, 5]))
        self.write(resolution_string)

    @property
    def status(self):
        """
        Returns an object representing the current status of the unit.

        """
        current_status = self.decode_status(self.get_status())
        return current_status

    @property
    def SRQ_mask(self):
        """Return current SRQ mask, this property can be set,

        bit assigment for SRQ:

        =========  ==========================
        Bit (dec)  Description
        =========  ==========================
         1         SRQ when Data ready
         4         SRQ when Syntax error
         8         SRQ when internal error
        16         front panel SQR button
        32         SRQ by invalid calibration
        =========  ==========================

        """
        mask = self.decode_status(self.get_status(), "SRQ")
        return mask

    @SRQ_mask.setter
    def SRQ_mask(self, value):
        mask_str = "M" + format(strict_range(value, [0, 63]), "2o")
        self.write(mask_str)

    @property
    def trigger(self):
        """Return current selected trigger mode, this property can be set

        Possibe values are:

        ========  ===========================================
        Value     Meaning
        ========  ===========================================
        auto      automatic trigger (internal)
        internal  automatic trigger (internal)
        external  external trigger (connector on back or GET)
        hold      holds the measurement
        fast      fast trigger for AC measurements
        ========  ===========================================

        """
        trigger = self.decode_trigger(self.get_status())
        return trigger

    @trigger.setter
    def trigger(self, value):
        trig_set = self.TRIGGERS[strict_discrete_set(value, self.TRIGGERS)]
        self.write(trig_set)

    # Functions using low-level access via instrument.adapter.connection methods

    def GPIB_trigger(self):
        """
        Initate trigger via low-level GPIB-command (aka GET - group execute trigger)

        """
        self.adapter.connection.assert_trigger()

    def reset(self):
        """
        Initatiates a reset (like a power-on reset) of the HP3478A

        """
        self.adapter.connection.clear()

    def shutdown(self):
        """
        provides a way to gracefully close the connection to the HP3478A

        """
        self.adapter.connection.clear()
        self.adapter.connection.close()
        super().shutdown()
Exemple #15
0
class SciMag(Instrument):
    """ Represents the Scientific Magnetics (Twickenham Scientific Instruments)
    Superconducting Magnet Controller SciMag S-11-52-13.

    Provides a high-level interface for
    interacting with the instrument.
    WARNING: Use at your own risk!
    Faulty operation of a superconducting magnet can lead to severe damage or injury!
    Read the manual: https://www.twicksci.co.uk/manuals/pdf/smc552+.pdf
    """

    _RAMP_RATE_MAX = 5
    _VOLTAGE_LIMIT_MAX = 1

    pause = Instrument.setting(
        "P%d",
        """A boolean property that controls the pause-status,
         which takes the values True (pause) and False (continue)
         """,
        validator=strict_discrete_set,
        values={
            True: 1,
            False: 0
        },
        map_values=True)

    unit = Instrument.setting(
        "T%d",
        """Sets the units displayed and can take the values
         "A"mpere or "T"esla
         """,
        validator=strict_discrete_set,
        values={
            "A": 0,
            "T": 1
        },
        map_values=True)

    reverse_switch_on = Instrument.setting(
        "D%d",
        """Sets the direction of the reversing switch, if installed.
        Forward if switch is off; Reverse if switch is on.
        Takes boolean value. True = Switch is on, Field is reverse.
        WARNING: Change only if Magnet has been ramped to Zero!
        """,
        validator=strict_discrete_set,
        values={
            False: 0,
            True: 1
        },
        map_values=True)

    ramp_target = Instrument.setting(
        "R%d",
        """Sets Ramp Target. Can take the values "Z"ero, "L"ower, "U"pper.
        """,
        validator=strict_discrete_set,
        values={
            "Z": 0,
            "L": 1,
            "U": 2
        },
        map_values=True)

    persistent_mode_heater = Instrument.setting("H%d",
                                                """Sets the persistent mode.
        Can take the values "O"ff, "C"onditional on, "U"nconditional on, "R"eset persistent current mode to zero.
        WARNING: Unconditional on will turn on the heater without any interlocks! Use at own risk.
        Standard mode to switch heater on is Conditional.
        Simplified Commands are "True" (Conditional on) and "False" (Off).
        """,
                                                validator=strict_discrete_set,
                                                values={
                                                    "O": 0,
                                                    "C": 1,
                                                    "U": 2,
                                                    "R": 9,
                                                    False: 0,
                                                    True: 1
                                                },
                                                map_values=True)

    ramp_rate = Instrument.setting(
        "A%08.5f",
        """Sets the Ramp rate in A/s.""",
        validator=strict_range,
        values=[0, _RAMP_RATE_MAX],
    )

    external_trip = Instrument.setting("X%d",
                                       """Sets the External trip:
         False = Off, True = Simple trip On, "Auto" = Auto-rampdown On.""",
                                       validator=strict_discrete_set,
                                       values={
                                           False: 0,
                                           True: 1,
                                           "Auto": 4
                                       },
                                       map_values=True)

    terminal_voltage_limit = Instrument.setting(
        "Y%04.1f",
        """Sets the terminal voltage limit to nn.n V.""",
        validator=strict_range,
        values=[0, _VOLTAGE_LIMIT_MAX])

    heater_current_output = Instrument.setting(
        "W%03d",
        """Sets the Heater current output to nnn mA.""",
        validator=strict_range,
        values=[0, 255])

    calibrate_tesla_per_amp = Instrument.setting(
        "C%08.6f",
        """Calibrate the output to Tesla per Amp value of the superconducting magnet.
         The Value is assumed to lie between 0.01 and 0.5 T/A
         and will be set to zero if a number outside this range is entered.
         """,
        validator=strict_range,
        values=[0.01, 0.5],
    )

    lower_setpoint = Instrument.setting("L%08.4f",
                                        """Sets the lower setpoint. 
        Unit depends on the unit set in the magnet controller.
        Unit can be "T" or "A" and must be set before.
        """,
                                        validator=strict_range,
                                        values=[0, 200])

    upper_setpoint = Instrument.setting("U%08.4f",
                                        """Sets the upper setpoint. 
        Unit depends on the unit set in the magnet controller.
        Unit can be "T" or "A" and must be set before.
        """,
                                        validator=strict_range,
                                        values=[0, 200])

    output_parameters_amps = Instrument.measurement(
        "G",
        """Returns dict of output parameters.""",
        preprocess_reply=extract_value_output_parameters_amps)

    output_parameters_tesla = Instrument.measurement(
        "N",
        """Returns dict of output parameters.""",
        preprocess_reply=extract_value_output_parameters_tesla)

    magnet_status = Instrument.measurement(
        "J",
        "Returns dict of magnet status.",
        preprocess_reply=extract_value_magnet_status)

    current_status = Instrument.measurement(
        "K",
        "Returns dict of current status.",
        preprocess_reply=extract_value_current_status)

    operating_parameters = Instrument.measurement(
        "O",
        "Returns dict of operation parameters",
        preprocess_reply=extract_value_operating_parameters)

    set_point_status = Instrument.measurement(
        "S",
        "Returns dict of set point status.",
        preprocess_reply=extract_value_set_point_status)

    def __init__(self,
                 resourceName,
                 ramp_rate_max=5,
                 voltage_limit_max=1,
                 field_max=10,
                 **kwargs):
        super().__init__(resourceName,
                         "Scientific Magnetics SMC",
                         includeSCPI=False,
                         **kwargs)
        self._RAMP_RATE_MAX = ramp_rate_max  # A/s
        self._VOLTAGE_LIMIT_MAX = voltage_limit_max  # V
        self._T_MAX = field_max  # T

    @property
    def tesla(self) -> float:
        """
        High-Level property for setting and getting the field in the solenoid.

        If installed, the setter-funktion toggles the
        reversal-switch automatically at zero-field.
        """
        self.unit = "T"
        value = self.output_parameters_tesla["field"]
        return value

    @tesla.setter
    def tesla(self, value: float):
        self.unit = "T"
        if value == 0:
            self.ramp_target = "Z"
        elif abs(value) < self._T_MAX:
            # 1. Get actual direction
            if self.operating_parameters["switch_direction"] == 0:
                sign = +1
            elif self.operating_parameters["switch_direction"] == 1:
                sign = -1
            else:
                sign = 0

            # 2. Check, if value and sign are both positive or both negative
            if value * sign > 0:
                self.upper_setpoint = abs(value)
            elif value * sign < 0:
                self.ramp_target = "Z"
                # At zero-field: switch direction.
                while not (self.current_status["ramp_status"] == 1):
                    time.sleep(1)
                if value > 0:
                    self.reverse_switch_on = False
                elif value < 0:
                    self.reverse_switch_on = True
                self.upper_setpoint = abs(value)
            self.ramp_target = "U"
        else:
            log.warning(
                "Intended field was: %08.4f T.\nAllowed maximum field is: %08.4f T"
                % (value, self._T_MAX))
            raise UserWarning(
                "Intended field value exceeded range of the solenoid. Value was NOT set."
            )

    def shutdown(self):
        """Brings the instrument to a safe and stable state.
        """
        self.isShutdown = True
        log.info("Shutting down %s" % self.name)
        self.persistent_mode_heater = True
        self.ramp_target = "Z"
        self.pause = False
class KeysightDSOX1102G(Instrument):
    """ Represents the Keysight DSOX1102G Oscilloscope interface for interacting
    with the instrument.

    Refer to the Keysight DSOX1102G Oscilloscope Programmer's Guide for further details about
    using the lower-level methods to interact directly with the scope.

    .. code-block:: python
    
        scope = KeysightDSOX1102G(resource)
        scope.autoscale()
        ch1_data_array, ch1_preamble = scope.download_data(source="channel1", points=2000)
        # ...
        scope.shutdown()

    Known issues:
    
    - The digitize command will be completed before the operation is. May lead to
      VI_ERROR_TMO (timeout) occuring when sending commands immediately after digitize.
      Current fix: if deemed necessary, add delay between digitize and follow-up command
      to scope.
    """

    BOOLS = {True: 1, False: 0}

    def __init__(self, adapter, **kwargs):
        super(KeysightDSOX1102G,
              self).__init__(adapter, "Keysight DSOX1102G Oscilloscope",
                             **kwargs)
        # Account for setup time for timebase_mode, waveform_points_mode
        self.adapter.connection.timeout = 6000
        self.ch1 = Channel(self, 1)
        self.ch2 = Channel(self, 2)

    #################
    # Channel setup #
    #################

    def autoscale(self):
        """ Autoscale displayed channels. """
        self.write(":autoscale")

    ##################
    # Timebase Setup #
    ##################

    @property
    def timebase(self):
        """ Read timebase setup as a dict containing the following keys:
            - "REF": position on screen of timebase reference (str)
            - "MAIN:RANG": full-scale timebase range (float)
            - "POS": interval between trigger and reference point (float)
            - "MODE": mode (str)"""
        return self._timebase()

    timebase_mode = Instrument.control(
        ":TIMebase:MODE?",
        ":TIMebase:MODE %s",
        """ A string parameter that sets the current time base. Can be "main", 
        "window", "xy", or "roll".""",
        validator=strict_discrete_set,
        values={
            "main": "MAIN",
            "window": "WIND",
            "xy": "XY",
            "roll": "ROLL"
        },
        map_values=True)

    timebase_offset = Instrument.control(
        ":TIMebase:POSition?",
        ":TIMebase:REFerence CENTer;:TIMebase:POSition %f",
        """ A float parameter that sets the time interval in seconds between the trigger 
        event and the reference position (at center of screen by default).""")

    timebase_range = Instrument.control(
        ":TIMebase:RANGe?", ":TIMebase:RANGe %f",
        """ A float parameter that sets the full-scale horizontal time in seconds for the 
        main window.""")

    timebase_scale = Instrument.control(
        ":TIMebase:SCALe?", ":TIMebase:SCALe %f",
        """ A float parameter that sets the horizontal scale (units per division) in seconds 
        for the main window.""")

    ###############
    # Acquisition #
    ###############

    acquisition_type = Instrument.control(
        ":ACQuire:TYPE?",
        ":ACQuire:TYPE %s",
        """ A string parameter that sets the type of data acquisition. Can be "normal", "average",
        "hresolution", or "peak".""",
        validator=strict_discrete_set,
        values={
            "normal": "NORM",
            "average": "AVER",
            "hresolution": "HRES",
            "peak": "PEAK"
        },
        map_values=True)

    acquisition_mode = Instrument.control(
        ":ACQuire:MODE?",
        ":ACQuire:MODE %s",
        """ A string parameter that sets the acquisition mode. Can be "realtime" or "segmented".""",
        validator=strict_discrete_set,
        values={
            "realtime": "RTIM",
            "segmented": "SEGM"
        },
        map_values=True)

    def run(self):
        """ Starts repetitive acquisitions. This is the same as pressing the Run key on the front panel."""
        self.write(":run")

    def stop(self):
        """  Stops the acquisition. This is the same as pressing the Stop key on the front panel."""
        self.write(":stop")

    def single(self):
        """ Causes the instrument to acquire a single trigger of data.
        This is the same as pressing the Single key on the front panel. """
        self.write(":single")

    _digitize = Instrument.setting(
        ":DIGitize %s",
        """ Acquire waveforms according to the settings of the :ACQuire commands and specified source,
         as a string parameter that can take the following values: "channel1", "channel2", "function",
         "math", "fft", "abus", or "ext". """,
        validator=strict_discrete_set,
        values={
            "channel1": "CHAN1",
            "channel2": "CHAN2",
            "function": "FUNC",
            "math": "MATH",
            "fft": "FFT",
            "abus": "ABUS",
            "ext": "EXT"
        },
        map_values=True)

    def digitize(self, source: str):
        """ Acquire waveforms according to the settings of the :ACQuire commands. Ensure a delay
        between the digitize operation and further commands, as timeout may be reached before
        digitize has completed.
        :param source: "channel1", "channel2", "function", "math", "fft", "abus", or "ext"."""
        self._digitize = source

    waveform_points_mode = Instrument.control(
        ":waveform:points:mode?",
        ":waveform:points:mode %s",
        """ A string parameter that sets the data record to be transferred with the waveform_data
         method. Can be "normal", "maximum", or "raw".""",
        validator=strict_discrete_set,
        values={
            "normal": "NORM",
            "maximum": "MAX",
            "raw": "RAW"
        },
        map_values=True)
    waveform_points = Instrument.control(
        ":waveform:points?",
        ":waveform:points %d",
        """ An integer parameter that sets the number of waveform points to be transferred with
        the waveform_data method. Can be any of the following values: 
        100, 250, 500, 1000, 2 000, 5 000, 10 000, 20 000, 50 000, 62 500.
        
        Note that the oscilloscope may provide less than the specified nb of points. """,
        validator=strict_discrete_set,
        values=[100, 250, 500, 1000, 2000, 5000, 10000, 20000, 50000, 62500])
    waveform_source = Instrument.control(
        ":waveform:source?",
        ":waveform:source %s",
        """ A string parameter that selects the analog channel, function, or reference waveform 
        to be used as the source for the waveform methods. Can be "channel1", "channel2", "function", 
        "fft", "wmemory1", "wmemory2", or "ext".""",
        validator=strict_discrete_set,
        values={
            "channel1": "CHAN1",
            "channel2": "CHAN2",
            "function": "FUNC",
            "fft": "FFT",
            "wmemory1": "WMEM1",
            "wmemory2": "WMEM2",
            "ext": "EXT"
        },
        map_values=True)
    waveform_format = Instrument.control(
        ":waveform:format?",
        ":waveform:format %s",
        """ A string parameter that controls how the data is formatted when sent from the 
        oscilloscope. Can be "ascii", "word" or "byte". Words are transmitted in big endian by default.""",
        validator=strict_discrete_set,
        values={
            "ascii": "ASC",
            "word": "WORD",
            "byte": "BYTE"
        },
        map_values=True)

    @property
    def waveform_preamble(self):
        """ Get preamble information for the selected waveform source as a dict with the following keys:
            - "format": byte, word, or ascii (str)
            - "type": normal, peak detect, or average (str)
            - "points": nb of data points transferred (int)
            - "count": always 1 (int)
            - "xincrement": time difference between data points (float)
            - "xorigin": first data point in memory (float)
            - "xreference": data point associated with xorigin (int)
            - "yincrement": voltage difference between data points (float)
            - "yorigin": voltage at center of screen (float)
            - "yreference": data point associated with yorigin (int)"""
        return self._waveform_preamble()

    @property
    def waveform_data(self):
        """ Get the binary block of sampled data points transmitted using the IEEE 488.2 arbitrary
        block data format."""
        # Other waveform formats raise UnicodeDecodeError
        self.waveform_format = "ascii"

        data = self.values(":waveform:data?")
        # Strip header from first data element
        data[0] = float(data[0][10:])

        return data

    ################
    # System Setup #
    ################

    @property
    def system_setup(self):
        """ A string parameter that sets up the oscilloscope. Must be in IEEE 488.2 format.
        It is recommended to only set a string previously obtained from this command."""
        return self.ask(":system:setup?")

    @system_setup.setter
    def system_setup(self, setup_string):
        self.write(":system:setup " + setup_string)

    def ch(self, channel_number):
        if channel_number == 1:
            return self.ch1
        elif channel_number == 2:
            return self.ch2
        else:
            raise ValueError("Invalid channel number. Must be 1 or 2.")

    def clear_status(self):
        """ Clear device status. """
        self.write("*CLS")

    def factory_reset(self):
        """ Factory default setup, no user settings remain unchanged. """
        self.write("*RST")

    def default_setup(self):
        """ Default setup, some user settings (like preferences) remain unchanged. """
        self.write(":SYSTem:PRESet")

    def timebase_setup(self,
                       mode=None,
                       offset=None,
                       horizontal_range=None,
                       scale=None):
        """ Set up timebase. Unspecified parameters are not modified. Modifying a single parameter might
        impact other parameters. Refer to oscilloscope documentation and make multiple consecutive calls
        to channel_setup if needed.

        :param mode: Timebase mode, can be "main", "window", "xy", or "roll".
        :param offset: Offset in seconds between trigger and center of screen.
        :param horizontal_range: Full-scale range in seconds.
        :param scale: Units-per-division in seconds."""

        if mode is not None: self.timebase_mode = mode
        if offset is not None: self.timebase_offset = offset
        if horizontal_range is not None: self.timebase_range = horizontal_range
        if scale is not None: self.timebase_scale = scale

    def download_image(self, format_="png", color_palette="color"):
        """ Get image of oscilloscope screen in bytearray of specified file format.

        :param format_: "bmp", "bmp8bit", or "png"
        :param color_palette: "color" or "grayscale"
        """
        query = f":DISPlay:DATA? {format_}, {color_palette}"
        # Using binary_values query because default interface does not support binary transfer
        img = self.binary_values(query, header_bytes=10, dtype=np.uint8)
        return bytearray(img)

    def download_data(self, source, points=62500):
        """ Get data from specified source of oscilloscope. Returned objects are a np.ndarray of data
        values (no temporal axis) and a dict of the waveform preamble, which can be used to build the
        corresponding time values for all data points.

        Multimeter will be stopped for proper acquisition.

        :param source: measurement source, can be "channel1", "channel2", "function", "fft", "wmemory1",
                        "wmemory2", or "ext".
        :param points: integer number of points to acquire. Note that oscilloscope may return less points than
                        specified, this is not an issue of this library. Can be 100, 250, 500, 1000,
                        2000, 5000, 10000, 20000, 50000, or 62500.

        :return data_ndarray, waveform_preamble_dict: see waveform_preamble property for dict format.
        """
        # TODO: Consider downloading from multiple sources at the same time.
        self.waveform_source = source
        self.waveform_points_mode = "normal"
        self.waveform_points = points

        preamble = self.waveform_preamble
        data_bytes = self.waveform_data
        return np.array(data_bytes), preamble

    def _timebase(self):
        """
        Reads setup data from timebase and converts it to a more convenient dict of values.
        """
        tb_setup_raw = self.ask(":timebase?").strip("\n")

        # tb_setup_raw hat the following format:
        # :TIM:MODE MAIN;REF CENT;MAIN:RANG +1.00E-03;POS +0.0E+00

        # Cut out the ":TIM:" at beginning and split string
        tb_setup_splitted = tb_setup_raw[5:].split(";")

        # Create dict of setup parameters
        tb_setup = dict(map(lambda v: v.split(" "), tb_setup_splitted))

        # Convert values to specific type
        to_str = ["MODE", "REF"]
        to_float = ["MAIN:RANG", "POS"]
        for key in tb_setup:
            if key in to_str:
                tb_setup[key] = str(tb_setup[key])
            elif key in to_float:
                tb_setup[key] = float(tb_setup[key])

        return tb_setup

    def _waveform_preamble(self):
        """
        Reads waveform preamble and converts it to a more convenient dict of values.
        """
        vals = self.values(":waveform:preamble?")
        # Get values to dict
        vals_dict = dict(
            zip([
                "format", "type", "points", "count", "xincrement", "xorigin",
                "xreference", "yincrement", "yorigin", "yreference"
            ], vals))
        # Map element values
        format_map = {0: "BYTE", 1: "WORD", 4: "ASCII"}
        type_map = {0: "NORMAL", 1: "PEAK DETECT", 2: "AVERAGE", 3: "HRES"}
        vals_dict["format"] = format_map[int(vals_dict["format"])]
        vals_dict["type"] = type_map[int(vals_dict["type"])]

        # Correct types
        to_int = ["points", "count", "xreference", "yreference"]
        to_float = ["xincrement", "xorigin", "yincrement", "yorigin"]
        for key in vals_dict:
            if key in to_int:
                vals_dict[key] = int(vals_dict[key])
            elif key in to_float:
                vals_dict[key] = float(vals_dict[key])

        return vals_dict
Exemple #17
0
class Axis(object):
    """ Represents a single open loop axis of the Attocube ANC350

    :param axis: axis identifier, integer from 1 to 7
    :param controller: ANC300Controller instance used for the communication
    """

    serial_nr = Instrument.measurement("getser", "Serial number of the axis")

    voltage = Instrument.control(
        "getv",
        "setv %.3f",
        """ Amplitude of the stepping voltage in volts from 0 to 150 V. This
            property can be set. """,
        validator=strict_range,
        values=[0, 150])

    frequency = Instrument.control(
        "getf",
        "setf %.3f",
        """ Frequency of the stepping motion in Hertz from 1 to 10000 Hz.
            This property can be set. """,
        validator=strict_range,
        values=[1, 10000],
        cast=int)

    mode = Instrument.control(
        "getm",
        "setm %s",
        """ Axis mode. This can be 'gnd', 'inp', 'cap', 'stp', 'off',
            'stp+', 'stp-'. Available modes depend on the actual axis model""",
        validator=strict_discrete_set,
        values=['gnd', 'inp', 'cap', 'stp', 'off', 'stp+', 'stp-'])

    offset_voltage = Instrument.control(
        "geta",
        "seta %.3f",
        """ Offset voltage in Volts from 0 to 150 V.
            This property can be set. """,
        validator=strict_range,
        values=[0, 150])

    pattern_up = Instrument.control(
        "getpu",
        "setpu %s",
        """ step up pattern of the piezo drive. 256 values ranging from 0
            to 255 representing the the sequence of output voltages within one
            step of the piezo drive. This property can be set, the set value
            needs to be an array with 256 integer values. """,
        validator=truncated_int_array_strict_length,
        values=[256, [0, 255]],
        set_process=lambda a: " ".join("%d" % v for v in a),
        separator='\r\n',
        cast=int)

    pattern_down = Instrument.control(
        "getpd",
        "setpd %s",
        """ step down pattern of the piezo drive. 256 values ranging from 0
            to 255 representing the the sequence of output voltages within one
            step of the piezo drive. This property can be set, the set value
            needs to be an array with 256 integer values. """,
        validator=truncated_int_array_strict_length,
        values=[256, [0, 255]],
        set_process=lambda a: " ".join("%d" % v for v in a),
        separator='\r\n',
        cast=int)

    output_voltage = Instrument.measurement("geto",
                                            """ Output voltage in volts.""")

    capacity = Instrument.measurement(
        "getc", """ Saved capacity value in nF of the axis.""")

    stepu = Instrument.setting(
        "stepu %d",
        """ Step upwards for N steps. Mode must be 'stp' and N must be
            positive.""",
        validator=strict_range,
        values=[0, inf])

    stepd = Instrument.setting(
        "stepd %d",
        """ Step downwards for N steps. Mode must be 'stp' and N must be
            positive.""",
        validator=strict_range,
        values=[0, inf])

    def __init__(self, controller, axis):
        self.axis = str(axis)
        self.controller = controller

    def _add_axis_id(self, command):
        """ add axis id to a command string at the correct position after the
        initial command, but before a potential value

        :param command: command string
        :returns: command string with added axis id
        """
        cmdparts = command.split()
        cmdparts.insert(1, self.axis)
        return ' '.join(cmdparts)

    def ask(self, command, **kwargs):
        return self.controller.ask(self._add_axis_id(command), **kwargs)

    def write(self, command, **kwargs):
        return self.controller.write(self._add_axis_id(command), **kwargs)

    def values(self, command, **kwargs):
        return self.controller.values(self._add_axis_id(command), **kwargs)

    def stop(self):
        """ Stop any motion of the axis """
        self.write('stop')

    def move(self, steps, gnd=True):
        """ Move 'steps' steps in the direction given by the sign of the
        argument. This method will change the mode of the axis automatically
        and ground the axis on the end if 'gnd' is True. The method returns
        only when the movement is finished.

        :param steps: finite integer value of steps to be performed. A positive
            sign corresponds to upwards steps, a negative sign to downwards
            steps.
        :param gnd: bool, flag to decide if the axis should be grounded after
            completion of the movement
        """
        self.mode = 'stp'
        # perform the movement
        if steps > 0:
            self.stepu = steps
        elif steps < 0:
            self.stepd = abs(steps)
        else:
            pass  # do not set stepu/d to 0 since it triggers a continous move
        # wait for the move to finish
        self.write('stepw')
        if gnd:
            self.mode = 'gnd'

    def measure_capacity(self):
        """ Obtains a new measurement of the capacity. The mode of the axis
        returns to 'gnd' after the measurement.

        :returns capacity: the freshly measured capacity in nF.
        """
        self.mode = 'cap'
        # wait for the measurement to finish
        self.ask('capw')
        return self.capacity
Exemple #18
0
class RohdeSMB100A(Instrument):
    """ Instrument code to control a Rohde & Schwarz SMB100A RF Signal Generator.

    .. code-block:: python

        from pymeasure.instruments.rohdeschwarz import RohdeSMB100A

        sig = RohdeSMB100A("GPIB0::29", read_termination = '\\n', write_termination = '\\n', timeout=None)

        # reset instrument to clear previous settings.
        sig.reset()

        # set freq and power units
        sig.freq_unit = "MHz"
        sig.power_unit = "DBM"

        # set frequency and power levels
        sig.power_level = -20 #dBm
        sig.fixed_freq = 700 #MHz

        # turn on signal
        sig.output = "ON"

        # perform any measurements needed.

        # turn off signal
        sig.output = "OFF"

    """
    def __init__(self, resourceName, **kwargs):
        super().__init__(resourceName,
                         "Rohde & Schwarz SMB100A RF Signal Generator",
                         **kwargs)
        self.freq_unit = "GHz"

#######
# POWER
#######

# RF power level

    power_level = Instrument.control(
        ":POW?",
        ":POW %s",
        """ Sets the RF power level in dBm applied to DUT.

        If a level offset is included, the output power level is adjusted accordingly. The power level is not checked to see if the instrument can support it because it depends on the option installed. When setting power level, please refer the operation manual to ensure that your instrument can support it at your operating frequency. Use :meth:`~.RohdeSMB100A.get_instrument_options` method to identify the options installed in the instrument.

        .. code-block:: python

            sig.power_level = -5
        """,
        check_set_errors=True,
        check_get_errors=True)

    # RF power offset
    power_offset = Instrument.control(":POW:OFFS?",
                                      ":POW:OFFS %s",
                                      """ Sets the RF offset power level in dB.

        Specifies the constant level offset of a downstream attenuator/amplifier. If level offset is entered, the level entered with :meth:`~.RohdeSMB100A.power_level` no longer corresponds to the RF output level at connector.

        .. code-block:: python

            sig.power_offset = 5
        """,
                                      validator=truncated_discrete_set,
                                      values=np.arange(-100, 100, 0.01),
                                      check_set_errors=True,
                                      check_get_errors=True)

    ###########
    # FREQUENCY
    ###########

    # fixed frequency

    @property
    def fixed_freq(self):
        """
        Sets the fixed frequency of the RF signal.

        The range of allowed frequencies depends on the instrument option, which can be checked using :meth:`~.RohdeSMB100A.get_instrument_options`.

        .. code-block:: python

            sig.fixed_freq = 1 #GHz (default)

        To change default frequency units, change the object variable `freq_unit`. For example, to set frequency to 1 MHz,

        .. code-block:: python

            sig.freq_unit = "MHz"
            sig.fixed_freq = 1
        """
        value = self.ask(":FREQ?")
        return value

    @fixed_freq.setter
    def fixed_freq(self, value):
        self.write(":FREQ {}{}".format(value, self.freq_unit))

########
# OUTPUT
########

    output = Instrument.setting(":OUTP %s",
                                """ Turns the RF output on and off.

        - Values: :code:`ON`, :code:`OFF`

        .. code-block:: python

            sig.output = "ON"
        """,
                                validator=strict_discrete_set,
                                values={'OFF', 'ON'},
                                map_values=False,
                                check_set_errors=True,
                                check_get_errors=True)

    #######
    # UNITS
    #######

    power_unit = Instrument.control("UNIT:POW?",
                                    "UNIT:POW %s",
                                    """
        Defines the default unit for power parameters. This setting affects the GUI, as well as all remote control commands that determine power values.

        - Values: :code:`V`, :code:`DBUV`, :code:`DBM`

        .. code-block:: python

            sig.power_unit = "DBM" # default setting
        """,
                                    validator=strict_discrete_set,
                                    values={'V', 'DBUV', 'DBM'},
                                    map_values=False,
                                    check_set_errors=True,
                                    check_get_errors=True)

    #########
    # GENERAL
    #########

    get_instrument_options = Instrument.measurement(
        "*OPT?", """ Get the options installed on instrument. 
        
        .. code-block:: python

            sig.get_instrument_options
        """)