Exemplo n.º 1
0
class HP6632b(SCPIInstrument, HP6652a):

    """
    The HP6632b is a system dc power supply with an output rating of 0-20V/0-5A,
    precision low current measurement and low output noise.

    According to the manual this class MIGHT be usable for any HP power supply
    with a model number

    - HP663Xb with X in {1, 2, 3, 4},
    - HP661Xc with X in {1,2, 3, 4} and
    - HP663X2A for X in {1, 3}, without the additional measurement capabilities.

    HOWEVER, it has only been tested by the author with HP6632b supplies.

    Example usage:

    >>> import instruments as ik
    >>> psu = ik.hp.HP6632b.open_gpibusb('/dev/ttyUSB0', 6)
    >>> psu.voltage = 10             # Sets voltage to 10V.
    >>> psu.output = True            # Enable output
    >>> psu.voltage
    array(10.0) * V
    >>> psu.voltage_trigger = 20     # Set transient trigger voltage
    >>> psu.init_output_trigger()    # Prime instrument to initiated state, ready for trigger
    >>> psu.trigger()                # Send trigger
    >>> psu.voltage
    array(10.0) * V
    """

    # ENUMS ##

    class ALCBandwidth(IntEnum):
        """
        Enum containing valid ALC bandwidth modes for the hp6632b
        """
        normal = 1.5e4
        fast = 6e4

    class DigitalFunction(Enum):
        """
        Enum containing valid digital function modes for the hp6632b
        """
        remote_inhibit = 'RIDF'
        data = 'DIG'

    class DFISource(Enum):
        """
        Enum containing valid DFI sources for the hp6632b
        """
        questionable = 'QUES'
        operation = 'OPER'
        event_status_bit = 'ESB'
        request_service_bit = 'RQS'
        off = 'OFF'

    class ErrorCodes(IntEnum):
        """
        Enum containing generic-SCPI error codes along with codes specific
        to the HP6632b.
        """
        no_error = 0

        # -100 BLOCK: COMMAND ERRORS ##
        command_error = -100
        invalid_character = -101
        syntax_error = -102
        invalid_separator = -103
        data_type_error = -104
        get_not_allowed = -105
        # -106 and -107 not specified.
        parameter_not_allowed = -108
        missing_parameter = -109
        command_header_error = -110
        header_separator_error = -111
        program_mnemonic_too_long = -112
        undefined_header = -113
        header_suffix_out_of_range = -114
        unexpected_number_of_parameters = -115
        numeric_data_error = -120
        invalid_character_in_number = -121
        exponent_too_large = -123
        too_many_digits = -124
        numeric_data_not_allowed = -128
        suffix_error = -130
        invalid_suffix = -131
        suffix_too_long = -134
        suffix_not_allowed = -138
        character_data_error = -140
        invalid_character_data = -141
        character_data_too_long = -144
        character_data_not_allowed = -148
        string_data_error = -150
        invalid_string_data = -151
        string_data_not_allowed = -158
        block_data_error = -160
        invalid_block_data = -161
        block_data_not_allowed = -168
        expression_error = -170
        invalid_expression = -171
        expression_not_allowed = -178
        macro_error_180 = -180
        invalid_outside_macro_definition = -181
        invalid_inside_macro_definition = -183
        macro_parameter_error = -184

        # -200 BLOCK: EXECUTION ERRORS ##
        # -300 BLOCK: DEVICE-SPECIFIC ERRORS ##
        # Note that device-specific errors also include all positive numbers.
        # -400 BLOCK: QUERY ERRORS ##

        # OTHER ERRORS ##

        #: Raised when the instrument detects that it has been turned from
        #: off to on.
        power_on = -500  # Yes, SCPI 1999 defines the instrument turning on as
        # an error. Yes, this makes my brain hurt.
        user_request_event = -600
        request_control_event = -700
        operation_complete = -800

        # -200 BLOCK: EXECUTION ERRORS
        execution_error = -200
        data_out_of_range = -222
        too_much_data = -223
        illegal_parameter_value = -224
        out_of_memory = -225
        macro_error_270 = -270
        macro_execution_error = -272
        illegal_macro_label = -273
        macro_recursion_error = -276
        macro_redefinition_not_allowed = -277

        # -300 BLOCK: DEVICE-SPECIFIC ERRORS
        system_error = -310
        too_many_errors = -350

        # -400 BLOCK: QUERY ERRORS
        query_error = -400
        query_interrupted = -410
        query_unterminated = -420
        query_deadlocked = -430
        query_unterminated_after_indefinite_response = -440

        # DEVICE ERRORS
        ram_rd0_checksum_failed = 1
        ram_config_checksum_failed = 2
        ram_cal_checksum_failed = 3
        ram_state_checksum_failed = 4
        ram_rst_checksum_failed = 5
        ram_selftest = 10
        vdac_idac_selftest1 = 11
        vdac_idac_selftest2 = 12
        vdac_idac_selftest3 = 13
        vdac_idac_selftest4 = 14
        ovdac_selftest = 15
        digital_io_selftest = 80
        ingrd_recv_buffer_overrun = 213
        rs232_recv_framing_error = 216
        rs232_recv_parity_error = 217
        rs232_recv_overrun_error = 218
        front_panel_uart_overrun = 220
        front_panel_uart_framing = 221
        front_panel_uart_parity = 222
        front_panel_uart_buffer_overrun = 223
        front_panel_uart_timeout = 224
        cal_switch_prevents_cal = 401
        cal_password_incorrect = 402
        cal_not_enabled = 403
        computed_readback_cal_const_incorrect = 404
        computed_prog_cal_constants_incorrect = 405
        incorrect_seq_cal_commands = 406
        cv_or_cc_status_incorrect = 407
        output_mode_must_be_normal = 408
        too_many_sweep_points = 601
        command_only_applic_rs232 = 602
        curr_or_volt_fetch_incompat_with_last_acq = 603
        measurement_overrange = 604

    class RemoteInhibit(Enum):
        """
        Enum containing vlaid remote inhibit modes for the hp6632b.
        """
        latching = 'LATC'
        live = 'LIVE'
        off = 'OFF'

    class SenseWindow(Enum):
        """
        Enum containing valid sense window modes for the hp6632b.
        """
        hanning = 'HANN'
        rectangular = 'RECT'

    # PROPERTIES ##

    voltage_alc_bandwidth = enum_property(
        "VOLT:ALC:BAND",
        ALCBandwidth,
        input_decoration=lambda x: int(float(x)),
        readonly=True,
        doc="""
        Get the "automatic level control bandwidth" which for the HP66332A and
        HP6631-6634 determines if the output capacitor is in circuit. `Normal`
        denotes that it is, and `Fast` denotes that it is not.

        :type: `~HP6632b.ALCBandwidth`
        """
    )

    voltage_trigger = unitful_property(
        "VOLT:TRIG",
        u.volt,
        doc="""
        Gets/sets the pending triggered output voltage.

        Note there is no bounds checking on the value specified.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~quantities.Quantity`
        """
    )

    current_trigger = unitful_property(
        "CURR:TRIG",
        u.amp,
        doc="""
        Gets/sets the pending triggered output current.

        Note there is no bounds checking on the value specified.

        :units: As specified, or assumed to be :math:`\\text{A}` otherwise.
        :type: `float` or `~quantities.Quantity`
        """
    )

    init_output_continuous = bool_property(
        "INIT:CONT:SEQ1",
        "1",
        "0",
        doc="""
        Get/set the continuous output trigger. In this state, the power supply
        will remain in the initiated state, and respond continuously on new
        incoming triggers by applying the set voltage and current trigger
        levels.

        :type: `bool`
        """
    )

    current_sense_range = unitful_property(
        'SENS:CURR:RANGE',
        u.ampere,
        doc="""
        Get/set the sense current range by the current max value.

        A current of 20mA or less selects the low-current range, a current
        value higher than that selects the high-current range. The low current
        range increases the low current measurement sensitivity and accuracy.

        :units: As specified, or assumed to be :math:`\\text{A}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """
    )

    output_dfi = bool_property(
        'OUTP:DFI',
        '1',
        '0',
        doc="""
        Get/set the discrete fault indicator (DFI) output from the dc
        source. The DFI is an open-collector logic signal connected to the read
        panel FLT connection, that can be used to signal external devices when
        a fault is detected.

        :type: `bool`
        """
    )

    output_dfi_source = enum_property(
        "OUTP:DFI:SOUR",
        DFISource,
        doc="""
        Get/set the source for discrete fault indicator (DFI) events.

        :type: `~HP6632b.DFISource`
        """
    )

    output_remote_inhibit = enum_property(
        "OUTP:RI:MODE",
        RemoteInhibit,
        doc="""
        Get/set the remote inhibit signal. Remote inhibit is an external,
        chassis-referenced logic signal routed through the rear panel INH
        connection, which allows an external device to signal a fault.

        :type: `~HP6632b.RemoteInhibit`
        """
    )

    digital_function = enum_property(
        "DIG:FUNC",
        DigitalFunction,
        doc="""
        Get/set the inhibit+fault port to digital in+out or vice-versa.

        :type: `~HP6632b.DigitalFunction`
        """
    )

    digital_data = int_property(
        "DIG:DATA",
        valid_set=range(0, 8),
        doc="""
        Get/set digital in+out port to data. Data can be an integer from 0-7.

        :type: `int`
        """
    )

    sense_sweep_points = unitless_property(
        "SENS:SWE:POIN",
        doc="""
        Get/set the number of points in a measurement sweep.

        :type: `int`
        """
    )

    sense_sweep_interval = unitful_property(
        "SENS:SWE:TINT",
        u.second,
        doc="""
        Get/set the digitizer sample spacing. Can be set from 15.6 us to 31200
        seconds, the interval will be rounded to the nearest 15.6 us increment.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.Quantity`
        """
    )

    sense_window = enum_property(
        "SENS:WIND",
        SenseWindow,
        doc="""
        Get/set the measurement window function.

        :type: `~HP6632b.SenseWindow`
        """
    )

    output_protection_delay = unitful_property(
        "OUTP:PROT:DEL",
        u.second,
        doc="""
        Get/set the time between programming of an output change that produces
        a constant current condition and the recording of that condigition in
        the Operation Status Condition register. This command also delays over
        current protection, but not overvoltage protection.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.Quantity`
        """
    )

    # FUNCTIONS ##

    def init_output_trigger(self):
        """
        Set the output trigger system to the initiated state. In this state,
        the power supply will respond to the next output trigger command.
        """
        self.sendcmd('INIT:NAME TRAN')

    def abort_output_trigger(self):
        """
        Set the output trigger system to the idle state.
        """
        self.sendcmd('ABORT')

    # SCPIInstrument commands that need local overrides

    @property
    def line_frequency(self):
        raise NotImplementedError

    @line_frequency.setter
    def line_frequency(self, newval):
        raise NotImplementedError

    @property
    def display_brightness(self):
        raise NotImplementedError

    @display_brightness.setter
    def display_brightness(self, newval):
        raise NotImplementedError

    @property
    def display_contrast(self):
        raise NotImplementedError

    @display_contrast.setter
    def display_contrast(self, newval):
        raise NotImplementedError

    def check_error_queue(self):
        """
        Checks and clears the error queue for this device, returning a list of
        :class:`~SCPIInstrument.ErrorCodes` or `int` elements for each error
        reported by the connected instrument.
        """
        done = False
        result = []
        while not done:
            err = int(self.query('SYST:ERR?').split(',')[0])
            if err == self.ErrorCodes.no_error:
                done = True
            else:
                result.append(
                    self.ErrorCodes(err)
                    if any(err == item.value for item in self.ErrorCodes)
                    else err
                )

        return result
