def power_unit(self, unit): if isinstance(unit, str): unit = unit.upper() unit = strict_discrete_set(unit, ('W', 'DBM')) unit = {'W': 0, 'DBM': 1}[unit] unit = strict_discrete_set(int(unit), (0, 1)) self.dll('set{}PowerUnit'.format(self.meas_type), c_int16(unit))
def __init__(self, instrument): super().__init__(instrument, command_prefix="POWer") self.unit = Instrument.control( self._cmd("UNIT?"), self._cmd("UNIT %s"), "Power Unit setting, W or dBm", validator=lambda v: strict_discrete_set(v, ("W", "dBm", "dbm")) )
def home(self, type=1): """ Drives the axis to the home position, which may be the negative hardware limit for some actuators (e.g. LTA-HS). type can take integer values from 0 to 6. """ home_type = strict_discrete_set(type, [0,1,2,3,4,5,6]) self.write("OR%d" % home_type)
def home(self, type=1): """ Drives the axis to the home position, which may be the negative hardware limit for some actuators (e.g. LTA-HS). type can take integer values from 0 to 6. """ home_type = strict_discrete_set(type, [0, 1, 2, 3, 4, 5, 6]) self.write("OR%d" % home_type)
def range(self, value): if isinstance(value, str): value = value.upper() value = strict_discrete_set( value, ('MIN', 'MAX', 'min', 'max')) else: value = strict_range(value, (self.range_min, self.range_max)) value = "%g" % value self.write(self._cmd("RANGe %s" % value))
def reference(self, value): if isinstance(value, str): value = value.upper() value = strict_discrete_set( value, ('MIN', 'MAX', 'DEF', 'DEFAULT')) else: value = strict_range( value, (self.reference_min, self.reference_max)) value = "%g" % value self.write(self._cmd("REFerence %s" % value))
def configure_frequency_array_measurement(self, n_samples, channel): """ Configure the counter for an array of measurements. :param n_samples: The number of samples :param channel: Measurment channel (A, B, C, E, INTREF) """ n_samples = truncated_range(n_samples, [1, self.MAX_BUFFER_SIZE]) channel = strict_discrete_set(channel, self.CHANNELS) channel = self.CHANNELS[channel] self.write(f":CONF:ARR:FREQ {n_samples},(@{channel})")
def __init__(self, RTO_channel, waveform_index): """Waveform specific properties of RTO channel :param RTO_channel: RTO_channel instance :type RTO_channel: `class:RTO_channel` :param waveform_index: index of waveform :type waveform_index: int """ self.RTO_channel = RTO_channel waveform_index = strict_discrete_set(waveform_index, (1, 2, 3)) self.waveform_index = waveform_index self.waveform = "waveform%d" % self.waveform_index
def input_adapter_type(self, sensor_type): if isinstance(sensor_type, str): if 'PHOTODIODE' in sensor_type.upper(): sensor_type = 1 elif 'THERMOPILE' in sensor_type.upper(): sensor_type = 2 elif 'PYROELECTRIC' in sensor_type.upper(): sensor_type = 3 else: log.warning('Sensor type {} not known.'.format(sensor_type)) return sensor_type = strict_discrete_set(sensor_type, (1, 2, 3)) self.dll('setInputAdapterType', c_int16(sensor_type))
def signal_bitmask(enable_list): """Generate bitmask (integer value) from list of booleans. :param enable_list: either 6 (LEDs only) or 12 (LEDs + AUX) elements """ if len(enable_list) not in (6, 12): raise ValueError( 'List of channels must have 6 or 12 entries.') enable_list = [strict_discrete_set(v, (True, False, 0, 1)) for v in enable_list] bitmask = 0 for i, value in enumerate(enable_list): if bool(value) is True: bitmask += 2**i return bitmask
def calibration(self, number=1, subsystem=None): """ Function to either calibrate the whole modulator, when subsystem parameter is omitted, or calibrate a subsystem of the modulator. Valid subsystem selections: "NICam, VISion, SOUNd1, SOUNd2, CODer" """ if subsystem is None: self.write("CAL:MOD%d" % (number)) else: self.write("CAL:MOD%d:%s" % (number, strict_discrete_set(subsystem, [ "NIC", "NICAM", "VIS", "VISION", "SOUN1", "SOUND1", "SOUN2", "SOUND2", "COD", "CODER" ])))
def _preset_min_max_values(self, property_name, value, default=True): """Allows to pass `'MIN'/'MAX'/'DEFAULT'` as value specification for a property. Checks if value is within range. Returns numerical value to pass to instrument. :param property_name: name of property :type property_name: str :param value: value to set :type value: str or numeric :return: numerical value """ if isinstance(value, str): allowed = ('min', 'max') if default: allowed += ('default', ) value = strict_discrete_set(value.lower(), allowed) value = getattr(self, '{}_{}'.format(property_name, value)) value = strict_range(value, (getattr(self, '{}_min'.format(property_name)), getattr(self, '{}_max'.format(property_name)))) return value
def _acquire_multi_waveform(self, setting): setting = strict_discrete_set(setting, (True, False)) if setting == self._acquire_multi_waveform: # no changes! return # delete existing waveform attributes for wfm in self.waveforms: del (wfm) if setting: # multiple waveforms waveforms = [] for i in (1, 2, 3): setattr(self, "waveform%d" % i, RTO_channel_waveform(self, i)) waveforms.append(getattr(self, "waveform%d" % i)) self.waveforms = waveforms else: # single waveform = waveform1 waveforms = [] setattr(self, "waveform1", RTO_channel_waveform(self, 1)) waveforms.append(getattr(self, "waveform1")) self.waveforms = waveforms self._multi_waveform = setting
class Agilent4024A(Instrument): """ Represents the Agilent4024A Digital sampling oscilloscope and provides a high-level for interacting with the instrument .. code-block:: python osc= TDS620B("GPIB0::...") osc.timebase_reference = 'LEFT' # set pretrigger to 10% of recorded waveform osc.acquire_mode = 'RTIMe' # the default osc.acquire_stop_after = 'SEQUENCE' # Stop recording after 1 waveform after trigger osc.data_source = 'CH1' # set data source to channel 1 #emit trigger somehow data = osc.get_binary_curve() # get curve data from channel 1 with default binary encoding """ timebase_reference = Instrument.control( ":TIMebase:REFerence?", ":TIMebase:REFerence %s", """A string control that sets the timebase reference location. Also called 'pretrigger' on other scopes. Options are 'LEFT', 1 div from left; 'CENT',5 divs from left and right; 'RIGH', 1 div from the right; and 'CUST', where the position is set by timebase_reference_loc.""", validator=strict_discrete_set(), values=['LEFT', 'RIGH', 'CENT', 'CUST']) timebase_reference_loc = Instrument.control( ":TIMebase:REFerence:LOCation?", ":TIMebase:REFerence CUST;:TIMebase:REFerence:LOCation %g", """A decimal property that explicitly sets the timebase reference location (also called pretrigger). Can take any value between 0 (trigger is left of record) to 1 (trigger is right of record). Sets timebase_reference to custom to produce expected behavior.""", validator=strict_range, values=[0, 1]) acquire_mode = Instrument.control( ":ACQuire:MODe?", ":ACQuire:MODe %s", """A string property that controls how data are acquired. Can be set to: ['RTIMe', 'ETIMe', 'SEGmented']""", validator=string_validator, values=['RTIMe', 'ETIMe', 'SEGmented']) acquire_naverages = Instrument.control( "ACQuire:NUMAVg?", "ACQuire:NUMAVg %d", """A integer property ranging from 2 to 10,000 that controls the number of averages taken if the acquire_mode is AVERAGE.""", validator=strict_range, values=[2, 10000]) acquire_num_acq = Instrument.measurement( "ACQuire:NUMACq?", """A integer property indicating the number of acquisitions that have taken place since starting acquisition.""", ) acquire_state = Instrument.control( "ACQuire:STATE?", "ACQuire:STATE %s", """A string property that starts or stops acquisitions. Equivalent to pressing RUN/STOP button on front panel. Can be set to: RUN,STOP""", validator=string_validator, values=['RUN', 'STOP']) acquire_stop_after = Instrument.control( "ACQuire:STOPAfter?", "ACQuire:STOPAfter %s", """A string property that sets when to stop taking acquisitions. Can be set to: RUNSTOP, SEQUENCE, LIMIT. RUNSTOP -> run until front panel run/stop is pressed, SEQUENCE -> stop after collecting trace following a trigger event. LIMIT-> stop after limit test condition is met.""", validator=string_validator, values=['RUNSTOP', 'SEQUENCE', 'LIMIT']) data = Instrument.control( "DATa?", "DATa: %s", """A string property that sets the data record start and stop positions through keywords. Options are 'INIT' to return to factory default, and 'SNAP' to snap to vertical cursors""", validator=string_validator, values=['INIT', 'SNAP']) data_start = Instrument.control( "DATa:STARt?", "DATa:STARt %d", """An integer proper that sets the record start point""", validator=truncated_range, values=[0, 5000]) data_stop = Instrument.control( "DATa:STOP?", "DATa:STOP %d", """An interger property that sets the data record stop""", validator=truncated_range, values=[0, 5000]) data_encoding = Instrument.control( "DATa:ENCdg?", "DATa:ENCdg %s", """A string property that sets the data encoding for transfer. Options are 'RIPBINARY', 'RPBINARY', 'SRIBINARY', 'SRPBINARY'. The 'SRIBINARY' is the default, tested, value for the defaults of pymeasure's get_binary_values""", validator=string_validator, values=['ASCII', 'RIPBINARY', 'RPBINARY', 'SRIBINARY', 'SRPBINARY']) data_source = Instrument.control( "DATa:SOUrce?", "DATa:SOUrce %s", """A string property that sets the data source for transfer. """, validator=string_validator, values=['CH1', 'CH2', 'CH3', 'CH4', 'MATH1', 'MATH2', 'MATH3']) data_width = Instrument.control( "DATa:WIDth?", "DATa:WIDth %d", """A string property that sets the data source for transfer. """, validator=strict_discrete_set, values=[1, 2]) trigger_details = Instrument.measurement( "TRIGGER?", """Outputs a semicolon-delimited list of the current trigger parameters of the scope""", ) trigger_main_details = Instrument.measurement( "TRIGGER:MAIn:EDGE?", """Outputs a semicolon-delimited list of the main trigger parameters of the scope""", ) trigger_main_edge_coupling = Instrument.control( "TRIGger:MAIn:EDGE:COUPling?", "TRIGger:MAIn:EDGE:COUPling %s", """A string property that sets the main coupling. Options are: 'AC', 'DC', 'HFRej', 'LFRej', 'NOISErej' """, validator=string_validator, values=['AC', 'DC', 'HFRej', 'LFRej', 'NOISErej']) trigger_main_edge_slope = Instrument.control( "TRIGger:MAIn:EDGE:SLOpe?", "TRIGger:MAIn:EDGE:SLOpe %s", """A string property that sets the trigger slope to look at the rising ('RISe') or falling ('FALL') edge """, validator=string_validator, values=['FALL', 'RISe']) trigger_main_edge_source = Instrument.control( "TRIGger:MAIn:EDGE:SOUrce?", "TRIGger:MAIn:EDGE:SOUrce %s", """A string property that sets the main trigger source. Options are 'CH1', 'CH2', 'CH3', 'CH4', 'LINE' """, validator=string_validator, values=['CH1', 'CH2', 'CH3', 'CH4', 'LINE']) trigger_main_holdoff_time = Instrument.control( "TRIGger:MAIn:HOLDOff:TIMe?", "TRIGger:MAIn:HOLDOff:TIMe %g", """A float property setting the holdoff time in seconds. Limits are 250 ns to 12 s. """, validator=truncated_range, values=[250e-9, 12]) trigger_main_level = Instrument.control( "TRIGger:MAIn:LEVel?", "TRIGger:MAIn:LEVel %g", """A float property that sets the main trigger level. """, validator=truncated_range, values=[-10.0, 10.0]) trigger_main_type = Instrument.control( "TRIGger:MAIn:TYPe?", "TRIGger:MAIn:TYPe %s", """A string property that sets the main trigger type. 'EDGE' is the normal, classic trigger. PULse and LOGIc are also included but untested.""", validator=string_validator, values=['EDGE', 'LOGIc', 'PULse']) horizontal_secperdiv = Instrument.control( "HORizontal:SECdiv?", "HORizontal:SECdiv %g", """A float property that sets seconds per division. Ranges from 200 ps/div to 10 s/div, range is not continuous, and I was too lazy to figure out the values. The scope picks the next smallest sec/div if you specify one that is not a allowed.""", validator=truncated_range, values=[.2e-9, 10]) horizontal_recordlength = Instrument.control( "HORizontal:RECOrdlength?", "HORizontal:RECOrdlength %d", """An integer property that sets the length of the waveform record. Values are 500,1000,2500,5000,15000 points. Longer records will extend past the end of the scope screen and take longer to transfer. There are 50 points/div""", validator=strict_discrete_set, values=[500, 1000, 2000, 2500, 5000, 15000]) CH1 = Instrument.measurement( "CH1?", """A property that returns all vertical paramters associated with CH1""", ) CH1_bandwidth = Instrument.control("CH1:BANdwidth?", "CH1:BANdwidth %s", """ String parameter that sets the channel bandwidth to 'TWEnty':20 MHz, 'TWOfifty':250 MHz, 'FULl': 500 MHz. """, validator=string_validator, values=['TWEnty', 'TWOfifty', 'FULl']) CH1_coupling = Instrument.control("CH1:COUPling?", "CH1:COUPling %s", """ String parameter that sets the channel coupling to 'AC', 'DC', 'GND'. """, validator=string_validator, values=['AC', 'DC', 'GND']) CH1_deskew = Instrument.control("CH1:DESKew?", "CH1:DESKew %g", """ Float parameter in range -25 ns to +25 ns with resolution of 1 ps. Used to compensate for cables of different lengths. Implemented for completeness, read about before using. """, validator=truncated_range, values=[-25e-9, 25e-9]) CH1_impedance = Instrument.control("CH1:IMPedance?", "CH1:IMPedance %s", """ String parameter that sets the channel impedance to 'FIFty', 'MEG'. """, validator=string_validator, values=['FIFty', 'MEG']) CH1_offset = Instrument.control("CH1:OFFSet?", "CH1:OFFSet %g", """ Float parameter that sets the channel offset. There are restrictions based on V/div """, validator=truncated_range, values=[-10, 10]) CH1_scale = Instrument.control("CH1:SCAle?", "CH1:SCAle %g", """ Float parameter that sets the channel scale (V/div), range is 100 mV to 1 mV """, validator=truncated_range, values=[1e-3, 1e-1]) CH2 = Instrument.measurement( "CH2?", """A property that returns all vertical paramters associated with CH2""", ) CH2_bandwidth = Instrument.control("CH2:BANdwidth?", "CH2:BANdwidth %s", """ String parameter that sets the channel bandwidth to 'TWEnty':20 MHz, 'TWOfifty':250 MHz, 'FULl': 500 MHz. """, validator=string_validator, values=['TWEnty', 'TWOfifty', 'FULl']) CH2_coupling = Instrument.control("CH2:COUPling?", "CH2:COUPling %s", """ String parameter that sets the channel coupling to 'AC', 'DC', 'GND'. """, validator=string_validator, values=['AC', 'DC', 'GND']) CH2_deskew = Instrument.control("CH2:DESKew?", "CH2:DESKew %g", """ Float parameter in range -25 ns to +25 ns with resolution of 1 ps. Used to compensate for cables of different lengths. Implemented for completeness, read about before using. """, validator=truncated_range, values=[-25e-9, 25e-9]) CH2_impedance = Instrument.control("CH2:IMPedance?", "CH2:IMPedance %s", """ String parameter that sets the channel impedance to 'FIFty', 'MEG'. """, validator=string_validator, values=['FIFty', 'MEG']) CH2_offset = Instrument.control("CH2:OFFSet?", "CH2:OFFSet %g", """ Float parameter that sets the channel offset. There are restrictions based on V/div """, validator=truncated_range, values=[-10, 10]) CH2_scale = Instrument.control("CH2:SCAle?", "CH2:SCAle %g", """ Float parameter that sets the channel scale (V/div), range is 100 mV to 1 mV """, validator=truncated_range, values=[1e-3, 1e-1]) def force_trigger(self): """ Forces a trigger event to occur """ self.write('TRIGGER FORCe') def get_waveform_parameters(self, source): """ Get the waveform parameters for a given waveform source. """ self.data_source = source return self.ask('WFMPre:' + source + '?') def get_binary_curve(self, source='CH1', encoding='SRIBINARY', hires=False): """ Convenience function to get the curve data from the sources available in self.data_source using binary encoding. Transfer is signed, 0 is in center. 5 divs above and below recorded (though only +-4 shown on scope) If trigger is unchanged then pulse is starts being measured at 50% of time scale, so halfway """ self.data_encoding = encoding self.data_source = source if not hires: self.data_width = 1 out = self.binary_values('CURVe?', dtype=np.int8) else: self.data_width = 2 out = self.binary_values('CURVe?', dtype=np.int16) return out def get_ascii_curve(self, source='CH1', hires=False): """ Function to get the curve data from the sources available in self.data_source using ascii encoding. """ self.data_encoding = 'ASCII' self.data_source = source if not hires: self.data_width = 1 else: self.data_width = 2 out = self.ask('CURVe?') return out class Measurement(object): SOURCE_VALUES = ['CH1', 'CH2', 'MATH'] TYPE_VALUES = [ 'FREQ', 'MEAN', 'PERI', 'PHA', 'PK2', 'CRM', 'MINI', 'MAXI', 'RIS', 'FALL', 'PWI', 'NWI' ] UNIT_VALUES = ['V', 's', 'Hz'] def __init__(self, parent, preamble="MEASU:IMM:"): self.parent = parent self.preamble = preamble @property def value(self): return self.parent.values("%sVAL?" % self.preamble) @property def source(self): return self.parent.ask("%sSOU?" % self.preamble).strip() @source.setter def source(self, value): if value in TDS620B.Measurement.SOURCE_VALUES: self.parent.write("%sSOU %s" % (self.preamble, value)) else: raise ValueError("Invalid source ('%s') provided to %s" % (self.parent, value)) @property def type(self): return self.parent.ask("%sTYP?" % self.preamble).strip() @type.setter def type(self, value): if value in TDS620B.Measurement.TYPE_VALUES: self.parent.write("%sTYP %s" % (self.preamble, value)) else: raise ValueError("Invalid type ('%s') provided to %s" % (self.parent, value)) @property def unit(self): return self.parent.ask("%sUNI?" % self.preamble).strip() @unit.setter def unit(self, value): if value in TDS620B.Measurement.UNIT_VALUES: self.parent.write("%sUNI %s" % (self.preamble, value)) else: raise ValueError("Invalid unit ('%s') provided to %s" % (self.parent, value)) def __init__(self, resourceName, **kwargs): super(TDS620B, self).__init__(resourceName, "Tektronix TDS 620B Oscilliscope", **kwargs) self.measurement = TDS620B.Measurement(self)
def trigger(self, value): trig_set = self.TRIGGERS[strict_discrete_set(value, self.TRIGGERS)] self.write(trig_set)
def resolution(self, value): resolution_string = "N" + str(strict_discrete_set(value, [3, 4, 5])) self.write(resolution_string)
def power_state(self, power): log.info('{0}: Set LED power states to {1}'.format( self.name, power)) self._set_LED_parameters( 'setLED_HeadPowerStates', power, c_bool, validator=lambda v: strict_discrete_set(v, (True, False, 0, 1)))
def __init__(self, dll, channel): super().__init__(dll) channel = strict_discrete_set(channel, [0, 1]) self.channel = channel
def __init__(self, dll, meas_type): super().__init__(dll) meas_type = strict_discrete_set( meas_type.title(), ('Energy', 'Current', 'Voltage', 'Power')) self.meas_type = meas_type
def frame_format(self, form): allowed_formats = ["mono_8", "mono_16"] strict_discrete_set(form, allowed_formats) self._frame_format = form
def data_format(self, setting): setting = strict_discrete_set( setting, ["ASCii", "ASC,0", "REAL,32", "INT,8", "INT,16"]) self.write("format:data %s" % setting) self.check_errors
def acquire_multi_waveform(self, setting): setting = strict_discrete_set(int(setting), (0, 1)) self.write("acquire:muwaveform %d" % setting) # add/remove additional waveform properties of channels for ch in self.channels: ch._acquire_multi_waveform = bool(setting)
def __init__(self, dll, sensor_type): super().__init__(dll) sensor_type = strict_discrete_set( sensor_type.title(), ('Photodiode', 'Thermopile', 'Pyrosensor')) self.sensor = sensor_type
def range(self, value): cur_mode = self.mode value = strict_discrete_set(value, self.RANGES[cur_mode]) set_range = self.RANGES[cur_mode][value] self.write(set_range)
def auto_zero_enabled(self, value): az_set = int(value) AZ_str = "Z" + str(int(strict_discrete_set(az_set, [0, 1]))) self.write(AZ_str)
def mode(self, value): mode_set = self.MODES[strict_discrete_set(value, self.MODES)] self.write(mode_set)
def test_strict_discrete_set(): assert strict_discrete_set(5, range(10)) == 5 with pytest.raises(ValueError): strict_discrete_set(5.1, range(10)) with pytest.raises(ValueError): strict_discrete_set(20, range(10))
def test_strict_discrete_set(): assert strict_discrete_set(5, range(10)) == 5 with pytest.raises(ValueError) as e_info: strict_discrete_set(5.1, range(10)) with pytest.raises(ValueError) as e_info: strict_discrete_set(20, range(10))