Пример #1
0
def test_bounded_unitful_property_valid_range_none(mock_unitful_property):
    bounded_unitful_property(command='MOCK',
                             units=u.Hz,
                             valid_range=(None, None))
    mock_unitful_property.assert_called_with('MOCK',
                                             u.Hz,
                                             valid_range=(None, None))
def test_bounded_unitful_property_valid_range_none(mock_unitful_property):
    bounded_unitful_property(
        command='MOCK',
        units=pq.Hz,
        valid_range=(None, None)
    )
    mock_unitful_property.assert_called_with(
        'MOCK',
        pq.Hz,
        valid_range=(None, None)
    )
Пример #3
0
def test_bounded_unitful_property_passes_kwargs(mock_unitful_property):
    bounded_unitful_property(
        command='MOCK',
        units=pq.Hz,
        derp="foobar"
    )
    mock_unitful_property.assert_called_with(
        'MOCK',
        pq.Hz,
        derp="foobar",
        valid_range=(mock.ANY, mock.ANY)
    )
Пример #4
0
 class BoundedUnitfulMock(MockInstrument):
     property, property_min, property_max = bounded_unitful_property(
         'MOCK', units=u.hertz, valid_range=(10, 9999))
Пример #5
0
 class BoundedUnitfulMock(MockInstrument):
     property, property_min, property_max = bounded_unitful_property(
         'MOCK', units=u.hertz, max_fmt_str="{} MAX?")
Пример #6
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`
        """)
Пример #7
0
class Keithley6220(SCPIInstrument, PowerSupply):

    """
    The Keithley 6220 is a single channel constant current supply.

    Because this is a constant current supply, most features that a regular
    power supply have are not present on the 6220.

    Example usage:

    >>> import quantities as pq
    >>> import instruments as ik
    >>> ccs = ik.keithley.Keithley6220.open_gpibusb("/dev/ttyUSB0", 10)
    >>> ccs.current = 10 * pq.milliamp # Sets current to 10mA
    >>> ccs.disable() # Turns off the output and sets the current to 0A
    """

    # PROPERTIES ##

    @property
    def channel(self):
        """
        For most power supplies, this would return a channel specific object.
        However, the 6220 only has a single channel, so this function simply
        returns a tuple containing itself. This is for compatibility reasons
        if a multichannel supply is replaced with the single-channel 6220.

        For example, the following commands are the same and both set the
        current to 10mA:

        >>> ccs.channel[0].current = 0.01
        >>> ccs.current = 0.01
        """
        return self,

    @property
    def voltage(self):
        """
        This property is not supported by the Keithley 6220.
        """
        raise NotImplementedError("The Keithley 6220 does not support voltage "
                                  "settings.")

    @voltage.setter
    def voltage(self, newval):
        raise NotImplementedError("The Keithley 6220 does not support voltage "
                                  "settings.")

    current, current_min, current_max = bounded_unitful_property(
        "SOUR:CURR",
        pq.amp,
        valid_range=(-105 * pq.milliamp, +105 * pq.milliamp),
        doc="""
        Gets/sets the output current of the source. Value must be between
        -105mA and +105mA.

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

    # METHODS #

    def disable(self):
        """
        Set the output current to zero and disable the output.
        """
        self.sendcmd("SOUR:CLE:IMM")
Пример #8
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`
            """)
Пример #9
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))
class Yokogawa6370(OpticalSpectrumAnalyzer):

    """
    The Yokogawa 6370 is a optical spectrum analyzer.

    Example usage:

    >>> import instruments as ik
    >>> import instruments.units as u
    >>> inst = ik.yokogawa.Yokogawa6370.open_visa('TCPIP0:192.168.0.35')
    >>> inst.start_wl = 1030e-9 * u.m
    """

    def __init__(self, *args, **kwargs):
        super(Yokogawa6370, self).__init__(*args, **kwargs)
        # Set data Format to binary
        self.sendcmd(":FORMat:DATA REAL,64")  # TODO: Find out where we want this

    # INNER CLASSES #

    class Channel(OSAChannel):

        """
        Class representing the channels on the Yokogawa 6370.

        This class inherits from `OSAChannel`.

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

        # METHODS #

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

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

    # ENUMS #

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

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

    # PROPERTIES #

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

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

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

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

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

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

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

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

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

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

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

    # METHODS #

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

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

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

        After changing the sweep mode, the device needs to be triggered before it will update.
        """
        self.sendcmd("*CLS;:init")
Пример #11
0
 class BoundedUnitfulMock(MockInstrument):
     property, property_min, property_max = bounded_unitful_property(
         'MOCK',
         units=pq.hertz
     )
Пример #12
0
 class BoundedUnitfulMock(MockInstrument):
     property, property_min, property_max = bounded_unitful_property(
         'MOCK',
         units=pq.hertz,
         valid_range=(None, None)
     )
Пример #13
0
 class BoundedUnitfulMock(MockInstrument):
     property, property_min, property_max = bounded_unitful_property(
         'MOCK',
         units=pq.hertz,
         valid_range=(10 * pq.kilohertz, 9999 * pq.kilohertz)
     )