class Inst: """Mock instrument class.""" def __init__(self): """Set up the mocker spies and send command placeholder.""" # spies self.spy_query = mocker.spy(self, 'query') self.spy_sendcmd = mocker.spy(self, 'sendcmd') # variable to set with send command self._sendcmd = None def query(self, cmd): """Return the command minus the ? which is sent along.""" return f"{cmd[:-1]}" def sendcmd(self, cmd): """Sets the command to `self._sendcmd`.""" self._sendcmd = cmd class SomeEnum(Enum): test = "enum" bool_property = bool_property("ON") # return True enum_property = enum_property("enum", SomeEnum) unitless_property = unitless_property("42") int_property = int_property("42") unitful_property = unitful_property("42", u.K) string_property = string_property("'STRING'")
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 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 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 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 UnitlessMock(MockInstrument): mock_property = unitless_property('MOCK', set_cmd='FOOBAR')
class UnitlessMock(MockInstrument): mock_property = unitless_property('MOCK', readonly=True)
class UnitlessMock(MockInstrument): mock_property = unitless_property('MOCK', format_code='{:f}')
class UnitlessMock(MockInstrument): mock_property = unitless_property('MOCK')
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