Esempio n. 1
0
    class Channel(DataSource, OscilloscopeChannel):
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # Rigols are 1-based.

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

        def sendcmd(self, cmd):
            self._parent.sendcmd(":CHAN{}:{}".format(self._idx, cmd))

        def query(self, cmd):
            return self._parent.query(":CHAN{}:{}".format(self._idx, cmd))

        coupling = enum_property("COUP", Coupling)

        bw_limit = bool_property("BWL", "ON", "OFF")
        display = bool_property("DISP", "ON", "OFF")
        invert = bool_property("INV", "ON", "OFF")

        # TODO: :CHAN<n>:OFFset
        # TODO: :CHAN<n>:PROBe
        # TODO: :CHAN<n>:SCALe

        filter = bool_property("FILT", "ON", "OFF")

        # TODO: :CHAN<n>:MEMoryDepth

        vernier = bool_property("VERN", "ON", "OFF")
Esempio n. 2
0
    class Channel(DataSource, OscilloscopeChannel):
        """
        Class representing a channel on the Rigol DS1000.

        This class inherits from `~RigolDS1000Series.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `RigolDS1000Series` class.
        """
        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the Rigol DS1000
            """
            ac = "AC"
            dc = "DC"
            ground = "GND"

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

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

        def sendcmd(self, cmd):
            """
            Passes a command from the `Channel` class to the parent
            `RigolDS1000Series`, appending the required channel identification.

            :param str cmd: The command string to send to the instrument
            """
            self._parent.sendcmd(":CHAN{}:{}".format(self._idx, cmd))

        def query(self, cmd):
            """
            Passes a command from the `Channel` class to the parent
            `RigolDS1000Series`, appending the required channel identification.

            :param str cmd: The command string to send to the instrument
            :return: The result as returned by the instrument
            :rtype: `str`
            """
            return self._parent.query(":CHAN{}:{}".format(self._idx, cmd))

        coupling = enum_property("COUP", Coupling)

        bw_limit = bool_property("BWL", inst_true="ON", inst_false="OFF")
        display = bool_property("DISP", inst_true="ON", inst_false="OFF")
        invert = bool_property("INV", inst_true="ON", inst_false="OFF")

        # TODO: :CHAN<n>:OFFset
        # TODO: :CHAN<n>:PROBe
        # TODO: :CHAN<n>:SCALe

        filter = bool_property("FILT", inst_true="ON", inst_false="OFF")

        # TODO: :CHAN<n>:MEMoryDepth

        vernier = bool_property("VERN", inst_true="ON", inst_false="OFF")
    class Inst:
        """Mock instrument class."""
        def __init__(self):
            """Set up the mocker spies and send command placeholder."""
            # spies
            self.spy_query = mocker.spy(self, 'query')
            self.spy_sendcmd = mocker.spy(self, 'sendcmd')

            # variable to set with send command
            self._sendcmd = None

        def query(self, cmd):
            """Return the command minus the ? which is sent along."""
            return f"{cmd[:-1]}"

        def sendcmd(self, cmd):
            """Sets the command to `self._sendcmd`."""
            self._sendcmd = cmd

        class SomeEnum(Enum):
            test = "enum"

        bool_property = bool_property("ON")  # return True

        enum_property = enum_property("enum", SomeEnum)

        unitless_property = unitless_property("42")

        int_property = int_property("42")

        unitful_property = unitful_property("42", u.K)

        string_property = string_property("'STRING'")
Esempio n. 4
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))
Esempio n. 5
0
class HPe3631a(PowerSupply, PowerSupplyChannel, SCPIInstrument):
    """
    The HPe3631a is a three channels voltage/current supply.
    - Channel 1 is a positive +6V/5A channel (P6V)
    - Channel 2 is a positive +25V/1A channel (P25V)
    - Channel 3 is a negative -25V/1A channel (N25V)

    This module is designed for the power supply to be set to
    a specific channel and remain set afterwards as this device
    does not offer commands to set or read multiple channels
    without calling the channel set command each time (0.5s). It is
    possible to call a specific channel through psu.channel[idx],
    which will automatically reset the channel id, when necessary.

    This module is likely to work as is for the Agilent E3631 and
    Keysight E3631 which seem to be rebranded but identical devices.

    Example usage:

    >>> import instruments as ik
    >>> psu = ik.hp.HPe3631a.open_gpibusb("/dev/ttyUSB0", 10)
    >>> psu.channelid = 2           # Sets channel to P25V
    >>> psu.voltage = 12.5          # Sets voltage to 12.5V
    >>> psu.voltage                 # Reads back set voltage
    array(12.5) * V
    >>> psu.voltage_sense           # Reads back sensed voltage
    array(12.501) * V
    """
    def __init__(self, filelike):
        super(HPe3631a, self).__init__(filelike)
        self.sendcmd("SYST:REM")  # Puts the device in remote operation
        time.sleep(0.1)

    # INNER CLASSES #

    class Channel:
        """
        Class representing a power output channel on the HPe3631a.

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

        def __getitem__(self, idx):
            # Check that the channel is available. If it is, set the
            # channelid of the device and return the device object.
            if self._parent.channelid != idx:
                self._parent.channelid = idx
                time.sleep(0.5)
            return self._parent

        def __len__(self):
            return len(self._valid_set)

    # PROPERTIES ##

    @property
    def channel(self):
        """
        Gets a specific channel object. The desired channel is specified like
        one would access a list.

        :rtype: `HPe3631a.Channel`

        .. seealso::
            `HPe3631a` for example using this property.
        """
        return self.Channel(self, [1, 2, 3])

    @property
    def mode(self):
        """
        Gets/sets the mode for the specified channel.

        The constant-voltage/constant-current modes of the power supply
        are selected automatically depending on the load (resistance)
        connected to the power supply. If the load greater than the set
        V/I is connected, a voltage V is applied and the current flowing
        is lower than I. If the load is smaller than V/I, the set current
        I acts as a current limiter and the voltage is lower than V.
        """
        raise AttributeError("The `HPe3631a` sets its mode automatically")

    channelid = int_property("INST:NSEL",
                             valid_set=[1, 2, 3],
                             doc="""
        Gets/Sets the active channel ID.

        :type: `HPe3631a.ChannelType`
        """)

    @property
    def voltage(self):
        """
        Gets/sets the output voltage of the source.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~pint.Quantity`
        """
        raw = self.query("SOUR:VOLT?")
        return u.Quantity(*split_unit_str(raw, u.volt)).to(u.volt)

    @voltage.setter
    def voltage(self, newval):
        """
        Gets/sets the output voltage of the source.

        :units: As specified, or assumed to be :math:`\\text{V}` otherwise.
        :type: `float` or `~pint.Quantity`
        """
        min_value, max_value = self.voltage_range
        if newval < min_value:
            raise ValueError("Voltage quantity is too low. Got {}, minimum "
                             "value is {}".format(newval, min_value))

        if newval > max_value:
            raise ValueError("Voltage quantity is too high. Got {}, maximum "
                             "value is {}".format(newval, max_value))

        # Rescale to the correct unit before printing. This will also
        # catch bad units.
        strval = "{:e}".format(
            assume_units(newval, u.volt).to(u.volt).magnitude)
        self.sendcmd('SOUR:VOLT {}'.format(strval))

    @property
    def voltage_min(self):
        """
        Gets the minimum voltage for the current channel.

        :units: :math:`\\text{V}`.
        :type: `~pint.Quantity`
        """
        return self.voltage_range[0]

    @property
    def voltage_max(self):
        """
        Gets the maximum voltage for the current channel.

        :units: :math:`\\text{V}`.
        :type: `~pint.Quantity`
        """
        return self.voltage_range[1]

    @property
    def voltage_range(self):
        """
        Gets the voltage range for the current channel.

        The MAX function SCPI command is designed in such a way
        on this device that it always returns the largest absolute value.
        There is no need to query MIN, as it is always 0., but one has to
        order the values as MAX can be negative.

        :units: :math:`\\text{V}`.
        :type: array of `~pint.Quantity`
        """
        value = u.Quantity(
            *split_unit_str(self.query("SOUR:VOLT? MAX"), u.volt))
        if value < 0.:
            return value, 0.
        return 0., value

    current, current_min, current_max = bounded_unitful_property(
        "SOUR:CURR",
        u.amp,
        min_fmt_str="{}? MIN",
        max_fmt_str="{}? MAX",
        doc="""
        Gets/sets the output current of the source.

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

    voltage_sense = unitful_property("MEAS:VOLT",
                                     u.volt,
                                     readonly=True,
                                     doc="""
        Gets the actual output voltage as measured by the sense wires.

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

    current_sense = unitful_property("MEAS:CURR",
                                     u.amp,
                                     readonly=True,
                                     doc="""
        Gets the actual output current as measured by the sense wires.

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

    output = bool_property("OUTP",
                           inst_true="1",
                           inst_false="0",
                           doc="""
        Gets/sets the outputting status of the specified channel.

        This is a toggle setting. ON will turn on the channel output
        while OFF will turn it off.

        :type: `bool`
        """)
Esempio n. 6
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], (n_bytes if n_bytes is not None else ""), {
            TekDPO70000.BinaryFormat.int: "i",
            TekDPO70000.BinaryFormat.uint: "u",
            TekDPO70000.BinaryFormat.float: "f"
        }[binary_format])

    # 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=None)
                self._parent.sendcmd("CURV?")
                raw = self._parent.binblockread(n_bytes, fmt=dtype)
                # Clear the queue by reading the end of line character
                self._parent._file.read_raw(1)

                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 = enum_property("SPEC:MAG",
                                     Mag,
                                     doc="""
            Whether the spectral magnitude is linear, db, or dbm.
            """)

        spectral_phase = enum_property("SPEC:PHASE",
                                       Phase,
                                       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
            if numpy:
                return self.scale * (
                    (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                    (2**15) - self.position)

            scale = self.scale
            position = self.position
            rval = tuple(scale * ((TekDPO70000.VERT_DIVS / 2) * d /
                                  (2**15) - position)
                         for d in map(float, data))
            return rval

    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):
            scale = self.scale
            position = self.position
            offset = self.offset

            if numpy:
                return scale * (
                    (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                    (2**15) - position) + offset

            return tuple(scale * ((TekDPO70000.VERT_DIVS / 2) * d /
                                  (2**15) - position) + offset
                         for d in map(float, data))

    # 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.
        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")
Esempio n. 7
0
    class Channel(PowerSupplyChannel):
        """
        Class representing a power output channel on the HP6624a.

        .. warning:: This class should NOT be manually created by the user. It is
            designed to be initialized by the `HP6624a` class.
        """
        def __init__(self, hp, idx):
            self._hp = hp
            self._idx = idx + 1

        # COMMUNICATION METHODS #

        def _format_cmd(self, cmd):
            cmd = cmd.split(" ")
            if len(cmd) == 1:
                cmd = "{cmd} {idx}".format(cmd=cmd[0], idx=self._idx)
            else:
                cmd = "{cmd} {idx},{value}".format(cmd=cmd[0],
                                                   idx=self._idx,
                                                   value=cmd[1])
            return cmd

        def sendcmd(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            """
            cmd = self._format_cmd(cmd)
            self._hp.sendcmd(cmd)

        def query(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            :return: The result from the query
            :rtype: `str`
            """
            cmd = self._format_cmd(cmd)
            return self._hp.query(cmd)

        # PROPERTIES #

        @property
        def mode(self):
            """
            Gets/sets the mode for the specified channel.
            """
            raise NotImplementedError

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

        voltage = unitful_property("VSET",
                                   u.volt,
                                   set_fmt="{} {:.1f}",
                                   output_decoration=float,
                                   doc="""
            Gets/sets the voltage of the specified channel. If the device is in
            constant current mode, this sets the voltage limit.

            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.Quantity`
            """)

        current = unitful_property("ISET",
                                   u.amp,
                                   set_fmt="{} {:.1f}",
                                   output_decoration=float,
                                   doc="""
            Gets/sets the current of the specified channel. If the device is in
            constant voltage mode, this sets the current limit.

            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.Quantity`
            """)

        voltage_sense = unitful_property("VOUT",
                                         u.volt,
                                         readonly=True,
                                         doc="""
            Gets the actual voltage as measured by the sense wires for the
            specified channel.

            :units: :math:`\\text{V}` (volts)
            :rtype: `~quantities.quantity.Quantity`
            """)

        current_sense = unitful_property("IOUT",
                                         u.amp,
                                         readonly=True,
                                         doc="""
            Gets the actual output current as measured by the instrument for
            the specified channel.

            :units: :math:`\\text{A}` (amps)
            :rtype: `~quantities.quantity.Quantity`
            """)

        overvoltage = unitful_property("OVSET",
                                       u.volt,
                                       set_fmt="{} {:.1f}",
                                       output_decoration=float,
                                       doc="""
            Gets/sets the overvoltage protection setting for the specified channel.

            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.Quantity`
            """)

        overcurrent = bool_property("OVP",
                                    inst_true="1",
                                    inst_false="0",
                                    doc="""
            Gets/sets the overcurrent protection setting for the specified channel.

            This is a toggle setting. It is either on or off.

            :type: `bool`
            """)

        output = bool_property("OUT",
                               inst_true="1",
                               inst_false="0",
                               doc="""
            Gets/sets the outputting status of the specified channel.

            This is a toggle setting. True will turn on the channel output
            while False will turn it off.

            :type: `bool`
            """)

        # METHODS ##

        def reset(self):
            """
            Reset overvoltage and overcurrent errors to resume operation.
            """
            self.sendcmd('OVRST')
            self.sendcmd('OCRST')
Esempio n. 8
0
class SRS830(SCPIInstrument):
    """
    Communicates with a Stanford Research Systems 830 Lock-In Amplifier.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> srs = ik.srs.SRS830.open_gpibusb('/dev/ttyUSB0', 1)
    >>> srs.frequency = 1000 * u.hertz # Lock-In frequency
    >>> data = srs.take_measurement(1, 10) # 1Hz sample rate, 10 samples total
    """
    def __init__(self, filelike, outx_mode=None):
        """
        Class initialization method.

        :param int outx_mode: Manually over-ride which ``OUTX`` command to send
            at startup. This is a command that needs to be sent as specified
            by the SRS830 manual. If left default, the correct ``OUTX`` command
            will be sent depending on what type of communicator self._file is.
        """
        super(SRS830, self).__init__(filelike)
        if outx_mode == 1:
            self.sendcmd("OUTX 1")
        elif outx_mode == 2:
            self.sendcmd("OUTX 2")
        else:
            if isinstance(self._file, GPIBCommunicator):
                self.sendcmd("OUTX 1")
            elif isinstance(self._file, SerialCommunicator):
                self.sendcmd("OUTX 2")
            elif isinstance(self._file, LoopbackCommunicator):
                pass
            else:
                warnings.warn(
                    "OUTX command has not been set. Instrument "
                    "behaviour is unknown.", UserWarning)

    # ENUMS #

    class FreqSource(IntEnum):
        """
        Enum for the SRS830 frequency source settings.
        """
        external = 0
        internal = 1

    class Coupling(IntEnum):
        """
        Enum for the SRS830 channel coupling settings.
        """
        ac = 0
        dc = 1

    class BufferMode(IntEnum):
        """
        Enum for the SRS830 buffer modes.
        """
        one_shot = 0
        loop = 1

    class Mode(Enum):
        """
        Enum containing valid modes for the SRS 830
        """
        x = "x"
        y = "y"
        r = "r"
        theta = "theta"
        xnoise = "xnoise"
        ynoise = "ynoise"
        aux1 = "aux1"
        aux2 = "aux2"
        aux3 = "aux3"
        aux4 = "aux4"
        ref = "ref"
        ch1 = "ch1"
        ch2 = "ch2"
        none = "none"

    # CONSTANTS #

    _XYR_MODE_MAP = {Mode.x: 1, Mode.y: 2, Mode.r: 3}

    # PROPERTIES #

    frequency_source = enum_property("FMOD",
                                     FreqSource,
                                     input_decoration=int,
                                     doc="""
        Gets/sets the frequency source used. This is either an external source,
            or uses the internal reference.

        :type: `SRS830.FreqSource`
        """)

    frequency = unitful_property("FREQ",
                                 u.hertz,
                                 valid_range=(0, None),
                                 doc="""
        Gets/sets the lock-in amplifier reference frequency.

        :units: As specified (if a `~pint.Quantity`) or assumed to be
            of units Hertz.
        :type: `~pint.Quantity` with units Hertz.
        """)

    phase, phase_min, phase_max = bounded_unitful_property(
        "PHAS",
        u.degrees,
        valid_range=(-360 * u.degrees, 730 * u.degrees),
        doc="""
        Gets/set the phase of the internal reference signal.

        Set value should be -360deg <= newval < +730deg.

        :units: As specified (if a `~pint.Quantity`) or assumed to be
            of units degrees.
        :type: `~pint.Quantity` with units degrees.
        """)

    amplitude, amplitude_min, amplitude_max = bounded_unitful_property(
        "SLVL",
        u.volt,
        valid_range=(0.004 * u.volt, 5 * u.volt),
        doc="""
        Gets/set the amplitude of the internal reference signal.

        Set value should be 0.004 <= newval <= 5.000

        :units: As specified (if a `~pint.Quantity`) or assumed to be
            of units volts. Value should be specified as peak-to-peak.
        :type: `~pint.Quantity` with units volts peak-to-peak.
        """)

    input_shield_ground = bool_property("IGND",
                                        inst_true="1",
                                        inst_false="0",
                                        doc="""
        Function sets the input shield grounding to either 'float' or 'ground'.

        :type: `bool`
        """)

    coupling = enum_property("ICPL",
                             Coupling,
                             input_decoration=int,
                             doc="""
        Gets/sets the input coupling to either 'ac' or 'dc'.

        :type: `SRS830.Coupling`
        """)

    @property
    def sample_rate(self):
        r"""
        Gets/sets the data sampling rate of the lock-in.

        Acceptable set values are :math:`2^n` where :math:`n \in \{-4...+9\}` or
        the string `trigger`.

        :type: `~pint.Quantity` with units Hertz.
        """
        value = int(self.query('SRAT?'))
        if value == 14:
            return "trigger"
        return u.Quantity(VALID_SAMPLE_RATES[value], u.Hz)

    @sample_rate.setter
    def sample_rate(self, newval):
        if isinstance(newval, str):
            newval = newval.lower()

        if newval in VALID_SAMPLE_RATES:
            self.sendcmd('SRAT {}'.format(VALID_SAMPLE_RATES.index(newval)))
        else:
            raise ValueError('Valid samples rates given by {} '
                             'and "trigger".'.format(VALID_SAMPLE_RATES))

    buffer_mode = enum_property("SEND",
                                BufferMode,
                                input_decoration=int,
                                doc="""
        Gets/sets the end of buffer mode.

        This sets the behaviour of the instrument when the data storage buffer
        is full. Setting to `one_shot` will stop acquisition, while `loop`
        will repeat from the start.

        :type: `SRS830.BufferMode`
        """)

    @property
    def num_data_points(self):
        """
        Gets the number of data sets in the SRS830 buffer.

        :type: `int`
        """
        resp = None
        i = 0
        while not resp and i < 10:
            resp = self.query('SPTS?').strip()
            i += 1
        if not resp:
            raise IOError(
                "Expected integer response from instrument, got {}".format(
                    repr(resp)))
        return int(resp)

    data_transfer = bool_property("FAST",
                                  inst_true="2",
                                  inst_false="0",
                                  doc="""
        Gets/sets the data transfer status.

        Note that this function only makes use of 2 of the 3 data transfer modes
        supported by the SRS830. The supported modes are FAST0 and FAST2. The
        other, FAST1, is for legacy systems which this package does not support.

        :type: `bool`
        """)

    # AUTO- METHODS #

    def auto_offset(self, mode):
        """
        Sets a specific channel mode to auto offset. This is the same as
        pressing the auto offset key on the display.

        It sets the offset of the mode specified to zero.

        :param mode: Target mode of auto_offset function. Valid inputs are
            {X|Y|R}.
        :type mode: `~SRS830.Mode` or `str`
        """
        if isinstance(mode, str):
            mode = mode.lower()
            mode = SRS830.Mode[mode]

        if mode not in self._XYR_MODE_MAP:
            raise ValueError('Specified mode not valid for this function.')

        mode = self._XYR_MODE_MAP[mode]

        self.sendcmd('AOFF {}'.format(mode))

    def auto_phase(self):
        """
        Sets the lock-in to auto phase.
        This does the same thing as pushing the auto phase button.

        Do not send this message again without waiting the correct amount
        of time for the lock-in to finish.
        """
        self.sendcmd('APHS')

    # META-METHODS #

    def init(self, sample_rate, buffer_mode):
        r"""
        Wrapper function to prepare the SRS830 for measurement.
        Sets both the data sampling rate and the end of buffer mode

        :param sample_rate: The desired sampling
            rate. Acceptable set values are :math:`2^n` where
            :math:`n \in \{-4...+9\}` in units Hertz or the string `trigger`.
        :type sample_rate: `~pint.Quantity` or `str`

        :param `SRS830.BufferMode` buffer_mode: This sets the behaviour of the
            instrument when the data storage buffer is full. Setting to
            `one_shot` will stop acquisition, while `loop` will repeat from
            the start.
        """
        self.clear_data_buffer()
        self.sample_rate = sample_rate
        self.buffer_mode = buffer_mode

    def start_data_transfer(self):
        """
        Wrapper function to start the actual data transfer.
        Sets the transfer mode to FAST2, and triggers the data transfer
        to start after a delay of 0.5 seconds.
        """
        self.data_transfer = True
        self.start_scan()

    def take_measurement(self, sample_rate, num_samples):
        """
        Wrapper function that allows you to easily take measurements with a
        specified sample rate and number of desired samples.

        Function will call time.sleep() for the required amount of time it will
        take the instrument to complete this sampling operation.

        Returns a list containing two items, each of which are lists containing
        the channel data. The order is [[Ch1 data], [Ch2 data]].

        :param `int` sample_rate: Set the desired sample rate of the
            measurement. See `~SRS830.sample_rate` for more information.

        :param `int` num_samples: Number of samples to take.

        :rtype: `tuple`[`tuple`[`float`, ...], `tuple`[`float`, ...]]
            or if numpy is installed, `numpy.array`[`numpy.array`, `numpy.array`]
        """
        if num_samples > 16383:
            raise ValueError('Number of samples cannot exceed 16383.')

        sample_time = math.ceil(num_samples / sample_rate)

        self.init(sample_rate, SRS830.BufferMode['one_shot'])
        self.start_data_transfer()

        time.sleep(sample_time + 0.1)

        self.pause()

        # The following should fail. We do this to force the instrument
        # to flush its internal buffers.
        # Note that this causes a redundant transmission, and should be fixed
        # in future versions.
        try:
            self.num_data_points
        except IOError:
            pass

        ch1 = self.read_data_buffer('ch1')
        ch2 = self.read_data_buffer('ch2')

        if numpy:
            return numpy.array([ch1, ch2])
        return ch1, ch2

    # OTHER METHODS #

    def set_offset_expand(self, mode, offset, expand):
        """
        Sets the channel offset and expand parameters.
        Offset is a percentage, and expand is given as a multiplication
        factor of 1, 10, or 100.

        :param mode: The channel mode that you wish to change the
            offset and/or the expand of. Valid modes are X, Y, and R.
        :type mode: `SRS830.Mode` or `str`

        :param float offset: Offset of the mode, given as a percent.
            offset = <-105...+105>.

        :param int expand: Expansion factor for the measurement. Valid input
            is {1|10|100}.
        """
        if isinstance(mode, str):
            mode = mode.lower()
            mode = SRS830.Mode[mode]

        if mode not in self._XYR_MODE_MAP:
            raise ValueError('Specified mode not valid for this function.')

        mode = self._XYR_MODE_MAP[mode]

        if not isinstance(offset, (int, float)):
            raise TypeError('Offset parameter must be an integer or a float.')
        if not isinstance(expand, (int, float)):
            raise TypeError('Expand parameter must be an integer or a float.')

        if (offset > 105) or (offset < -105):
            raise ValueError('Offset mustbe -105 <= offset <= +105.')

        valid = [1, 10, 100]
        if expand in valid:
            expand = valid.index(expand)
        else:
            raise ValueError('Expand must be 1, 10, 100.')

        self.sendcmd('OEXP {},{},{}'.format(mode, int(offset), expand))

    def start_scan(self):
        """
        After setting the data transfer on via the dataTransfer function,
        this is used to start the scan. The scan starts after a delay of
        0.5 seconds.
        """
        self.sendcmd('STRD')

    def pause(self):
        """
        Has the instrument pause data capture.
        """
        self.sendcmd('PAUS')

    _data_snap_modes = {
        Mode.x: 1,
        Mode.y: 2,
        Mode.r: 3,
        Mode.theta: 4,
        Mode.aux1: 5,
        Mode.aux2: 6,
        Mode.aux3: 7,
        Mode.aux4: 8,
        Mode.ref: 9,
        Mode.ch1: 10,
        Mode.ch2: 11
    }

    def data_snap(self, mode1, mode2):
        """
        Takes a snapshot of the current parameters are defined by variables
        mode1 and mode2.

        For combinations (X,Y) and (R,THETA), they are taken at the same
        instant. All other combinations are done sequentially, and may
        not represent values taken from the same timestamp.

        Returns a list of floats, arranged in the order that they are
        given in the function input parameters.

        :param mode1: Mode to take data snap for channel 1. Valid inputs are
            given by: {X|Y|R|THETA|AUX1|AUX2|AUX3|AUX4|REF|CH1|CH2}
        :type mode1: `~SRS830.Mode` or `str`
        :param mode2: Mode to take data snap for channel 2. Valid inputs are
            given by: {X|Y|R|THETA|AUX1|AUX2|AUX3|AUX4|REF|CH1|CH2}
        :type mode2: `~SRS830.Mode` or `str`

        :rtype: `list`
        """
        if isinstance(mode1, str):
            mode1 = mode1.lower()
            mode1 = SRS830.Mode[mode1]
        if isinstance(mode2, str):
            mode2 = mode2.lower()
            mode2 = SRS830.Mode[mode2]

        if ((mode1 not in self._data_snap_modes)
                or (mode2 not in self._data_snap_modes)):
            raise ValueError('Specified mode not valid for this function.')

        mode1 = self._XYR_MODE_MAP[mode1]
        mode2 = self._XYR_MODE_MAP[mode2]

        if mode1 == mode2:
            raise ValueError('Both parameters for the data snapshot are the '
                             'same.')

        result = self.query('SNAP? {},{}'.format(mode1, mode2))
        return list(map(float, result.split(',')))

    _valid_read_data_buffer = {Mode.ch1: 1, Mode.ch2: 2}

    def read_data_buffer(self, channel):
        """
        Reads the entire data buffer for a specific channel.
        Transfer is done in ASCII mode. Although binary would be faster,
        this is not currently implemented.

        Returns a list of floats containing instrument's measurements.

        :param channel: Channel data buffer to read from. Valid channels are
            given by {CH1|CH2}.
        :type channel: `SRS830.Mode` or `str`

        :rtype: `tuple`[`float`, ...] or if numpy is installed, `numpy.array`
        """
        if isinstance(channel, str):
            channel = channel.lower()
            channel = SRS830.Mode[channel]

        if channel not in self._valid_read_data_buffer:
            raise ValueError('Specified mode not valid for this function.')

        channel = self._valid_read_data_buffer[channel]

        N = self.num_data_points  # Retrieve number of data points stored

        # Query device for entire buffer, returning in ASCII, then
        # converting to a list of floats before returning to the
        # calling method
        data = self.query('TRCA?{},0,{}'.format(channel, N)).strip()
        if numpy:
            return numpy.fromstring(data, sep=',')
        return tuple(map(float, data.split(",")))

    def clear_data_buffer(self):
        """
        Clears the data buffer of the SRS830.
        """
        self.sendcmd('REST')

    _valid_channel_display = [
        {  # channel1
            Mode.x: 0,
            Mode.r: 1,
            Mode.xnoise: 2,
            Mode.aux1: 3,
            Mode.aux2: 4
        },
        {  # channel2
            Mode.y: 0,
            Mode.theta: 1,
            Mode.ynoise: 2,
            Mode.aux3: 3,
            Mode.aux4: 4
        }
    ]

    _valid_channel_ratio = [
        {
            Mode.none: 0,
            Mode.aux1: 1,
            Mode.aux2: 2
        },  # channel1
        {
            Mode.none: 0,
            Mode.aux3: 1,
            Mode.aux4: 2
        }  # channel2
    ]

    _valid_channel = {Mode.ch1: 1, Mode.ch2: 2}

    def set_channel_display(self, channel, display, ratio):
        """
        Sets the display of the two channels.
        Channel 1 can display X, R, X Noise, Aux In 1, Aux In 2
        Channel 2 can display Y, Theta, Y Noise, Aux In 3, Aux In 4

        Channel 1 can have ratio of None, Aux In 1, Aux In 2
        Channel 2 can have ratio of None, Aux In 3, Aux In 4

        :param channel: Channel you wish to set the display of. Valid input is
            one of {CH1|CH2}.
        :type channel: `~SRS830.Mode` or `str`

        :param display: Setting the channel will be changed to. Valid
            input is one of {X|Y|R|THETA|XNOISE|YNOISE|AUX1|AUX2|AUX3|AUX4}
        :type display: `~SRS830.Mode` or `str`

        :param ratio: Desired ratio setting for this channel. Valid input
            is one of {NONE|AUX1|AUX2|AUX3|AUX4}
        :type ratio: `~SRS830.Mode` or `str`
        """
        if isinstance(channel, str):
            channel = channel.lower()
            channel = SRS830.Mode[channel]
        if isinstance(display, str):
            display = display.lower()
            display = SRS830.Mode[display]
        if isinstance(ratio, str):
            ratio = ratio.lower()
            ratio = SRS830.Mode[ratio]

        if channel not in self._valid_channel:
            raise ValueError('Specified channel not valid for this function.')

        channel = self._valid_channel[channel]

        if display not in self._valid_channel_display[channel - 1]:
            raise ValueError('Specified display mode not valid for this '
                             'function.')
        if ratio not in self._valid_channel_ratio[channel - 1]:
            raise ValueError('Specified display ratio not valid for this '
                             'function.')

        display = self._valid_channel_display[channel - 1][display]
        ratio = self._valid_channel_ratio[channel - 1][ratio]

        self.sendcmd('DDEF {},{},{}'.format(channel, display, ratio))
Esempio n. 9
0
 class BoolMock(MockInstrument):
     mock1 = bool_property('MOCK1', set_cmd='FOOBAR')
Esempio n. 10
0
 class BoolMock(MockInstrument):
     mock1 = bool_property('MOCK1', set_fmt="{}={}")
Esempio n. 11
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(object):
        """
        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 not 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?")) * pq.ohm

    ## ENUMS ##

    class InputSource(IntEnum):
        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, xrange(8))

    remote = bool_property(name="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(name="INP",
                                 enum=InputSource,
                                 doc="""
        Gets/sets the input source.
        
        :type: `PicowattAVS47.InputSource`
        """)

    mux_channel = int_property(name="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(name="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(name="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))
Esempio n. 12
0
class HP6652a(PowerSupply, PowerSupplyChannel):
    """
    The HP6652a is a single output power supply.

    Because it is a single channel output, this object inherits from both
    PowerSupply and PowerSupplyChannel.

    According to the manual, this class MIGHT be usable for any HP power supply
    with a model number HP66XYA, where X is in {4,5,7,8,9} and Y is a digit(?).
    (e.g. HP6652A and HP6671A)

    HOWEVER, it has only been tested by the author with an HP6652A power supply.

    Example usage:

    >>> import time
    >>> import instruments as ik
    >>> psu = ik.hp.HP6652a.open_serial('/dev/ttyUSB0', 57600)
    >>> psu.voltage = 3 # Sets output voltage to 3V.
    >>> psu.output = True
    >>> psu.voltage
    array(3.0) * V
    >>> psu.voltage_sense < 5
    True
    >>> psu.output = False
    >>> psu.voltage_sense < 1
    True
    >>> psu.display_textmode=True
    >>> psu.display_text("test GOOD")
    'TEST GOOD'
    >>> time.sleep(5)
    >>> psu.display_textmode=False
    """

    # ENUMS ##

    # I don't know of any possible enumerations supported
    # by this instrument.

    # PROPERTIES ##

    voltage = unitful_property("VOLT",
                               u.volt,
                               doc="""
        Gets/sets the 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 `~pint.Quantity`
        """)

    current = unitful_property("CURR",
                               u.amp,
                               doc="""
        Gets/sets the 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 `~pint.Quantity`
        """)

    voltage_sense = unitful_property("MEAS:VOLT",
                                     u.volt,
                                     readonly=True,
                                     doc="""
        Gets the actual output voltage as measured by the sense wires.

        :units: :math:`\\text{V}` (volts)
        :rtype: `~pint.Quantity`
        """)

    current_sense = unitful_property("MEAS:CURR",
                                     u.amp,
                                     readonly=True,
                                     doc="""
        Gets the actual output current as measured by the sense wires.

        :units: :math:`\\text{A}` (amps)
        :rtype: `~pint.Quantity`
        """)

    overvoltage = unitful_property("VOLT:PROT",
                                   u.volt,
                                   doc="""
        Gets/sets the overvoltage protection setting in volts.

        Note there is no bounds checking on the value specified.

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

    overcurrent = bool_property("CURR:PROT:STAT",
                                inst_true="1",
                                inst_false="0",
                                doc="""
        Gets/sets the overcurrent protection setting.

        This is a toggle setting. It is either on or off.

        :type: `bool`
        """)

    output = bool_property("OUTP",
                           inst_true="1",
                           inst_false="0",
                           doc="""
        Gets/sets the output status.

        This is a toggle setting. True will turn on the instrument output
        while False will turn it off.

        :type: `bool`
        """)

    display_textmode = bool_property("DISP:MODE",
                                     inst_true="TEXT",
                                     inst_false="NORM",
                                     doc="""
        Gets/sets the display mode.

        This is a toggle setting. True will allow text to be sent to the
        front-panel LCD with the display_text() method.  False returns to
        the normal display mode.

        .. seealso:: `~HP6652a.display_text()`

        :type: `bool`
        """)

    @property
    def name(self):
        """
        The name of the connected instrument, as reported by the
        standard SCPI command ``*IDN?``.

        :rtype: `str`
        """
        idn_string = self.query("*IDN?")
        idn_list = idn_string.split(',')
        return ' '.join(idn_list[:2])

    @property
    def mode(self):
        """
        Unimplemented.
        """
        raise NotImplementedError("Setting the mode is not implemented.")

    @mode.setter
    def mode(self, newval):
        """
        Unimplemented.
        """
        raise NotImplementedError("Setting the mode is not implemented.")

    # METHODS ##

    def reset(self):
        """
        Reset overvoltage and overcurrent errors to resume operation.
        """
        self.sendcmd('OUTP:PROT:CLE')

    def display_text(self, text_to_display):
        """
        Sends up to 12 (uppercase) alphanumerics to be sent to the
        front-panel LCD display.  Some punctuation is allowed, and
        can affect the number of characters allowed.  See the
        programming manual for the HP6652A for more details.

        Because the maximum valid number of possible characters is
        15 (counting the possible use of punctuation), the text will
        be truncated to 15 characters before the command is sent to
        the instrument.

        If an invalid string is sent, the command will fail silently.
        Any lowercase letters in the text_to_display will be converted
        to uppercase before the command is sent to the instrument.

        No attempt to validate punctuation is currently made.

        Because the string cannot be read back from the instrument,
        this method returns the actual string value sent.

        :param text_to_display: The text that you wish to have displayed
            on the front-panel LCD
        :type text_to_display: 'str'
        :return: Returns the version of the provided string that will
            be send to the instrument. This means it will be truncated to
            a maximum of 15 characters and changed to all upper case.
        :rtype: `str`
        """

        if len(text_to_display) > 15:
            text_to_display = text_to_display[:15]
        text_to_display = text_to_display.upper()

        self.sendcmd('DISP:TEXT "{}"'.format(text_to_display))

        return text_to_display

    @property
    def channel(self):
        """
        Return the channel (which in this case is the entire instrument, since
        there is only 1 channel on the HP6652a.)

        :rtype: 'tuple' of length 1 containing a reference back to the parent
            HP6652a object.
        """
        return self,
    class Channel(PowerSupplyChannel):
        """
        Class representing a channel on the EpqisDemoInstrument.

        This class inherits from `PowerSupplyChannel`.

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

        # COMMUNICATION METHODS #

        def sendcmd(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            """
            self._parent.sendcmd("CH{} {}".format(self._idx, cmd))

        def query(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            :return: The result from the query
            :rtype: `str`
            """
            return self._parent.query("CH{} {}".format(self._idx, cmd))

        # PROPERTIES #
        @property
        def mode(self):
            """
            Gets/sets the mode for the specified channel.
            """
            raise NotImplementedError(
                'This instrument does not support querying '
                'or setting the output current.')

        @mode.setter
        def mode(self, newval):
            raise NotImplementedError(
                'This instrument does not support querying '
                'or setting the output current.')

        @property
        def voltage(self):
            """
            Gets/sets the voltage for the specified channel.

            Example use:

            >>> import instruments as ik
            >>> import quantities as pq
            >>> inst = ik.generic_scpi.SCPIInstrument.open_tcpip("localhost",8042)
            >>> inst.voltage
                10 * pq.V
            >>> inst.voltage = 42 * pq.mV

            :type: `float`
            """
            value = self.query("VOLTS?")
            return assume_units(float(value), pq.millivolt).rescale(pq.volt)

        @voltage.setter
        def voltage(self, newval):
            newval = assume_units(newval,
                                  pq.volt).rescale(pq.millivolt).magnitude
            self.sendcmd("VOLTS {}".format(newval))

        @property
        def current(self):
            """
            Gets/sets the current for the specified channel.
            """
            raise NotImplementedError(
                'This instrument does not support querying '
                'or setting the output current.')

        @current.setter
        def current(self, newval):
            raise NotImplementedError(
                'This instrument does not support querying '
                'or setting the output current.')

        output = bool_property("ENABLE",
                               inst_true="ON",
                               inst_false="OFF",
                               doc="""
            Sets the outputting status of the specified channel.

            This is a toggle setting that can be ON or OFF. 

            :type: `bool`
            """)
Esempio n. 14
0
class Lakeshore475(SCPIInstrument):
    """
    The Lakeshore475 is a DSP Gaussmeter with field ranges from 35mG to 350kG.

    Example usage:

    >>> import instruments as ik
    >>> import quantities as pq
    >>> gm = ik.lakeshore.Lakeshore475.open_gpibusb('/dev/ttyUSB0', 1)
    >>> print(gm.field)
    >>> gm.field_units = pq.tesla
    >>> gm.field_setpoint = 0.05 * pq.tesla
    """

    # ENUMS ##

    class Mode(IntEnum):
        """
        Enum containing valid measurement modes for the Lakeshore 475
        """
        dc = 1
        rms = 2
        peak = 3

    class Filter(IntEnum):
        """
        Enum containing valid filter modes for the Lakeshore 475
        """
        wide = 1
        narrow = 2
        lowpass = 3

    class PeakMode(IntEnum):
        """
        Enum containing valid peak modes for the Lakeshore 475
        """
        periodic = 1
        pulse = 2

    class PeakDisplay(IntEnum):
        """
        Enum containing valid peak displays for the Lakeshore 475
        """
        positive = 1
        negative = 2
        both = 3

    # PROPERTIES ##

    @property
    def field(self):
        """
        Read field from connected probe.

        :type: `~quantities.quantity.Quantity`
        """
        return float(self.query('RDGFIELD?')) * self.field_units

    @property
    def field_units(self):
        """
        Gets/sets the units of the Gaussmeter.

        Acceptable units are Gauss, Tesla, Oersted, and Amp/meter.

        :type: `~quantities.unitquantity.UnitQuantity`
        """
        value = int(self.query('UNIT?'))
        return LAKESHORE_FIELD_UNITS[value]

    @field_units.setter
    def field_units(self, newval):
        if isinstance(newval, pq.unitquantity.UnitQuantity):
            if newval in LAKESHORE_FIELD_UNITS_INV:
                self.sendcmd('UNIT ' + LAKESHORE_FIELD_UNITS_INV[newval])
            else:
                raise ValueError('Not an acceptable Python quantities object')
        else:
            raise TypeError('Field units must be a Python quantity')

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

        Acceptable units are celcius and kelvin.

        :type: `~quantities.unitquantity.UnitQuantity`
        """
        value = int(self.query('TUNIT?'))
        return LAKESHORE_TEMP_UNITS[value]

    @temp_units.setter
    def temp_units(self, newval):
        if isinstance(newval, pq.unitquantity.UnitQuantity):
            if newval in LAKESHORE_TEMP_UNITS_INV:
                self.sendcmd('TUNIT ' + LAKESHORE_TEMP_UNITS_INV[newval])
            else:
                raise TypeError('Not an acceptable Python quantities object')
        else:
            raise TypeError('Temperature units must be a Python quantity')

    @property
    def field_setpoint(self):
        """
        Gets/sets the final setpoint of the field control ramp.

        :units: As specified (if a `~quantities.Quantity`) or assumed to be
            of units Gauss.
        :type: `~quantities.quantity.Quantity` with units Gauss
        """
        value = self.query('CSETP?').strip()
        units = self.field_units
        return float(value) * units

    @field_setpoint.setter
    def field_setpoint(self, newval):
        units = self.field_units
        newval = float(assume_units(newval, pq.gauss).rescale(units).magnitude)
        self.sendcmd('CSETP {}'.format(newval))

    @property
    def field_control_params(self):
        """
        Gets/sets the parameters associated with the field control ramp.
        These are (in this order) the P, I, ramp rate, and control slope limit.

        :type: `tuple` of 2 `float` and 2 `~quantities.quantity.Quantity`
        """
        params = self.query('CPARAM?').strip().split(',')
        params = [float(x) for x in params]
        params[2] = params[2] * self.field_units / pq.minute
        params[3] = params[3] * pq.volt / pq.minute
        return tuple(params)

    @field_control_params.setter
    def field_control_params(self, newval):
        if not isinstance(newval, tuple):
            raise TypeError('Field control parameters must be specified as '
                            ' a tuple')
        newval = list(newval)
        newval[0] = float(newval[0])
        newval[1] = float(newval[1])

        unit = self.field_units / pq.minute
        newval[2] = float(
            assume_units(newval[2], unit).rescale(unit).magnitude)
        unit = pq.volt / pq.minute
        newval[3] = float(
            assume_units(newval[3], unit).rescale(unit).magnitude)

        self.sendcmd('CPARAM {},{},{},{}'.format(
            newval[0],
            newval[1],
            newval[2],
            newval[3],
        ))

    @property
    def p_value(self):
        """
        Gets/sets the P value for the field control ramp.

        :type: `float`
        """
        return self.field_control_params[0]

    @p_value.setter
    def p_value(self, newval):
        newval = float(newval)
        values = list(self.field_control_params)
        values[0] = newval
        self.field_control_params = tuple(values)

    @property
    def i_value(self):
        """
        Gets/sets the I value for the field control ramp.

        :type: `float`
        """
        return self.field_control_params[1]

    @i_value.setter
    def i_value(self, newval):
        newval = float(newval)
        values = list(self.field_control_params)
        values[1] = newval
        self.field_control_params = tuple(values)

    @property
    def ramp_rate(self):
        """
        Gets/sets the ramp rate value for the field control ramp.

        :units: As specified (if a `~quantities.Quantity`) or assumed to be
            of current field units / minute.
        :type: `~quantities.quantity.Quantity`
        """
        return self.field_control_params[2]

    @ramp_rate.setter
    def ramp_rate(self, newval):
        unit = self.field_units / pq.minute
        newval = float(assume_units(newval, unit).rescale(unit).magnitude)
        values = list(self.field_control_params)
        values[2] = newval
        self.field_control_params = tuple(values)

    @property
    def control_slope_limit(self):
        """
        Gets/sets the I value for the field control ramp.

        :units: As specified (if a `~quantities.Quantity`) or assumed to be
            of units volt / minute.
        :type: `~quantities.quantity.Quantity`
        """
        return self.field_control_params[3]

    @control_slope_limit.setter
    def control_slope_limit(self, newval):
        unit = pq.volt / pq.minute
        newval = float(assume_units(newval, unit).rescale(unit).magnitude)
        values = list(self.field_control_params)
        values[3] = newval
        self.field_control_params = tuple(values)

    control_mode = bool_property(name="CMODE",
                                 inst_true="1",
                                 inst_false="0",
                                 doc="""
        Gets/sets the control mode setting. False corresponds to the field
        control ramp being disables, while True enables the closed loop PI
        field control.

        :type: `bool`
        """)

    # METHODS ##

    # pylint: disable=too-many-arguments
    def change_measurement_mode(self, mode, resolution, filter_type, peak_mode,
                                peak_disp):
        """
        Change the measurement mode of the Gaussmeter.

        :param mode: The desired measurement mode.
        :type mode: `Lakeshore475.Mode`

        :param `int` resolution: Digit resolution of the measured field. One of
            `{3|4|5}`.

        :param filter_type: Specify the signal filter
            used by the instrument. Available types include wide band, narrow
            band, and low pass.
        :type filter_type: `Lakeshore475.Filter`

        :param peak_mode: Peak measurement mode to be
            used.
        :type peak_mode: `Lakeshore475.PeakMode`

        :param peak_disp: Peak display mode to be
            used.
        :type peak_disp: `Lakeshore475.PeakDisplay`
        """
        if not isinstance(mode, Lakeshore475.Mode):
            raise TypeError("Mode setting must be a "
                            "`Lakeshore475.Mode` value, got {} "
                            "instead.".format(type(mode)))
        if not isinstance(resolution, int):
            raise TypeError('Parameter "resolution" must be an integer.')
        if not isinstance(filter_type, Lakeshore475.Filter):
            raise TypeError("Filter type setting must be a "
                            "`Lakeshore475.Filter` value, got {} "
                            "instead.".format(type(filter_type)))
        if not isinstance(peak_mode, Lakeshore475.PeakMode):
            raise TypeError("Filter type setting must be a "
                            "`Lakeshore475.PeakMode` value, got {} "
                            "instead.".format(type(peak_mode)))
        if not isinstance(peak_disp, Lakeshore475.PeakDisplay):
            raise TypeError("Filter type setting must be a "
                            "`Lakeshore475.PeakDisplay` value, got {} "
                            "instead.".format(type(peak_disp)))

        mode = mode.value
        filter_type = filter_type.value
        peak_mode = peak_mode.value
        peak_disp = peak_disp.value

        # Parse the resolution
        if resolution in range(3, 6):
            resolution -= 2
        else:
            raise ValueError('Only 3,4,5 are valid resolutions.')

        self.sendcmd('RDGMODE {},{},{},{},{}'.format(mode, resolution,
                                                     filter_type, peak_mode,
                                                     peak_disp))
Esempio n. 15
0
class RigolDS1000Series(SCPIInstrument, Oscilloscope):
    class DataSource(OscilloscopeDataSource):
        def __init__(self, parent, name):
            self._parent = parent
            self._name = name

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

        def read_waveform(self):
            # TODO: add DIG, FFT.
            if self.name not in ["CHAN1", "CHAN2", "DIG", "MATH", "FFT"]:
                raise NotImplementedError(
                    "Rigol DS1000 series does not support reading waveforms from {}."
                    .format(self.name))
            self._parent.sendcmd(":WAV:DATA? {}".format(self.name))
            data = self._parent.binblockread(2)  # TODO: check width
            return data

    class Channel(DataSource, OscilloscopeChannel):
        def __init__(self, parent, idx):
            self._parent = parent
            self._idx = idx + 1  # Rigols are 1-based.

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

        def sendcmd(self, cmd):
            self._parent.sendcmd(":CHAN{}:{}".format(self._idx, cmd))

        def query(self, cmd):
            return self._parent.query(":CHAN{}:{}".format(self._idx, cmd))

        coupling = enum_property("COUP", Coupling)

        bw_limit = bool_property("BWL", "ON", "OFF")
        display = bool_property("DISP", "ON", "OFF")
        invert = bool_property("INV", "ON", "OFF")

        # TODO: :CHAN<n>:OFFset
        # TODO: :CHAN<n>:PROBe
        # TODO: :CHAN<n>:SCALe

        filter = bool_property("FILT", "ON", "OFF")

        # TODO: :CHAN<n>:MEMoryDepth

        vernier = bool_property("VERN", "ON", "OFF")

    ## PROPERTIES ##

    @property
    def channel(self):
        # Rigol DS1000 series oscilloscopes all have two channels,
        # according to the documentation.
        return ProxyList(self, self.Channel, xrange(2))

    @property
    def math(self):
        return DataSource("MATH")

    @property
    def ref(self):
        return DataSource("REF")

    acquire_type = enum_property(":ACQ:TYPE", AcquisitionType)
    # TODO: implement :ACQ:MODE. This is confusing in the documentation, though.

    @property
    def acquire_averages(self):
        return int(self.query(":ACQ:AVER?"))

    @acquire_averages.setter
    def acquire_averages(self, newval):
        if newval not in [2**i for i in xrange(1, 9)]:
            raise ValueError(
                "Number of averages {} not supported by instrument; "
                "must be a power of 2 from 2 to 256.".format(newval))
        self.sendcmd(":ACQ:AVER {}".format(newval))

    # TODO: implement :ACQ:SAMP in a meaningful way. This should probably be
    #       under Channel, and needs to be unitful.
    # TODO: I don't understand :ACQ:MEMD yet.

    ## METHODS ##

    def force_trigger(self):
        self.sendcmd(":FORC")

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

    def stop(self):
        self.sendcmd(":STOP")

    # TODO: unitful timebase!

    ## FRONT-PANEL KEY EMULATION METHODS ##
    # These methods correspond one-to-one with physical keys on the front
    # (local) control panel, except for release_panel, which enables the local
    # panel and disables any remote lockouts, and for panel_locked.
    #
    # Many of the :KEY: commands are not yet implemented as methods.

    panel_locked = bool_property(":KEY:LOCK", "ON", "OFF")

    def release_panel(self):
        # TODO: better name?
        # NOTE: method may be redundant with the panel_locked property.
        """
        Releases any lockout of the local control panel.
        """
        self.sendcmf(":KEY:FORC")
class HP3456a(Multimeter):

    """The `HP3456a` is a 6 1/2 digit bench multimeter.

    It supports DCV, ACV, ACV + DCV, 2 wire Ohms, 4 wire Ohms, DCV/DCV Ratio,
    ACV/DCV Ratio, Offset compensated 2 wire Ohms and Offset compensated 4 wire
    Ohms measurements.

    Measurements can be further extended using a system math mode that allows
    for pass/fail, statistics, dB/dBm, null, scale and percentage readings.

    `HP3456a` is a HPIB / pre-448.2 instrument.
    """

    def __init__(self, filelike):
        """
        Initialise the instrument, and set the required eos, eoi needed for
        communication.
        """
        super(HP3456a, self).__init__(filelike)
        self.timeout = 15 * u.second
        self.terminator = "\r"
        self.sendcmd("HO0T4SO1")
        self._null = False

    # ENUMS ##

    class MathMode(IntEnum):

        """
        Enum with the supported math modes
        """
        off = 0
        pass_fail = 1
        statistic = 2
        null = 3
        dbm = 4
        thermistor_f = 5
        thermistor_c = 6
        scale = 7
        percent = 8
        db = 9

    class Mode(Enum):

        """
        Enum containing the supported mode codes
        """
        #: DC voltage
        dcv = "S0F1"
        #: AC voltage
        acv = "S0F2"
        #: RMS of DC + AC voltage
        acvdcv = "S0F3"
        #: 2 wire resistance
        resistance_2wire = "S0F4"
        #: 4 wire resistance
        resistance_4wire = "S0F5"
        #: ratio DC / DC voltage
        ratio_dcv_dcv = "S1F1"
        #: ratio AC / DC voltage
        ratio_acv_dcv = "S1F2"
        #: ratio (AC + DC) / DC voltage
        ratio_acvdcv_dcv = "S1F3"
        #: offset compensated 2 wire resistance
        oc_resistence_2wire = "S1F4"
        #: offset compensated 4 wire resistance
        oc_resistence_4wire = "S1F5"

    class Register(Enum):

        """
        Enum with the register names for all `HP3456a` internal registers.
        """
        number_of_readings = "N"
        number_of_digits = "G"
        nplc = "I"
        delay = "D"
        mean = "M"
        variance = "V"
        count = "C"
        lower = "L"
        r = "R"
        upper = "U"
        y = "Y"
        z = "Z"

    class TriggerMode(IntEnum):

        """
        Enum with valid trigger modes.
        """
        internal = 1
        external = 2
        single = 3
        hold = 4

    class ValidRange(Enum):

        """
        Enum with the valid ranges for voltage, resistance, and number of
        powerline cycles to integrate over.

        """
        voltage = (1e-1, 1e0, 1e1, 1e2, 1e3)
        resistance = (1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9)
        nplc = (1e-1, 1e0, 1e1, 1e2)

    # PROPERTIES ##

    mode = enum_property(
        "",
        Mode,
        doc="""Set the measurement mode.

        :type: `HP3456a.Mode`
        """,
        writeonly=True,
        set_fmt="{}{}")

    autozero = bool_property(
        "Z",
        inst_true="1",
        inst_false="0",
        doc="""Set the autozero mode.

        This is used to compensate for offsets in the dc
        input amplifier circuit of the multimeter. If set, the amplifier"s input
        circuit is shorted to ground prior to actual measurement in order to
        take an offset reading. This offset is then used to compensate for
        drift in the next measurement. When disabled, one offset reading
        is taken immediately and stored into memory to be used for all
        successive measurements onwards. Disabling autozero increases the
        `HP3456a`"s measurement speed, and also makes the instrument more
        suitable for high impendance measurements since no input switching is
        done.""",
        writeonly=True,
        set_fmt="{}{}")

    filter = bool_property(
        "FL",
        inst_true="1",
        inst_false="0",
        doc="""Set the analog filter mode.

        The `HP3456a` has a 3 pole active filter with
        greater than 60dB attenuation at frequencies of 50Hz and higher. The
        filter is applied between the input terminals and input amplifier. When
        in ACV or ACV+DCV functions the filter is applied to the output of the
        ac converter and input amplifier. In these modes select the filter for
        measurements below 400Hz.""",
        writeonly=True,
        set_fmt="{}{}")

    math_mode = enum_property(
        "M",
        MathMode,
        doc="""Set the math mode.

        The `HP3456a` has a number of different math modes that
        can change measurement output, or can provide additional
        statistics. Interaction with these modes is done via the
        `HP3456a.Register`.

        :type: `HP3456a.MathMode`
        """,
        writeonly=True,
        set_fmt="{}{}")

    trigger_mode = enum_property(
        "T",
        TriggerMode,
        doc="""Set the trigger mode.

        Note that using `HP3456a.measure()` will override the `trigger_mode` to
        `HP3456a.TriggerMode.single`.

        :type: `HP3456a.TriggerMode`

        """,
        writeonly=True,
        set_fmt="{}{}")

    @property
    def number_of_readings(self):
        """Get/set the number of readings done per trigger/measurement cycle
        using `HP3456a.Register.number_of_readings`.

        :type: `float`
        :rtype: `float`

        """
        return self._register_read(HP3456a.Register.number_of_readings)

    @number_of_readings.setter
    def number_of_readings(self, value):
        self._register_write(HP3456a.Register.number_of_readings, value)

    @property
    def number_of_digits(self):
        """Get/set the number of digits used in measurements using
        `HP3456a.Register.number_of_digits`.

        Set to higher values to increase accuracy at the cost of measurement
        speed.

        :type: `int`
        """
        return int(self._register_read(HP3456a.Register.number_of_digits))

    @number_of_digits.setter
    def number_of_digits(self, newval):
        newval = int(newval)
        if newval not in range(3, 7):
            raise ValueError("Valid number_of_digits are: "
                             "{}".format(list(range(3, 7))))

        self._register_write(HP3456a.Register.number_of_digits, newval)

    @property
    def nplc(self):
        """Get/set the number of powerline cycles to integrate per measurement
        using `HP3456a.Register.nplc`.

        Setting higher values increases accuracy at the cost of a longer
        measurement time. The implicit assumption is that the input reading is
        stable over the number of powerline cycles to integrate.

        :type: `int`
        """
        return int(self._register_read(HP3456a.Register.nplc))

    @nplc.setter
    def nplc(self, newval):
        newval = int(newval)
        valid = HP3456a.ValidRange["nplc"].value
        if newval in valid:
            self._register_write(HP3456a.Register.nplc, newval)
        else:
            raise ValueError("Valid nplc settings are: "
                             "{}".format(valid))

    @property
    def delay(self):
        """Get/set the delay that is waited after a trigger for the input to
        settle using `HP3456a.Register.delay`.

        :type: As specified, assumed to be `~quantaties.Quantity.s` otherwise
        :rtype: `~quantaties.Quantity.s`

        """
        return self._register_read(HP3456a.Register.delay) * u.s

    @delay.setter
    def delay(self, value):
        delay = assume_units(value, u.s).rescale(u.s).magnitude
        self._register_write(HP3456a.Register.delay, delay)

    @property
    def mean(self):
        """
        Get the mean over `HP3456a.Register.count` measurements from
        `HP3456a.Register.mean` when in `HP3456a.MathMode.statistic`.

        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.mean)

    @property
    def variance(self):
        """
        Get the variance over `HP3456a.Register.count` measurements from
        `HP3456a.Register.variance` when in `HP3456a.MathMode.statistic`.

        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.variance)

    @property
    def count(self):
        """
        Get the number of measurements taken from `HP3456a.Register.count` when
        in `HP3456a.MathMode.statistic`.

        :rtype: `int`
        """
        return int(self._register_read(HP3456a.Register.count))

    @property
    def lower(self):
        """
        Get/set the value in `HP3456a.Register.lower`, which indicates the
        lowest value measurement made while in `HP3456a.MathMode.statistic`, or
        the lowest value preset for `HP3456a.MathMode.pass_fail`.

        :type: `float`
        """
        return self._register_read(HP3456a.Register.lower)

    @lower.setter
    def lower(self, value):
        self._register_write(HP3456a.Register.lower, value)

    @property
    def upper(self):
        """
        Get/set the value in `HP3456a.Register.upper`, which indicates the
        highest value measurement made while in `HP3456a.MathMode.statistic`,
        or the highest value preset for `HP3456a.MathMode.pass_fail`.

        :type: `float`
        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.upper)

    @upper.setter
    def upper(self, value):
        return self._register_write(HP3456a.Register.upper, value)

    @property
    def r(self):
        """
        Get/set the value in `HP3456a.Register.r`, which indicates the resistor
        value used while in `HP3456a.MathMode.dbm` or the number of recalled
        readings in reading storage mode.

        :type: `float`
        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.r)

    @r.setter
    def r(self, value):
        self._register_write(HP3456a.Register.r, value)

    @property
    def y(self):
        """
        Get/set the value in `HP3456a.Register.y` to be used in calculations
        when in `HP3456a.MathMode.scale` or `HP3456a.MathMode.percent`.

        :type: `float`
        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.y)

    @y.setter
    def y(self, value):
        self._register_write(HP3456a.Register.y, value)

    @property
    def z(self):
        """
        Get/set the value in `HP3456a.Register.z` to be used in calculations
        when in `HP3456a.MathMode.scale` or the first reading when in
        `HP3456a.MathMode.statistic`.

        :type: `float`
        :rtype: `float`
        """
        return self._register_read(HP3456a.Register.z)

    @z.setter
    def z(self, value):
        self._register_write(HP3456a.Register.z, value)

    @property
    def input_range(self):
        """Set the input range to be used.

        The `HP3456a` has separate ranges for `~quantities.ohm` and for
        `~quantities.volt`. The range value sent to the instrument depends on
        the unit set on the input range value. `auto` selects auto ranging.

        :type: `~quantities.Quantity`
        """
        raise NotImplementedError

    @input_range.setter
    def input_range(self, value):
        if isinstance(value, str):
            if value.lower() == "auto":
                self.sendcmd("R1W")
            else:
                raise ValueError("Only 'auto' is acceptable when specifying "
                                 "the input range as a string.")

        elif isinstance(value, u.quantity.Quantity):
            if value.units == u.volt:
                valid = HP3456a.ValidRange.voltage.value
                value = value.rescale(u.volt)
            elif value.units == u.ohm:
                valid = HP3456a.ValidRange.resistance.value
                value = value.rescale(u.ohm)
            else:
                raise ValueError("Value {} not quantity.volt or quantity.ohm"
                                 "".format(value))

            value = float(value)
            if value not in valid:
                raise ValueError("Value {} outside valid ranges "
                                 "{}".format(value, valid))
            value = valid.index(value) + 2
            self.sendcmd("R{}W".format(value))
        else:
            raise TypeError("Range setting must be specified as a float, int, "
                            "or the string 'auto', got {}".format(type(value)))

    @property
    def relative(self):
        """
        Enable or disable `HP3456a.MathMode.Null` on the instrument.

        :type: `bool`
        """
        return self._null

    @relative.setter
    def relative(self, value):
        if value is True:
            self._null = True
            self.sendcmd("M{}".format(HP3456a.MathMode.null.value))
        elif value is False:
            self._null = False
            self.sendcmd("M{}".format(HP3456a.MathMode.off.value))
        else:
            raise TypeError("Relative setting must be specified as a bool, "
                            "got {}".format(type(value)))

    # METHODS ##

    def auto_range(self):
        """
        Set input range to auto. The `HP3456a` should upscale when a reading
        is at 120% and downscale when it below 11% full scale. Note that auto
        ranging can increase the measurement time.
        """
        self.input_range = "auto"

    def fetch(self, mode=None):
        """Retrieve n measurements after the HP3456a has been instructed to
        perform a series of similar measurements. Typically the mode, range,
        nplc, analog filter, autozero is set along with the number of
        measurements to take. The series is then started at the trigger
        command.

        Example usage:

        >>> dmm.number_of_digits = 6
        >>> dmm.auto_range()
        >>> dmm.nplc = 1
        >>> dmm.mode = dmm.Mode.resistance_2wire
        >>> n = 100
        >>> dmm.number_of_readings = n
        >>> dmm.trigger()
        >>> time.sleep(n * 0.04)
        >>> v = dmm.fetch(dmm.Mode.resistance_2wire)
        >>> print len(v)
        10

        :param mode: Desired measurement mode. If not specified, the previous
            set mode will be used, but no measurement unit will be returned.

        :type mode: `HP3456a.Mode`

        :return: A series of measurements from the multimeter.
        :rtype: `~quantities.quantity.Quantity`
        """
        if mode is not None:
            units = UNITS[mode]
        else:
            units = 1

        value = self.query("", size=-1)
        values = [float(x) * units for x in value.split(",")]
        return values

    def measure(self, mode=None):
        """Instruct the HP3456a to perform a one time measurement. The
        measurement will use the current set registers for the measurement
        (number_of_readings, number_of_digits, nplc, delay, mean, lower, upper,
        y and z) and will immediately take place.

        Note that using `HP3456a.measure()` will override the `trigger_mode` to
        `HP3456a.TriggerMode.single`

        Example usage:

        >>> dmm = ik.hp.HP3456a.open_gpibusb("/dev/ttyUSB0", 22)
        >>> dmm.number_of_digits = 6
        >>> dmm.nplc = 1
        >>> print dmm.measure(dmm.Mode.resistance_2wire)

        :param mode: Desired measurement mode. If not specified, the previous
            set mode will be used, but no measurement unit will be
            returned.

        :type mode: `HP3456a.Mode`

        :return: A measurement from the multimeter.
        :rtype: `~quantities.quantity.Quantity`

        """
        if mode is not None:
            modevalue = mode.value
            units = UNITS[mode]
        else:
            modevalue = ""
            units = 1

        self.sendcmd("{}W1STNT3".format(modevalue))

        value = self.query("", size=-1)
        return float(value) * units

    def _register_read(self, name):
        """
        Read a register on the HP3456a.

        :param name: The name of the register to read from
        :type name: `HP3456a.Register`
        :rtype: `float`
        """
        try:
            name = HP3456a.Register[name]
        except KeyError:
            pass
        if not isinstance(name, HP3456a.Register):
            raise TypeError("register must be specified as a "
                            "HP3456a.Register, got {} "
                            "instead.".format(name))
        self.sendcmd("RE{}".format(name.value))
        if not self._testing:  # pragma: no cover
            time.sleep(.1)
        return float(self.query("", size=-1))

    def _register_write(self, name, value):
        """
        Write a register on the HP3456a.

        :param name: The name of the register to write to
        :type name: `HP3456a.Register`
        :type value: `float`
        """
        try:
            name = HP3456a.Register[name]
        except KeyError:
            pass
        if not isinstance(name, HP3456a.Register):
            raise TypeError("register must be specified as a "
                            "HP3456a.Register, got {} "
                            "instead.".format(name))
        if name in [
                HP3456a.Register.mean,
                HP3456a.Register.variance,
                HP3456a.Register.count
        ]:
            raise ValueError("register {} is read only".format(name))
        self.sendcmd("W{}ST{}".format(value, name.value))
        if not self._testing:  # pragma: no cover
            time.sleep(.1)

    def trigger(self):
        """
        Signal a single manual trigger event to the `HP3456a`.
        """
        self.sendcmd("T3")
Esempio n. 17
0
 class BoolMock(MockInstrument):
     mock1 = bool_property('MOCK1')
     mock2 = bool_property('MOCK2', inst_true='YES', inst_false='NO')
class Keithley6514(SCPIInstrument, Electrometer):
    """
    The `Keithley 6514`_ 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.Keithley6514.open_gpibusb('/dev/ttyUSB0', 12)
    """

    # ENUMS #

    class Mode(Enum):
        """
        Enum containing valid measurement modes for the Keithley 6514
        """
        voltage = 'VOLT:DC'
        current = 'CURR:DC'
        resistance = 'RES'
        charge = 'CHAR'

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

    class ArmSource(Enum):
        """
        Enum containing valid trigger arming sources for the Keithley 6514
        """
        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 6514
        """
        voltage = (2, 20, 200)
        current = (20e-12, 200e-12, 2e-9, 20e-9, 200e-9, 2e-6, 20e-6, 200e-6,
                   2e-3, 20e-3)
        resistance = (2e3, 20e3, 200e3, 2e6, 20e6, 200e6, 2e9, 20e9, 200e9)
        charge = (20e-9, 200e-9, 2e-6, 20e-6)

    # CONSTANTS #

    _MODE_UNITS = {
        Mode.voltage: u.volt,
        Mode.current: u.amp,
        Mode.resistance: u.ohm,
        Mode.charge: u.coulomb
    }

    # PRIVATE METHODS #

    def _valid_range(self, mode):
        if mode == self.Mode.voltage:
            return self.ValidRange.voltage
        elif mode == self.Mode.current:
            return self.ValidRange.current
        elif mode == self.Mode.resistance:
            return self.ValidRange.resistance
        elif mode == self.Mode.charge:
            return self.ValidRange.charge
        else:
            raise ValueError('Invalid mode.')

    def _parse_measurement(self, ascii):
        # TODO: don't assume ASCII data format # pylint: disable=fixme
        vals = list(map(float, ascii.split(',')))
        reading = vals[0] * self.unit
        timestamp = vals[1]
        status = vals[2]
        return reading, timestamp, status

    # PROPERTIES #

    # The mode values have quotes around them for some annoying reason.
    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 6514.
        """)

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

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

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

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

    @property
    def unit(self):
        return self._MODE_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 True if out == '1' else False

    @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: `~quantities.Quantity`
        """
        # pylint: disable=no-member
        mode = self.mode
        out = self.query('{}:RANGE:UPPER?'.format(mode.value))
        return float(out) * self._MODE_UNITS[mode]

    @input_range.setter
    def input_range(self, newval):
        # pylint: disable=no-member
        mode = self.mode
        val = newval.rescale(self._MODE_UNITS[mode]).item()
        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)
        """
        raw = self.query('FETC?')
        reading, timestamp, _ = self._parse_measurement(raw)
        return reading, timestamp

    def read_measurements(self):
        """
        Trigger and acquire readings using the current mode.
        Returns a tuple of the form (reading, timestamp)
        """
        raw = self.query('READ?')
        reading, timestamp, _ = self._parse_measurement(raw)
        return reading, timestamp
Esempio n. 19
0
 class BoolMock(MockInstrument):
     mock1 = bool_property('MOCK1', writeonly=True)
Esempio n. 20
0
class RigolDS1000Series(SCPIInstrument, Oscilloscope):
    """
    The Rigol DS1000-series is a popular budget oriented oscilloscope
    that has featured wide adoption across hobbyist circles.

    .. warning:: This instrument is not complete, and probably not even
        functional!
    """

    # ENUMS #

    class AcquisitionType(Enum):
        """
        Enum containing valid acquisition types for the Rigol DS1000
        """
        normal = "NORM"
        average = "AVER"
        peak_detect = "PEAK"

    # INNER CLASSES #

    class DataSource(OscilloscopeDataSource):
        """
        Class representing a data source (channel, math, or ref) on the
        Rigol DS1000

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

        def read_waveform(self, bin_format=True):
            # TODO: add DIG, FFT.
            if self.name not in ["CHAN1", "CHAN2", "DIG", "MATH", "FFT"]:
                raise NotImplementedError("Rigol DS1000 series does not "
                                          "supportreading waveforms from "
                                          "{}.".format(self.name))
            self._parent.sendcmd(":WAV:DATA? {}".format(self.name))
            data = self._parent.binblockread(2)  # TODO: check width
            return data

    class Channel(DataSource, OscilloscopeChannel):
        """
        Class representing a channel on the Rigol DS1000.

        This class inherits from `~RigolDS1000Series.DataSource`.

        .. warning:: This class should NOT be manually created by the user. It
            is designed to be initialized by the `RigolDS1000Series` class.
        """
        class Coupling(Enum):
            """
            Enum containing valid coupling modes for the Rigol DS1000
            """
            ac = "AC"
            dc = "DC"
            ground = "GND"

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

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

        def sendcmd(self, cmd):
            """
            Passes a command from the `Channel` class to the parent
            `RigolDS1000Series`, appending the required channel identification.

            :param str cmd: The command string to send to the instrument
            """
            self._parent.sendcmd(":CHAN{}:{}".format(self._idx, cmd))

        def query(self, cmd):
            """
            Passes a command from the `Channel` class to the parent
            `RigolDS1000Series`, appending the required channel identification.

            :param str cmd: The command string to send to the instrument
            :return: The result as returned by the instrument
            :rtype: `str`
            """
            return self._parent.query(":CHAN{}:{}".format(self._idx, cmd))

        coupling = enum_property("COUP", Coupling)

        bw_limit = bool_property("BWL", inst_true="ON", inst_false="OFF")
        display = bool_property("DISP", inst_true="ON", inst_false="OFF")
        invert = bool_property("INV", inst_true="ON", inst_false="OFF")

        # TODO: :CHAN<n>:OFFset
        # TODO: :CHAN<n>:PROBe
        # TODO: :CHAN<n>:SCALe

        filter = bool_property("FILT", inst_true="ON", inst_false="OFF")

        # TODO: :CHAN<n>:MEMoryDepth

        vernier = bool_property("VERN", inst_true="ON", inst_false="OFF")

    # PROPERTIES #

    @property
    def channel(self):
        # Rigol DS1000 series oscilloscopes all have two channels,
        # according to the documentation.
        return ProxyList(self, self.Channel, range(2))

    @property
    def math(self):
        return self.DataSource(parent=self, name="MATH")

    @property
    def ref(self):
        return self.DataSource(parent=self, name="REF")

    acquire_type = enum_property(":ACQ:TYPE", AcquisitionType)
    # TODO: implement :ACQ:MODE. This is confusing in the documentation,
    # though.

    @property
    def acquire_averages(self):
        """
        Gets/sets the number of averages the oscilloscope should take per
        acquisition.

        :type: `int`
        """
        return int(self.query(":ACQ:AVER?"))

    @acquire_averages.setter
    def acquire_averages(self, newval):
        if newval not in [2**i for i in range(1, 9)]:
            raise ValueError(
                "Number of averages {} not supported by instrument; "
                "must be a power of 2 from 2 to 256.".format(newval))
        self.sendcmd(":ACQ:AVER {}".format(newval))

    # TODO: implement :ACQ:SAMP in a meaningful way. This should probably be
    #       under Channel, and needs to be unitful.
    # TODO: I don't understand :ACQ:MEMD yet.

    # METHODS ##

    def force_trigger(self):
        self.sendcmd(":FORC")

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

    def stop(self):
        """
        Stops running the oscilloscope trigger.
        """
        self.sendcmd(":STOP")

    # TODO: unitful timebase!

    # FRONT-PANEL KEY EMULATION METHODS ##
    # These methods correspond one-to-one with physical keys on the front
    # (local) control panel, except for release_panel, which enables the local
    # panel and disables any remote lockouts, and for panel_locked.
    #
    # Many of the :KEY: commands are not yet implemented as methods.

    panel_locked = bool_property(":KEY:LOCK",
                                 inst_true="ENAB",
                                 inst_false="DIS")

    def release_panel(self):
        # TODO: better name?
        # NOTE: method may be redundant with the panel_locked property.
        """
        Releases any lockout of the local control panel.
        """
        self.sendcmd(":KEY:FORC")
Esempio n. 21
0
    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: ndarray

            :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
            dat_val = np.array(retval.replace('"', '').split(),
                               dtype=np.float)

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

            # create time base
            dat_time = np.arange(
                horiz_off,
                horiz_off + horiz_int * (len(dat_val)),
                horiz_int
            )

            # 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)]

            return np.stack((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
            """
        )
Esempio n. 22
0
class Keithley6485(SCPIInstrument):
    """
    The `Keithley 6485` is an electrometer capable of doing sensitive current,
    charge, voltage and resistance measurements.

    WARNING: Must set the terminator to `LF` on the define for this to work.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> dmm = ik.keithley.Keithley6485.open_serial('/dev/ttyUSB0', baud=9600)
    >>> dmm.measure()
    <Quantity(0.000123, 'ampere')>
    """
    def __init__(self, filelike):
        """
        Resets device to be read, disables zero check.
        """
        super(Keithley6485, self).__init__(filelike)
        self.reset()
        self.zero_check = False

    # PROPERTIES ##

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

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

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

        :type: `bool`
        """
        # pylint: disable=no-member
        out = self.query('RANG:AUTO?')
        return out == '1'

    @auto_range.setter
    def auto_range(self, newval):
        # pylint: disable=no-member
        self.sendcmd('RANG:AUTO {}'.format('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
        out = self.query('RANG?')
        return float(out) * u.amp

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

    # METHODS ##

    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):
        """
        Trigger and acquire readings.
        Returns the measurement reading only.
        """
        return self.read_measurements()[0]

    # PRIVATE METHODS ##

    @staticmethod
    def _valid_range():
        return (2e-9, 20e-9, 200e-9, 2e-6, 20e-6, 200e-6, 2e-3, 20e-3)

    @staticmethod
    def _parse_measurement(ascii):
        # Split the string in three comma-separated parts (value, time, number of triggers)
        vals = ascii.split(',')
        reading = float(vals[0][:-1]) * u.amp
        timestamp = float(vals[1]) * u.second
        trigger_count = int(float(vals[2]))
        return reading, timestamp, trigger_count
Esempio n. 23
0
    class Channel(SGChannel):
        """
        Class representing a physical channel on the Holzworth HS9000

        .. warning:: This class should NOT be manually created by the user. It
        is designed to be initialized by the `HS9000` class.
        """
        def __init__(self, hs, idx_chan):
            self._hs = hs
            self._idx = idx_chan

            # We unpacked the channel index from the string of the form "CH1",
            # in order to make the API more Pythonic, but now we need to put
            # it back.
            # Some channel names, like "REF", are special and are preserved
            # as strs.
            self._ch_name = (idx_chan if isinstance(idx_chan, str) else
                             "CH{}".format(idx_chan + 1))

        # PRIVATE METHODS #

        def sendcmd(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            """
            self._hs.sendcmd(":{ch}:{cmd}".format(ch=self._ch_name, cmd=cmd))

        def query(self, cmd):
            """
            Function used to send a command to the instrument while wrapping
            the command with the neccessary identifier for the channel.

            :param str cmd: Command that will be sent to the instrument after
                being prefixed with the channel identifier
            :return: The result from the query
            :rtype: `str`
            """
            return self._hs.query(":{ch}:{cmd}".format(ch=self._ch_name,
                                                       cmd=cmd))

        # STATE METHODS #

        def reset(self):
            """
            Resets the setting of the specified channel

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> hs.channel[0].reset()
            """
            self.sendcmd("*RST")

        def recall_state(self):
            """
            Recalls the state of the specified channel from memory.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> hs.channel[0].recall_state()
            """
            self.sendcmd("*RCL")

        def save_state(self):
            """
            Saves the current state of the specified channel.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> hs.channel[0].save_state()
            """
            self.sendcmd("*SAV")

        # PROPERTIES #

        @property
        def temperature(self):
            """
            Gets the current temperature of the specified channel.

            :units: As specified by the instrument.
            :rtype: `~quantities.quantity.Quantity`
            """
            val, units = split_unit_str(self.query("TEMP?"))
            units = "deg{}".format(units)
            return pq.Quantity(val, units)

        frequency, frequency_min, frequency_max = bounded_unitful_property(
            "FREQ",
            units=pq.GHz,
            doc="""
            Gets/sets the frequency of the specified channel. When setting,
            values are bounded between what is returned by `frequency_min`
            and `frequency_max`.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> print(hs.channel[0].frequency)
            >>> print(hs.channel[0].frequency_min)
            >>> print(hs.channel[0].frequency_max)

            :type: `~quantities.quantity.Quantity`
            :units: As specified or assumed to be of units GHz
            """)
        power, power_min, power_max = bounded_unitful_property("PWR",
                                                               units=dBm,
                                                               doc="""
            Gets/sets the output power of the specified channel. When setting,
            values are bounded between what is returned by `power_min`
            and `power_max`.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> print(hs.channel[0].power)
            >>> print(hs.channel[0].power_min)
            >>> print(hs.channel[0].power_max)

            :type: `~quantities.quantity.Quantity`
            :units: `instruments.units.dBm`
            """)
        phase, phase_min, phase_max = bounded_unitful_property("PHASE",
                                                               units=pq.degree,
                                                               doc="""
            Gets/sets the output phase of the specified channel. When setting,
            values are bounded between what is returned by `phase_min`
            and `phase_max`.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> print(hs.channel[0].phase)
            >>> print(hs.channel[0].phase_min)
            >>> print(hs.channel[0].phase_max)

            :type: `~quantities.quantity.Quantity`
            :units: As specified or assumed to be of units degrees
            """)

        output = bool_property("PWR:RF",
                               inst_true="ON",
                               inst_false="OFF",
                               set_fmt="{}:{}",
                               doc="""
            Gets/sets the output status of the channel. Setting to `True` will
            turn the channel's output stage on, while a value of `False` will
            turn it off.

            Example usage:
            >>> import instruments as ik
            >>> hs = ik.holzworth.HS9000.open_tcpip("192.168.0.2", 8080)
            >>> print(hs.channel[0].output)
            >>> hs.channel[0].output = True

            :type: `bool`
            """)
Esempio n. 24
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 quantities as pq
    >>> inst = ik.agilent.Agilent33220a.open_gpibusb('/dev/ttyUSB0', 1)
    >>> inst.function = inst.Function.sinusoid
    >>> inst.frequency = 1 * pq.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 #

    @property
    def frequency(self):
        return super(Agilent33220a, self).frequency

    @frequency.setter
    def frequency(self, newval):
        super(Agilent33220a, self).frequency = newval

    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) * pq.ohm
        except ValueError:
            return self.LoadResistance(value.strip())

    @load_resistance.setter
    def load_resistance(self, newval):
        if isinstance(newval, 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))

    @property
    def phase(self):
        raise NotImplementedError

    @phase.setter
    def phase(self, newval):
        raise NotImplementedError
Esempio n. 25
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 = enum_property("SPEC:MAG",
                                     Mag,
                                     doc="""
            Whether the spectral magnitude is linear, db, or dbm.
            """)

        spectral_phase = enum_property("SPEC:PHASE",
                                       Phase,
                                       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
            if numpy:
                return self.scale * (
                    (TekDPO70000.VERT_DIVS / 2) * data.astype(float) /
                    (2**15) - self.position)

            scale = self.scale
            position = self.position
            rval = tuple(scale * ((TekDPO70000.VERT_DIVS / 2) * d /
                                  (2**15) - position)
                         for d in map(float, data))
            return rval
Esempio n. 26
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",
                                       pq.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",
                                       pq.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',
                                           pq.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",
                                            pq.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",
                                               pq.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
Esempio n. 27
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",
                                 u.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 `~pint.Quantity`) or assumed
            to be of units Hertz.
        :rtype: `~pint.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",
                                u.V,
                                format_code="{:.1f}",
                                set_fmt="{}={}",
                                valid_range=(0, 25),
                                doc="""
        Gets/sets the voltage value for output 1.

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

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

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

    min_voltage = unitful_property("min",
                                   u.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 `~pint.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~pint.Quantity`
        """)

    max_voltage = unitful_property("max",
                                   u.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 `~pint.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~pint.Quantity`
        """)

    dwell = unitful_property("dwell",
                             units=u.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 `~pint.Quantity`) or assumed
            to be of units milliseconds.
        :rtype: `~pint.Quantity`
        """)

    increment = unitful_property("increment",
                                 units=u.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 `~pint.Quantity`) or assumed
            to be of units Volts.
        :rtype: `~pint.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)
Esempio n. 28
0
class Keithley6514(SCPIInstrument, Electrometer):
    """
    The Keithley 6514 is an electrometer capable of doing sensitive current, 
    charge, voltage and resistance measurements.
    
    Example usage:
    
    >>> import instruments as ik
    >>> import quantities as pq
    >>> dmm = ik.keithley.Keithley6514.open_gpibusb('/dev/ttyUSB0', 12)
    
    .. _Keithley 6514 user's guide: http://www.tunl.duke.edu/documents/public/electronics/Keithley/keithley-6514-electrometer-manual.pdf
    """

    ## ENUMS ##

    class Mode(Enum):
        voltage = 'VOLT:DC'
        current = 'CURR:DC'
        resistance = 'RES'
        charge = 'CHAR'

    class TriggerMode(Enum):
        immediate = 'IMM'
        tlink = 'TLINK'

    class ArmSource(Enum):
        immediate = 'IMM'
        timer = 'TIM'
        bus = 'BUS'
        tlink = 'TLIN'
        stest = 'STES'
        pstest = 'PST'
        nstest = 'NST'
        manual = 'MAN'

    class ValidRange(Enum):
        voltage = (2, 20, 200)
        current = (20e-12, 200e-12, 2e-9, 20e-9, 200e-9, 2e-6, 20e-6, 200e-6,
                   2e-3, 20e-3)
        resistance = (2e3, 20e3, 200e3, 2e6, 20e6, 200e6, 2e9, 20e9, 200e9)
        charge = (20e-9, 200e-9, 2e-6, 20e-6)

    ## CONSTANTS ##

    _MODE_UNITS = {
        Mode.voltage: pq.volt,
        Mode.current: pq.amp,
        Mode.resistance: pq.ohm,
        Mode.charge: pq.coulomb
    }

    ## PRIVATE METHODS ##

    def _get_auto_range(self, mode):
        out = self.query('{}:RANGE:AUTO?'.format(mode.value))
        return out.strip() == '1'

    def _set_auto_range(self, mode, value):
        self.sendcmd('{}:RANGE:AUTO {}'.format(mode.value,
                                               '1' if value else '0'))

    def _get_range(self, mode):
        out = self.query('{}:RANGE:UPPER?'.format(mode.value)).strip()
        return float(out) * self._MODE_UNITS[mode]

    def _set_range(self, mode, value):
        val = value.rescale(self._MODE_UNITS[mode]).item()
        if val not in self._valid_range(mode):
            raise ValueError(
                'Unexpected range limit for currently selected mode.')
        self.sendcmd('{}:RANGE:UPPER {:e}'.format(mode.value, val))

    def _valid_range(self, mode):
        if mode == self.Mode.voltage:
            vrange = self.ValidRange.voltage
        elif mode == self.Mode.current:
            vrange = self.ValidRange.current
        elif mode == self.Mode.resistance:
            vrange = self.ValidRange.resistance
        elif mode == self.Mode.charge:
            vrange = self.ValidRange.charge
        else:
            raise ValueError('Invalid mode.')
        return vrange

    def _parse_measurement(self, ascii):
        # TODO: don't assume ASCII data format
        vals = map(float, ascii.split(','))
        reading = vals[0] * self.unit
        timestamp = vals[1]
        status = vals[2]
        return reading, timestamp, status

    ## PROPERTIES ##

    # The mode values have quotes around them for some annoying reason.
    mode = enum_property(
        'FUNCTION',
        Mode,
        doc='Gets/sets the measurement mode of the Keithley 6514.',
        input_decoration=lambda val: val[1:-1],
        output_decoration=lambda val: '"{}"'.format(val))

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

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

    zero_check = bool_property(
        'SYST:ZCH', 'ON', 'OFF',
        'Gets/sets the zero checking status of the Keithley 6514.')
    zero_correct = bool_property(
        'SYST:ZCOR', 'ON', 'OFF',
        'Gets/sets the zero correcting status of the Keithley 6514.')

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

    @property
    def auto_range(self):
        """
        Gets/sets the auto range setting
               
        :type: `Keithley6514.TriggerMode`
        """
        return self._get_auto_range(self.mode)

    @auto_range.setter
    def auto_range(self, newval):
        self._set_auto_range(self.mode, newval)

    @property
    def input_range(self):
        """
        Gets/sets the upper limit of the current range.
               
        :type: `Keithley6514.TriggerMode`
        """
        return self._get_range(self.mode)

    @input_range.setter
    def input_range(self, newval):
        self._set_range(self.mode, newval)

    ## 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)
        '''
        # TODO: figure out what to do with the status info
        raw = self.query('FETC?')
        reading, timestamp, status = self._parse_measurement(raw)
        return reading, timestamp

    def read(self):
        '''
        Trigger and acquire readings using the current mode.
        Returns a tuple of the form (reading, timestamp)
        '''
        # TODO: figure out what to do with the status info
        raw = self.query('READ?')
        reading, timestamp, status = self._parse_measurement(raw)
        return reading, timestamp
Esempio n. 29
0
class SC10(Instrument):
    """
    The SC10 is a shutter controller, to be used with the Thorlabs SH05 and SH1.
    The user manual can be found here:
    http://www.thorlabs.com/thorcat/8600/SC10-Manual.pdf
    """
    def __init__(self, filelike):
        super(SC10, 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 SC10
        """
        manual = 1
        auto = 2
        single = 3
        repeat = 4
        external = 5

    # PROPERTIES #

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

        :return: Name and verison number of the device
        :rtype: `str`
        """
        return self.query("id?")

    enable = bool_property("ens",
                           inst_true="1",
                           inst_false="0",
                           set_fmt="{}={}",
                           doc="""
        Gets/sets the shutter enable status, False for disabled, True if
        enabled

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

        :rtype: `bool`
        """)

    repeat = int_property("rep",
                          valid_set=range(1, 100),
                          set_fmt="{}={}",
                          doc="""
        Gets/sets the repeat count for repeat mode. Valid range is [1,99]
        inclusive.

        :type: `int`
        """)

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

        :rtype: `SC10.Mode`
        """)

    trigger = int_property("trig",
                           valid_set=range(0, 2),
                           set_fmt="{}={}",
                           doc="""
        Gets/sets the trigger source.

        0 for internal trigger, 1 for external trigger

        :type: `int`
        """)

    out_trigger = int_property("xto",
                               valid_set=range(0, 2),
                               set_fmt="{}={}",
                               doc="""
        Gets/sets the out trigger source.

        0 trigger out follows shutter output, 1 trigger out follows
        controller output

        :type: `int`
        """)

    open_time = unitful_property("open",
                                 u.ms,
                                 format_code="{:.0f}",
                                 set_fmt="{}={}",
                                 valid_range=(0, 999999),
                                 doc="""
        Gets/sets the amount of time that the shutter is open, in ms

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

    shut_time = unitful_property("shut",
                                 u.ms,
                                 format_code="{:.0f}",
                                 set_fmt="{}={}",
                                 valid_range=(0, 999999),
                                 doc="""
        Gets/sets the amount of time that the shutter is closed, in ms

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

    @property
    def baud_rate(self):
        """
        Gets/sets the instrument baud rate.

        Valid baud rates are 9600 and 115200.

        :type: `int`
        """
        response = self.query("baud?")
        return 115200 if int(response) else 9600

    @baud_rate.setter
    def baud_rate(self, newval):
        if newval != 9600 and newval != 115200:
            raise ValueError("Invalid baud rate mode")
        else:
            self.sendcmd("baud={}".format(0 if newval == 9600 else 1))

    closed = bool_property("closed",
                           inst_true="1",
                           inst_false="0",
                           readonly=True,
                           doc="""
        Gets the shutter closed status.

        `True` represents the shutter is closed, and `False` for the shutter is
        open.

        :rtype: `bool`
        """)

    interlock = bool_property("interlock",
                              inst_true="1",
                              inst_false="0",
                              readonly=True,
                              doc="""
        Gets the interlock tripped status.

        Returns `True` if the interlock is tripped, and `False` otherwise.

        :rtype: `bool`
        """)

    # Methods #

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

        Returns 1 if successful, zero 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("savp")
        return check_cmd(response)

    def save_mode(self):
        """
        Stores output trigger mode and baud rate settings in memory.

        Returns 1 if successful, zero otherwise.

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

    def restore(self):
        """
        Loads the settings from memory.

        Returns 1 if successful, zero otherwise.

        :rtype: `int`
        """
        response = self.query("resp")
        return check_cmd(response)
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