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 SCPIFunctionGenerator(FunctionGenerator, SCPIInstrument): ## CONSTANTS ## # TODO: document these. _UNIT_MNEMONICS = { FunctionGenerator.VoltageMode.peak_to_peak: "VPP", FunctionGenerator.VoltageMode.rms: "VRMS", FunctionGenerator.VoltageMode.dBm: "DBM", } _MNEMONIC_UNITS = dict( (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.iteritems()) ## FunctionGenerator CONTRACT ## def _get_amplitude_(self): """ """ units = self.query("VOLT:UNIT?").strip() return (float(self.query("VOLT?").strip()), self._MNEMONIC_UNITS[units]) def _set_amplitude_(self, magnitude, units): """ """ self.sendcmd("VOLT:UNIT {}".format(self._UNIT_MNEMONICS[units])) self.sendcmd("VOLT {}".format(magnitude)) ## PROPERTIES ## frequency = unitful_property(name="FREQ", units=pq.Hz, doc=""" Gets/sets the output frequency. :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) function = enum_property(name="FUNC", enum=lambda: self.Function, doc=""" Gets/sets the output function of the function generator :type: `SCPIFunctionGenerator.Function` """) offset = unitful_property(name="VOLT:OFFS", units=pq.volt, doc=""" Gets/sets the offset voltage of the function generator. Set value should be within correct bounds of instrument. :units: As specified (if a `~quntities.quantity.Quantity`) or assumed to be of units volts. :type: `~quantities.quantity.Quantity` with units volts. """) @property def phase(self): raise NotImplementedError @phase.setter def phase(self, newval): raise NotImplementedError
class UnitfulMock(MockInstrument): unitful_property = unitful_property('MOCK', pq.hertz, writeonly=True)
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` """)
class UnitfulMock(MockInstrument): unitful_property = unitful_property('MOCK', u.hertz, readonly=True)
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")
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)
class SCPIFunctionGenerator(FunctionGenerator, SCPIInstrument): """ This class is used for communicating with generic SCPI-compliant function generators. Example usage: >>> import instruments as ik >>> import quantities as pq >>> inst = ik.generic_scpi.SCPIFunctionGenerator.open_tcpip("192.168.1.1") >>> inst.frequency = 1 * pq.kHz """ # CONSTANTS # _UNIT_MNEMONICS = { FunctionGenerator.VoltageMode.peak_to_peak: "VPP", FunctionGenerator.VoltageMode.rms: "VRMS", FunctionGenerator.VoltageMode.dBm: "DBM", } _MNEMONIC_UNITS = dict( (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.items()) # FunctionGenerator CONTRACT # def _get_amplitude_(self): """ Gets the amplitude for a generic SCPI function generator :type: `tuple` containing `float` for value, and `FunctionGenerator.VoltageMode` for the type of measurement (eg VPP, VRMS, DBM). """ units = self.query("VOLT:UNIT?").strip() return (float(self.query("VOLT?").strip()), self._MNEMONIC_UNITS[units]) def _set_amplitude_(self, magnitude, units): """ Sets the amplitude for a generic SCPI function generator :param magnitude: Desired amplitude magnitude :type magnitude: `float` :param units: The type of voltage measurements units :type units: `FunctionGenerator.VoltageMode` """ self.sendcmd("VOLT:UNIT {}".format(self._UNIT_MNEMONICS[units])) self.sendcmd("VOLT {}".format(magnitude)) # PROPERTIES # frequency = unitful_property(command="FREQ", units=pq.Hz, doc=""" Gets/sets the output frequency. :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) function = enum_property(command="FUNC", enum=FunctionGenerator.Function, doc=""" Gets/sets the output function of the function generator :type: `SCPIFunctionGenerator.Function` """) offset = unitful_property(command="VOLT:OFFS", units=pq.volt, doc=""" Gets/sets the offset voltage of the function generator. Set value should be within correct bounds of instrument. :units: As specified (if a `~quantities.quantity.Quantity`) or assumed to be of units volts. :type: `~quantities.quantity.Quantity` with units volts. """) @property def phase(self): raise NotImplementedError @phase.setter def phase(self, newval): raise NotImplementedError
class SRS345(SCPIInstrument, FunctionGenerator): """ The SRS DS345 is a 30MHz function generator. Example usage: >>> import instruments as ik >>> import instruments.units as u >>> srs = ik.srs.SRS345.open_gpib('/dev/ttyUSB0', 1) >>> srs.frequency = 1 * u.MHz >>> print(srs.offset) >>> srs.function = srs.Function.triangle """ # FIXME: need to add OUTX 1 here, but doing so seems to cause a syntax # error on the instrument. # CONSTANTS # _UNIT_MNEMONICS = { FunctionGenerator.VoltageMode.peak_to_peak: "VP", FunctionGenerator.VoltageMode.rms: "VR", FunctionGenerator.VoltageMode.dBm: "DB", } _MNEMONIC_UNITS = dict( (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.items()) # FunctionGenerator CONTRACT # def _get_amplitude_(self): resp = self.query("AMPL?").strip() return (float(resp[:-2]), self._MNEMONIC_UNITS[resp[-2:]]) def _set_amplitude_(self, magnitude, units): self.sendcmd("AMPL {}{}".format(magnitude, self._UNIT_MNEMONICS[units])) # ENUMS ## class Function(IntEnum): """ Enum containing valid output function modes for the SRS 345 """ sinusoid = 0 square = 1 triangle = 2 ramp = 3 noise = 4 arbitrary = 5 # PROPERTIES ## frequency = unitful_property(command="FREQ", units=u.Hz, doc=""" Gets/sets the output frequency. :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) function = enum_property(command="FUNC", enum=Function, input_decoration=int, doc=""" Gets/sets the output function of the function generator. :type: `~SRS345.Function` """) offset = unitful_property(command="OFFS", units=u.volt, doc=""" Gets/sets the offset voltage for the output waveform. :units: As specified, or assumed to be :math:`\\text{V}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) phase = unitful_property(command="PHSE", units=u.degree, doc=""" Gets/sets the phase for the output waveform. :units: As specified, or assumed to be degrees (:math:`{}^{\\circ}`) otherwise. :type: `float` or `~quantities.quantity.Quantity` """)
class MC1(Instrument): """ The MC1 is a controller for the qubitekk motor controller. Used with a linear actuator to perform a HOM dip. """ def __init__(self, filelike): super(MC1, self).__init__(filelike) self.terminator = "\r" self._increment = 1 * pq.ms self._lower_limit = -300 * pq.ms self._upper_limit = 300 * pq.ms self._firmware = None self._controller = None # ENUMS # class MotorType(Enum): """ Enum for the motor types for the MC1 """ radio = "Radio" relay = "Relay" # PROPERTIES # @property def increment(self): """ Gets/sets the stepping increment value of the motor controller :units: As specified, or assumed to be of units milliseconds :type: `~quantities.Quantity` """ return self._increment @increment.setter def increment(self, newval): self._increment = assume_units(newval, pq.ms).rescale(pq.ms) @property def lower_limit(self): """ Gets/sets the stepping lower limit value of the motor controller :units: As specified, or assumed to be of units milliseconds :type: `~quantities.Quantity` """ return self._lower_limit @lower_limit.setter def lower_limit(self, newval): self._lower_limit = assume_units(newval, pq.ms).rescale(pq.ms) @property def upper_limit(self): """ Gets/sets the stepping upper limit value of the motor controller :units: As specified, or assumed to be of units milliseconds :type: `~quantities.Quantity` """ return self._upper_limit @upper_limit.setter def upper_limit(self, newval): self._upper_limit = assume_units(newval, pq.ms).rescale(pq.ms) direction = unitful_property(name="DIRE", doc=""" Get the internal direction variable, which is a function of how far the motor needs to go. :type: `~quantities.Quantity` :units: milliseconds """, units=pq.ms, readonly=True) inertia = unitful_property(name="INER", doc=""" Gets/Sets the amount of force required to overcome static inertia. Must be between 0 and 100 milliseconds. :type: `~quantities.Quantity` :units: milliseconds """, format_code='{:.0f}', units=pq.ms, valid_range=(0 * pq.ms, 100 * pq.ms), set_fmt=":{} {}") @property def internal_position(self): """ Get the internal motor state position, which is equivalent to the total number of milliseconds that voltage has been applied to the motor in the positive direction minus the number of milliseconds that voltage has been applied to the motor in the negative direction. :type: `~quantities.Quantity` :units: milliseconds """ response = int(self.query("POSI?")) * self.step_size return response metric_position = unitful_property(name="METR", doc=""" Get the estimated motor position, in millimeters. :type: `~quantities.Quantity` :units: millimeters """, units=pq.mm, readonly=True) setting = int_property(name="OUTP", doc=""" Gets/sets the output port of the optical switch. 0 means input 1 is directed to output 1, and input 2 is directed to output 2. 1 means that input 1 is directed to output 2 and input 2 is directed to output 1. :type: `int` """, valid_set=range(2), set_fmt=":{} {}") step_size = unitful_property(name="STEP", doc=""" Gets/Sets the number of milliseconds per step. Must be between 1 and 100 milliseconds. :type: `~quantities.Quantity` :units: milliseconds """, format_code='{:.0f}', units=pq.ms, valid_range=(1 * pq.ms, 100 * pq.ms), set_fmt=":{} {}") @property def firmware(self): """ Gets the firmware version :rtype: `tuple`(Major:`int`, Minor:`int`, Patch`int`) """ # the firmware is assumed not to change while the device is active # firmware is stored locally as it will be gotten often # pylint: disable=no-member if self._firmware is None: while self._firmware is None: self._firmware = self.query("FIRM?") value = self._firmware.split(".") if len(value) < 3: for _ in range(3 - len(value)): value.append(0) value = tuple(map(int, value)) self._firmware = value return self._firmware controller = enum_property('MOTO', MotorType, doc=""" Get the motor controller type. """, readonly=True) @property def move_timeout(self): """ Get the motor's timeout value, which indicates the number of milliseconds before the motor can start moving again. :type: `~quantities.Quantity` :units: milliseconds """ response = int(self.query("TIME?")) return response * self.step_size # METHODS # def is_centering(self): """ Query whether the motor is in its centering phase :return: False if not centering, True if centering :rtype: `bool` """ response = self.query("CENT?") return True if int(response) == 1 else False def center(self): """ Commands the motor to go to the center of its travel range """ self.sendcmd(":CENT") def reset(self): """ Sends the stage to the limit of one of its travel ranges """ self.sendcmd(":RESE") def move(self, new_position): """ Move to a specified location. Position is unitless and is defined as the number of motor steps. It varies between motors. :param new_position: the location :type new_position: `~quantities.Quantity` """ if self.lower_limit <= new_position <= self.upper_limit: new_position = assume_units(new_position, pq.ms).rescale(pq.ms) clock_cycles = new_position / self.step_size cmd = ":MOVE " + str(int(clock_cycles)) self.sendcmd(cmd) else: raise ValueError("Location out of range")
class SCPIMultimeter(SCPIInstrument, Multimeter): """ This class is used for communicating with generic SCPI-compliant multimeters. Example usage: >>> import instruments as ik >>> inst = ik.generic_scpi.SCPIMultimeter.open_tcpip("192.168.1.1") >>> print(inst.measure(inst.Mode.resistance)) """ # ENUMS ## class Mode(Enum): """ Enum of valid measurement modes for (most) SCPI compliant multimeters """ capacitance = "CAP" continuity = "CONT" current_ac = "CURR:AC" current_dc = "CURR:DC" diode = "DIOD" frequency = "FREQ" fourpt_resistance = "FRES" period = "PER" resistance = "RES" temperature = "TEMP" voltage_ac = "VOLT:AC" voltage_dc = "VOLT:DC" class TriggerMode(Enum): """ Valid trigger sources for most SCPI Multimeters. "Immediate": This is a continuous trigger. This means the trigger signal is always present. "External": External TTL pulse on the back of the instrument. It is active low. "Bus": Causes the instrument to trigger when a ``*TRG`` command is sent by software. This means calling the trigger() function. """ immediate = "IMM" external = "EXT" bus = "BUS" class InputRange(Enum): """ Valid device range parameters outside of directly specifying the range. """ minimum = "MIN" maximum = "MAX" default = "DEF" automatic = "AUTO" class Resolution(Enum): """ Valid measurement resolution parameters outside of directly the resolution. """ minimum = "MIN" maximum = "MAX" default = "DEF" class TriggerCount(Enum): """ Valid trigger count parameters outside of directly the value. """ minimum = "MIN" maximum = "MAX" default = "DEF" infinity = "INF" class SampleCount(Enum): """ Valid sample count parameters outside of directly the value. """ minimum = "MIN" maximum = "MAX" default = "DEF" class SampleSource(Enum): """ Valid sample source parameters. #. "immediate": The trigger delay time is inserted between successive samples. After the first measurement is completed, the instrument waits the time specified by the trigger delay and then performs the next sample. #. "timer": Successive samples start one sample interval after the START of the previous sample. """ immediate = "IMM" timer = "TIM" # PROPERTIES ## # pylint: disable=unnecessary-lambda,undefined-variable mode = enum_property( command="CONF", enum=Mode, doc=""" Gets/sets the current measurement mode for the multimeter. Example usage: >>> dmm.mode = dmm.Mode.voltage_dc :type: `~SCPIMultimeter.Mode` """, input_decoration=lambda x: SCPIMultimeter._mode_parse(x), set_fmt="{}:{}") trigger_mode = enum_property(command="TRIG:SOUR", enum=TriggerMode, doc=""" Gets/sets the SCPI Multimeter trigger mode. Example usage: >>> dmm.trigger_mode = dmm.TriggerMode.external :type: `~SCPIMultimeter.TriggerMode` """) @property def input_range(self): """ Gets/sets the device input range for the device range for the currently set multimeter mode. Example usages: >>> dmm.input_range = dmm.InputRange.automatic >>> dmm.input_range = 1 * u.millivolt :units: As appropriate for the current mode setting. :type: `~quantities.Quantity`, or `~SCPIMultimeter.InputRange` """ value = self.query('CONF?') mode = self.Mode(self._mode_parse(value)) value = value.split(" ")[1].split(",")[0] # Extract device range try: return float(value) * UNITS[mode] except ValueError: return self.InputRange(value.strip()) @input_range.setter def input_range(self, newval): current = self.query("CONF?") mode = self.Mode(self._mode_parse(current)) units = UNITS[mode] if isinstance(newval, self.InputRange): newval = newval.value else: newval = assume_units(newval, units).rescale(units).magnitude self.sendcmd("CONF:{} {}".format(mode.value, newval)) @property def resolution(self): """ Gets/sets the measurement resolution for the multimeter. When specified as a float it is assumed that the user is providing an appropriate value. Example usage: >>> dmm.resolution = 3e-06 >>> dmm.resolution = dmm.Resolution.maximum :type: `int`, `float` or `~SCPIMultimeter.Resolution` """ value = self.query('CONF?') value = value.split(" ")[1].split(",")[1] # Extract resolution try: return float(value) except ValueError: return self.Resolution(value.strip()) @resolution.setter def resolution(self, newval): current = self.query("CONF?") mode = self.Mode(self._mode_parse(current)) input_range = current.split(" ")[1].split(",")[0] if isinstance(newval, self.Resolution): newval = newval.value elif not isinstance(newval, (float, int)): raise TypeError("Resolution must be specified as an int, float, " "or SCPIMultimeter.Resolution value.") self.sendcmd("CONF:{} {},{}".format(mode.value, input_range, newval)) @property def trigger_count(self): """ Gets/sets the number of triggers that the multimeter will accept before returning to an "idle" trigger state. Note that if the sample_count propery has been changed, the number of readings taken total will be a multiplication of sample count and trigger count (see property `SCPIMulimeter.sample_count`). If specified as a `~SCPIMultimeter.TriggerCount` value, the following options apply: #. "minimum": 1 trigger #. "maximum": Maximum value as per instrument manual #. "default": Instrument default as per instrument manual #. "infinity": Continuous. Typically when the buffer is filled in this case, the older data points are overwritten. Note that when using triggered measurements, it is recommended that you disable autorange by either explicitly disabling it or specifying your desired range. :type: `int` or `~SCPIMultimeter.TriggerCount` """ value = self.query('TRIG:COUN?') try: return int(value) except ValueError: return self.TriggerCount(value.strip()) @trigger_count.setter def trigger_count(self, newval): if isinstance(newval, self.TriggerCount): newval = newval.value elif not isinstance(newval, int): raise TypeError("Trigger count must be specified as an int " "or SCPIMultimeter.TriggerCount value.") self.sendcmd("TRIG:COUN {}".format(newval)) @property def sample_count(self): """ Gets/sets the number of readings (samples) that the multimeter will take per trigger event. The time between each measurement is defined with the sample_timer property. Note that if the trigger_count propery has been changed, the number of readings taken total will be a multiplication of sample count and trigger count (see property `SCPIMulimeter.trigger_count`). If specified as a `~SCPIMultimeter.SampleCount` value, the following options apply: #. "minimum": 1 sample per trigger #. "maximum": Maximum value as per instrument manual #. "default": Instrument default as per instrument manual Note that when using triggered measurements, it is recommended that you disable autorange by either explicitly disabling it or specifying your desired range. :type: `int` or `~SCPIMultimeter.SampleCount` """ value = self.query('SAMP:COUN?') try: return int(value) except ValueError: return self.SampleCount(value.strip()) @sample_count.setter def sample_count(self, newval): if isinstance(newval, self.SampleCount): newval = newval.value elif not isinstance(newval, int): raise TypeError("Sample count must be specified as an int " "or SCPIMultimeter.SampleCount value.") self.sendcmd("SAMP:COUN {}".format(newval)) trigger_delay = unitful_property(command="TRIG:DEL", units=u.second, doc=""" Gets/sets the time delay which the multimeter will use following receiving a trigger event before starting the measurement. :units: As specified, or assumed to be of units seconds otherwise. :type: `~quantities.Quantity` """) sample_source = enum_property(command="SAMP:SOUR", enum=SampleSource, doc=""" Gets/sets the multimeter sample source. This determines whether the trigger delay or the sample timer is used to dtermine sample timing when the sample count is greater than 1. In both cases, the first sample is taken one trigger delay time period after the trigger event. After that, it depends on which mode is used. :type: `SCPIMultimeter.SampleSource` """) sample_timer = unitful_property(command="SAMP:TIM", units=u.second, doc=""" Gets/sets the sample interval when the sample counter is greater than one and when the sample source is set to timer (see `SCPIMultimeter.sample_source`). This command does not effect the delay between the trigger occuring and the start of the first sample. This trigger delay is set with the `~SCPIMultimeter.trigger_delay` property. :units: As specified, or assumed to be of units seconds otherwise. :type: `~quantities.Quantity` """) @property def relative(self): raise NotImplementedError @relative.setter def relative(self, newval): raise NotImplementedError # METHODS ## def measure(self, mode=None): """ Instruct the multimeter to perform a one time measurement. The instrument will use default parameters for the requested measurement. The measurement will immediately take place, and the results are directly sent to the instrument's output buffer. Method returns a Python quantity consisting of a numpy array with the instrument value and appropriate units. If no appropriate units exist, (for example, continuity), then return type is `float`. :param mode: Desired measurement mode. If set to `None`, will default to the current mode. :type mode: `~SCPIMultimeter.Mode` """ if mode is None: mode = self.mode if not isinstance(mode, SCPIMultimeter.Mode): raise TypeError("Mode must be specified as a SCPIMultimeter.Mode " "value, got {} instead.".format(type(mode))) # pylint: disable=no-member value = float(self.query('MEAS:{}?'.format(mode.value))) return value * UNITS[mode] # INTERNAL FUNCTIONS ## @staticmethod def _mode_parse(val): """ When given a string of the form "VOLT +1.00000000E+01,+3.00000000E-06" this function will return just the first component representing the mode the multimeter is currently in. :param str val: Input string to be parsed. :rtype: `str` """ val = val.split(" ")[0] if val == "VOLT": val = "VOLT:DC" return val
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
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 UnitfulMock(MockInstrument): a = unitful_property( 'MOCK', units=u.hertz, set_cmd='FOOBAR' )
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')
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")
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 UnitfulMock(MockInstrument): a = unitful_property('MOCK:A', pq.hertz, input_decoration=float)
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))
class UnitfulMock(MockInstrument): a = unitful_property('MOCK:A', pq.hertz, output_decoration=bool)
class TC200(Instrument): """ The TC200 is is a controller for the voltage across a heating element. It can also read in the temperature off of a thermistor and implements a PID control to keep the temperature at a set value. The user manual can be found here: http://www.thorlabs.com/thorcat/12500/TC200-Manual.pdf """ def __init__(self, filelike): super(TC200, self).__init__(filelike) self.terminator = "\r" self.prompt = "> " def _ack_expected(self, msg=""): return msg # ENUMS # class Mode(IntEnum): """ Enum containing valid output modes of the TC200. """ normal = 0 cycle = 1 class Sensor(Enum): """ Enum containing valid temperature sensor types for the TC200. """ ptc100 = "ptc100" ptc1000 = "ptc1000" th10k = "th10k" ntc10k = "ntc10k" # PROPERTIES # def name(self): """ Gets the name and version number of the device :return: the name string of the device :rtype: str """ response = self.query("*idn?") return response @property def mode(self): """ Gets/sets the output mode of the TC200 :type: `TC200.Mode` """ response = self.status response_code = (int(response) >> 1) % 2 return TC200.Mode(response_code) @mode.setter def mode(self, newval): if not isinstance(newval, TC200.Mode): raise TypeError("Mode setting must be a `TC200.Mode` value, " "got {} instead.".format(type(newval))) out_query = "mode={}".format(newval.name) # there is an issue with the TC200; it responds with a spurious # Command Error on mode=normal. Thus, the sendcmd() method cannot # be used. if newval == TC200.Mode.normal: self.prompt = "Command error CMD_ARG_RANGE_ERR\n\r> " self.sendcmd(out_query) self.prompt = "> " else: self.sendcmd(out_query) @property def enable(self): """ Gets/sets the heater enable status. If output enable is on (`True`), there is a voltage on the output. :type: `bool` """ response = self.status return True if int(response) % 2 == 1 else False @enable.setter def enable(self, newval): if not isinstance(newval, bool): raise TypeError("TC200 enable property must be specified with a " "boolean.") # the "ens" command is a toggle, we need to track two different cases, # when it should be on and it is off, and when it is off and # should be on # if no sensor is attached, the unit will respond with an error. # There is no current error handling in the way that thorlabs # responds with errors if newval and not self.enable: response1 = self._file.query("ens") while response1 != ">": response1 = self._file.read(1) self._file.read(1) elif not newval and self.enable: response1 = self._file.query("ens") while response1 != ">": response1 = self._file.read(1) self._file.read(1) @property def status(self): """ Gets the the status code of the TC200 :rtype: `int` """ _ = self._file.query(str("stat?")) response = self.read(5) return int(response.split(" ")[0]) temperature = unitful_property( "tact", units=u.degC, readonly=True, input_decoration=lambda x: x.replace(" C", "").replace( " F", "").replace(" K", ""), doc=""" Gets the actual temperature of the sensor :units: As specified (if a `~quantities.quantity.Quantity`) or assumed to be of units degrees C. :type: `~quantities.quantity.Quantity` or `int` :return: the temperature (in degrees C) :rtype: `~quantities.quantity.Quantity` """) max_temperature = unitful_property("tmax", units=u.degC, format_code="{:.1f}", set_fmt="{}={}", valid_range=(20 * u.degC, 205 * u.degC), doc=""" Gets/sets the maximum temperature :return: the maximum temperature (in deg C) :units: As specified or assumed to be degree Celsius. Returns with units degC. :rtype: `~quantities.quantity.Quantity` """) @property def temperature_set(self): """ Gets/sets the actual temperature of the sensor :units: As specified (if a `~quantities.quantity.Quantity`) or assumed to be of units degrees C. :type: `~quantities.quantity.Quantity` or `int` :return: the temperature (in degrees C) :rtype: `~quantities.quantity.Quantity` """ response = self.query("tset?").replace(" C", "").replace(" F", "").replace( " K", "") return float(response) * u.degC @temperature_set.setter def temperature_set(self, newval): # the set temperature is always in celsius newval = convert_temperature(newval, u.degC).magnitude if newval < 20.0 or newval > self.max_temperature: raise ValueError("Temperature set is out of range.") out_query = "tset={}".format(newval) self.sendcmd(out_query) @property def p(self): """ Gets/sets the p-gain. Valid numbers are [1,250]. :return: the p-gain (in nnn) :rtype: `int` """ return self.pid[0] @p.setter def p(self, newval): if newval not in range(1, 251): raise ValueError("P-value not in [1, 250]") self.sendcmd("pgain={}".format(newval)) @property def i(self): """ Gets/sets the i-gain. Valid numbers are [1,250] :return: the i-gain (in nnn) :rtype: `int` """ return self.pid[1] @i.setter def i(self, newval): if newval not in range(0, 251): raise ValueError("I-value not in [0, 250]") self.sendcmd("igain={}".format(newval)) @property def d(self): """ Gets/sets the d-gain. Valid numbers are [0, 250] :return: the d-gain (in nnn) :type: `int` """ return self.pid[2] @d.setter def d(self, newval): if newval not in range(0, 251): raise ValueError("D-value not in [0, 250]") self.sendcmd("dgain={}".format(newval)) @property def pid(self): """ Gets/sets all three PID values at the same time. See `TC200.p`, `TC200.i`, and `TC200.d` for individual restrictions. If `None` is specified then the corresponding PID value is not changed. :return: List of integers of PID values. In order [P, I, D]. :type: `list` or `tuple` :rtype: `list` """ return list(map(int, self.query("pid?").split())) @pid.setter def pid(self, newval): if not isinstance(newval, (list, tuple)): raise TypeError("Setting PID must be specified as a list or tuple") if newval[0] is not None: self.p = newval[0] if newval[1] is not None: self.i = newval[1] if newval[2] is not None: self.d = newval[2] @property def degrees(self): """ Gets/sets the units of the temperature measurement. :return: The temperature units (degC/F/K) the TC200 is measuring in :type: `~quantities.unitquantity.UnitTemperature` """ response = self.status if (response >> 4) % 2 and (response >> 5) % 2: return u.degC elif (response >> 5) % 2: return u.degK return u.degF @degrees.setter def degrees(self, newval): if newval is u.degC: self.sendcmd("unit=c") elif newval is u.degF: self.sendcmd("unit=f") elif newval is u.degK: self.sendcmd("unit=k") else: raise TypeError("Invalid temperature type") sensor = enum_property("sns", Sensor, input_decoration=lambda x: x.split(",")[0].split( "=")[1].strip().lower(), set_fmt="{}={}", doc=""" Gets/sets the current thermistor type. Used for converting resistances to temperatures. :return: The thermistor type :type: `TC200.Sensor` """) beta = int_property("beta", valid_set=range(2000, 6001), set_fmt="{}={}", doc=""" Gets/sets the beta value of the thermistor curve. Value within [2000, 6000] :return: the gain (in nnn) :type: `int` """) max_power = unitful_property("pmax", units=u.W, format_code="{:.1f}", set_fmt="{}={}", valid_range=(0.1 * u.W, 18.0 * u.W), doc=""" Gets/sets the maximum power :return: The maximum power :units: Watts (linear units) :type: `~quantities.quantity.Quantity` """)
class UnitfulMock(MockInstrument): unitful_property = unitful_property('MOCK', pq.hertz, format_code='{:f}')
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 UnitfulMock(MockInstrument): unitful_property = unitful_property('MOCK', pq.hertz)
class UnitfulMock(MockInstrument): unitful_property = unitful_property('MOCK', u.hertz, valid_range=(0, 10))
class Wavetek39A(SCPIInstrument, FunctionGenerator): """ The Wavetek 39A is a 40MS/s function generator. Arbitraty waveforms can have up to 65536 horizontal points, vertical range is -2048 to +2047 (12 bit), maximum peak-to-peak is 20V. Up to 100 waveforms, 256 KB NVRAM. Channel memory is 64 KB. Example usage: >>> import instruments as ik >>> import instruments.units as u >>> fg = ik.wavetek.Wavetek39A.open_gpib('/dev/ttyUSB0', 1) >>> fg.frequency = 1 * u.MHz >>> print(fg.offset) >>> fg.function = fg.Function.triangle """ def __init__(self, filelike): super(Wavetek39A, self).__init__(filelike) self.terminator = "" # CONSTANTS # _UNIT_MNEMONICS = { FunctionGenerator.VoltageMode.peak_to_peak: "VPP", FunctionGenerator.VoltageMode.rms: "VRMS", FunctionGenerator.VoltageMode.dBm: "DBM", } _MNEMONIC_UNITS = dict( (mnem, unit) for unit, mnem in _UNIT_MNEMONICS.items()) # FunctionGenerator CONTRACT # def _get_amplitude_(self): return ( 0.0, # amplitude is writeonly (FIXME: trigger exception instead?) self._MNEMONIC_UNITS["VPP"]) def _set_amplitude_(self, magnitude, units): self.sendcmd("AMPUNIT {}".format(self._UNIT_MNEMONICS[units])) self.sendcmd("AMPL {}".format(magnitude)) # ENUMS ## class Function(Enum): """ Enum containing valid output function modes for the Wavetek 39A """ #: sinusoidal sinusoid = "SINE" #: square square = "SQUARE" #: triangular triangle = "TRIANG" #: constant voltage dc = "DC" #: positive ramp positive_ramp = "POSRMP" #: negative ramp negative_ramp = "NEGRMP" #: cosine cosine = "COSINE" #: haversine, sin^2(x/2)=(1-cos x)/2 haversine = "HAVSIN" #: havercosine, (1+cos x)/2 havercosine = "HAVCOS" #: sinc(x)=sin(x)/x sinc = "SINC" #: pulse pulse = "PULSE" #: pulse train pulse_train = "PULSTRN" #: arbitrary waveform arbitrary = "ARB" #: sequence of up to 16 waveforms sequence = "SEQ" class ZLoad(Enum): """ Enum containing the output load settings """ #: 50 Ohm termination Z50 = "50" #: 600 Ohm termination Z600 = "600" #: Z=ininity, open circuit OPEN = "OPEN" class OutputMode(Enum): """ Enum containing the output mode settings """ #: normal (non-inverted) output normal = "NORMAL" #: inverted output (around the same offset if offset is non-zero!) invert = "INVERT" class Mode(Enum): """ Enum containing the mode settings """ #: continuous operation cont = "CONT" continuous = "CONT" #: gated gate = "GATE" gated = "GATE" #: triggered burst mode (each active edge of the trigger signal produces one #: burst of the waveform) trig = "TRIG" triggered = "TRIG" #: sweep sweep = "SWEEP" #: tone mode tone = "TONE" class SweepType(Enum): """ Enum containing the sweep type """ #: continuous operation cont = "CONT" continuous = "CONT" #: triggered sweep (front TRIG IN socket, remote command, manually with MAN TRIG key) #: Sweep is initiated on the rising edge of the trigger signal. trig = "TRIG" triggered = "TRIG" #: triggered, hold and reset triggered_hold_reset = "THLDRST" #: manual sweeping (using rotary control or cursor keys) manual = "MANUAL" class SweepDirection(Enum): """ Enum containing the sweep direction """ #: up up = "UP" #: down down = "DOWN" #: up/down updn = "UPDN" updown = "UPDN" #: down/up dnup = "DNUP" downup = "DNUP" class SweepSpacing(Enum): """ Enum containing the sweep spacing """ #: linear lin = "LIN" linear = "LIN" #: logarithmic log = "LOG" logarithmic = "LOG" class SweepManual(Enum): """ Enum containing the sweep manual parameters [???] """ #: up up = "UP" #: down down = "DOWN" class SweepManualSpeed(Enum): """ Enum containing the manual sweep step size. """ #: fast fast = "FAST" #: slow slow = "SLOW" class SweepManualWrap(Enum): """ Enum containing the manual sweep wrapping. """ #: wrap on wrapon = "WRAPON" #: wrap off wrapoff = "WRAPOFF" class SyncOutMode(Enum): """ Enum containing sync output settings """ #: automatic auto = "AUTO" #: waveform sync (sync marker, for standward waveform raising edge at 0 deg point, #: for arbitrary waveform coincident with the first point) waveform_sync = "WFMSYNC" #: position marker for arbitrary waveform, for standard waveforms short pulse at the start of cycle position_marker = "POSNMKR" #: burst sequence done (low while the waveform is active) burst_done = "BSTDONE" #: sync signal low during the last cycle of the last waveform in a sequence, high at all other times sequence_sync = "SEQSYNC" #: positive going version of the trigger signal trigger = "TRIGGER" #: goes high at the start of the sweep, goes low at the end of the sweep sweep = "SWPTRG" #: positive edge coincident with the start of the current waveform phase_lock = "PHASLOC" class TriggerInput(Enum): """ Enum containing trigger input settings """ internal = "INT" external = "EXT" manual = "MAN" class TriggerInputEdge(Enum): """ Enum containing external trigger input edge """ positive = "POS" negative = "NEG" class HoldMode(Enum): """ Enum containing the hold mode """ #: on/off are the same as pressing the MAN HOLD key on = "ON" off = "OFF" #: enable/disable enable or disable the action of the MAN HOLD key enable = "ENAB" disable = "DISAB" class Filter(Enum): """ Enum containing the output filter types """ #: automatic (most appropriate for the current waveform) auto = "AUTO" #: 10MHz elliptic elliptic10 = "EL10" #: 16MHz elliptic (sine, cosine, haversine, havercosine above 10Mhz) elliptic16 = "EL16" #: 10MHz Bessel (positive and negative ramps, arbitrary and sequence) Bessel = "BESS" #: no output filtering (square wave, pulse, pulse trains) none = "NONE" class BeepMode(Enum): """ Enum containing beep modes """ on = "ON" off = "OFF" warnings = "WARN" errors = "ERROR" # PROPERTIES ## frequency = unitful_property(command="WAVFREQ", units=u.Hz, writeonly=True, doc=""" Sets the output frequency. :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) period = unitful_property(command="WAVPER", units=u.s, writeonly=True, doc=""" Sets the output period. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) clock_frequency = unitful_property(command="CLKFREQ", units=u.Hz, writeonly=True, doc=""" Sets the arbitrary sample clock frequency. Range 0.1Hz to 40MHz. :units: As specified, or assumed to be :math:`\\text{Hz}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) clock_period = unitful_property(command="CLKPER", units=u.s, writeonly=True, doc=""" Sets the arbitrary sample clock period. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) zload = enum_property(command="ZLOAD", enum=ZLoad, writeonly=True, doc=""" Sets the output load. :type: `~Wavetek39A.ZLoad` """) offset = unitful_property(command="DCOFFS", units=u.volt, writeonly=True, doc=""" Sets the offset voltage for the output waveform. :units: As specified, or assumed to be :math:`\\text{V}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) function = enum_property(command="WAVE", enum=Function, writeonly=True, doc=""" Sets the output function of the function generator. :type: `~Wavetek39A.Function` """) pulse_period = unitful_property(command="PULSPER", units=u.s, writeonly=True, doc=""" Sets the pulse period. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) pulse_width = unitful_property(command="PULSWID", units=u.s, writeonly=True, doc=""" Sets the pulse width. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) pulse_delay = unitful_property(command="PULSDLY", units=u.s, writeonly=True, doc=""" Sets the pulse delay. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) pulse_train_length = int_property(command="PULSTRNLEN", writeonly=True, doc=""" Sets the number of pulses in the pulse-train. :units: Number. :type: `int` """) pulse_train_period = unitful_property(command="PULSTRNPER", units=u.s, writeonly=True, doc=""" Sets the pulse-train period. :units: As specified, or assumed to be :math:`\\text{s}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) pulse_train_base_line = unitful_property(command="PULSTRNBASE", units=u.V, writeonly=True, doc=""" Sets the pulse-train base line voltage. :units: As specified, or assumed to be :math:`\\text{V}` otherwise. :type: `float` or `~quantities.quantity.Quantity` """) # pulse_train_level = unitful_property( ## has two parameters! arbitrary = string_property(command="ARB", writeonly=True, bookmark_symbol='', doc=""" Select an arbitray waveform for output. :type: `str` """) arbitrary_list_ch = string_property(command="ARBLISTCH", readonly=True, bookmark_symbol='', doc=""" List of all arbitrary waveforms in the channel's memory. :type: `str` """) arbitrary_list = string_property(command="ARBLIST", readonly=True, bookmark_symbol='', doc=""" List of all arbitrary waveforms in the backup memory. :type: `str` """) def arbitrary_delete(self, cpd): """ Delete an arbitrary wavefrom from backup memory. A waveform used by a non-active sequence can be deleted but the sequence will not subsequently run properly and should be modified to exclude the deleted waveform. :type: `str` """ check_arb_name(cpd) self.sendcmd("ARBDELETE {}".format(cpd)) def arbitrary_clear(self, cpd): """ Delete an arbitrary wavefrom from channel memory. A waveform cannot be deleted from a channel’s memory if it is running on that channel. If an arb waveform sequence is running no waveforms can be deleted from that channel, whether they are used in the sequence or not. Waveforms must be deleted from the channel’s memory before they can be deleted from the back-up memory. (i.e. call arbitrary_clear before arbitrary_delete) :type: `str` """ check_arb_name(cpd) self.sendcmd("ARBCLR {}".format(cpd)) def arbitrary_create(self, cpd, nrf): """ Create a new blank arbitrary waveform. :type cpd: `str` :type nrf: `int` """ check_arb_name(cpd) self.sendcmd("ARBCREATE {},{}".format(cpd, nrf)) def _arbitrary_send_data_csv(self, cpd, csv, command): length, csvint = prepare_for_sending(cpd, csv) cmd = "{} {},{},{}".format(command, cpd, str(length), ",".join([str(i) for i in csvint])) self.sendcmd(cmd) def _arbitrary_send_data(self, cpd, csv, command): length, csvint = prepare_for_sending(cpd, csv) bin_data = struct.pack('>{}h'.format(length), *csvint) size_str = str(len(bin_data)) len_size_str = len(size_str) header = '#{}{}'.format(len_size_str, size_str) cmd = "{} {},{},{}{}".format(command, cpd, str(length), header, bin_data) self.sendcmd(cmd) def arbitrary_define_csv(self, cpd, csv): """ Define a new or existing arbitrary waveform from a list. :type cpd: `str` :type csv: `iterable` """ self._arbitrary_send_data_csv(cpd, csv, "ARBDEFCSV") def arbitrary_define(self, cpd, csv): """ Define a new or existing arbitrary waveform from a list. :type cpd: `str` :type csv: `iterable` """ self._arbitrary_send_data(cpd, csv, "ARBDEF") def arbitrary_get_data_csv(self, cpd): """ Returns the arbitrary waveform data as ASCII data. :rtype: `str` """ check_arb_name(cpd) self.query("ARBDATACSV? {}".format(cpd)) def arbitray_edit_limits(self, nrf1, nrf2): """ Define editing limits for the currently edited arbitrary waveform. :type nrf1: `int` :type nrf2: `int` """ self.sendcmd("ARBEDLMTS {},{}".format(nrf1, nrf2)) def arbitrary_data_csv(self, cpd, csv): self._arbitrary_send_data_csv(cpd, csv, "ARBDATACSV") def arbitrary_data(self, cpd, csv): self._arbitrary_send_data(cpd, csv, "ARBDATA") phase = unitful_property(command="PHASE", units=u.degree, writeonly=True, doc=""" Sets the phase for the output waveform. :units: As specified, or assumed to be degrees (:math:`{}^{\\circ}`) otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_start_frequency = unitful_property(command="SWPSTARTFRQ", units=u.Hz, writeonly=True, doc=""" Sets the sweep start frequency. Minimum is 1 mHz. :units: As specified, or assumed to be Hz otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_stop_frequency = unitful_property(command="SWPSTOPFRQ", units=u.Hz, writeonly=True, doc=""" Sets the sweep stop frequency. Maximum is 16 MHz for all waveforms, including triangle, ramp and square wave. :units: As specified, or assumed to be Hz otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_centre_frequency = unitful_property(command="SWPCENTFRQ", units=u.Hz, writeonly=True, doc=""" Sets the sweep centre frequency. :units: As specified, or assumed to be Hz otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_span = unitful_property(command="SWPSPAN", units=u.Hz, writeonly=True, doc=""" Sets the sweep frequency span. :units: As specified, or assumed to be Hz otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_time = unitful_property(command="SWPTIME", units=u.s, writeonly=True, doc=""" Sets the sweep time. 0.03s to 999s with 3-digit resolution. :units: As specified, or assumed to be s otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_type = enum_property(command="SWPTYPE", enum=SweepType, writeonly=True, doc=""" Sets the sweep type. :type: `~Wavetek39A.SweepType` """) sweep_direction = enum_property(command="SWPDIRN", enum=SweepDirection, writeonly=True, doc=""" Sets the sweep direction. :type: `~Wavetek39A.SweepDirection` """) sweep_sync = bool_property("SWPSYNC", inst_true="ON", inst_false="OFF", writeonly=True, doc=""" Sets the sweep syncs on and off. If on (default), the generator steps from the stop frequency to zero frequency and then starts the next sweep from the first point of the waveform, synchronized to the internally generated trigger signal. """) sweep_spacing = enum_property(command="SWPSPACING", enum=SweepSpacing, writeonly=True, doc=""" Sets the sweep spacing. :type: `~Wavetek39A.SweepSpacing` """) sweep_marker = unitful_property(command="SWPMARKER", units=u.Hz, writeonly=True, doc=""" Sets the sweep marker (rear panel CURSOR/MARKER OUT socket). :units: As specified, or assumed to be Hz otherwise. :type: `float` or `~quantities.quantity.Quantity` """) sweep_manual_speed = enum_property(command="SWPMANUAL", enum=SweepManualSpeed, writeonly=True, doc=""" Sets the manual step size. :type: `~Wavetek39A.SweepManualSpeed` """) sweep_manual_wrap = bool_property("SWPMANUAL", inst_true="WRAPON", inst_false="WRAPOFF", writeonly=True, doc=""" Sets the sweep wrapping on/off. """) output = bool_property("OUTPUT", inst_true="ON", inst_false="OFF", writeonly=True, doc=""" Sets the output on and off. """) output_mode = enum_property(command="OUTPUT", enum=OutputMode, writeonly=True, doc=""" Sets the output mode (normal vs. inverted). :type: `~Wavetek39A.OutputMode` """) mode = enum_property(command="MODE", enum=Mode, writeonly=True, doc=""" Sets the mode. :type: `~Wavetek39A.Mode` """) syncout = bool_property("SYNCOUT", inst_true="ON", inst_false="OFF", writeonly=True, doc=""" Sets the sync output on and off. """) syncout_mode = enum_property(command="SYNCOUT", enum=SyncOutMode, writeonly=True, doc=""" Sets the sync output mode. :type: `~Wavetek39A.SyncOut` """) trigger_input = enum_property(command="TRIGIN", enum=TriggerInput, writeonly=True, doc=""" Sets the trigger input. :type: `~Wavetek39A.TriggerInput` """) trigger_input_edge = enum_property(command="TRIGIN", enum=TriggerInputEdge, writeonly=True, doc=""" Sets the edge for external trigger input. :type: `~Wavetek39A.TriggerInputEdge` """) trigger_period = unitful_property(command="TRIGPER", units=u.s, writeonly=True, doc=""" Sets the internal trigger period. :units: As specified, or assumed to be seconds otherwise. :type: `float` or `~quantities.quantity.Quantity` """) def reset(self): """ Resets the instrument parameters to their default values. """ self.sendcmd("*RST") def force_trigger(self): """ Force a trigger """ self.sendcmd("FORCETRG") burst_count = int_property(command="BSTCNT", writeonly=True, doc=""" Sets the burst count. :units: Number of cycles. :type: `int` """) def recall(self, nrf): """ Recall the set up in store 'nrf'. 0-9. 0 are default settings. """ if not 0 <= nrf <= 9: raise RuntimeError("out of range {}".format(nrf)) self.sendcmd("*RCL {}".format(nrf)) def save(self, nrf): """ Save the set up in store 'nrf'. 1-9. """ if not 1 <= nrf <= 9: raise RuntimeError("out of range {}".format(nrf)) self.sendcmd("*SAV {}".format(nrf)) def manual_trigger(self): """ Same as pressing the MAN TRIG key. """ self.sendcmd("*TRG") holdmode = enum_property(command="HOLD", enum=HoldMode, writeonly=True, doc=""" Sets the hold mode. :type: `~Wavetek39A.HoldMode` """) filter = enum_property(command="FILTER", enum=Filter, writeonly=True, doc=""" Sets the output filter type. :type: `~Wavetek39A.Filter` """) beepmode = enum_property(command="BEEPMODE", enum=BeepMode, writeonly=True, doc=""" Sets the beep mode. :type: `~Wavetek39A.BeepMode` """) def beep(self): """ Beep once """ self.sendcmd("BEEP") def local(self): """ Returns the instrument to local operation and unlock the keyboard. """ self.sendcmd("LOCAL")