Exemplo n.º 2
0
class Wavetek39A(SCPIInstrument, FunctionGenerator):
    """
    The Wavetek 39A is a 40MS/s function generator. Arbitraty waveforms can have up to 65536 horizontal points,
    vertical range is -2048 to +2047 (12 bit), maximum peak-to-peak is 20V. Up to 100 waveforms, 256 KB NVRAM.
    Channel memory is 64 KB.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> fg = ik.wavetek.Wavetek39A.open_gpib('/dev/ttyUSB0', 1)
    >>> fg.frequency = 1 * u.MHz
    >>> print(fg.offset)
    >>> fg.function = fg.Function.triangle
    """
    def __init__(self, filelike):
        super(Wavetek39A, self).__init__(filelike)
        self.terminator = ""

    # CONSTANTS #

    _UNIT_MNEMONICS = {
        FunctionGenerator.VoltageMode.peak_to_peak: "VPP",
        FunctionGenerator.VoltageMode.rms: "VRMS",
        FunctionGenerator.VoltageMode.dBm: "DBM",
    }

    _MNEMONIC_UNITS = dict(
        (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.items())

    # FunctionGenerator CONTRACT #

    def _get_amplitude_(self):
        return (
            0.0,  # amplitude is writeonly (FIXME: trigger exception instead?)
            self._MNEMONIC_UNITS["VPP"])

    def _set_amplitude_(self, magnitude, units):
        self.sendcmd("AMPUNIT {}".format(self._UNIT_MNEMONICS[units]))
        self.sendcmd("AMPL {}".format(magnitude))

    # ENUMS ##

    class Function(Enum):
        """
        Enum containing valid output function modes for the Wavetek 39A
        """
        #: sinusoidal
        sinusoid = "SINE"
        #: square
        square = "SQUARE"
        #: triangular
        triangle = "TRIANG"
        #: constant voltage
        dc = "DC"
        #: positive ramp
        positive_ramp = "POSRMP"
        #: negative ramp
        negative_ramp = "NEGRMP"
        #: cosine
        cosine = "COSINE"
        #: haversine, sin^2(x/2)=(1-cos x)/2
        haversine = "HAVSIN"
        #: havercosine, (1+cos x)/2
        havercosine = "HAVCOS"
        #: sinc(x)=sin(x)/x
        sinc = "SINC"
        #: pulse
        pulse = "PULSE"
        #: pulse train
        pulse_train = "PULSTRN"
        #: arbitrary waveform
        arbitrary = "ARB"
        #: sequence of up to 16 waveforms
        sequence = "SEQ"

    class ZLoad(Enum):
        """
        Enum containing the output load settings
        """
        #: 50 Ohm termination
        Z50 = "50"
        #: 600 Ohm termination
        Z600 = "600"
        #: Z=ininity, open circuit
        OPEN = "OPEN"

    class OutputMode(Enum):
        """
        Enum containing the output mode settings
        """
        #: normal (non-inverted) output
        normal = "NORMAL"
        #: inverted output (around the same offset if offset is non-zero!)
        invert = "INVERT"

    class Mode(Enum):
        """
        Enum containing the mode settings
        """
        #: continuous operation
        cont = "CONT"
        continuous = "CONT"
        #: gated
        gate = "GATE"
        gated = "GATE"
        #: triggered burst mode (each active edge of the trigger signal produces one
        #: burst of the waveform)
        trig = "TRIG"
        triggered = "TRIG"
        #: sweep
        sweep = "SWEEP"
        #: tone mode
        tone = "TONE"

    class SweepType(Enum):
        """
        Enum containing the sweep type
        """
        #: continuous operation
        cont = "CONT"
        continuous = "CONT"
        #: triggered sweep (front TRIG IN socket, remote command, manually with MAN TRIG key)
        #: Sweep is initiated on the rising edge of the trigger signal.
        trig = "TRIG"
        triggered = "TRIG"
        #: triggered, hold and reset
        triggered_hold_reset = "THLDRST"
        #: manual sweeping (using rotary control or cursor keys)
        manual = "MANUAL"

    class SweepDirection(Enum):
        """
        Enum containing the sweep direction
        """
        #: up
        up = "UP"
        #: down
        down = "DOWN"
        #: up/down
        updn = "UPDN"
        updown = "UPDN"
        #: down/up
        dnup = "DNUP"
        downup = "DNUP"

    class SweepSpacing(Enum):
        """
        Enum containing the sweep spacing
        """
        #: linear
        lin = "LIN"
        linear = "LIN"
        #: logarithmic
        log = "LOG"
        logarithmic = "LOG"

    class SweepManual(Enum):
        """
        Enum containing the sweep manual parameters [???]
        """
        #: up
        up = "UP"
        #: down
        down = "DOWN"

    class SweepManualSpeed(Enum):
        """
        Enum containing the manual sweep step size.
        """
        #: fast
        fast = "FAST"
        #: slow
        slow = "SLOW"

    class SweepManualWrap(Enum):
        """
        Enum containing the manual sweep wrapping.
        """
        #: wrap on
        wrapon = "WRAPON"
        #: wrap off
        wrapoff = "WRAPOFF"

    class SyncOutMode(Enum):
        """
        Enum containing sync output settings
        """
        #: automatic
        auto = "AUTO"
        #: waveform sync (sync marker, for standward waveform raising edge at 0 deg point,
        #: for arbitrary waveform coincident with the first point)
        waveform_sync = "WFMSYNC"
        #: position marker for arbitrary waveform, for standard waveforms short pulse at the start of cycle
        position_marker = "POSNMKR"
        #: burst sequence done (low while the waveform is active)
        burst_done = "BSTDONE"
        #: sync signal low during the last cycle of the last waveform in a sequence, high at all other times
        sequence_sync = "SEQSYNC"
        #: positive going version of the trigger signal
        trigger = "TRIGGER"
        #: goes high at the start of the sweep, goes low at the end of the sweep
        sweep = "SWPTRG"
        #: positive edge coincident with the start of the current waveform
        phase_lock = "PHASLOC"

    class TriggerInput(Enum):
        """
        Enum containing trigger input settings
        """
        internal = "INT"
        external = "EXT"
        manual = "MAN"

    class TriggerInputEdge(Enum):
        """
        Enum containing external trigger input edge
        """
        positive = "POS"
        negative = "NEG"

    class HoldMode(Enum):
        """
        Enum containing the hold mode
        """
        #: on/off are the same as pressing the MAN HOLD key
        on = "ON"
        off = "OFF"
        #: enable/disable enable or disable the action of the MAN HOLD key
        enable = "ENAB"
        disable = "DISAB"

    class Filter(Enum):
        """
        Enum containing the output filter types
        """
        #: automatic (most appropriate for the current waveform)
        auto = "AUTO"
        #: 10MHz elliptic
        elliptic10 = "EL10"
        #: 16MHz elliptic (sine, cosine, haversine, havercosine above 10Mhz)
        elliptic16 = "EL16"
        #: 10MHz Bessel (positive and negative ramps, arbitrary and sequence)
        Bessel = "BESS"
        #: no output filtering (square wave, pulse, pulse trains)
        none = "NONE"

    class BeepMode(Enum):
        """
        Enum containing beep modes
        """
        on = "ON"
        off = "OFF"
        warnings = "WARN"
        errors = "ERROR"

    # PROPERTIES ##

    frequency = unitful_property(command="WAVFREQ",
                                 units=u.Hz,
                                 writeonly=True,
                                 doc="""
        Sets the output frequency.

        :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    period = unitful_property(command="WAVPER",
                              units=u.s,
                              writeonly=True,
                              doc="""
        Sets the output period.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    clock_frequency = unitful_property(command="CLKFREQ",
                                       units=u.Hz,
                                       writeonly=True,
                                       doc="""
        Sets the arbitrary sample clock frequency. Range 0.1Hz to 40MHz.

        :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    clock_period = unitful_property(command="CLKPER",
                                    units=u.s,
                                    writeonly=True,
                                    doc="""
        Sets the arbitrary sample clock period.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    zload = enum_property(command="ZLOAD",
                          enum=ZLoad,
                          writeonly=True,
                          doc="""
        Sets the output load.

        :type: `~Wavetek39A.ZLoad`
        """)

    offset = unitful_property(command="DCOFFS",
                              units=u.volt,
                              writeonly=True,
                              doc="""
        Sets the offset voltage for the output waveform.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    function = enum_property(command="WAVE",
                             enum=Function,
                             writeonly=True,
                             doc="""
        Sets the output function of the function generator.

        :type: `~Wavetek39A.Function`
        """)

    pulse_period = unitful_property(command="PULSPER",
                                    units=u.s,
                                    writeonly=True,
                                    doc="""
        Sets the pulse period.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    pulse_width = unitful_property(command="PULSWID",
                                   units=u.s,
                                   writeonly=True,
                                   doc="""
        Sets the pulse width.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    pulse_delay = unitful_property(command="PULSDLY",
                                   units=u.s,
                                   writeonly=True,
                                   doc="""
        Sets the pulse delay.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    pulse_train_length = int_property(command="PULSTRNLEN",
                                      writeonly=True,
                                      doc="""
        Sets the number of pulses in the pulse-train.

        :units: Number.
        :type: `int`
        """)

    pulse_train_period = unitful_property(command="PULSTRNPER",
                                          units=u.s,
                                          writeonly=True,
                                          doc="""
        Sets the pulse-train period.

        :units: As specified, or assumed to be :math:`\\text{s}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    pulse_train_base_line = unitful_property(command="PULSTRNBASE",
                                             units=u.V,
                                             writeonly=True,
                                             doc="""
        Sets the pulse-train base line voltage.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    # pulse_train_level = unitful_property(  ## has two parameters!

    arbitrary = string_property(command="ARB",
                                writeonly=True,
                                bookmark_symbol='',
                                doc="""
        Select an arbitray waveform for output.

        :type: `str`
        """)

    arbitrary_list_ch = string_property(command="ARBLISTCH",
                                        readonly=True,
                                        bookmark_symbol='',
                                        doc="""
        List of all arbitrary waveforms in the channel's memory.

        :type: `str`
        """)

    arbitrary_list = string_property(command="ARBLIST",
                                     readonly=True,
                                     bookmark_symbol='',
                                     doc="""
        List of all arbitrary waveforms in the backup memory.

        :type: `str`
        """)

    def arbitrary_delete(self, cpd):
        """
        Delete an arbitrary wavefrom from backup memory.
        A waveform used by a non-active sequence can be deleted but the sequence will not subsequently
        run properly and should be modified to exclude the deleted waveform.

        :type: `str`
        """
        check_arb_name(cpd)
        self.sendcmd("ARBDELETE {}".format(cpd))

    def arbitrary_clear(self, cpd):
        """
        Delete an arbitrary wavefrom from channel memory.
        A waveform cannot be deleted from a channel’s memory if it is running on that channel.
        If an arb waveform sequence is running no waveforms can be deleted from that channel,
        whether they are used in the sequence or not.
        Waveforms must be deleted from the channel’s memory before they can be deleted from the back-up memory.
        (i.e. call arbitrary_clear before arbitrary_delete)

        :type: `str`
        """
        check_arb_name(cpd)
        self.sendcmd("ARBCLR {}".format(cpd))

    def arbitrary_create(self, cpd, nrf):
        """
        Create a new blank arbitrary waveform.

        :type cpd: `str`
        :type nrf: `int`
        """
        check_arb_name(cpd)
        self.sendcmd("ARBCREATE {},{}".format(cpd, nrf))

    def _arbitrary_send_data_csv(self, cpd, csv, command):
        length, csvint = prepare_for_sending(cpd, csv)
        cmd = "{} {},{},{}".format(command, cpd, str(length),
                                   ",".join([str(i) for i in csvint]))
        self.sendcmd(cmd)

    def _arbitrary_send_data(self, cpd, csv, command):
        length, csvint = prepare_for_sending(cpd, csv)
        bin_data = struct.pack('>{}h'.format(length), *csvint)
        size_str = str(len(bin_data))
        len_size_str = len(size_str)
        header = '#{}{}'.format(len_size_str, size_str)
        cmd = "{} {},{},{}{}".format(command, cpd, str(length), header,
                                     bin_data)
        self.sendcmd(cmd)

    def arbitrary_define_csv(self, cpd, csv):
        """
        Define a new or existing arbitrary waveform from a list.

        :type cpd: `str`
        :type csv: `iterable`
        """
        self._arbitrary_send_data_csv(cpd, csv, "ARBDEFCSV")

    def arbitrary_define(self, cpd, csv):
        """
        Define a new or existing arbitrary waveform from a list.

        :type cpd: `str`
        :type csv: `iterable`
        """
        self._arbitrary_send_data(cpd, csv, "ARBDEF")

    def arbitrary_get_data_csv(self, cpd):
        """
        Returns the arbitrary waveform data as ASCII data.

        :rtype: `str`
        """
        check_arb_name(cpd)
        self.query("ARBDATACSV? {}".format(cpd))

    def arbitray_edit_limits(self, nrf1, nrf2):
        """
        Define editing limits for the currently edited arbitrary waveform.

        :type nrf1: `int`
        :type nrf2: `int`
        """
        self.sendcmd("ARBEDLMTS {},{}".format(nrf1, nrf2))

    def arbitrary_data_csv(self, cpd, csv):
        self._arbitrary_send_data_csv(cpd, csv, "ARBDATACSV")

    def arbitrary_data(self, cpd, csv):
        self._arbitrary_send_data(cpd, csv, "ARBDATA")

    phase = unitful_property(command="PHASE",
                             units=u.degree,
                             writeonly=True,
                             doc="""
        Sets the phase for the output waveform.

        :units: As specified, or assumed to be degrees (:math:`{}^{\\circ}`)
            otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    sweep_start_frequency = unitful_property(command="SWPSTARTFRQ",
                                             units=u.Hz,
                                             writeonly=True,
                                             doc="""
        Sets the sweep start frequency. Minimum is 1 mHz.

        :units: As specified, or assumed to be Hz otherwise.
        :type: `float` or `~quantities.quantity.Quantity`   
        """)

    sweep_stop_frequency = unitful_property(command="SWPSTOPFRQ",
                                            units=u.Hz,
                                            writeonly=True,
                                            doc="""
        Sets the sweep stop frequency. Maximum is 16 MHz for all waveforms,
        including triangle, ramp and square wave.

        :units: As specified, or assumed to be Hz otherwise.
        :type: `float` or `~quantities.quantity.Quantity`   
        """)

    sweep_centre_frequency = unitful_property(command="SWPCENTFRQ",
                                              units=u.Hz,
                                              writeonly=True,
                                              doc="""
        Sets the sweep centre frequency.

        :units: As specified, or assumed to be Hz otherwise.
        :type: `float` or `~quantities.quantity.Quantity`   
        """)

    sweep_span = unitful_property(command="SWPSPAN",
                                  units=u.Hz,
                                  writeonly=True,
                                  doc="""
        Sets the sweep frequency span.

        :units: As specified, or assumed to be Hz otherwise.
        :type: `float` or `~quantities.quantity.Quantity`   
        """)

    sweep_time = unitful_property(command="SWPTIME",
                                  units=u.s,
                                  writeonly=True,
                                  doc="""
        Sets the sweep time. 0.03s to 999s with 3-digit resolution.

        :units: As specified, or assumed to be s otherwise.
        :type: `float` or `~quantities.quantity.Quantity`   
        """)

    sweep_type = enum_property(command="SWPTYPE",
                               enum=SweepType,
                               writeonly=True,
                               doc="""
        Sets the sweep type.

        :type: `~Wavetek39A.SweepType`
        """)

    sweep_direction = enum_property(command="SWPDIRN",
                                    enum=SweepDirection,
                                    writeonly=True,
                                    doc="""
        Sets the sweep direction.

        :type: `~Wavetek39A.SweepDirection`
        """)

    sweep_sync = bool_property("SWPSYNC",
                               inst_true="ON",
                               inst_false="OFF",
                               writeonly=True,
                               doc="""
        Sets the sweep syncs on and off. If on (default), the generator steps from the stop
        frequency to zero frequency and then starts the next sweep from the first point of the
        waveform, synchronized to the internally generated trigger signal.
        """)

    sweep_spacing = enum_property(command="SWPSPACING",
                                  enum=SweepSpacing,
                                  writeonly=True,
                                  doc="""
        Sets the sweep spacing.

        :type: `~Wavetek39A.SweepSpacing`
        """)

    sweep_marker = unitful_property(command="SWPMARKER",
                                    units=u.Hz,
                                    writeonly=True,
                                    doc="""
        Sets the sweep marker (rear panel CURSOR/MARKER OUT socket).

        :units: As specified, or assumed to be Hz otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    sweep_manual_speed = enum_property(command="SWPMANUAL",
                                       enum=SweepManualSpeed,
                                       writeonly=True,
                                       doc="""
        Sets the manual step size.

        :type: `~Wavetek39A.SweepManualSpeed`
        """)

    sweep_manual_wrap = bool_property("SWPMANUAL",
                                      inst_true="WRAPON",
                                      inst_false="WRAPOFF",
                                      writeonly=True,
                                      doc="""
        Sets the sweep wrapping on/off.
        """)

    output = bool_property("OUTPUT",
                           inst_true="ON",
                           inst_false="OFF",
                           writeonly=True,
                           doc="""
        Sets the output on and off.
        """)

    output_mode = enum_property(command="OUTPUT",
                                enum=OutputMode,
                                writeonly=True,
                                doc="""
        Sets the output mode (normal vs. inverted).

        :type: `~Wavetek39A.OutputMode`
        """)

    mode = enum_property(command="MODE",
                         enum=Mode,
                         writeonly=True,
                         doc="""
        Sets the mode.

        :type: `~Wavetek39A.Mode`
        """)

    syncout = bool_property("SYNCOUT",
                            inst_true="ON",
                            inst_false="OFF",
                            writeonly=True,
                            doc="""
        Sets the sync output on and off.
        """)

    syncout_mode = enum_property(command="SYNCOUT",
                                 enum=SyncOutMode,
                                 writeonly=True,
                                 doc="""
        Sets the sync output mode.

        :type: `~Wavetek39A.SyncOut`
        """)

    trigger_input = enum_property(command="TRIGIN",
                                  enum=TriggerInput,
                                  writeonly=True,
                                  doc="""
        Sets the trigger input.

        :type: `~Wavetek39A.TriggerInput`
        """)

    trigger_input_edge = enum_property(command="TRIGIN",
                                       enum=TriggerInputEdge,
                                       writeonly=True,
                                       doc="""
        Sets the edge for external trigger input.

        :type: `~Wavetek39A.TriggerInputEdge`
        """)

    trigger_period = unitful_property(command="TRIGPER",
                                      units=u.s,
                                      writeonly=True,
                                      doc="""
        Sets the internal trigger period.

        :units: As specified, or assumed to be seconds otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    def reset(self):
        """
        Resets the instrument parameters to their default values.
        """
        self.sendcmd("*RST")

    def force_trigger(self):
        """
        Force a trigger
        """
        self.sendcmd("FORCETRG")

    burst_count = int_property(command="BSTCNT",
                               writeonly=True,
                               doc="""
        Sets the burst count.

        :units: Number of cycles.
        :type: `int`
        """)

    def recall(self, nrf):
        """
        Recall the set up in store 'nrf'. 0-9. 0 are default settings.
        """
        if not 0 <= nrf <= 9:
            raise RuntimeError("out of range {}".format(nrf))
        self.sendcmd("*RCL {}".format(nrf))

    def save(self, nrf):
        """
        Save the set up in store 'nrf'. 1-9.
        """
        if not 1 <= nrf <= 9:
            raise RuntimeError("out of range {}".format(nrf))
        self.sendcmd("*SAV {}".format(nrf))

    def manual_trigger(self):
        """
        Same as pressing the MAN TRIG key.
        """
        self.sendcmd("*TRG")

    holdmode = enum_property(command="HOLD",
                             enum=HoldMode,
                             writeonly=True,
                             doc="""
        Sets the hold mode.

        :type: `~Wavetek39A.HoldMode`
        """)

    filter = enum_property(command="FILTER",
                           enum=Filter,
                           writeonly=True,
                           doc="""
        Sets the output filter type.

        :type: `~Wavetek39A.Filter`
        """)

    beepmode = enum_property(command="BEEPMODE",
                             enum=BeepMode,
                             writeonly=True,
                             doc="""
        Sets the beep mode.

        :type: `~Wavetek39A.BeepMode`
        """)

    def beep(self):
        """
        Beep once
        """
        self.sendcmd("BEEP")

    def local(self):
        """
        Returns the instrument to local operation and unlock the keyboard.
        """
        self.sendcmd("LOCAL")
Exemplo n.º 3
0
 class EnumMock(MockInstrument):
     a = enum_property('MOCK:A', SillyEnum, set_cmd='FOOBAR:A')
Exemplo n.º 4
0
    class EnumMock(MockInstrument):

        a = enum_property('MOCK:A', SillyEnum, output_decoration=float)
Exemplo n.º 5
0
class MC1(Instrument):
    """
    The MC1 is a controller for the qubitekk motor controller. Used with a
    linear actuator to perform a HOM dip.
    """
    def __init__(self, filelike):
        super(MC1, self).__init__(filelike)
        self.terminator = "\r"
        self._increment = 1*pq.ms
        self._lower_limit = -300*pq.ms
        self._upper_limit = 300*pq.ms
        self._firmware = None
        self._controller = None

    # ENUMS #

    class MotorType(Enum):
        """
        Enum for the motor types for the MC1
        """
        radio = "Radio"
        relay = "Relay"

    # PROPERTIES #

    @property
    def increment(self):
        """
        Gets/sets the stepping increment value of the motor controller

        :units: As specified, or assumed to be of units milliseconds
        :type: `~quantities.Quantity`
        """
        return self._increment

    @increment.setter
    def increment(self, newval):
        self._increment = assume_units(newval, pq.ms).rescale(pq.ms)

    @property
    def lower_limit(self):
        """
        Gets/sets the stepping lower limit value of the motor controller

        :units: As specified, or assumed to be of units milliseconds
        :type: `~quantities.Quantity`
        """
        return self._lower_limit

    @lower_limit.setter
    def lower_limit(self, newval):
        self._lower_limit = assume_units(newval, pq.ms).rescale(pq.ms)

    @property
    def upper_limit(self):
        """
        Gets/sets the stepping upper limit value of the motor controller

        :units: As specified, or assumed to be of units milliseconds
        :type: `~quantities.Quantity`
        """
        return self._upper_limit

    @upper_limit.setter
    def upper_limit(self, newval):
        self._upper_limit = assume_units(newval, pq.ms).rescale(pq.ms)

    direction = unitful_property(
        command="DIRE",
        doc="""
        Get the internal direction variable, which is a function of how far
        the motor needs to go.

        :type: `~quantities.Quantity`
        :units: milliseconds
        """,
        units=pq.ms,
        readonly=True
    )

    inertia = unitful_property(
        command="INER",
        doc="""
        Gets/Sets the amount of force required to overcome static inertia. Must
         be between 0 and 100 milliseconds.

        :type: `~quantities.Quantity`
        :units: milliseconds
        """,
        format_code='{:.0f}',
        units=pq.ms,
        valid_range=(0*pq.ms, 100*pq.ms),
        set_fmt=":{} {}"
    )

    @property
    def internal_position(self):
        """
        Get the internal motor state position, which is equivalent to the total
         number of milliseconds that voltage has been applied to the motor in
         the positive direction minus the number of milliseconds that voltage
         has been applied to the motor in the negative direction.

        :type: `~quantities.Quantity`
        :units: milliseconds
        """
        response = int(self.query("POSI?"))*self.step_size
        return response

    metric_position = unitful_property(
        command="METR",
        doc="""
        Get the estimated motor position, in millimeters.

        :type: `~quantities.Quantity`
        :units: millimeters
        """,
        units=pq.mm,
        readonly=True
    )

    setting = int_property(
        command="OUTP",
        doc="""
        Gets/sets the output port of the optical switch. 0 means input 1 is
        directed to output 1, and input 2 is directed to output 2. 1 means that
         input 1 is directed to output 2 and input 2 is directed to output 1.

        :type: `int`
        """,
        valid_set=range(2),
        set_fmt=":{} {}"
    )

    step_size = unitful_property(
        command="STEP",
        doc="""
        Gets/Sets the number of milliseconds per step. Must be between 1
        and 100 milliseconds.

        :type: `~quantities.Quantity`
        :units: milliseconds
        """,
        format_code='{:.0f}',
        units=pq.ms,
        valid_range=(1*pq.ms, 100*pq.ms),
        set_fmt=":{} {}"
    )

    @property
    def firmware(self):
        """
        Gets the firmware version

        :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`)
        """
        # the firmware is assumed not to change while the device is active
        # firmware is stored locally as it will be gotten often
        # pylint: disable=no-member
        if self._firmware is None:
            while self._firmware is None:
                self._firmware = self.query("FIRM?")
                value = self._firmware.split(".")
                if len(value) < 3:
                    for _ in range(3-len(value)):
                        value.append(0)
                value = tuple(map(int, value))
                self._firmware = value
        return self._firmware

    controller = enum_property(
        'MOTO',
        MotorType,
        doc="""
        Get the motor controller type.
        """,
        readonly=True
    )

    @property
    def move_timeout(self):
        """
        Get the motor's timeout value, which indicates the number of
        milliseconds before the motor can start moving again.

        :type: `~quantities.Quantity`
        :units: milliseconds
        """
        response = int(self.query("TIME?"))
        return response*self.step_size

    # METHODS #

    def is_centering(self):
        """
        Query whether the motor is in its centering phase

        :return: False if not centering, True if centering
        :rtype: `bool`
        """
        response = self.query("CENT?")
        return True if int(response) == 1 else False

    def center(self):
        """
        Commands the motor to go to the center of its travel range
        """
        self.sendcmd(":CENT")

    def reset(self):
        """
        Sends the stage to the limit of one of its travel ranges
        """
        self.sendcmd(":RESE")

    def move(self, new_position):
        """
        Move to a specified location. Position is unitless and is defined as
        the number of motor steps. It varies between motors.

        :param new_position: the location
        :type new_position: `~quantities.Quantity`
        """
        if self.lower_limit <= new_position <= self.upper_limit:
            new_position = assume_units(new_position, pq.ms).rescale(pq.ms)
            clock_cycles = new_position/self.step_size
            cmd = ":MOVE "+str(int(clock_cycles))
            self.sendcmd(cmd)
        else:
            raise ValueError("Location out of range")
Exemplo n.º 6
0
class Agilent33220a(SCPIFunctionGenerator):
    def __init__(self, filelike):
        super(Agilent33220a, self).__init__(filelike)

    ## ENUMS ##

    class Function(Enum):
        sinusoid = "SIN"
        square = "SQU"
        ramp = "RAMP"
        pulse = "PULS"
        noise = "NOIS"
        dc = "DC"
        user = "******"

    class LoadResistance(Enum):
        minimum = "MIN"
        maximum = "MAX"
        high_impedance = "INF"

    class OutputPolarity(Enum):
        normal = "NORM"
        inverted = "INV"

    ## PROPERTIES ##

    function = enum_property(name="FUNC",
                             enum=Function,
                             doc="""
        Gets/sets the output function of the function generator
        
        :type: `Agilent33220a.Function`
        """,
                             set_fmt="{}:{}")

    duty_cycle = int_property(name="FUNC:SQU:DCYC",
                              doc="""
        Gets/sets the duty cycle of a square wave.
        
        Duty cycle represents the amount of time that the square wave is at a 
        high level.
        
        :type: `int`
        """,
                              valid_set=xrange(101))

    ramp_symmetry = int_property(name="FUNC:RAMP:SYMM",
                                 doc="""
        Gets/sets the ramp symmetry for ramp waves.
        
        Symmetry represents the amount of time per cycle that the ramp wave is 
        rising (unless polarity is inverted).
        
        :type: `int`
        """,
                                 valid_set=xrange(101))

    output = bool_property(name="OUTP",
                           inst_true="ON",
                           inst_false="OFF",
                           doc="""
        Gets/sets the output enable status of the front panel output connector.
        
        The value `True` corresponds to the output being on, while `False` is
        the output being off.
        
        :type: `bool`
        """)

    output_sync = bool_property(name="OUTP:SYNC",
                                inst_true="ON",
                                inst_false="OFF",
                                doc="""
        Gets/sets the enabled status of the front panel sync connector.
        
        :type: `bool`
        """)

    output_polarity = enum_property(name="OUTP:POL",
                                    enum=OutputPolarity,
                                    doc="""
        Gets/sets the polarity of the waveform relative to the offset voltage.
        
        :type: `~Agilent33220a.OutputPolarity`
        """)

    @property
    def load_resistance(self):
        """
        Gets/sets the desired output termination load (ie, the impedance of the 
        load attached to the front panel output connector).
        
        The instrument has a fixed series output impedance of 50ohms. This 
        function allows the instrument to compensate of the voltage divider 
        and accurately report the voltage across the attached load.
        
        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units :math:`\\Omega` (ohm).
        :type: `~quantities.quantity.Quantity` or `Agilent33220a.LoadResistance`
        """
        value = self.query("OUTP:LOAD?")
        try:
            return int(value) * pq.ohm
        except:
            return self.LoadResistance[value.strip()]

    @load_resistance.setter
    def load_resistance(self, newval):
        if (not isinstance(newval, EnumValue)) or (newval.enum
                                                   is not self.LoadResistance):
            newval = newval.value
        elif isinstance(newval, int):
            if (newval < 0) or (newval > 10000):
                raise ValueError(
                    "Load resistance must be between 0 and 10,000")
            newval = assume_units(newval, pq.ohm).rescale(pq.ohm).magnitude
        else:
            raise TypeError("Not a valid load resistance type.")
        self.sendcmd("OUTP:LOAD {}".format(newval))
Exemplo n.º 7
0
class LCC25(Instrument):
    """
    The LCC25 is a controller for the thorlabs liquid crystal modules.
    it can set two voltages and then oscillate between them at a specific
    repetition rate.

    The user manual can be found here:
    http://www.thorlabs.com/thorcat/18800/LCC25-Manual.pdf
    """
    def __init__(self, filelike):
        super(LCC25, self).__init__(filelike)
        self.terminator = "\r"
        self.prompt = ">"

    def _ack_expected(self, msg=""):
        return msg

    # ENUMS #

    class Mode(IntEnum):
        """
        Enum containing valid output modes of the LCC25
        """
        normal = 0
        voltage1 = 1
        voltage2 = 2

    # PROPERTIES #

    @property
    def name(self):
        """
        Gets the name and version number of the device

        :rtype: `str`
        """
        return self.query("*idn?")

    frequency = unitful_property("freq",
                                 pq.Hz,
                                 format_code="{:.1f}",
                                 set_fmt="{}={}",
                                 valid_range=(5, 150),
                                 doc="""
        Gets/sets the frequency at which the LCC oscillates between the
        two voltages.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units Hertz.
        :rtype: `~quantities.quantity.Quantity`
        """)

    mode = enum_property("mode",
                         Mode,
                         input_decoration=int,
                         set_fmt="{}={}",
                         doc="""
        Gets/sets the output mode of the LCC25

        :rtype: `LCC25.Mode`
        """)

    enable = bool_property("enable",
                           inst_true="1",
                           inst_false="0",
                           set_fmt="{}={}",
                           doc="""
        Gets/sets the output enable status.

        If output enable is on (`True`), there is a voltage on the output.

        :rtype: `bool`
        """)

    extern = bool_property("extern",
                           inst_true="1",
                           inst_false="0",
                           set_fmt="{}={}",
                           doc="""
        Gets/sets the use of the external TTL modulation.

        Value is `True` for external TTL modulation and `False` for internal
        modulation.

        :rtype: `bool`
        """)

    remote = bool_property("remote",
                           inst_true="1",
                           inst_false="0",
                           set_fmt="{}={}",
                           doc="""
        Gets/sets front panel lockout status for remote instrument operation.

        Value is `False` for normal operation and `True` to lock out the front
        panel buttons.

        :rtype: `bool`
        """)

    voltage1 = unitful_property("volt1",
                                pq.V,
                                format_code="{:.1f}",
                                set_fmt="{}={}",
                                valid_range=(0, 25),
                                doc="""
        Gets/sets the voltage value for output 1.

        :units: As specified (if a `~quantities.quantity.Quantity`) or
            assumed to be of units Volts.
        :rtype: `~quantities.quantity.Quantity`
        """)

    voltage2 = unitful_property("volt2",
                                pq.V,
                                format_code="{:.1f}",
                                set_fmt="{}={}",
                                valid_range=(0, 25),
                                doc="""
        Gets/sets the voltage value for output 2.

        :units: As specified (if a `~quantities.quantity.Quantity`) or
            assumed to be of units Volts.
        :rtype: `~quantities.quantity.Quantity`
        """)

    min_voltage = unitful_property("min",
                                   pq.V,
                                   format_code="{:.1f}",
                                   set_fmt="{}={}",
                                   valid_range=(0, 25),
                                   doc="""
        Gets/sets the minimum voltage value for the test mode.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~quantities.quantity.Quantity`
        """)

    max_voltage = unitful_property("max",
                                   pq.V,
                                   format_code="{:.1f}",
                                   set_fmt="{}={}",
                                   valid_range=(0, 25),
                                   doc="""
        Gets/sets the maximum voltage value for the test mode. If the maximum
        voltage is less than the minimum voltage, nothing happens.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~quantities.quantity.Quantity`
        """)

    dwell = unitful_property("dwell",
                             units=pq.ms,
                             format_code="{:n}",
                             set_fmt="{}={}",
                             valid_range=(0, None),
                             doc="""
        Gets/sets the dwell time for voltages for the test mode.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units milliseconds.
        :rtype: `~quantities.quantity.Quantity`
        """)

    increment = unitful_property("increment",
                                 units=pq.V,
                                 format_code="{:.1f}",
                                 set_fmt="{}={}",
                                 valid_range=(0, None),
                                 doc="""
        Gets/sets the voltage increment for voltages for the test mode.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~quantities.quantity.Quantity`
        """)

    # METHODS #

    def default(self):
        """
        Restores instrument to factory settings.

        Returns 1 if successful, 0 otherwise

        :rtype: `int`
        """
        response = self.query("default")
        return check_cmd(response)

    def save(self):
        """
        Stores the parameters in static memory

        Returns 1 if successful, zero otherwise.

        :rtype: `int`
        """
        response = self.query("save")
        return check_cmd(response)

    def set_settings(self, slot):
        """
        Saves the current settings to memory.

        Returns 1 if successful, zero otherwise.

        :param slot: Memory slot to use, valid range `[1,4]`
        :type slot: `int`
        :rtype: `int`
        """
        if slot not in range(1, 5):
            raise ValueError("Cannot set memory out of `[1,4]` range")
        response = self.query("set={}".format(slot))
        return check_cmd(response)

    def get_settings(self, slot):
        """
        Gets the current settings to memory.

        Returns 1 if successful, zero otherwise.

        :param slot: Memory slot to use, valid range `[1,4]`
        :type slot: `int`
        :rtype: `int`
        """
        if slot not in range(1, 5):
            raise ValueError("Cannot set memory out of `[1,4]` range")
        response = self.query("get={}".format(slot))
        return check_cmd(response)

    def test_mode(self):
        """
        Puts the LCC in test mode - meaning it will increment the output
        voltage from the minimum value to the maximum value, in increments,
        waiting for the dwell time

        Returns 1 if successful, zero otherwise.

        :rtype: `int`
        """
        response = self.query("test")
        return check_cmd(response)
Exemplo n.º 8
0
class PM100USB(SCPIInstrument):
    """
    Instrument class for the `ThorLabs PM100USB`_ power meter.
    Note that as this is an SCPI-compliant instrument, the properties and
    methods of :class:`~instruments.generic_scpi.SCPIInstrument` may be used
    as well.

    .. _ThorLabs PM100USB: http://www.thorlabs.com/thorproduct.cfm?partnumber=PM100USB
    """

    # ENUMS #

    class SensorFlags(IntEnum):
        """
        Enum containing valid sensor flags for the PM100USB
        """
        is_power_sensor = 1
        is_energy_sensor = 2
        response_settable = 16
        wavelength_settable = 32
        tau_settable = 64
        has_temperature_sensor = 256

    class MeasurementConfiguration(Enum):
        """
        Enum containing valid measurement modes for the PM100USB
        """
        current = "CURR"
        power = "POW"
        voltage = "VOLT"
        energy = "ENER"
        frequency = "FREQ"
        power_density = "PDEN"
        energy_density = "EDEN"
        resistance = "RES"
        temperature = "TEMP"

    # We will cheat and also represent things by a named tuple over bools.
    # TODO: make a flagtuple into a new type in util_fns, copying this out
    #       as a starting point.
    _SensorFlags = namedtuple(
        "SensorFlags",
        [
            flag.name for flag in SensorFlags  # pylint: disable=not-an-iterable
        ])

    # INNER CLASSES #

    class Sensor(object):
        """
        Class representing a sensor on the ThorLabs PM100USB

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `PM100USB` class.
        """
        def __init__(self, parent):
            self._parent = parent

            # Pull details about the sensor from SYST:SENSOR:IDN?
            sensor_idn = parent.sendcmd("SYST:SENSOR:IDN?")
            (self._name, self._serial_number, self._calibration_message,
             self._sensor_type, self._sensor_subtype,
             self._flags) = sensor_idn.split(",")

            # Normalize things to enums as appropriate.
            # We want flags to be a named tuple over bools.
            # pylint: disable=protected-access
            self._flags = parent._SensorFlags(
                **{
                    e.name: bool(e & self._flags)
                    for e in PM100USB.SensorFlags  # pylint: disable=not-an-iterable
                })

        @property
        def name(self):
            """
            Gets the name associated with the sensor channel

            :type: `str`
            """
            return self._name

        @property
        def serial_number(self):
            """
            Gets the serial number of the sensor channel

            :type: `str`
            """
            return self._serial_number

        @property
        def calibration_message(self):
            """
            Gets the calibration message of the sensor channel

            :type: `str`
            """
            return self._calibration_message

        @property
        def type(self):
            """
            Gets the sensor type of the sensor channel

            :type: `str`
            """
            return self._sensor_type, self._sensor_subtype

        @property
        def flags(self):
            """
            Gets any sensor flags set on the sensor channel

            :type: `collections.namedtuple`
            """
            return self._flags

    # PRIVATE ATTRIBUTES #

    _cache_units = False

    # UNIT CACHING #

    @property
    def cache_units(self):
        """
        If enabled, then units are not checked every time a measurement is
        made, reducing by half the number of round-trips to the device.

        .. warning::

            Setting this to `True` may cause incorrect values to be returned,
            if any commands are sent to the device either by its local panel,
            or by software other than InstrumentKit.

        :type: `bool`
        """
        return bool(self._cache_units)

    @cache_units.setter
    def cache_units(self, newval):
        self._cache_units = (self._READ_UNITS[self.measurement_configuration]
                             if newval else False)

    # SENSOR PROPERTIES #

    @property
    def sensor(self):
        """
        Returns information about the currently connected sensor.

        :type: :class:`PM100USB.Sensor`
        """
        return self.Sensor(self)

    # SENSING CONFIGURATION PROPERTIES #

    # TODO: make a setting of this refresh cache_units.
    measurement_configuration = enum_property("CONF",
                                              MeasurementConfiguration,
                                              doc="""
        Returns the current measurement configuration.

        :rtype: :class:`PM100USB.MeasurementConfiguration`
        """)

    @property
    def averaging_count(self):
        """
        Integer specifying how many samples to collect and average over for
        each measurement, with each sample taking approximately 3 ms.
        """
        return int(self.query("SENS:AVER:COUN?"))

    @averaging_count.setter
    def averaging_count(self, newval):
        if newval < 1:
            raise ValueError("Must count at least one time.")
        self.sendcmd("SENS:AVER:COUN {}".format(newval))

    # METHODS ##

    _READ_UNITS = defaultdict(lambda: pq.dimensionless)
    _READ_UNITS.update({
        MeasurementConfiguration.power: pq.W,
        MeasurementConfiguration.current: pq.A,
        MeasurementConfiguration.frequency: pq.Hz,
        MeasurementConfiguration.voltage: pq.V,
    })

    def read(self, size=-1):
        """
        Reads a measurement from this instrument, according to its current
        configuration mode.

        :param int size: Number of bytes to read from the instrument. Default
            of ``-1`` reads until a termination character is found.

        :units: As specified by :attr:`~PM100USB.measurement_configuration`.
        :rtype: :class:`~quantities.Quantity`
        """
        # Get the current configuration to find out the units we need to
        # attach.
        units = (self._READ_UNITS[self.measurement_configuration]
                 if not self._cache_units else self._cache_units)
        return float(self.query('READ?', size)) * units
Exemplo n.º 9
0
class MAUI(Oscilloscope):

    """
    Medium to high-end Teledyne-Lecroy Oscilloscopes are shipped with
    the MAUI user interface. This class can be used to communicate with
    these instruments.

    By default, the IEEE 488.2 commands are used. However, commands
    based on MAUI's `app` definition can be submitted too using the
    appropriate send / query commands.

    Your scope must be set up to communicate via LXI (VXI11) to be used
    with pyvisa. Make sure that either the pyvisa-py or the NI-VISA
    backend is installed. Please see the pyvisa documentation for more
    information.

    This class inherits from: `Oscilloscope`

    Example usage (more examples below):
        >>> import instruments as ik
        >>> import instruments.units as u
        >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
        >>> # start the trigger in automatic mode
        >>> inst.run()
        >>> print(inst.trigger_state)  # print the trigger state
        <TriggerState.auto: 'AUTO'>
        >>> # set timebase to 20 ns per division
        >>> inst.time_div = u.Quantity(20, u.ns)
        >>> # call the first oscilloscope channel
        >>> channel = inst.channel[0]
        >>> channel.trace = True  # turn the trace on
        >>> channel.coupling = channel.Coupling.dc50  # coupling to 50 Ohm
        >>> channel.scale = u.Quantity(1, u.V)  # vertical scale to 1V/division
        >>> # transfer a waveform into xdat and ydat:
        >>> xdat, ydat = channel.read_waveform()
    """

    # CONSTANTS #

    # number of horizontal and vertical divisions on the scope
    # HOR_DIVS = 10
    # VERT_DIVS = 8

    def __init__(self, filelike):
        super(MAUI, self).__init__(filelike)

        # turn off command headers -> for SCPI like behavior
        self.sendcmd("COMM_HEADER OFF")

        # constants
        self._number_channels = 4
        self._number_functions = 2
        self._number_measurements = 6

    # ENUMS #

    class MeasurementParameters(Enum):
        """
        Enum containing valid measurement parameters that only require
        one or more sources. Only single source parameters are currently
        implemented.
        """
        amplitude = "AMPL"
        area = "AREA"
        base = "BASE"
        delay = "DLY"
        duty_cycle = "DUTY"
        fall_time_80_20 = "FALL82"
        fall_time_90_10 = "FALL"
        frequency = "FREQ"
        maximum = "MAX"
        minimum = "MIN"
        mean = "MEAN"
        none = "NULL"
        overshoot_pos = "OVSP"
        overshoot_neg = "OVSN"
        peak_to_peak = "PKPK"
        period = "PER"
        phase = "PHASE"
        rise_time_20_80 = "RISE28"
        rise_time_10_90 = "RISE"
        rms = "RMS"
        stdev = "SDEV"
        top = "TOP"
        width_50_pos = "WID"
        width_50_neg = "WIDN"

    class TriggerState(Enum):

        """
        Enum containing valid trigger state for the oscilloscope.
        """
        auto = "AUTO"
        normal = "NORM"
        single = "SINGLE"
        stop = "STOP"

    class TriggerType(Enum):
        """Enum containing valid trigger state.

        Availability depends on oscilloscope options. Please consult
        your manual. Only simple types are currently included.

        .. warning:: Some of the trigger types are untested and might
            need further parameters in order to be appropriately set.
        """
        dropout = "DROPOUT"
        edge = "EDGE"
        glitch = "GLIT"
        interval = "INTV"
        pattern = "PA"
        runt = "RUNT"
        slew_rate = "SLEW"
        width = "WIDTH"
        qualified = "TEQ"
        tv = "TV"

    class TriggerSource(Enum):
        """Enum containing valid trigger sources.

        This is an enum for the default values.

        .. note:: This class is initialized like this for four channels,
            which is the default setting. If you change the number of
            channels, `TriggerSource` will be recreated using the
            routine `_create_trigger_source_enum`. This will make
            further channels available to you or remove channels that
            are not present in your setup.
        """
        c0 = "C1"
        c1 = "C2"
        c2 = "C3"
        c3 = "C4"
        ext = "EX"
        ext5 = "EX5"
        ext10 = "EX10"
        etm10 = "ETM10"
        line = "LINE"

    def _create_trigger_source_enum(self):
        """Create an Enum for the trigger source class.

        Needs to be dynamically generated, in case channel number
        changes!

        .. note:: Not all trigger sources are available on all scopes.
            Please consult the manual for your oscilloscope.
        """
        names = ['ext', 'ext5', 'ext10', 'etm10', 'line']
        values = ['EX', 'EX5', 'EX10', 'ETM10', 'LINE']
        # now add the channels
        for it in range(self._number_channels):
            names.append("c{}".format(it))
            values.append("C{}".format(it+1))  # to send to scope
        # create and store the enum
        self.TriggerSource = Enum('TriggerSource', zip(names, values))

    # CLASSES #

    class DataSource(OscilloscopeDataSource):

        """
        Class representing a data source (channel, math, ref) on a MAUI
        oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        # PROPERTIES #

        @property
        def name(self):
            return self._name

        # METHODS #

        def read_waveform(self, bin_format=False, single=True):
            """
            Reads the waveform and returns an array of floats with the
            data.

            :param bin_format: Not implemented, always False
            :type bin_format: bool
            :param single: Run a single trigger? Default True. In case
                a waveform from a channel is required, this option
                is recommended to be set to True. This means that the
                acquisition system is first stopped, a single trigger
                is issued, then the waveform is transfered, and the
                system is set back into the state it was in before.
                If sampling math with multiple samples, set this to
                false, otherwise the sweeps are cleared by the
                oscilloscope prior when a single trigger command is
                issued.
            :type single: bool

            :return: Data (time, signal) where time is in seconds and
                signal in V
            :rtype: `tuple`[`tuple`[`~pint.Quantity`, ...], `tuple`[`~pint.Quantity`, ...]]
                or if numpy is installed, `tuple`[`numpy.array`, `numpy.array`]

            :raises NotImplementedError: Bin format was chosen, but
                it is not implemented.

            Example usage:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> xdat, ydat = channel.read_waveform()  # read waveform
            """
            if bin_format:
                raise NotImplementedError("Bin format reading is currently "
                                          "not implemented for the MAUI "
                                          "routine.")

            if single:
                # get current trigger state (to reset after read)
                trig_state = self._parent.trigger_state
                # trigger state to single
                self._parent.trigger_state = self._parent.TriggerState.single

            # now read the data
            retval = self.query("INSPECT? 'SIMPLE'")  # pylint: disable=E1101

            # read the parameters to create time-base array
            horiz_off = self.query("INSPECT? 'HORIZ_OFFSET'")  # pylint: disable=E1101
            horiz_int = self.query("INSPECT? 'HORIZ_INTERVAL'")  # pylint: disable=E1101

            if single:
                # reset trigger
                self._parent.trigger_state = trig_state

            # format the string to appropriate data
            retval = retval.replace('"', '').split()
            if numpy:
                dat_val = numpy.array(retval, dtype=numpy.float)  # Convert to ndarray
            else:
                dat_val = tuple(map(float, retval))

            # format horizontal data into floats
            horiz_off = float(horiz_off.replace('"', '').split(':')[1])
            horiz_int = float(horiz_int.replace('"', '').split(':')[1])

            # create time base
            if numpy:
                dat_time = numpy.arange(
                    horiz_off,
                    horiz_off + horiz_int * (len(dat_val)),
                    horiz_int
                )
            else:
                dat_time = tuple(val * horiz_int + horiz_off for val in range(len(dat_val)))

            # fix length bug, sometimes dat_time is longer than dat_signal
            if len(dat_time) > len(dat_val):
                dat_time = dat_time[0:len(dat_val)]
            else:  # in case the opposite is the case
                dat_val = dat_val[0:len(dat_time)]

            if numpy:
                return numpy.stack((dat_time, dat_val))
            else:
                return dat_time, dat_val

        trace = bool_property(
            command="TRA",
            doc="""
            Gets/Sets if a given trace is turned on or off.
            
            Example usage:
            
            >>> import instruments as ik
            >>> address = "TCPIP0::192.168.0.10::INSTR"
            >>> inst = inst = ik.teledyne.MAUI.open_visa(address)
            >>> channel = inst.channel[0]
            >>> channel.trace = False
            """
        )

    class Channel(DataSource, OscilloscopeChannel):

        """
        Class representing a channel on a MAUI oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1   # 1-based

            # Initialize as a data source with name C{}.
            super(MAUI.Channel, self).__init__(
                self._parent,
                "C{}".format(self._idx)
            )

        # ENUMS #

        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the oscilloscope
            channel. 1 MOhm and 50 Ohm are included.
            """
            ac1M = "A1M"
            dc1M = "D1M"
            dc50 = "D50"
            ground = "GND"

        coupling = enum_property(
            "CPL",
            Coupling,
            doc="""
            Gets/sets the coupling for the specified channel.

            Example usage:

            >>> import instruments as ik
            >>> address = "TCPIP0::192.168.0.10::INSTR"
            >>> inst = inst = ik.teledyne.MAUI.open_visa(address)
            >>> channel = inst.channel[0]
            >>> channel.coupling = channel.Coupling.dc50
            """
        )

        # PROPERTIES #

        @property
        def offset(self):
            """
            Sets/gets the vertical offset of the specified input
            channel.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> channel.offset = u.Quantity(-1, u.V)
            """
            return u.Quantity(
                float(self.query('OFST?')), u.V
            )

        @offset.setter
        def offset(self, newval):
            newval = assume_units(newval, 'V').to(u.V).magnitude
            self.sendcmd('OFST {}'.format(newval))

        @property
        def scale(self):
            """
            Sets/Gets the vertical scale of the channel.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> channel.scale = u.Quantity(20, u.mV)
            """
            return u.Quantity(
                float(self.query('VDIV?')), u.V
            )

        @scale.setter
        def scale(self, newval):
            newval = assume_units(newval, 'V').to(u.V).magnitude
            self.sendcmd('VDIV {}'.format(newval))

        # METHODS #

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class
            with identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("C{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Executes the given query. Wraps commands sent from property
            factories in this class with identifiers for the specified
            channel.

            :param str cmd: String containing the query to
                execute.
            :param int size: Number of bytes to be read. Default is read
                until termination character is found.
            :return: The result of the query as returned by the
                connected instrument.
            :rtype: `str`
            """
            return self._parent.query("C{}:{}".format(self._idx, cmd),
                                      size=size)

    class Math(DataSource):

        """
        Class representing a function on a MAUI oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1   # 1-based

            # Initialize as a data source with name C{}.
            super(MAUI.Math, self).__init__(
                self._parent,
                "F{}".format(self._idx)
            )

        # CLASSES #

        class Operators:
            """
            Sets the operator for a given channel.
            Most operators need a source `src`. If the source is given
            as an integer, it is assume that the a signal channel is
            requested. If you want to select another math channel for
            example, you will need to specify the source as a tuple:
            Example: `src=('f', 0)` would represent the first function
            channel (called F1 in the MAUI manual). A channel could be
            selected by calling `src=('c', 1)`, which would request the
            second channel (oscilloscope channel 2). Please consult the
            oscilloscope manual / the math setup itself for further
            possibilities.

            .. note:: Your oscilloscope might not have all functions
            that are described here. Also: Not all possibilities are
            currently implemented. However, extension of this
            functionality should be simple when following the given
            structure
            """

            def __init__(self, parent):
                self._parent = parent

            # PROPERTIES #

            @property
            def current_setting(self):
                """
                Gets the current setting and returns it as the full
                command, as sent to the scope when setting an operator.
                """
                return self._parent.query('DEF?')

            # METHODS - OPERATORS #

            def absolute(self, src):
                """
                Absolute of wave form.

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                send_str = "'ABS({})'".format(src_str)
                self._send_operator(send_str)

            def average(self, src, average_type='summed', sweeps=1000):
                """
                Average of wave form.

                :param int,tuple src: Source, see info above
                :param str average_type: `summed` or `continuous`
                :param int sweeps: In summed mode, how many sweeps to
                    collect. In `continuous` mode the weight of each
                    sweep is equal to 1/`1`sweeps`
                """
                src_str = _source(src)

                avgtp_str = 'SUMMED'
                if average_type == 'continuous':
                    avgtp_str = 'CONTINUOUS'

                send_str = "'AVG({})',AVERAGETYPE,{},SWEEPS,{}".format(
                    src_str,
                    avgtp_str,
                    sweeps
                )

                self._send_operator(send_str)

            def derivative(self, src, vscale=1e6, voffset=0,
                           autoscale=True):
                """
                Derivative of waveform using subtraction of adjacent
                samples. If vscale and voffset are unitless, V/s are
                assumed.

                :param int,tuple src: Source, see info above
                :param float vscale: vertical units to display (V/s)
                :param float voffset: vertical offset (V/s)
                :param bool autoscale: auto scaling of vscale, voffset?
                """
                src_str = _source(src)

                vscale = assume_units(vscale, u.V/u.s).to(
                    u.V/u.s
                ).magnitude

                voffset = assume_units(voffset, u.V/u.s).to(
                    u.V/u.s
                ).magnitude

                autoscale_str = 'OFF'
                if autoscale:
                    autoscale_str = 'ON'

                send_str = "'DERI({})',VERSCALE,{},VEROFFSET,{}," \
                           "ENABLEAUTOSCALE,{}".format(
                               src_str,
                               vscale,
                               voffset,
                               autoscale_str)

                self._send_operator(send_str)

            def difference(self, src1, src2, vscale_variable=False):
                """
                Difference between two sources, `src1`-`src2`.

                :param int,tuple src1: Source 1, see info above
                :param int,tuple src2: Source 2, see info above
                :param bool vscale_variable: Horizontal and vertical
                    scale for addition and subtraction must be
                    identical. Allow for variable vertical scale in
                    result?
                """
                src1_str = _source(src1)
                src2_str = _source(src2)

                opt_str = 'FALSE'
                if vscale_variable:
                    opt_str = 'TRUE'

                send_str = "'{}-{}',VERSCALEVARIABLE,{}".format(
                    src1_str,
                    src2_str,
                    opt_str
                )

                self._send_operator(send_str)

            def envelope(self, src, sweeps=1000, limit_sweeps=True):
                """
                Highest and lowest Y values at each X in N sweeps.

                :param int,tuple src: Source, see info above
                :param int sweeps: Number of sweeps
                :param bool limit_sweeps: Limit the number of sweeps?
                """
                src_str = _source(src)
                send_str = "'EXTR({})',SWEEPS,{},LIMITNUMSWEEPS,{}".\
                    format(src_str, sweeps, limit_sweeps)
                self._send_operator(send_str)

            def eres(self, src, bits=0.5):
                """
                Smoothing function defined by extra bits of resolution.

                :param int,tuple src: Source, see info above
                :param float bits: Number of bits. Possible values are
                    (0.5, 1.0, 1.5, 2.0, 2.5, 3.0). If not in list,
                    default to 0.5.
                """
                src_str = _source(src)

                bits_possible = (0.5, 1.0, 1.5, 2.0, 2.5, 3.0)
                if bits not in bits_possible:
                    bits = 0.5

                send_str = "'ERES({})',BITS,{}".format(src_str, bits)

                self._send_operator(send_str)

            def fft(self, src, type='powerspectrum', window='vonhann',
                    suppress_dc=True):
                """
                Fast Fourier Transform of signal.

                :param int,tuple src: Source, see info above
                :param str type: Type of power spectrum. Possible
                    options are: ['real', 'imaginary', 'magnitude',
                    'phase', 'powerspectrum', 'powerdensity'].
                    Default: 'powerspectrum'
                :param str window: Window. Possible options are:
                    ['blackmanharris', 'flattop', 'hamming',
                    'rectangular', 'vonhann']. Default: 'vonhann'
                :param bool suppress_dc: Supress DC?
                """
                src_str = _source(src)

                type_possible = ['real', 'imaginary', 'magnitude', 'phase',
                                 'powerspectrum', 'powerdensity']
                if type not in type_possible:
                    type = 'powerspectrum'

                window_possible = ['blackmanharris', 'flattop', 'hamming',
                                   'rectangular', 'vonhann']
                if window not in window_possible:
                    window = 'vonhann'

                if suppress_dc:
                    opt = 'ON'
                else:
                    opt = 'OFF'

                send_str = "'FFT({})',TYPE,{},WINDOW,{},SUPPRESSDC,{}".format(
                    src_str,
                    type,
                    window,
                    opt
                )

                self._send_operator(send_str)

            def floor(self, src, sweeps=1000, limit_sweeps=True):
                """
                Lowest vertical value at each X value in N sweeps.

                :param int,tuple src: Source, see info above
                :param int sweeps: Number of sweeps
                :param bool limit_sweeps: Limit the number of sweeps?
                """
                src_str = _source(src)
                send_str = "'FLOOR({})',SWEEPS,{},LIMITNUMSWEEPS,{}".\
                    format(src_str, sweeps, limit_sweeps)
                self._send_operator(send_str)

            def integral(self, src, multiplier=1, adder=0, vscale=1e-3,
                         voffset=0):
                """
                Integral of waveform.

                :param int,tuple src: Source, see info above
                :param float multiplier: 0 to 1e15
                :param float adder: 0 to 1e15
                :param float vscale: vertical units to display (Wb)
                :param float voffset: vertical offset (Wb)
                """
                src_str = _source(src)

                vscale = assume_units(vscale, u.Wb).to(
                    u.Wb
                ).magnitude

                voffset = assume_units(voffset, u.Wb).to(
                    u.Wb
                ).magnitude

                send_str = "'INTG({}),MULTIPLIER,{},ADDER,{},VERSCALE,{}," \
                           "VEROFFSET,{}".format(
                               src_str,
                               multiplier,
                               adder,
                               vscale,
                               voffset)

                self._send_operator(send_str)

            def invert(self, src):
                """
                Inversion of waveform (-waveform).

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                self._send_operator("'-{}'".format(src_str))

            def product(self, src1, src2):
                """
                Product of two sources, `src1`*`src2`.

                :param int,tuple src1: Source 1, see info above
                :param int,tuple src2: Source 2, see info above
                """
                src1_str = _source(src1)
                src2_str = _source(src2)

                send_str = "'{}*{}'".format(
                    src1_str,
                    src2_str
                )

                self._send_operator(send_str)

            def ratio(self, src1, src2):
                """
                Ratio of two sources, `src1`/`src2`.

                :param int,tuple src1: Source 1, see info above
                :param int,tuple src2: Source 2, see info above
                """
                src1_str = _source(src1)
                src2_str = _source(src2)

                send_str = "'{}/{}'".format(
                    src1_str,
                    src2_str
                )

                self._send_operator(send_str)

            def reciprocal(self, src):
                """
                Reciprocal of waveform (1/waveform).

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                self._send_operator("'1/{}'".format(src_str))

            def rescale(self, src, multiplier=1, adder=0):
                """
                Rescales the waveform (w) in the style.
                multiplier * w + adder

                :param int,tuple src: Source, see info above
                :param float multiplier: multiplier
                :param float adder: addition in V or assuming V
                """
                src_str = _source(src)

                adder = assume_units(adder, u.V).to(
                    u.V
                ).magnitude

                send_str = "'RESC({})',MULTIPLIER,{},ADDER,{}".format(
                    src_str,
                    multiplier,
                    adder
                )

                self._send_operator(send_str)

            def sinx(self, src):
                """
                Sin(x)/x interpolation to produce 10x output samples.

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                self._send_operator("'SINX({})'".format(src_str))

            def square(self, src):
                """
                Square of the input waveform.

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                self._send_operator("'SQR({})'".format(src_str))

            def square_root(self, src):
                """
                Square root of the input waveform.

                :param int,tuple src: Source, see info above
                """
                src_str = _source(src)
                self._send_operator("'SQRT({})'".format(src_str))

            def sum(self, src1, src2):
                """
                Product of two sources, `src1`+`src2`.

                :param int,tuple src1: Source 1, see info above
                :param int,tuple src2: Source 2, see info above
                """
                src1_str = _source(src1)
                src2_str = _source(src2)

                send_str = "'{}+{}'".format(
                    src1_str,
                    src2_str
                )

                self._send_operator(send_str)

            def trend(self, src, vscale=1, center=0, autoscale=True):
                """
                Trend of the values of a paramter

                :param float vscale: vertical units to display (V)
                :param float center: center (V)
                """
                src_str = _source(src)

                vscale = assume_units(vscale, u.V).to(
                    u.V
                ).magnitude

                center = assume_units(center, u.V).to(
                    u.V
                ).magnitude

                if autoscale:
                    auto_str = 'ON'
                else:
                    auto_str = 'OFF'

                send_str = "'TREND({})',VERSCALE,{},CENTER,{}," \
                           "AUTOFINDSCALE,{}".format(src_str, vscale,
                                                     center, auto_str)

                self._send_operator(send_str)

            def roof(self, src, sweeps=1000, limit_sweeps=True):
                """
                Highest vertical value at each X value in N sweeps.

                :param int,tuple src: Source, see info above
                :param int sweeps: Number of sweeps
                :param bool limit_sweeps: Limit the number of sweeps?
                """
                src_str = _source(src)
                send_str = "'ROOF({})',SWEEPS,{},LIMITNUMSWEEPS,{}".\
                    format(src_str, sweeps, limit_sweeps)
                self._send_operator(send_str)

            def _send_operator(self, cmd):
                """
                Set the operator in the scope.
                """
                self._parent.sendcmd("{},{}".format(
                    "DEFINE EQN",
                    cmd))

        # PROPERTIES #

        @property
        def operator(self):
            """Get an operator object to set use to do math.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> # set up the first math function
                >>> function = inst.math[0]
                >>> function.trace = True  # turn the trace on
                >>> # set function to average the first oscilloscope channel
                >>> function.operator.average(0)
            """
            return self.Operators(self)

        # METHODS #

        def clear_sweeps(self):
            """Clear the sweeps in a measurement."""
            self._parent.clear_sweeps()   # re-implemented because handy

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class
            with identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("F{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Executes the given query. Wraps commands sent from property
            factories in this class with identifiers for the specified
            channel.

            :param str cmd: String containing the query to
                execute.
            :param int size: Number of bytes to be read. Default is read
                until termination character is found.
            :return: The result of the query as returned by the
                connected instrument.
            :rtype: `str`
            """
            return self._parent.query("F{}:{}".format(self._idx, cmd),
                                      size=size)

    class Measurement:

        """
        Class representing a measurement on a MAUI oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1   # 1-based

        # CLASSES #

        class State(Enum):
            """
            Enum class for Measurement Parameters. Required to turn it
            on or off.
            """
            statistics = "CUST,STAT"
            histogram_icon = "CUST,HISTICON"
            both = "CUST,BOTH"
            off = "CUST,OFF"

        # PROPERTIES #

        measurement_state = enum_property(
            command="PARM",
            enum=State,
            doc="""
                Sets / Gets the measurement state. Valid values are
                'statistics', 'histogram_icon', 'both', 'off'.
                
                Example:
                    >>> import instruments as ik
                    >>> import instruments.units as u
                    >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                    >>> msr1 = inst.measurement[0]  # set up first measurement
                    >>> msr1.measurement_state = msr1.State.both  # set to `both`
                """
        )

        @property
        def statistics(self):
            """
            Gets the statistics for the selected parameter. The scope
            must be in `My_Measure` mode.

            :return tuple: (average, low, high, sigma, sweeps)
            :return type: (float, float, float, float, float)
            """
            ret_str = self.query("PAST? CUST, P{}".format(self._idx)).\
                rstrip().split(',')
            # parse the return string -> put into dictionary:
            ret_dict = {ret_str[it]: ret_str[it+1] for it in
                        range(0, len(ret_str), 2)}
            try:
                stats = (
                    float(ret_dict['AVG']),
                    float(ret_dict['LOW']),
                    float(ret_dict['HIGH']),
                    float(ret_dict['SIGMA']),
                    float(ret_dict['SWEEPS'])
                )
            except ValueError:  # some statistics did not return
                raise ValueError("Some statistics did not return useful "
                                 "values. The return string is {}. Please "
                                 "ensure that statistics is properly turned "
                                 "on.".format(ret_str))
            return stats

        # METHODS #

        def delete(self):
            """
            Deletes the given measurement parameter.
            """
            self.sendcmd("PADL {}".format(self._idx))

        def set_parameter(self, param, src):
            """
            Sets a given parameter that should be measured on this
            given channel.

            :param `inst.MeasurementParameters` param: The parameter
                to set from the given enum list.
            :param int,tuple src: Source, either as an integer if a
                channel is requested (e.g., src=0 for Channel 1) or as
                a tuple in the form, e.g., ('F', 1). Here 'F' refers
                to a mathematical function and 1 would take the second
                mathematical function `F2`.

            :raises AttributeError: The chosen parameter is invalid.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> msr1 = inst.measurement[0]  # set up first measurement
                >>> # setup to measure the 10 - 90% rise time on first channel
                >>> msr1.set_parameter(inst.MeasurementParameters.rise_time_10_90, 0)
            """
            if not isinstance(param, self._parent.MeasurementParameters):
                raise AttributeError("Parameter must be selected from {}.".
                                     format(self._parent.MeasurementParameters)
                                     )

            send_str = \
                "PACU {},{},{}".format(
                    self._idx,
                    param.value,
                    _source(src)
                )

            self.sendcmd(send_str)

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class
            with identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd(cmd)

        def query(self, cmd, size=-1):
            """
            Executes the given query. Wraps commands sent from property
            factories in this class with identifiers for the specified
            channel.

            :param str cmd: String containing the query to
                execute.
            :param int size: Number of bytes to be read. Default is read
                until termination character is found.
            :return: The result of the query as returned by the
                connected instrument.
            :rtype: `str`
            """
            return self._parent.query(cmd,
                                      size=size)

    # PROPERTIES #

    @property
    def channel(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        channel objects on the oscilloscope instrument.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> channel = inst.channel[0]  # get first channel
        """
        return ProxyList(self, self.Channel, range(self.number_channels))

    @property
    def math(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        math data sources objects on the oscilloscope instrument.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> math = inst.math[0]  # get first math function
        """
        return ProxyList(self, self.Math, range(self.number_functions))

    @property
    def measurement(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        measurement data sources objects on the oscilloscope instrument.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> msr = inst.measurement[0]  # get first measurement parameter
        """
        return ProxyList(self, self.Measurement,
                         range(self.number_measurements))

    @property
    def ref(self):
        raise NotImplementedError

    # PROPERTIES

    @property
    def number_channels(self):
        """
        Sets/Gets the number of channels available on the specific
        oscilloscope. Defaults to 4.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.number_channel = 2  # for a oscilloscope with 2 channels
            >>> inst.number_channel
            2
        """
        return self._number_channels

    @number_channels.setter
    def number_channels(self, newval):
        self._number_channels = newval
        # create new trigger source enum
        self._create_trigger_source_enum()

    @property
    def number_functions(self):
        """
        Sets/Gets the number of functions available on the specific
        oscilloscope. Defaults to 2.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.number_functions = 4  # for a oscilloscope with 4 math functions
            >>> inst.number_functions
            4
        """
        return self._number_functions

    @number_functions.setter
    def number_functions(self, newval):
        self._number_functions = newval

    @property
    def number_measurements(self):
        """
        Sets/Gets the number of measurements available on the specific
        oscilloscope. Defaults to 6.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.number_measurements = 4  # for a oscilloscope with 4 measurements
            >>> inst.number_measurements
            4
        """
        return self._number_measurements

    @number_measurements.setter
    def number_measurements(self, newval):
        self._number_measurements = newval

    @property
    def self_test(self):
        """
        Runs an oscilloscope's internal self test and returns the
        result. The self-test includes testing the hardware of all
        channels, the timebase and the trigger circuits.
        Hardware failures are identified by a unique binary code in the
        returned <status> number. A status of 0 indicates that no
        failures occurred.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.self_test()
        """
        # increase timeout x 10 to allow for enough time to test
        self.timeout *= 10
        retval = self.query("*TST?")
        self.timeout /= 10
        return retval

    @property
    def show_id(self):
        """
        Gets the scope information and returns it. The response
        comprises manufacturer, oscilloscope model, serial number,
        and firmware revision level.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.show_id()
        """
        return self.query("*IDN?")

    @property
    def show_options(self):
        """
        Gets and returns oscilloscope options: installed software or
        hardware that is additional to the standard instrument
        configuration. The response consists of a series of response
        fields listing all the installed options.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.show_options()
        """
        return self.query("*OPT?")

    @property
    def time_div(self):
        """
        Sets/Gets the time per division, modifies the timebase setting.
        Unitful.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.time_div = u.Quantity(200, u.ns)
        """
        return u.Quantity(
            float(self.query('TDIV?')), u.s
        )

    @time_div.setter
    def time_div(self, newval):
        newval = assume_units(newval, 's').to(u.s).magnitude
        self.sendcmd('TDIV {}'.format(newval))

    # TRIGGER PROPERTIES

    trigger_state = enum_property(
        command="TRMD",
        enum=TriggerState,
        doc="""
            Sets / Gets the trigger state. Valid values are are defined
            in `TriggerState` enum class.
            
            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> inst.trigger_state = inst.TriggerState.normal
            """
    )

    @property
    def trigger_delay(self):
        """
        Sets/Gets the trigger offset with respect to time zero (i.e.,
        a horizontal shift). Unitful.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.trigger_delay = u.Quantity(60, u.ns)

        """
        return u.Quantity(
            float(self.query('TRDL?')), u.s
        )

    @trigger_delay.setter
    def trigger_delay(self, newval):
        newval = assume_units(newval, 's').to(u.s).magnitude
        self.sendcmd('TRDL {}'.format(newval))

    @property
    def trigger_source(self):
        """Sets / Gets the trigger source.

        .. note:: The `TriggerSource` class is dynamically generated
        when the number of channels is switched. The above shown class
        is only the default! Channels are added and removed, as
        required.

        .. warning:: If a trigger type is currently set on the
            oscilloscope that is not implemented in this class,
            setting the source will fail. The oscilloscope is set up
            such that the the trigger type and source are set at the
            same time. However, for convenience, these two properties
            are split apart here.

        :return: Trigger source.
        :rtype: Member of `TriggerSource` class.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.trigger_source = inst.TriggerSource.ext  # external trigger
        """
        retval = self.query('TRIG_SELECT?').split(',')[2]
        return self.TriggerSource(retval)

    @trigger_source.setter
    def trigger_source(self, newval):
        curr_trig_typ = self.trigger_type
        cmd = 'TRIG_SELECT {},SR,{}'.format(curr_trig_typ.value,
                                            newval.value)
        self.sendcmd(cmd)

    @property
    def trigger_type(self):
        """Sets / Gets the trigger type.

        .. warning:: If a trigger source is currently set on the
            oscilloscope that is not implemented in this class,
            setting the source will fail. The oscilloscope is set up
            such that the the trigger type and source are set at the
            same time. However, for convenience, these two properties
            are split apart here.

        :return: Trigger type.
        :rtype: Member of `TriggerType` enum class.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.trigger_type = inst.TriggerType.edge  # trigger on edge
        """
        retval = self.query('TRIG_SELECT?').split(',')[0]
        return self.TriggerType(retval)

    @trigger_type.setter
    def trigger_type(self, newval):
        curr_trig_src = self.trigger_source
        cmd = 'TRIG_SELECT {},SR,{}'.format(newval.value,
                                            curr_trig_src.value)
        self.sendcmd(cmd)

    # METHODS #

    def clear_sweeps(self):
        """Clears the sweeps in a measurement.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.clear_sweeps()
        """
        self.sendcmd("CLEAR_SWEEPS")

    def force_trigger(self):
        """Forces a trigger event to occur on the attached oscilloscope.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.force_trigger()
        """
        self.sendcmd("ARM")

    def run(self):
        """Enables the trigger for the oscilloscope and sets it to auto.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.run()
        """
        self.trigger_state = self.TriggerState.auto

    def stop(self):
        """ Disables the trigger for the oscilloscope.

        Example:
            >>> import instruments as ik
            >>> import instruments.units as u
            >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
            >>> inst.stop()
        """
        self.sendcmd("STOP")
Exemplo n.º 10
0
    class Measurement:

        """
        Class representing a measurement on a MAUI oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1   # 1-based

        # CLASSES #

        class State(Enum):
            """
            Enum class for Measurement Parameters. Required to turn it
            on or off.
            """
            statistics = "CUST,STAT"
            histogram_icon = "CUST,HISTICON"
            both = "CUST,BOTH"
            off = "CUST,OFF"

        # PROPERTIES #

        measurement_state = enum_property(
            command="PARM",
            enum=State,
            doc="""
                Sets / Gets the measurement state. Valid values are
                'statistics', 'histogram_icon', 'both', 'off'.
                
                Example:
                    >>> import instruments as ik
                    >>> import instruments.units as u
                    >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                    >>> msr1 = inst.measurement[0]  # set up first measurement
                    >>> msr1.measurement_state = msr1.State.both  # set to `both`
                """
        )

        @property
        def statistics(self):
            """
            Gets the statistics for the selected parameter. The scope
            must be in `My_Measure` mode.

            :return tuple: (average, low, high, sigma, sweeps)
            :return type: (float, float, float, float, float)
            """
            ret_str = self.query("PAST? CUST, P{}".format(self._idx)).\
                rstrip().split(',')
            # parse the return string -> put into dictionary:
            ret_dict = {ret_str[it]: ret_str[it+1] for it in
                        range(0, len(ret_str), 2)}
            try:
                stats = (
                    float(ret_dict['AVG']),
                    float(ret_dict['LOW']),
                    float(ret_dict['HIGH']),
                    float(ret_dict['SIGMA']),
                    float(ret_dict['SWEEPS'])
                )
            except ValueError:  # some statistics did not return
                raise ValueError("Some statistics did not return useful "
                                 "values. The return string is {}. Please "
                                 "ensure that statistics is properly turned "
                                 "on.".format(ret_str))
            return stats

        # METHODS #

        def delete(self):
            """
            Deletes the given measurement parameter.
            """
            self.sendcmd("PADL {}".format(self._idx))

        def set_parameter(self, param, src):
            """
            Sets a given parameter that should be measured on this
            given channel.

            :param `inst.MeasurementParameters` param: The parameter
                to set from the given enum list.
            :param int,tuple src: Source, either as an integer if a
                channel is requested (e.g., src=0 for Channel 1) or as
                a tuple in the form, e.g., ('F', 1). Here 'F' refers
                to a mathematical function and 1 would take the second
                mathematical function `F2`.

            :raises AttributeError: The chosen parameter is invalid.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> msr1 = inst.measurement[0]  # set up first measurement
                >>> # setup to measure the 10 - 90% rise time on first channel
                >>> msr1.set_parameter(inst.MeasurementParameters.rise_time_10_90, 0)
            """
            if not isinstance(param, self._parent.MeasurementParameters):
                raise AttributeError("Parameter must be selected from {}.".
                                     format(self._parent.MeasurementParameters)
                                     )

            send_str = \
                "PACU {},{},{}".format(
                    self._idx,
                    param.value,
                    _source(src)
                )

            self.sendcmd(send_str)

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class
            with identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd(cmd)

        def query(self, cmd, size=-1):
            """
            Executes the given query. Wraps commands sent from property
            factories in this class with identifiers for the specified
            channel.

            :param str cmd: String containing the query to
                execute.
            :param int size: Number of bytes to be read. Default is read
                until termination character is found.
            :return: The result of the query as returned by the
                connected instrument.
            :rtype: `str`
            """
            return self._parent.query(cmd,
                                      size=size)
Exemplo n.º 11
0
    class Channel(DataSource, OscilloscopeChannel):

        """
        Class representing a channel on a MAUI oscilloscope.

        .. warning:: This class should NOT be manually created by the
            user. It is designed to be initialized by the `MAUI` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1   # 1-based

            # Initialize as a data source with name C{}.
            super(MAUI.Channel, self).__init__(
                self._parent,
                "C{}".format(self._idx)
            )

        # ENUMS #

        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the oscilloscope
            channel. 1 MOhm and 50 Ohm are included.
            """
            ac1M = "A1M"
            dc1M = "D1M"
            dc50 = "D50"
            ground = "GND"

        coupling = enum_property(
            "CPL",
            Coupling,
            doc="""
            Gets/sets the coupling for the specified channel.

            Example usage:

            >>> import instruments as ik
            >>> address = "TCPIP0::192.168.0.10::INSTR"
            >>> inst = inst = ik.teledyne.MAUI.open_visa(address)
            >>> channel = inst.channel[0]
            >>> channel.coupling = channel.Coupling.dc50
            """
        )

        # PROPERTIES #

        @property
        def offset(self):
            """
            Sets/gets the vertical offset of the specified input
            channel.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> channel.offset = u.Quantity(-1, u.V)
            """
            return u.Quantity(
                float(self.query('OFST?')), u.V
            )

        @offset.setter
        def offset(self, newval):
            newval = assume_units(newval, 'V').to(u.V).magnitude
            self.sendcmd('OFST {}'.format(newval))

        @property
        def scale(self):
            """
            Sets/Gets the vertical scale of the channel.

            Example:
                >>> import instruments as ik
                >>> import instruments.units as u
                >>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
                >>> channel = inst.channel[0]  # set up channel
                >>> channel.scale = u.Quantity(20, u.mV)
            """
            return u.Quantity(
                float(self.query('VDIV?')), u.V
            )

        @scale.setter
        def scale(self, newval):
            newval = assume_units(newval, 'V').to(u.V).magnitude
            self.sendcmd('VDIV {}'.format(newval))

        # METHODS #

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class
            with identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("C{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Executes the given query. Wraps commands sent from property
            factories in this class with identifiers for the specified
            channel.

            :param str cmd: String containing the query to
                execute.
            :param int size: Number of bytes to be read. Default is read
                until termination character is found.
            :return: The result of the query as returned by the
                connected instrument.
            :rtype: `str`
            """
            return self._parent.query("C{}:{}".format(self._idx, cmd),
                                      size=size)
Exemplo n.º 12
0
    class EnumMock(MockInstrument):

        a = enum_property('MOCK:A', SillyEnum, input_decoration=int)
Exemplo n.º 13
0
 class EnumMock(MockInstrument):
     a = enum_property('MOCK:A', SillyEnum, set_fmt="{}={}")
class Keithley6517b(SCPIInstrument, Electrometer):
    """
    The `Keithley 6517b` is an electrometer capable of doing sensitive current,
    charge, voltage and resistance measurements.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> dmm = ik.keithley.Keithley6517b.open_serial('/dev/ttyUSB0', baud=115200)
    >>> dmm.measure(dmm.Mode.current)
    <Quantity(0.123, 'ampere')>
    """
    def __init__(self, filelike):
        """
        Auto configs the instrument in to the current readout mode
        (sets the trigger and communication types rights)
        """
        super(Keithley6517b, self).__init__(filelike)
        self.auto_config(self.mode)

    # ENUMS ##

    class Mode(Enum):
        """
        Enum containing valid measurement modes for the Keithley 6517b
        """
        voltage_dc = 'VOLT:DC'
        current_dc = 'CURR:DC'
        resistance = 'RES'
        charge = 'CHAR'

    class TriggerMode(Enum):
        """
        Enum containing valid trigger modes for the Keithley 6517b
        """
        immediate = 'IMM'
        tlink = 'TLINK'

    class ArmSource(Enum):
        """
        Enum containing valid trigger arming sources for the Keithley 6517b
        """
        immediate = 'IMM'
        timer = 'TIM'
        bus = 'BUS'
        tlink = 'TLIN'
        stest = 'STES'
        pstest = 'PST'
        nstest = 'NST'
        manual = 'MAN'

    class ValidRange(Enum):
        """
        Enum containing valid measurement ranges for the Keithley 6517b
        """
        voltage_dc = (2, 20, 200)
        current_dc = (20e-12, 200e-12, 2e-9, 20e-9, 200e-9, 2e-6, 20e-6,
                      200e-6, 2e-3, 20e-3)
        resistance = (2e6, 20e6, 200e6, 2e9, 20e9, 200e9, 2e12, 20e12, 200e12)
        charge = (2e-9, 20e-9, 200e-9, 2e-6)

    # PROPERTIES ##

    mode = enum_property(
        'FUNCTION',
        Mode,
        input_decoration=lambda val: val[1:-1],
        # output_decoration=lambda val: '"{}"'.format(val),
        set_fmt='{} "{}"',
        doc="""
        Gets/sets the measurement mode of the Keithley 6517b.
        """)

    trigger_mode = enum_property('TRIGGER:SOURCE',
                                 TriggerMode,
                                 doc="""
        Gets/sets the trigger mode of the Keithley 6517b.
        """)

    arm_source = enum_property('ARM:SOURCE',
                               ArmSource,
                               doc="""
        Gets/sets the arm source of the Keithley 6517b.
        """)

    zero_check = bool_property('SYST:ZCH',
                               inst_true='ON',
                               inst_false='OFF',
                               doc="""
        Gets/sets the zero checking status of the Keithley 6517b.
        """)

    zero_correct = bool_property('SYST:ZCOR',
                                 inst_true='ON',
                                 inst_false='OFF',
                                 doc="""
        Gets/sets the zero correcting status of the Keithley 6517b.
        """)

    @property
    def unit(self):
        return UNITS[self.mode]

    @property
    def auto_range(self):
        """
        Gets/sets the auto range setting

        :type: `bool`
        """
        # pylint: disable=no-member
        out = self.query('{}:RANGE:AUTO?'.format(self.mode.value))
        return out == '1'

    @auto_range.setter
    def auto_range(self, newval):
        # pylint: disable=no-member
        self.sendcmd('{}:RANGE:AUTO {}'.format(self.mode.value,
                                               '1' if newval else '0'))

    @property
    def input_range(self):
        """
        Gets/sets the upper limit of the current range.

        :type: `~pint.Quantity`
        """
        # pylint: disable=no-member
        mode = self.mode
        out = self.query('{}:RANGE:UPPER?'.format(mode.value))
        return float(out) * UNITS[mode]

    @input_range.setter
    def input_range(self, newval):
        # pylint: disable=no-member
        mode = self.mode
        val = newval.to(UNITS[mode]).magnitude
        if val not in self._valid_range(mode).value:
            raise ValueError(
                'Unexpected range limit for currently selected mode.')
        self.sendcmd('{}:RANGE:UPPER {:e}'.format(mode.value, val))

    # METHODS ##

    def auto_config(self, mode):
        """
        This command causes the device to do the following:
            - Switch to the specified mode
            - Reset all related controls to default values
            - Set trigger and arm to the 'immediate' setting
            - Set arm and trigger counts to 1
            - Set trigger delays to 0
            - Place unit in idle state
            - Disable all math calculations
            - Disable buffer operation
            - Enable autozero
        """
        self.sendcmd('CONF:{}'.format(mode.value))

    def fetch(self):
        """
        Request the latest post-processed readings using the current mode.
        (So does not issue a trigger)
        Returns a tuple of the form (reading, timestamp, trigger_count)
        """
        return self._parse_measurement(self.query('FETC?'))

    def read_measurements(self):
        """
        Trigger and acquire readings using the current mode.
        Returns a tuple of the form (reading, timestamp, trigger_count)
        """
        return self._parse_measurement(self.query('READ?'))

    def measure(self, mode=None):
        """
        Trigger and acquire readings using the requested mode.
        Returns the measurement reading only.
        """
        # Check the current mode, change if necessary
        if mode is not None:
            if mode != self.mode:
                self.auto_config(mode)

        return self.read_measurements()[0]

    # PRIVATE METHODS ##

    def _valid_range(self, mode):
        if mode == self.Mode.voltage_dc:
            return self.ValidRange.voltage_dc
        if mode == self.Mode.current_dc:
            return self.ValidRange.current_dc
        if mode == self.Mode.resistance:
            return self.ValidRange.resistance
        if mode == self.Mode.charge:
            return self.ValidRange.charge

        raise ValueError('Invalid mode.')

    def _parse_measurement(self, ascii):
        # Split the string in three comma-separated parts (value, time, number of triggers)
        vals = ascii.split(',')
        reading = float(vals[0].split('N')[0]) * self.unit
        timestamp = float(vals[1].split('s')[0]) * u.second
        trigger_count = int(vals[2][:-5].split('R')[0])
        return reading, timestamp, trigger_count
Exemplo n.º 15
0
class SRS345(SCPIInstrument, FunctionGenerator):
    """
    The SRS DS345 is a 30MHz function generator.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> srs = ik.srs.SRS345.open_gpib('/dev/ttyUSB0', 1)
    >>> srs.frequency = 1 * u.MHz
    >>> print(srs.offset)
    >>> srs.function = srs.Function.triangle
    """
    # FIXME: need to add OUTX 1 here, but doing so seems to cause a syntax
    #        error on the instrument.

    # CONSTANTS #

    _UNIT_MNEMONICS = {
        FunctionGenerator.VoltageMode.peak_to_peak: "VP",
        FunctionGenerator.VoltageMode.rms: "VR",
        FunctionGenerator.VoltageMode.dBm: "DB",
    }

    _MNEMONIC_UNITS = dict(
        (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.items())

    # FunctionGenerator CONTRACT #

    def _get_amplitude_(self):
        resp = self.query("AMPL?").strip()

        return (float(resp[:-2]), self._MNEMONIC_UNITS[resp[-2:]])

    def _set_amplitude_(self, magnitude, units):
        self.sendcmd("AMPL {}{}".format(magnitude,
                                        self._UNIT_MNEMONICS[units]))

    # ENUMS ##

    class Function(IntEnum):
        """
        Enum containing valid output function modes for the SRS 345
        """
        sinusoid = 0
        square = 1
        triangle = 2
        ramp = 3
        noise = 4
        arbitrary = 5

    # PROPERTIES ##

    frequency = unitful_property(command="FREQ",
                                 units=u.Hz,
                                 doc="""
        Gets/sets the output frequency.

        :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise.
        :type: `float` or `~pint.Quantity`
        """)

    function = enum_property(command="FUNC",
                             enum=Function,
                             input_decoration=int,
                             doc="""
        Gets/sets the output function of the function generator.

        :type: `~SRS345.Function`
        """)

    offset = unitful_property(command="OFFS",
                              units=u.volt,
                              doc="""
        Gets/sets the offset voltage for the output waveform.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~pint.Quantity`
        """)

    phase = unitful_property(command="PHSE",
                             units=u.degree,
                             doc="""
        Gets/sets the phase for the output waveform.

        :units: As specified, or assumed to be degrees (:math:`{}^{\\circ}`)
            otherwise.
        :type: `float` or `~pint.Quantity`
        """)
Exemplo n.º 16
0
class TC200(Instrument):
    """
    The TC200 is is a controller for the voltage across a heating element.
    It can also read in the temperature off of a thermistor and implements
    a PID control to keep the temperature at a set value.

    The user manual can be found here:
    http://www.thorlabs.com/thorcat/12500/TC200-Manual.pdf
    """
    def __init__(self, filelike):
        super(TC200, self).__init__(filelike)
        self.terminator = "\r"
        self.prompt = "> "

    def _ack_expected(self, msg=""):
        return msg

    # ENUMS #

    class Mode(IntEnum):
        """
        Enum containing valid output modes of the TC200.
        """
        normal = 0
        cycle = 1

    class Sensor(Enum):
        """
        Enum containing valid temperature sensor types for the TC200.
        """
        ptc100 = "ptc100"
        ptc1000 = "ptc1000"
        th10k = "th10k"
        ntc10k = "ntc10k"

    # PROPERTIES #

    def name(self):
        """
        Gets the name and version number of the device

        :return: the name string of the device
        :rtype: str
        """
        response = self.query("*idn?")
        return response

    @property
    def mode(self):
        """
        Gets/sets the output mode of the TC200

        :type: `TC200.Mode`
        """
        response = self.status
        response_code = (int(response) >> 1) % 2
        return TC200.Mode(response_code)

    @mode.setter
    def mode(self, newval):
        if not isinstance(newval, TC200.Mode):
            raise TypeError("Mode setting must be a `TC200.Mode` value, "
                            "got {} instead.".format(type(newval)))
        out_query = "mode={}".format(newval.name)
        # there is an issue with the TC200; it responds with a spurious
        # Command Error on mode=normal. Thus, the sendcmd() method cannot
        # be used.
        if newval == TC200.Mode.normal:
            self.prompt = "Command error CMD_ARG_RANGE_ERR\n\r> "
            self.sendcmd(out_query)
            self.prompt = "> "
        else:
            self.sendcmd(out_query)

    @property
    def enable(self):
        """
        Gets/sets the heater enable status.

        If output enable is on (`True`), there is a voltage on the output.

        :type: `bool`
        """
        response = self.status
        return True if int(response) % 2 is 1 else False

    @enable.setter
    def enable(self, newval):
        if not isinstance(newval, bool):
            raise TypeError("TC200 enable property must be specified with a "
                            "boolean.")
        # the "ens" command is a toggle, we need to track two different cases,
        # when it should be on and it is off, and when it is off and
        # should be on

        # if no sensor is attached, the unit will respond with an error.
        # There is no current error handling in the way that thorlabs
        # responds with errors
        if newval and not self.enable:

            response1 = self._file.query("ens")
            while response1 != ">":
                response1 = self._file.read(1)
            self._file.read(1)

        elif not newval and self.enable:
            response1 = self._file.query("ens")
            while response1 != ">":
                response1 = self._file.read(1)
            self._file.read(1)

    @property
    def status(self):
        """
        Gets the the status code of the TC200

        :rtype: `int`
        """
        _ = self._file.query(str("stat?"))
        response = self.read(5)
        return int(response.split(" ")[0])

    temperature = unitful_property(
        "tact",
        units=pq.degC,
        readonly=True,
        input_decoration=lambda x: x.replace(" C", "").replace(
            " F", "").replace(" K", ""),
        doc="""
        Gets the actual temperature of the sensor

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units degrees C.
        :type: `~quantities.quantity.Quantity` or `int`
        :return: the temperature (in degrees C)
        :rtype: `~quantities.quantity.Quantity`
        """)

    max_temperature = unitful_property("tmax",
                                       units=pq.degC,
                                       format_code="{:.1f}",
                                       set_fmt="{}={}",
                                       valid_range=(20 * pq.degC,
                                                    205 * pq.degC),
                                       doc="""
        Gets/sets the maximum temperature

        :return: the maximum temperature (in deg C)
        :units: As specified or assumed to be degree Celsius. Returns with
            units degC.
        :rtype: `~quantities.quantity.Quantity`
        """)

    @property
    def temperature_set(self):
        """
        Gets/sets the actual temperature of the sensor

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units degrees C.
        :type: `~quantities.quantity.Quantity` or `int`
        :return: the temperature (in degrees C)
        :rtype: `~quantities.quantity.Quantity`
        """
        response = self.query("tset?").replace(" C", "").replace(" F",
                                                                 "").replace(
                                                                     " K", "")
        return float(response) * pq.degC

    @temperature_set.setter
    def temperature_set(self, newval):
        # the set temperature is always in celsius
        newval = convert_temperature(newval, pq.degC).magnitude
        if newval < 20.0 or newval > self.max_temperature:
            raise ValueError("Temperature set is out of range.")
        out_query = "tset={}".format(newval)
        self.sendcmd(out_query)

    @property
    def p(self):
        """
        Gets/sets the p-gain. Valid numbers are [1,250].

        :return: the p-gain (in nnn)
        :rtype: `int`
        """
        return self.pid[0]

    @p.setter
    def p(self, newval):
        if newval not in range(1, 251):
            raise ValueError("P-value not in [1, 250]")
        self.sendcmd("pgain={}".format(newval))

    @property
    def i(self):
        """
        Gets/sets the i-gain. Valid numbers are [1,250]

        :return: the i-gain (in nnn)
        :rtype: `int`
        """
        return self.pid[1]

    @i.setter
    def i(self, newval):
        if newval not in range(0, 251):
            raise ValueError("I-value not in [0, 250]")
        self.sendcmd("igain={}".format(newval))

    @property
    def d(self):
        """
        Gets/sets the d-gain. Valid numbers are [0, 250]

        :return: the d-gain (in nnn)
        :type: `int`
        """
        return self.pid[2]

    @d.setter
    def d(self, newval):
        if newval not in range(0, 251):
            raise ValueError("D-value not in [0, 250]")
        self.sendcmd("dgain={}".format(newval))

    @property
    def pid(self):
        """
        Gets/sets all three PID values at the same time. See `TC200.p`,
        `TC200.i`, and `TC200.d` for individual restrictions.

        If `None` is specified then the corresponding PID value is not changed.

        :return: List of integers of PID values. In order [P, I, D].
        :type: `list` or `tuple`
        :rtype: `list`
        """
        return list(map(int, self.query("pid?").split()))

    @pid.setter
    def pid(self, newval):
        if not isinstance(newval, (list, tuple)):
            raise TypeError("Setting PID must be specified as a list or tuple")
        if newval[0] is not None:
            self.p = newval[0]
        if newval[1] is not None:
            self.i = newval[1]
        if newval[2] is not None:
            self.d = newval[2]

    @property
    def degrees(self):
        """
        Gets/sets the units of the temperature measurement.

        :return: The temperature units (degC/F/K) the TC200 is measuring in
        :type: `~quantities.unitquantity.UnitTemperature`
        """
        response = self.status
        if (response >> 4) % 2 and (response >> 5) % 2:
            return pq.degC
        elif (response >> 5) % 2:
            return pq.degK
        else:
            return pq.degF

    @degrees.setter
    def degrees(self, newval):
        if newval is pq.degC:
            self.sendcmd("unit=c")
        elif newval is pq.degF:
            self.sendcmd("unit=f")
        elif newval is pq.degK:
            self.sendcmd("unit=k")
        else:
            raise TypeError("Invalid temperature type")

    sensor = enum_property("sns",
                           Sensor,
                           input_decoration=lambda x: x.split(",")[0].split(
                               "=")[1].strip().lower(),
                           set_fmt="{}={}",
                           doc="""
        Gets/sets the current thermistor type. Used for converting resistances
        to temperatures.

        :return: The thermistor type
        :type: `TC200.Sensor`
        """)

    beta = int_property("beta",
                        valid_set=range(2000, 6001),
                        set_fmt="{}={}",
                        doc="""
        Gets/sets the beta value of the thermistor curve.

        Value within [2000, 6000]

        :return: the gain (in nnn)
        :type: `int`
        """)

    max_power = unitful_property("pmax",
                                 units=pq.W,
                                 format_code="{:.1f}",
                                 set_fmt="{}={}",
                                 valid_range=(0.1 * pq.W, 18.0 * pq.W),
                                 doc="""
        Gets/sets the maximum power

        :return: The maximum power
        :units: Watts (linear units)
        :type: `~quantities.quantity.Quantity`
        """)
Exemplo n.º 17
0
    class Math(DataSource):
        """
        Class representing a math channel on the Tektronix DPO 70000.

        This class inherits from `TekDPO70000.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `TekDPO70000` class.
        """
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # 1-based.

            # Initialize as a data source with name MATH{}.
            super(TekDPO70000.Math, self).__init__(parent,
                                                   "MATH{}".format(self._idx))

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class with
            identifiers for the specified math channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("MATH{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Wraps queries sent from property factories in this class with
            identifiers for the specified math channel.

            :param str cmd: Query command to send to the instrument
            :param int size: Number of characters to read from the response.
                Default value reads until a termination character is found.
            :return: The query response
            :rtype: `str`
            """
            return self._parent.query("MATH{}:{}".format(self._idx, cmd), size)

        class FilterMode(Enum):
            """
            Enum containing valid filter modes for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            centered = "CENT"
            shifted = "SHIF"

        class Mag(Enum):
            """
            Enum containing valid amplitude units for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            linear = "LINEA"
            db = "DB"
            dbm = "DBM"

        class Phase(Enum):
            """
            Enum containing valid phase units for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            degrees = "DEG"
            radians = "RAD"
            group_delay = "GROUPD"

        class SpectralWindow(Enum):
            """
            Enum containing valid spectral windows for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            rectangular = "RECTANG"
            hamming = "HAMM"
            hanning = "HANN"
            kaiser_besse = "KAISERB"
            blackman_harris = "BLACKMANH"
            flattop2 = "FLATTOP2"
            gaussian = "GAUSS"
            tek_exponential = "TEKEXP"

        define = string_property("DEF",
                                 doc="""
            A text string specifying the math to do, ex. CH1+CH2
            """)

        filter_mode = enum_property("FILT:MOD", FilterMode)

        filter_risetime = unitful_property("FILT:RIS", u.second)

        label = string_property("LAB:NAM",
                                doc="""
            Just a human readable label for the channel.
            """)

        label_xpos = unitless_property("LAB:XPOS",
                                       doc="""
            The x position, in divisions, to place the label.
            """)

        label_ypos = unitless_property(
            "LAB:YPOS",
            doc="""The y position, in divisions, to place the label.
            """)

        num_avg = unitless_property("NUMAV",
                                    doc="""
            The number of acquisistions over which exponential averaging is
            performed.
            """)

        spectral_center = unitful_property("SPEC:CENTER",
                                           u.Hz,
                                           doc="""
            The desired frequency of the spectral analyzer output data span
            in Hz.
            """)

        spectral_gatepos = unitful_property("SPEC:GATEPOS",
                                            u.second,
                                            doc="""
            The gate position. Units are represented in seconds, with respect
            to trigger position.
            """)

        spectral_gatewidth = unitful_property("SPEC:GATEWIDTH",
                                              u.second,
                                              doc="""
            The time across the 10-division screen in seconds.
            """)

        spectral_lock = bool_property("SPEC:LOCK",
                                      inst_true="ON",
                                      inst_false="OFF")

        spectral_mag = unitful_property("SPEC:MAG",
                                        Mag,
                                        doc="""
            Whether the spectral magnitude is linear, db, or dbm.
            """)

        spectral_phase = unitful_property("SPEC:PHASE",
                                          Mag,
                                          doc="""
            Whether the spectral phase is degrees, radians, or group delay.
            """)

        spectral_reflevel = unitless_property("SPEC:REFL",
                                              doc="""
            The value that represents the topmost display screen graticule.
            The units depend on spectral_mag.
            """)

        spectral_reflevel_offset = unitless_property("SPEC:REFLEVELO")

        spectral_resolution_bandwidth = unitful_property("SPEC:RESB",
                                                         u.Hz,
                                                         doc="""
            The desired resolution bandwidth value. Units are represented in
            Hertz.
            """)

        spectral_span = unitful_property("SPEC:SPAN",
                                         u.Hz,
                                         doc="""
            Specifies the frequency span of the output data vector from the
            spectral analyzer.
            """)

        spectral_suppress = unitless_property("SPEC:SUPP",
                                              doc="""
            The magnitude level that data with magnitude values below this
            value are displayed as zero phase.
            """)

        spectral_unwrap = bool_property("SPEC:UNWR",
                                        inst_true="ON",
                                        inst_false="OFF",
                                        doc="""
            Enables or disables phase wrapping.
            """)

        spectral_window = enum_property("SPEC:WIN", SpectralWindow)

        threshhold = unitful_property("THRESH",
                                      u.volt,
                                      doc="""
            The math threshhold in volts
            """)

        unit_string = string_property("UNITS",
                                      doc="""
            Just a label for the units...doesn"t actually change anything.
            """)

        autoscale = bool_property("VERT:AUTOSC",
                                  inst_true="ON",
                                  inst_false="OFF",
                                  doc="""
            Enables or disables the auto-scaling of new math waveforms.
            """)

        position = unitless_property("VERT:POS",
                                     doc="""
            The vertical position, in divisions from the center graticule.
            """)

        scale = unitful_property("VERT:SCALE",
                                 u.volt,
                                 doc="""
            The scale in volts per division. The range is from
            ``100e-36`` to ``100e+36``.
            """)

        def _scale_raw_data(self, data):
            # TODO: incorperate the unit_string somehow
            return self.scale * (
                (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                (2**15) - self.position)
Exemplo n.º 18
0
class Yokogawa6370(OpticalSpectrumAnalyzer):
    """
    The Yokogawa 6370 is a optical spectrum analyzer.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> inst = ik.yokogawa.Yokogawa6370.open_visa('TCPIP0:192.168.0.35')
    >>> inst.start_wl = 1030e-9 * u.m
    """
    def __init__(self, *args, **kwargs):
        super(Yokogawa6370, self).__init__(*args, **kwargs)
        # Set data Format to binary
        self.sendcmd(
            ":FORMat:DATA REAL,64")  # TODO: Find out where we want this

    # INNER CLASSES #

    class Channel(OSAChannel):
        """
        Class representing the channels on the Yokogawa 6370.

        This class inherits from `OSAChannel`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `Yokogawa6370` class.
        """
        def __init__(self, parent, idx):
            self._parent = parent
            self._name = idx

        # METHODS #

        def data(self, bin_format=True):
            cmd = ":TRAC:Y? {0}".format(self._name)
            self._parent.sendcmd(cmd)
            data = self._parent.binblockread(data_width=8, fmt="<d")
            self._parent._file.read_raw(1)  # pylint: disable=protected-access
            return data

        def wavelength(self, bin_format=True):
            cmd = ":TRAC:X? {0}".format(self._name)
            self._parent.sendcmd(cmd)
            data = self._parent.binblockread(data_width=8, fmt="<d")
            self._parent._file.read_raw(1)  # pylint: disable=protected-access
            return data

    # ENUMS #

    class SweepModes(IntEnum):
        """
        Enum containing valid output modes for the Yokogawa 6370
        """
        SINGLE = 1
        REPEAT = 2
        AUTO = 3

    class Traces(Enum):
        """
        Enum containing valid Traces for the Yokogawa 6370
        """
        A = "TRA"
        B = "TRB"
        C = "TRC"
        D = "TRD"
        E = "TRE"
        F = "TRF"
        G = "TRG"

    # PROPERTIES #

    @property
    def channel(self):
        """
        Gets the specific channel object.
        This channel is accessed as a list in the following manner::

        >>> import instruments as ik
        >>> osa = ik.yokogawa.Yokogawa6370.open_gpibusb('/dev/ttyUSB0')
        >>> dat = osa.channel["A"].data # Gets the data of channel 0

        :rtype: `list`[`~Yokogawa6370.Channel`]
        """
        return ProxyList(self, Yokogawa6370.Channel, Yokogawa6370.Traces)

    start_wl, start_wl_min, start_wl_max = bounded_unitful_property(
        ":SENS:WAV:STAR",
        u.meter,
        doc="""
        The start wavelength in m.
        """,
        valid_range=(600e-9, 1700e-9))

    stop_wl, stop_wl_min, stop_wl_max = bounded_unitful_property(
        ":SENS:WAV:STOP",
        u.meter,
        doc="""
        The stop wavelength in m.
        """,
        valid_range=(600e-9, 1700e-9))

    bandwidth = unitful_property(":SENS:BAND:RES",
                                 u.meter,
                                 doc="""
        The bandwidth in m.
        """)

    span = unitful_property(":SENS:WAV:SPAN",
                            u.meter,
                            doc="""
        A floating point property that controls the wavelength span in m.
        """)

    center_wl = unitful_property(":SENS:WAV:CENT",
                                 u.meter,
                                 doc="""
         A floating point property that controls the center wavelength m.
        """)

    points = unitless_property(":SENS:SWE:POIN",
                               doc="""
        An integer property that controls the number of points in a trace.
        """)

    sweep_mode = enum_property(":INIT:SMOD",
                               SweepModes,
                               input_decoration=int,
                               doc="""
        A property to control the Sweep Mode as one of Yokogawa6370.SweepMode. 
        Effective only after a self.start_sweep().""")

    active_trace = enum_property(":TRAC:ACTIVE",
                                 Traces,
                                 doc="""
        The active trace of the OSA of enum Yokogawa6370.Traces. Determines the 
        result of Yokogawa6370.data() and Yokogawa6370.wavelength().""")

    # METHODS #

    def data(self):
        """
        Function to query the active Trace data of the OSA.
        """
        return self.channel[self.active_trace].data()

    def wavelength(self):
        """
        Query the wavelength axis of the active trace.
        """
        return self.channel[self.active_trace].wavelength()

    def start_sweep(self):
        """
        Triggering function for the Yokogawa 6370.

        After changing the sweep mode, the device needs to be triggered before it will update.
        """
        self.sendcmd("*CLS;:init")
Exemplo n.º 19
0
class TekDPO70000(SCPIInstrument, Oscilloscope):
    """
    The Tektronix DPO70000 series  is a multi-channel oscilloscope with analog
    bandwidths ranging up to 33GHz.

    This class inherits from `~instruments.generic_scpi.SCPIInstrument`.

    Example usage:

    >>> import instruments as ik
    >>> tek = ik.tektronix.TekDPO70000.open_tcpip("192.168.0.2", 8888)
    >>> [x, y] = tek.channel[0].read_waveform()
    """

    # CONSTANTS #

    # The number of horizontal and vertical divisions.
    HOR_DIVS = 10
    VERT_DIVS = 10

    # ENUMS #

    class AcquisitionMode(Enum):
        """
        Enum containing valid acquisition modes for the Tektronix 70000 series
        oscilloscopes.
        """
        sample = "SAM"
        peak_detect = "PEAK"
        hi_res = "HIR"
        average = "AVE"
        waveform_db = "WFMDB"
        envelope = "ENV"

    class AcquisitionState(Enum):
        """
        Enum containing valid acquisition states for the Tektronix 70000 series
        oscilloscopes.
        """
        on = 'ON'
        off = 'OFF'
        run = 'RUN'
        stop = 'STOP'

    class StopAfter(Enum):
        """
        Enum containing valid stop condition modes for the Tektronix 70000
        series oscilloscopes.
        """
        run_stop = 'RUNST'
        sequence = 'SEQ'

    class SamplingMode(Enum):
        """
        Enum containing valid sampling modes for the Tektronix 70000
        series oscilloscopes.
        """
        real_time = "RT"
        equivalent_time_allowed = "ET"
        interpolation_allowed = "IT"

    class HorizontalMode(Enum):
        """
        Enum containing valid horizontal scan modes for the Tektronix 70000
        series oscilloscopes.
        """
        auto = "AUTO"
        constant = "CONST"
        manual = "MAN"

    class WaveformEncoding(Enum):
        """
        Enum containing valid waveform encoding modes for the Tektronix 70000
        series oscilloscopes.
        """
        # NOTE: For some reason, it uses the full names here instead of
        # returning the mneonics listed in the manual.
        ascii = "ASCII"
        binary = "BINARY"

    class BinaryFormat(Enum):
        """
        Enum containing valid binary formats for the Tektronix 70000
        series oscilloscopes (int, unsigned-int, floating-point).
        """
        int = "RI"
        uint = "RP"
        float = "FP"  # Single-precision!

    class ByteOrder(Enum):
        """
        Enum containing valid byte order (big-/little-endian) for the
        Tektronix 70000 series oscilloscopes.
        """
        little_endian = "LSB"
        big_endian = "MSB"

    class TriggerState(Enum):
        """
        Enum containing valid trigger states for the Tektronix 70000
        series oscilloscopes.
        """
        armed = "ARMED"
        auto = "AUTO"
        dpo = "DPO"
        partial = "PARTIAL"
        ready = "READY"

    # STATIC METHODS #

    @staticmethod
    def _dtype(binary_format, byte_order, n_bytes):
        return "{}{}{}".format({
            TekDPO70000.ByteOrder.big_endian: ">",
            TekDPO70000.ByteOrder.little_endian: "<"
        }[byte_order], {
            TekDPO70000.BinaryFormat.int: "i",
            TekDPO70000.BinaryFormat.uint: "u",
            TekDPO70000.BinaryFormat.float: "f"
        }[binary_format], n_bytes)

    # CLASSES #

    class DataSource(OscilloscopeDataSource):
        """
        Class representing a data source (channel, math, or ref) on the
        Tektronix DPO 70000.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `TekDPO70000` class.
        """
        @property
        def name(self):
            return self._name

        @abc.abstractmethod
        def _scale_raw_data(self, data):
            """
            Takes the int16 data and figures out how to make it unitful.
            """

        # pylint: disable=protected-access
        def read_waveform(self, bin_format=True):
            # We want to get the data back in binary, as it's just too much
            # otherwise.
            with self:
                self._parent.select_fastest_encoding()
                n_bytes = self._parent.outgoing_n_bytes
                dtype = self._parent._dtype(
                    self._parent.outgoing_binary_format,
                    self._parent.outgoing_byte_order, n_bytes)
                self._parent.sendcmd("CURV?")
                raw = self._parent.binblockread(n_bytes, fmt=dtype)
                # Clear the queue by trying to read.
                # FIXME: this is a hack-y way of doing so.
                if hasattr(self._parent._file, 'flush_input'):
                    self._parent._file.flush_input()
                else:
                    self._parent._file.read()

                return self._scale_raw_data(raw)

        def __enter__(self):
            self._old_dsrc = self._parent.data_source
            if self._old_dsrc != self:
                # Set the new data source, and let __exit__ cleanup.
                self._parent.data_source = self
            else:
                # There's nothing to do or undo in this case.
                self._old_dsrc = None

        def __exit__(self, type, value, traceback):
            if self._old_dsrc is not None:
                self._parent.data_source = self._old_dsrc

    class Math(DataSource):
        """
        Class representing a math channel on the Tektronix DPO 70000.

        This class inherits from `TekDPO70000.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `TekDPO70000` class.
        """
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # 1-based.

            # Initialize as a data source with name MATH{}.
            super(TekDPO70000.Math, self).__init__(parent,
                                                   "MATH{}".format(self._idx))

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class with
            identifiers for the specified math channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("MATH{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Wraps queries sent from property factories in this class with
            identifiers for the specified math channel.

            :param str cmd: Query command to send to the instrument
            :param int size: Number of characters to read from the response.
                Default value reads until a termination character is found.
            :return: The query response
            :rtype: `str`
            """
            return self._parent.query("MATH{}:{}".format(self._idx, cmd), size)

        class FilterMode(Enum):
            """
            Enum containing valid filter modes for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            centered = "CENT"
            shifted = "SHIF"

        class Mag(Enum):
            """
            Enum containing valid amplitude units for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            linear = "LINEA"
            db = "DB"
            dbm = "DBM"

        class Phase(Enum):
            """
            Enum containing valid phase units for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            degrees = "DEG"
            radians = "RAD"
            group_delay = "GROUPD"

        class SpectralWindow(Enum):
            """
            Enum containing valid spectral windows for a math channel on the
            TekDPO70000 series oscilloscope.
            """
            rectangular = "RECTANG"
            hamming = "HAMM"
            hanning = "HANN"
            kaiser_besse = "KAISERB"
            blackman_harris = "BLACKMANH"
            flattop2 = "FLATTOP2"
            gaussian = "GAUSS"
            tek_exponential = "TEKEXP"

        define = string_property("DEF",
                                 doc="""
            A text string specifying the math to do, ex. CH1+CH2
            """)

        filter_mode = enum_property("FILT:MOD", FilterMode)

        filter_risetime = unitful_property("FILT:RIS", u.second)

        label = string_property("LAB:NAM",
                                doc="""
            Just a human readable label for the channel.
            """)

        label_xpos = unitless_property("LAB:XPOS",
                                       doc="""
            The x position, in divisions, to place the label.
            """)

        label_ypos = unitless_property(
            "LAB:YPOS",
            doc="""The y position, in divisions, to place the label.
            """)

        num_avg = unitless_property("NUMAV",
                                    doc="""
            The number of acquisistions over which exponential averaging is
            performed.
            """)

        spectral_center = unitful_property("SPEC:CENTER",
                                           u.Hz,
                                           doc="""
            The desired frequency of the spectral analyzer output data span
            in Hz.
            """)

        spectral_gatepos = unitful_property("SPEC:GATEPOS",
                                            u.second,
                                            doc="""
            The gate position. Units are represented in seconds, with respect
            to trigger position.
            """)

        spectral_gatewidth = unitful_property("SPEC:GATEWIDTH",
                                              u.second,
                                              doc="""
            The time across the 10-division screen in seconds.
            """)

        spectral_lock = bool_property("SPEC:LOCK",
                                      inst_true="ON",
                                      inst_false="OFF")

        spectral_mag = unitful_property("SPEC:MAG",
                                        Mag,
                                        doc="""
            Whether the spectral magnitude is linear, db, or dbm.
            """)

        spectral_phase = unitful_property("SPEC:PHASE",
                                          Mag,
                                          doc="""
            Whether the spectral phase is degrees, radians, or group delay.
            """)

        spectral_reflevel = unitless_property("SPEC:REFL",
                                              doc="""
            The value that represents the topmost display screen graticule.
            The units depend on spectral_mag.
            """)

        spectral_reflevel_offset = unitless_property("SPEC:REFLEVELO")

        spectral_resolution_bandwidth = unitful_property("SPEC:RESB",
                                                         u.Hz,
                                                         doc="""
            The desired resolution bandwidth value. Units are represented in
            Hertz.
            """)

        spectral_span = unitful_property("SPEC:SPAN",
                                         u.Hz,
                                         doc="""
            Specifies the frequency span of the output data vector from the
            spectral analyzer.
            """)

        spectral_suppress = unitless_property("SPEC:SUPP",
                                              doc="""
            The magnitude level that data with magnitude values below this
            value are displayed as zero phase.
            """)

        spectral_unwrap = bool_property("SPEC:UNWR",
                                        inst_true="ON",
                                        inst_false="OFF",
                                        doc="""
            Enables or disables phase wrapping.
            """)

        spectral_window = enum_property("SPEC:WIN", SpectralWindow)

        threshhold = unitful_property("THRESH",
                                      u.volt,
                                      doc="""
            The math threshhold in volts
            """)

        unit_string = string_property("UNITS",
                                      doc="""
            Just a label for the units...doesn"t actually change anything.
            """)

        autoscale = bool_property("VERT:AUTOSC",
                                  inst_true="ON",
                                  inst_false="OFF",
                                  doc="""
            Enables or disables the auto-scaling of new math waveforms.
            """)

        position = unitless_property("VERT:POS",
                                     doc="""
            The vertical position, in divisions from the center graticule.
            """)

        scale = unitful_property("VERT:SCALE",
                                 u.volt,
                                 doc="""
            The scale in volts per division. The range is from
            ``100e-36`` to ``100e+36``.
            """)

        def _scale_raw_data(self, data):
            # TODO: incorperate the unit_string somehow
            return self.scale * (
                (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                (2**15) - self.position)

    class Channel(DataSource, OscilloscopeChannel):
        """
        Class representing a channel on the Tektronix DPO 70000.

        This class inherits from `TekDPO70000.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `TekDPO70000` class.
        """
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # 1-based.

            # Initialize as a data source with name CH{}.
            super(TekDPO70000.Channel, self).__init__(self._parent,
                                                      "CH{}".format(self._idx))

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class with
            identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("CH{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Wraps queries sent from property factories in this class with
            identifiers for the specified channel.

            :param str cmd: Query command to send to the instrument
            :param int size: Number of characters to read from the response.
                Default value reads until a termination character is found.
            :return: The query response
            :rtype: `str`
            """
            return self._parent.query("CH{}:{}".format(self._idx, cmd), size)

        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the oscilloscope channel
            """
            ac = "AC"
            dc = "DC"
            dc_reject = "DCREJ"
            ground = "GND"

        coupling = enum_property("COUP",
                                 Coupling,
                                 doc="""
            Gets/sets the coupling for the specified channel.

            Example usage:

            >>> import instruments as ik
            >>> inst = ik.tektronix.TekDPO70000.open_tcpip("192.168.0.1", 8080)
            >>> channel = inst.channel[0]
            >>> channel.coupling = channel.Coupling.ac
            """)

        bandwidth = unitful_property('BAN', u.Hz)

        deskew = unitful_property('DESK', u.second)

        termination = unitful_property('TERM', u.ohm)

        label = string_property('LAB:NAM',
                                doc="""
            Just a human readable label for the channel.
            """)

        label_xpos = unitless_property('LAB:XPOS',
                                       doc="""
            The x position, in divisions, to place the label.
            """)

        label_ypos = unitless_property('LAB:YPOS',
                                       doc="""
            The y position, in divisions, to place the label.
            """)

        offset = unitful_property('OFFS',
                                  u.volt,
                                  doc="""
            The vertical offset in units of volts. Voltage is given by
            ``offset+scale*(5*raw/2^15 - position)``.
            """)

        position = unitless_property('POS',
                                     doc="""
            The vertical position, in divisions from the center graticule,
            ranging from ``-8`` to ``8``. Voltage is given by
            ``offset+scale*(5*raw/2^15 - position)``.
            """)

        scale = unitful_property('SCALE',
                                 u.volt,
                                 doc="""
            Vertical channel scale in units volts/division. Voltage is given
            by ``offset+scale*(5*raw/2^15 - position)``.
            """)

        def _scale_raw_data(self, data):
            return self.scale * (
                (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                (2**15) - self.position) + self.offset

    # PROPERTIES ##

    @property
    def channel(self):
        return ProxyList(self, self.Channel, range(4))

    @property
    def math(self):
        return ProxyList(self, self.Math, range(4))

    @property
    def ref(self):
        raise NotImplementedError

    # For some settings that probably won't be used that often, use
    # string_property instead of setting up an enum property.
    acquire_enhanced_enob = string_property('ACQ:ENHANCEDE',
                                            bookmark_symbol='',
                                            doc="""
        Valid values are AUTO and OFF.
        """)

    acquire_enhanced_state = bool_property(
        'ACQ:ENHANCEDE:STATE',
        inst_false='0',  # TODO: double check that these are correct
        inst_true='1')

    acquire_interp_8bit = string_property('ACQ:INTERPE',
                                          bookmark_symbol='',
                                          doc="""
        Valid values are AUTO, ON and OFF.
        """)

    acquire_magnivu = bool_property('ACQ:MAG',
                                    inst_true='ON',
                                    inst_false='OFF')

    acquire_mode = enum_property('ACQ:MOD', AcquisitionMode)

    acquire_mode_actual = enum_property('ACQ:MOD:ACT',
                                        AcquisitionMode,
                                        readonly=True)

    acquire_num_acquisitions = int_property('ACQ:NUMAC',
                                            readonly=True,
                                            doc="""
        The number of waveform acquisitions that have occurred since starting
        acquisition with the ACQuire:STATE RUN command
        """)

    acquire_num_avgs = int_property('ACQ:NUMAV',
                                    doc="""
        The number of waveform acquisitions to average.
        """)

    acquire_num_envelop = int_property('ACQ:NUME',
                                       doc="""
        The number of waveform acquisitions to be enveloped
        """)

    acquire_num_frames = int_property('ACQ:NUMFRAMESACQ',
                                      readonly=True,
                                      doc="""
        The number of frames acquired when in FastFrame Single Sequence and
        acquisitions are running.
        """)

    acquire_num_samples = int_property('ACQ:NUMSAM',
                                       doc="""
        The minimum number of acquired samples that make up a waveform
        database (WfmDB) waveform for single sequence mode and Mask Pass/Fail
        Completion Test. The default value is 16,000 samples. The range is
        5,000 to 2,147,400,000 samples.
        """)

    acquire_sampling_mode = enum_property('ACQ:SAMP', SamplingMode)

    acquire_state = enum_property('ACQ:STATE',
                                  AcquisitionState,
                                  doc="""
        This command starts or stops acquisitions.
        """)

    acquire_stop_after = enum_property('ACQ:STOPA',
                                       StopAfter,
                                       doc="""
        This command sets or queries whether the instrument continually
        acquires acquisitions or acquires a single sequence.
        """)

    data_framestart = int_property('DAT:FRAMESTAR')

    data_framestop = int_property('DAT:FRAMESTOP')

    data_start = int_property('DAT:STAR',
                              doc="""
        The first data point that will be transferred, which ranges from 1 to
        the record length.
        """)

    # TODO: Look into the following troublesome datasheet note: "When using the
    # CURVe command, DATa:STOP is ignored and WFMInpre:NR_Pt is used."
    data_stop = int_property('DAT:STOP',
                             doc="""
        The last data point that will be transferred.
        """)

    data_sync_sources = bool_property('DAT:SYNCSOU',
                                      inst_true='ON',
                                      inst_false='OFF')

    @property
    def data_source(self):
        """
        Gets/sets the data source for the oscilloscope. This will return
        the actual Channel/Math/DataSource object as if it was accessed
        through the usual `TekDPO70000.channel`, `TekDPO70000.math`, or
        `TekDPO70000.ref` properties.

        :type: `TekDPO70000.Channel` or `TekDPO70000.Math`
        """
        val = self.query('DAT:SOU?')
        if val[0:2] == 'CH':
            out = self.channel[int(val[2]) - 1]
        elif val[0:2] == 'MA':
            out = self.math[int(val[4]) - 1]
        elif val[0:2] == 'RE':
            out = self.ref[int(val[3]) - 1]
        else:
            raise NotImplementedError
        return out

    @data_source.setter
    def data_source(self, newval):
        if not isinstance(newval, self.DataSource):
            raise TypeError("{} is not a valid data source.".format(
                type(newval)))
        self.sendcmd("DAT:SOU {}".format(newval.name))

        # Some Tek scopes require this after the DAT:SOU command, or else
        # they will stop responding.
        if not self._testing:
            time.sleep(0.02)

    horiz_acq_duration = unitful_property('HOR:ACQDURATION',
                                          u.second,
                                          readonly=True,
                                          doc="""
        The duration of the acquisition.
        """)

    horiz_acq_length = int_property('HOR:ACQLENGTH',
                                    readonly=True,
                                    doc="""
        The record length.
        """)

    horiz_delay_mode = bool_property('HOR:DEL:MOD',
                                     inst_true='1',
                                     inst_false='0')

    horiz_delay_pos = unitful_property('HOR:DEL:POS',
                                       u.percent,
                                       doc="""
        The percentage of the waveform that is displayed left of the center
        graticule.
        """)

    horiz_delay_time = unitful_property('HOR:DEL:TIM',
                                        u.second,
                                        doc="""
        The base trigger delay time setting.
        """)

    horiz_interp_ratio = unitless_property('HOR:MAI:INTERPR',
                                           readonly=True,
                                           doc="""
        The ratio of interpolated points to measured points.
        """)

    horiz_main_pos = unitful_property('HOR:MAI:POS',
                                      u.percent,
                                      doc="""
        The percentage of the waveform that is displayed left of the center
        graticule.
        """)

    horiz_unit = string_property('HOR:MAI:UNI')

    horiz_mode = enum_property('HOR:MODE', HorizontalMode)

    horiz_record_length_lim = int_property('HOR:MODE:AUTO:LIMIT',
                                           doc="""
        The recond length limit in samples.
        """)

    horiz_record_length = int_property('HOR:MODE:RECO',
                                       doc="""
        The recond length in samples. See `horiz_mode`; manual mode lets you
        change the record length, while the length is readonly for auto and
        constant mode.
        """)

    horiz_sample_rate = unitful_property('HOR:MODE:SAMPLER',
                                         u.Hz,
                                         doc="""
        The sample rate in samples per second.
        """)

    horiz_scale = unitful_property('HOR:MODE:SCA',
                                   u.second,
                                   doc="""
        The horizontal scale in seconds per division. The horizontal scale is
        readonly when `horiz_mode` is manual.
        """)

    horiz_pos = unitful_property('HOR:POS',
                                 u.percent,
                                 doc="""
        The position of the trigger point on the screen, left is 0%, right
        is 100%.
        """)

    horiz_roll = string_property('HOR:ROLL',
                                 bookmark_symbol='',
                                 doc="""
        Valid arguments are AUTO, OFF, and ON.
        """)

    trigger_state = enum_property('TRIG:STATE', TriggerState)

    # Waveform Transfer Properties
    outgoing_waveform_encoding = enum_property('WFMO:ENC',
                                               WaveformEncoding,
                                               doc="""
        Controls the encoding used for outgoing waveforms (instrument → host).
        """)

    outgoing_binary_format = enum_property("WFMO:BN_F",
                                           BinaryFormat,
                                           doc="""
        Controls the data type of samples when transferring waveforms from
        the instrument to the host using binary encoding.
        """)

    outgoing_byte_order = enum_property("WFMO:BYT_O",
                                        ByteOrder,
                                        doc="""
        Controls whether binary data is returned in little or big endian.
        """)

    outgoing_n_bytes = int_property("WFMO:BYT_N",
                                    valid_set=set((1, 2, 4, 8)),
                                    doc="""
        The number of bytes per sample used in representing outgoing
        waveforms in binary encodings.

        Must be either 1, 2, 4 or 8.
        """)

    # METHODS #

    def select_fastest_encoding(self):
        """
        Sets the encoding for data returned by this instrument to be the
        fastest encoding method consistent with the current data source.
        """
        self.sendcmd("DAT:ENC FAS")

    def force_trigger(self):
        """
        Forces a trigger event to happen for the oscilloscope.
        """
        self.sendcmd('TRIG FORC')

    # TODO: consider moving the next few methods to Oscilloscope.
    def run(self):
        """
        Enables the trigger for the oscilloscope.
        """
        self.sendcmd(":RUN")

    def stop(self):
        """
        Disables the trigger for the oscilloscope.
        """
        self.sendcmd(":STOP")
Exemplo n.º 20
0
class SCPIFunctionGenerator(FunctionGenerator, SCPIInstrument):

    ## CONSTANTS ##

    # TODO: document these.
    _UNIT_MNEMONICS = {
        FunctionGenerator.VoltageMode.peak_to_peak: "VPP",
        FunctionGenerator.VoltageMode.rms: "VRMS",
        FunctionGenerator.VoltageMode.dBm: "DBM",
    }

    _MNEMONIC_UNITS = dict(
        (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.iteritems())

    ## FunctionGenerator CONTRACT ##

    def _get_amplitude_(self):
        """
        
        """
        units = self.query("VOLT:UNIT?").strip()

        return (float(self.query("VOLT?").strip()),
                self._MNEMONIC_UNITS[units])

    def _set_amplitude_(self, magnitude, units):
        """
        
        """
        self.sendcmd("VOLT:UNIT {}".format(self._UNIT_MNEMONICS[units]))
        self.sendcmd("VOLT {}".format(magnitude))

    ## PROPERTIES ##

    frequency = unitful_property(name="FREQ",
                                 units=pq.Hz,
                                 doc="""
        Gets/sets the output frequency.
        
        :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise.
        :type: `float` or `~quantities.quantity.Quantity`
        """)

    function = enum_property(name="FUNC",
                             enum=lambda: self.Function,
                             doc="""
        Gets/sets the output function of the function generator
        
        :type: `SCPIFunctionGenerator.Function`
        """)

    offset = unitful_property(name="VOLT:OFFS",
                              units=pq.volt,
                              doc="""
        Gets/sets the offset voltage of the function generator.
        
        Set value should be within correct bounds of instrument.
        
        :units: As specified  (if a `~quntities.quantity.Quantity`) or assumed 
            to be of units volts.
        :type: `~quantities.quantity.Quantity` with units volts.
        """)

    @property
    def phase(self):
        raise NotImplementedError

    @phase.setter
    def phase(self, newval):
        raise NotImplementedError
Exemplo n.º 21
0
    class Channel(DataSource, OscilloscopeChannel):
        """
        Class representing a channel on the Tektronix DPO 70000.

        This class inherits from `TekDPO70000.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `TekDPO70000` class.
        """
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # 1-based.

            # Initialize as a data source with name CH{}.
            super(TekDPO70000.Channel, self).__init__(self._parent,
                                                      "CH{}".format(self._idx))

        def sendcmd(self, cmd):
            """
            Wraps commands sent from property factories in this class with
            identifiers for the specified channel.

            :param str cmd: Command to send to the instrument
            """
            self._parent.sendcmd("CH{}:{}".format(self._idx, cmd))

        def query(self, cmd, size=-1):
            """
            Wraps queries sent from property factories in this class with
            identifiers for the specified channel.

            :param str cmd: Query command to send to the instrument
            :param int size: Number of characters to read from the response.
                Default value reads until a termination character is found.
            :return: The query response
            :rtype: `str`
            """
            return self._parent.query("CH{}:{}".format(self._idx, cmd), size)

        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the oscilloscope channel
            """
            ac = "AC"
            dc = "DC"
            dc_reject = "DCREJ"
            ground = "GND"

        coupling = enum_property("COUP",
                                 Coupling,
                                 doc="""
            Gets/sets the coupling for the specified channel.

            Example usage:

            >>> import instruments as ik
            >>> inst = ik.tektronix.TekDPO70000.open_tcpip("192.168.0.1", 8080)
            >>> channel = inst.channel[0]
            >>> channel.coupling = channel.Coupling.ac
            """)

        bandwidth = unitful_property('BAN', u.Hz)

        deskew = unitful_property('DESK', u.second)

        termination = unitful_property('TERM', u.ohm)

        label = string_property('LAB:NAM',
                                doc="""
            Just a human readable label for the channel.
            """)

        label_xpos = unitless_property('LAB:XPOS',
                                       doc="""
            The x position, in divisions, to place the label.
            """)

        label_ypos = unitless_property('LAB:YPOS',
                                       doc="""
            The y position, in divisions, to place the label.
            """)

        offset = unitful_property('OFFS',
                                  u.volt,
                                  doc="""
            The vertical offset in units of volts. Voltage is given by
            ``offset+scale*(5*raw/2^15 - position)``.
            """)

        position = unitless_property('POS',
                                     doc="""
            The vertical position, in divisions from the center graticule,
            ranging from ``-8`` to ``8``. Voltage is given by
            ``offset+scale*(5*raw/2^15 - position)``.
            """)

        scale = unitful_property('SCALE',
                                 u.volt,
                                 doc="""
            Vertical channel scale in units volts/division. Voltage is given
            by ``offset+scale*(5*raw/2^15 - position)``.
            """)

        def _scale_raw_data(self, data):
            return self.scale * (
                (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                (2**15) - self.position) + self.offset
Exemplo n.º 22
0
 class EnumMock(MockInstrument):
     a = enum_property('MOCK:A', SillyEnum, readonly=True)
Exemplo n.º 23
0
class Agilent33220a(SCPIFunctionGenerator):

    """
    The `Agilent/Keysight 33220a`_ is a 20MHz function/arbitrary waveform
    generator. This model has been replaced by the Keysight 33500 series
    waveform generators. This class may or may not work with these newer
    models.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> inst = ik.agilent.Agilent33220a.open_gpibusb('/dev/ttyUSB0', 1)
    >>> inst.function = inst.Function.sinusoid
    >>> inst.frequency = 1 * u.kHz
    >>> inst.output = True

    .. _Agilent/Keysight 33220a: http://www.keysight.com/en/pd-127539-pn-33220A

    """

    # ENUMS #

    class Function(Enum):

        """
        Enum containing valid functions for the Agilent/Keysight 33220a
        """
        sinusoid = "SIN"
        square = "SQU"
        ramp = "RAMP"
        pulse = "PULS"
        noise = "NOIS"
        dc = "DC"
        user = "******"

    class LoadResistance(Enum):

        """
        Enum containing valid load resistance for the Agilent/Keysight 33220a
        """
        minimum = "MIN"
        maximum = "MAX"
        high_impedance = "INF"

    class OutputPolarity(Enum):

        """
        Enum containg valid output polarity modes for the
        Agilent/Keysight 33220a
        """
        normal = "NORM"
        inverted = "INV"

    # PROPERTIES #

    function = enum_property(
        command="FUNC",
        enum=Function,
        doc="""
        Gets/sets the output function of the function generator

        :type: `Agilent33220a.Function`
        """,
        set_fmt="{}:{}"
    )

    duty_cycle = int_property(
        command="FUNC:SQU:DCYC",
        doc="""
        Gets/sets the duty cycle of a square wave.

        Duty cycle represents the amount of time that the square wave is at a
        high level.

        :type: `int`
        """,
        valid_set=range(101)
    )

    ramp_symmetry = int_property(
        command="FUNC:RAMP:SYMM",
        doc="""
        Gets/sets the ramp symmetry for ramp waves.

        Symmetry represents the amount of time per cycle that the ramp wave is
        rising (unless polarity is inverted).

        :type: `int`
        """,
        valid_set=range(101)
    )

    output = bool_property(
        command="OUTP",
        inst_true="ON",
        inst_false="OFF",
        doc="""
        Gets/sets the output enable status of the front panel output connector.

        The value `True` corresponds to the output being on, while `False` is
        the output being off.

        :type: `bool`
        """
    )

    output_sync = bool_property(
        command="OUTP:SYNC",
        inst_true="ON",
        inst_false="OFF",
        doc="""
        Gets/sets the enabled status of the front panel sync connector.

        :type: `bool`
        """
    )

    output_polarity = enum_property(
        command="OUTP:POL",
        enum=OutputPolarity,
        doc="""
        Gets/sets the polarity of the waveform relative to the offset voltage.

        :type: `~Agilent33220a.OutputPolarity`
        """
    )

    @property
    def load_resistance(self):
        """
        Gets/sets the desired output termination load (ie, the impedance of the
        load attached to the front panel output connector).

        The instrument has a fixed series output impedance of 50ohms. This
        function allows the instrument to compensate of the voltage divider
        and accurately report the voltage across the attached load.

        :units: As specified (if a `~quantities.quantity.Quantity`) or assumed
            to be of units :math:`\\Omega` (ohm).
        :type: `~quantities.quantity.Quantity` or `Agilent33220a.LoadResistance`
        """
        value = self.query("OUTP:LOAD?")
        try:
            return int(value) * u.ohm
        except ValueError:
            return self.LoadResistance(value.strip())

    @load_resistance.setter
    def load_resistance(self, newval):
        if isinstance(newval, self.LoadResistance):
            newval = newval.value
        else:
            newval = assume_units(newval, u.ohm).rescale(u.ohm).magnitude
            if (newval < 0) or (newval > 10000):
                raise ValueError(
                    "Load resistance must be between 0 and 10,000")
        self.sendcmd("OUTP:LOAD {}".format(newval))

    @property
    def phase(self):
        raise NotImplementedError

    @phase.setter
    def phase(self, newval):
        raise NotImplementedError
Exemplo n.º 24
0
 class EnumMock(MockInstrument):
     a = enum_property('MOCK:A', SillyEnum)
Exemplo n.º 25
0
class PicowattAVS47(SCPIInstrument):

    """
    The Picowatt AVS 47 is a resistance bridge used to measure the resistance
    of low-temperature sensors.

    Example usage:

    >>> import instruments as ik
    >>> bridge = ik.picowatt.PicowattAVS47.open_gpibusb('/dev/ttyUSB0', 1)
    >>> print bridge.sensor[0].resistance
    """

    def __init__(self, filelike):
        super(PicowattAVS47, self).__init__(filelike)
        self.sendcmd("HDR 0")  # Disables response headers from replies

    # INNER CLASSES #

    class Sensor:

        """
        Class representing a sensor on the PicowattAVS47

        .. warning:: This class should NOT be manually created by the user. It is
            designed to be initialized by the `PicowattAVS47` class.
        """

        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx  # The AVS47 is actually zero-based indexing! Wow!

        @property
        def resistance(self):
            """
            Gets the resistance. It first ensures that the next measurement
            reading is up to date by first sending the "ADC" command.

            :units: :math:`\\Omega` (ohms)
            :rtype: `~quantities.Quantity`
            """
            # First make sure the mux is on the correct channel
            if self._parent.mux_channel != self._idx:
                self._parent.input_source = self._parent.InputSource.ground
                self._parent.mux_channel = self._idx
                self._parent.input_source = self._parent.InputSource.actual
            # Next, prep a measurement with the ADC command
            self._parent.sendcmd("ADC")
            return float(self._parent.query("RES?")) * u.ohm

    # ENUMS #

    class InputSource(IntEnum):
        """
        Enum containing valid input source modes for the AVS 47
        """
        ground = 0
        actual = 1
        reference = 2

    # PROPERTIES #

    @property
    def sensor(self):
        """
        Gets a specific sensor object. The desired sensor is specified like
        one would access a list.

        :rtype: `~PicowattAVS47.Sensor`

        .. seealso::
            `PicowattAVS47` for an example using this property.
        """
        return ProxyList(self, PicowattAVS47.Sensor, range(8))

    remote = bool_property(
        command="REM",
        inst_true="1",
        inst_false="0",
        doc="""
        Gets/sets the remote mode state.

        Enabling the remote mode allows all settings to be changed by computer
        interface and locks-out the front panel.

        :type: `bool`
        """
    )

    input_source = enum_property(
        command="INP",
        enum=InputSource,
        input_decoration=int,
        doc="""
        Gets/sets the input source.

        :type: `PicowattAVS47.InputSource`
        """
    )

    mux_channel = int_property(
        command="MUX",
        doc="""
        Gets/sets the multiplexer sensor number.
        It is recommended that you ground the input before switching the
        multiplexer channel.

        Valid mux channel values are 0 through 7 (inclusive).

        :type: `int`
        """,
        valid_set=range(8)
    )

    excitation = int_property(
        command="EXC",
        doc="""
        Gets/sets the excitation sensor number.

        Valid excitation sensor values are 0 through 7 (inclusive).

        :type: `int`
        """,
        valid_set=range(8)
    )

    display = int_property(
        command="DIS",
        doc="""
        Gets/sets the sensor that is displayed on the front panel.

        Valid display sensor values are 0 through 7 (inclusive).

        :type: `int`
        """,
        valid_set=range(8)
    )