class Fake(FakeInstrument): x = Instrument.setting( "OUT %d", "", set_process=lambda v: int(bool(v)), dynamic=dynamic, )
class GenericInstrument(FakeInstrument): # Use truncated_range as this easily lets us test for the range boundaries fake_ctrl = Instrument.control( "", "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_setting = Instrument.setting( "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_measurement = Instrument.measurement( "", "docs", values={ 'X': 1, 'Y': 2, 'Z': 3 }, map_values=True, dynamic=True, )
class Nxds(Instrument): """ Represents the Edwards nXDS (10i) Vacuum Pump and provides a low-level interaction with the instrument. This could potentially work with Edwards pump that has a RS232 interface. This instrument is constructed to only start and stop pump. """ enable = Instrument.setting( "!C802 %d", """ Starts/stops pump with default settings.""", validator=strict_discrete_set, values=(0, 1), ) def __init__(self, resourceName, **kwargs): super(Nxds, self).__init__(resourceName, "Edwards NXDS Vacuum Pump", includeSCPI=False, **kwargs)
class ITC503(Instrument): """Represents the Oxford Intelligent Temperature Controller 503. .. code-block:: python itc = ITC503("GPIB::24") # Default channel for the ITC503 itc.control_mode = "RU" # Set the control mode to remote itc.heater_gas_mode = "AUTO" # Turn on auto heater and flow itc.auto_pid = True # Turn on auto-pid print(itc.temperature_setpoint) # Print the current set-point itc.temperature_setpoint = 300 # Change the set-point to 300 K itc.wait_for_temperature() # Wait for the temperature to stabilize print(itc.temperature_1) # Print the temperature at sensor 1 """ control_mode = Instrument.control( "X", "$C%d", """ A string property that sets the ITC in LOCAL or REMOTE and LOCKES, or UNLOCKES, the LOC/REM button. Allowed values are: LL: LOCAL & LOCKED RL: REMOTE & LOCKED LU: LOCAL & UNLOCKED RU: REMOTE & UNLOCKED. """, get_process=lambda v: int(v[5:6]), validator=strict_discrete_set, values={ "LL": 0, "RL": 1, "LU": 2, "RU": 3 }, map_values=True, ) heater_gas_mode = Instrument.control( "X", "$A%d", """ A string property that sets the heater and gas flow control to AUTO or MANUAL. Allowed values are: MANUAL: HEATER MANUAL, GAS MANUAL AM: HEATER AUTO, GAS MANUAL MA: HEATER MANUAL, GAS AUTO AUTO: HEATER AUTO, GAS AUTO. """, get_process=lambda v: int(v[3:4]), validator=strict_discrete_set, values={ "MANUAL": 0, "AM": 1, "MA": 2, "AUTO": 3 }, map_values=True, ) auto_pid = Instrument.control( "X", "$L%d", """ A boolean property that sets the Auto-PID mode on (True) or off (False). """, get_process=lambda v: int(v[12:13]), validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True, ) sweep_status = Instrument.control( "X", "$S%d", """ An integer property that sets the sweep status. Values are: 0: Sweep not running 1: Start sweep / sweeping to first set-point 2P - 1: Sweeping to set-point P 2P: Holding at set-point P. """, get_process=lambda v: int(v[7:9]), validator=strict_range, values=[0, 32]) temperature_setpoint = Instrument.control( "R0", "$T%f", """ A floating point property that controls the temperature set-point of the ITC in kelvin. """, get_process=lambda v: float(v[1:]), validator=truncated_range, values=[0, 301]) temperature_1 = Instrument.measurement( "R1", """ Reads the temperature of the sensor 1 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_2 = Instrument.measurement( "R2", """ Reads the temperature of the sensor 2 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_2 = Instrument.measurement( "R3", """ Reads the temperature of the sensor 3 in Kelvin. """, get_process=lambda v: float(v[1:]), ) temperature_error = Instrument.measurement( "R4", """ Reads the difference between the set-point and the measured temperature in Kelvin. Positive when set-point is larger than measured. """, get_process=lambda v: float(v[1:]), ) xpointer = Instrument.setting( "$x%d", """ An integer property to set pointers into tables for loading and examining values in the table. For programming the sweep table values from 1 to 16 are allowed, corresponding to the maximum number of steps. """, validator=strict_range, values=[0, 128]) ypointer = Instrument.setting( "$y%d", """ An integer property to set pointers into tables for loading and examining values in the table. For programming the sweep table the allowed values are: 1: Setpoint temperature, 2: Sweep-time to set-point, 3: Hold-time at set-point. """, validator=strict_range, values=[0, 128]) sweep_table = Instrument.control( "r", "$s%f", """ A property that sets values in the sweep table. Relies on the xpointer and ypointer to point at the location in the table that is to be set. """, get_process=lambda v: float(v[1:]), ) def __init__(self, resourceName, clear_buffer=True, **kwargs): super(ITC503, self).__init__(resourceName, "Oxford ITC503", includeSCPI=False, send_end=True, read_termination="\r", **kwargs) # Clear the buffer in order to prevent communication problems if clear_buffer: self.adapter.connection.clear() def wait_for_temperature(self, error=0.01, timeout=3600, check_interval=0.5, stability_interval=10, thermalize_interval=300, should_stop=lambda: False): """ Wait for the ITC to reach the set-point temperature. :param error: The maximum error in Kelvin under which the temperature is considered at set-point :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is set to zero, no timeout will be used. :param check_interval: The time between temperature queries to the ITC. :param stability_interval: The time over which the temperature_error is to be below error to be considered stable. :param thermalize_interval: The time to wait after stabilizing for the system to thermalize. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ number_of_intervals = int(stability_interval / check_interval) stable_intervals = 0 attempt = 0 t0 = time() while True: if abs(self.temperature_error) < error: stable_intervals += 1 else: stable_intervals = 0 attempt += 1 if stable_intervals >= number_of_intervals: break if timeout >= 0 and (time() - t0) > timeout: raise TimeoutError( "Timeout expired while waiting for the Oxford ITC305 to \ reach the set-point temperature") if should_stop(): return sleep(check_interval) if attempt == 0: return t1 = time() + thermalize_interval while time() < t1: sleep(check_interval) if should_stop(): return return def program_sweep(self, temperatures, sweep_time, hold_time, steps=None): """ Program a temperature sweep in the controller. Stops any running sweep. After programming the sweep, it can be started using OxfordITC503.sweep_status = 1. :param temperatures: An array containing the temperatures for the sweep :param sweep_time: The time (or an array of times) to sweep to a set-point in minutes (between 0 and 1339.9). :param hold_time: The time (or an array of times) to hold at a set-point in minutes (between 0 and 1339.9). :param steps: The number of steps in the sweep, if given, the temperatures, sweep_time and hold_time will be interpolated into (approximately) equal segments """ # Check if in remote control if not self.control_mode.startswith("R"): raise AttributeError("Oxford ITC503 not in remote control mode") # Stop sweep if running to be able to write the program self.sweep_status = 0 # Convert input np.ndarrays temperatures = numpy.array(temperatures, ndmin=1) sweep_time = numpy.array(sweep_time, ndmin=1) hold_time = numpy.array(hold_time, ndmin=1) # Make steps array if steps is None: steps = temperatures.size steps = numpy.linspace(1, steps, steps) # Create interpolated arrays interpolator = numpy.round( numpy.linspace(1, steps.size, temperatures.size)) temperatures = numpy.interp(steps, interpolator, temperatures) interpolator = numpy.round( numpy.linspace(1, steps.size, sweep_time.size)) sweep_time = numpy.interp(steps, interpolator, sweep_time) interpolator = numpy.round( numpy.linspace(1, steps.size, hold_time.size)) hold_time = numpy.interp(steps, interpolator, hold_time) # Pad with zeros to wipe unused steps (total 16) of the sweep program padding = 16 - temperatures.size temperatures = numpy.pad(temperatures, (0, padding), 'constant', constant_values=temperatures[-1]) sweep_time = numpy.pad(sweep_time, (0, padding), 'constant') hold_time = numpy.pad(hold_time, (0, padding), 'constant') # Setting the arrays to the controller for line, (setpoint, sweep, hold) in \ enumerate(zip(temperatures, sweep_time, hold_time), 1): self.xpointer = line self.ypointer = 1 self.sweep_table = setpoint self.ypointer = 2 self.sweep_table = sweep self.ypointer = 3 self.sweep_table = hold
class Agilent33500(Instrument): """Represents the Agilent 33500 Function/Arbitrary Waveform Generator family. Individual devices are represented by subclasses. .. code-block:: python generator = Agilent33500("GPIB::1") generator.shape = 'SIN' # Sets the output signal shape to sine generator.frequency = 1e3 # Sets the output frequency to 1 kHz generator.amplitude = 5 # Sets the output amplitude to 5 Vpp generator.output = 'on' # Enables the output generator.shape = 'ARB' # Set shape to arbitrary generator.arb_srate = 1e6 # Set sample rate to 1MSa/s generator.data_volatile_clear() # Clear volatile internal memory generator.data_arb( # Send data points of arbitrary waveform 'test', range(-10000, 10000, +20), # In this case a simple ramp data_format='DAC' # Data format is set to 'DAC' ) generator.arb_file = 'test' # Select the transmitted waveform 'test' """ id = Instrument.measurement("*IDN?", """ Reads the instrument identification """) def __init__(self, adapter, **kwargs): super().__init__( adapter, "Agilent 33500 Function/Arbitrary Waveform generator family", **kwargs) def beep(self): """ Causes a system beep. """ self.write("SYST:BEEP") shape = Instrument.control( "FUNC?", "FUNC %s", """ A string property that controls the output waveform. Can be set to: SIN<USOID>, SQU<ARE>, TRI<ANGLE>, RAMP, PULS<E>, PRBS, NOIS<E>, ARB, DC. """, validator=strict_discrete_set, values=[ "SIN", "SQU", "TRI", "RAMP", "PULS", "PRBS", "NOIS", "ARB", "DC" ], ) frequency = Instrument.control( "FREQ?", "FREQ %f", """ A floating point property that controls the frequency of the output waveform in Hz, from 1 uHz to 120 MHz (maximum range, can be lower depending on your device), depending on the specified function. Can be set. """, validator=strict_range, values=[1e-6, 120e+6], ) amplitude = Instrument.control( "VOLT?", "VOLT %f", """ A floating point property that controls the voltage amplitude of the output waveform in V, from 10e-3 V to 10 V. Depends on the output impedance. Can be set. """, validator=strict_range, values=[10e-3, 10], ) amplitude_unit = Instrument.control( "VOLT:UNIT?", "VOLT:UNIT %s", """ A string property that controls the units of the amplitude. Valid values are VPP (default), VRMS, and DBM. Can be set. """, validator=strict_discrete_set, values=["VPP", "VRMS", "DBM"], ) offset = Instrument.control( "VOLT:OFFS?", "VOLT:OFFS %f", """ A floating point property that controls the voltage offset of the output waveform in V, from 0 V to 4.995 V, depending on the set voltage amplitude (maximum offset = (Vmax - voltage) / 2). Can be set. """, validator=strict_range, values=[-4.995, +4.995], ) voltage_high = Instrument.control( "VOLT:HIGH?", "VOLT:HIGH %f", """ A floating point property that controls the upper voltage of the output waveform in V, from -4.990 V to 5 V (must be higher than low voltage by at least 1 mV). Can be set. """, validator=strict_range, values=[-4.99, 5], ) voltage_low = Instrument.control( "VOLT:LOW?", "VOLT:LOW %f", """ A floating point property that controls the lower voltage of the output waveform in V, from -5 V to 4.990 V (must be lower than high voltage by at least 1 mV). Can be set. """, validator=strict_range, values=[-5, 4.99], ) phase = Instrument.control( "PHAS?", "PHAS %f", """ A floating point property that controls the phase of the output waveform in degrees, from -360 degrees to 360 degrees. Not available for arbitrary waveforms or noise. Can be set. """, validator=strict_range, values=[-360, 360], ) square_dutycycle = Instrument.control( "FUNC:SQU:DCYC?", "FUNC:SQU:DCYC %f", """ A floating point property that controls the duty cycle of a square waveform function in percent, from 0.01% to 99.98%. The duty cycle is limited by the frequency and the minimal pulse width of 16 ns. See manual for more details. Can be set. """, validator=strict_range, values=[0.01, 99.98], ) ramp_symmetry = Instrument.control( "FUNC:RAMP:SYMM?", "FUNC:RAMP:SYMM %f", """ A floating point property that controls the symmetry percentage for the ramp waveform, from 0.0% to 100.0% Can be set. """, validator=strict_range, values=[0, 100], ) pulse_period = Instrument.control( "FUNC:PULS:PER?", "FUNC:PULS:PER %e", """ A floating point property that controls the period of a pulse waveform function in seconds, ranging from 33 ns to 1e6 s. Can be set and overwrites the frequency for *all* waveforms. If the period is shorter than the pulse width + the edge time, the edge time and pulse width will be adjusted accordingly. """, validator=strict_range, values=[33e-9, 1e6], ) pulse_hold = Instrument.control( "FUNC:PULS:HOLD?", "FUNC:PULS:HOLD %s", """ A string property that controls if either the pulse width or the duty cycle is retained when changing the period or frequency of the waveform. Can be set to: WIDT<H> or DCYC<LE>. """, validator=strict_discrete_set, values=["WIDT", "WIDTH", "DCYC", "DCYCLE"], ) pulse_width = Instrument.control( "FUNC:PULS:WIDT?", "FUNC:PULS:WIDT %e", """ A floating point property that controls the width of a pulse waveform function in seconds, ranging from 16 ns to 1e6 s, within a set of restrictions depending on the period. Can be set. """, validator=strict_range, values=[16e-9, 1e6], ) pulse_dutycycle = Instrument.control( "FUNC:PULS:DCYC?", "FUNC:PULS:DCYC %f", """ A floating point property that controls the duty cycle of a pulse waveform function in percent, from 0% to 100%. Can be set. """, validator=strict_range, values=[0, 100], ) pulse_transition = Instrument.control( "FUNC:PULS:TRAN?", "FUNC:PULS:TRAN:BOTH %e", """ A floating point property that controls the edge time in seconds for both the rising and falling edges. It is defined as the time between the 10% and 90% thresholds of the edge. Valid values are between 8.4 ns to 1 µs. Can be set. """, validator=strict_range, values=[8.4e-9, 1e-6], ) output = Instrument.control( "OUTP?", "OUTP %d", """ A boolean property that turns on (True, 'on') or off (False, 'off') the output of the function generator. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, 'on': 1, 'ON': 1, False: 0, 'off': 0, 'OFF': 0 }, ) output_load = Instrument.control( "OUTP:LOAD?", "OUTP:LOAD %s", """ Sets the expected load resistance (should be the load impedance connected to the output. The output impedance is always 50 Ohm, this setting can be used to correct the displayed voltage for loads unmatched to 50 Ohm. Valid values are between 1 and 10 kOhm or INF for high impedance. No validator is used since both numeric and string inputs are accepted, thus a value outside the range will not return an error. Can be set. """, ) burst_state = Instrument.control( "BURS:STAT?", "BURS:STAT %d", """ A boolean property that controls whether the burst mode is on (True) or off (False). Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) burst_mode = Instrument.control( "BURS:MODE?", "BURS:MODE %s", """ A string property that controls the burst mode. Valid values are: TRIG<GERED>, GAT<ED>. This setting can be set. """, validator=strict_discrete_set, values=["TRIG", "TRIGGERED", "GAT", "GATED"], ) burst_period = Instrument.control( "BURS:INT:PER?", "BURS:INT:PER %e", """ A floating point property that controls the period of subsequent bursts. Has to follow the equation burst_period > (burst_ncycles / frequency) + 1 µs. Valid values are 1 µs to 8000 s. Can be set. """, validator=strict_range, values=[1e-6, 8000], ) burst_ncycles = Instrument.control( "BURS:NCYC?", "BURS:NCYC %d", """ An integer property that sets the number of cycles to be output when a burst is triggered. Valid values are 1 to 100000. This can be set. """, validator=strict_range, values=range(1, 100000), ) arb_file = Instrument.control( "FUNC:ARB?", "FUNC:ARB %s", """ A string property that selects the arbitrary signal from the volatile memory of the device. String has to match an existing arb signal in volatile memore (set by data_arb()). Can be set. """) arb_advance = Instrument.control( "FUNC:ARB:ADV?", "FUNC:ARB:ADV %s", """ A string property that selects how the device advances from data point to data point. Can be set to 'TRIG<GER>' or 'SRAT<E>' (default). """, validator=strict_discrete_set, values=["TRIG", "TRIGGER", "SRAT", "SRATE"], ) arb_filter = Instrument.control( "FUNC:ARB:FILT?", "FUNC:ARB:FILT %s", """ A string property that selects the filter setting for arbitrary signals. Can be set to 'NORM<AL>', 'STEP' and 'OFF'. """, validator=strict_discrete_set, values=["NORM", "NORMAL", "STEP", "OFF"], ) # TODO: This implementation is currently not working. Do not know why. # arb_period = Instrument.control( # "FUNC:ARB:PER?", "FUNC:ARB:PER %e", # """ A floating point property that controls the period of the arbitrary signal. # Limited by number of signal points. Check for instrument errors when setting # this property. Can be set. """, # validator=strict_range, # values=[33e-9, 1e6], # ) # # arb_frequency = Instrument.control( # "FUNC:ARB:FREQ?", "FUNC:ARB:FREQ %f", # """ A floating point property that controls the frequency of the arbitrary signal. # Limited by number of signal points. Check for instrument # errors when setting this property. Can be set. """, # validator=strict_range, # values=[1e-6, 30e+6], # ) # # arb_npoints = Instrument.measurement( # "FUNC:ARB:POIN?", # """ Returns the number of points in the currently selected arbitrary trace. """ # ) # # arb_voltage = Instrument.control( # "FUNC:ARB:PTP?", "FUNC:ARB:PTP %f", # """ An floating point property that sets the peak-to-peak voltage for the # currently selected arbitrary signal. Valid values are 1 mV to 10 V. This can be # set. """, # validator=strict_range, # values=[0.001, 10], # ) arb_srate = Instrument.control( "FUNC:ARB:SRAT?", "FUNC:ARB:SRAT %f", """ An floating point property that sets the sample rate of the currently selected arbitrary signal. Valid values are 1 µSa/s to 250 MSa/s (maximum range, can be lower depending on your device). This can be set. """, validator=strict_range, values=[1e-6, 250e6], ) def data_volatile_clear(self): """ Clear all arbitrary signals from the volatile memory. This should be done if the same name is used continuously to load different arbitrary signals into the memory, since an error will occur if a trace is loaded which already exists in the memory. """ self.write("DATA:VOL:CLE") def data_arb(self, arb_name, data_points, data_format='DAC'): """ Uploads an arbitrary trace into the volatile memory of the device. The data_points can be given as comma separated 16 bit DAC values (ranging from -32767 to +32767), as comma separated floating point values (ranging from -1.0 to +1.0) or as a binary data stream. Check the manual for more information. The storage depends on the device type and ranges from 8 Sa to 16 MSa (maximum). TODO: *Binary is not yet implemented* :param arb_name: The name of the trace in the volatile memory. This is used to access the trace. :param data_points: Individual points of the trace. The format depends on the format parameter. format = 'DAC' (default): Accepts list of integer values ranging from -32767 to +32767. Minimum of 8 a maximum of 65536 points. format = 'float': Accepts list of floating point values ranging from -1.0 to +1.0. Minimum of 8 a maximum of 65536 points. format = 'binary': Accepts a binary stream of 8 bit data. :param data_format: Defines the format of data_points. Can be 'DAC' (default), 'float' or 'binary'. See documentation on parameter data_points above. """ if data_format == 'DAC': separator = ', ' data_points_str = [str(item) for item in data_points ] # Turn list entries into strings data_string = separator.join( data_points_str) # Join strings with separator print(f"DATA:ARB:DAC {arb_name}, {data_string}") self.write(f"DATA:ARB:DAC {arb_name}, {data_string}") return elif data_format == 'float': separator = ', ' data_points_str = [str(item) for item in data_points ] # Turn list entries into strings data_string = separator.join( data_points_str) # Join strings with separator print(f"DATA:ARB {arb_name}, {data_string}") self.write(f"DATA:ARB {arb_name}, {data_string}") return elif data_format == 'binary': raise NotImplementedError( 'The binary format has not yet been implemented. Use "DAC" or "float" instead.' ) else: raise ValueError( 'Undefined format keyword was used. Valid entries are "DAC", "float" and "binary"' ) display = Instrument.setting( "DISP:TEXT '%s'", """ A string property which is displayed on the front panel of the device. Can be set. """, ) def clear_display(self): """ Removes a text message from the display. """ self.write("DISP:TEXT:CLE") def trigger(self): """ Send a trigger signal to the function generator. """ self.write("*TRG;*WAI") def wait_for_trigger(self, timeout=3600, should_stop=lambda: False): """ Wait until the triggering has finished or timeout is reached. :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is set to zero, no timeout will be used. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ self.write("*OPC?") t0 = time() while True: try: ready = bool(self.read()) except VisaIOError: ready = False if ready: return if timeout != 0 and time() - t0 > timeout: raise TimeoutError( "Timeout expired while waiting for the Agilent 33220A" + " to finish the triggering.") if should_stop: return trigger_source = Instrument.control( "TRIG:SOUR?", "TRIG:SOUR %s", """ A string property that controls the trigger source. Valid values are: IMM<EDIATE> (internal), EXT<ERNAL> (rear input), BUS (via trigger command). This setting can be set. """, validator=strict_discrete_set, values=["IMM", "IMMEDIATE", "EXT", "EXTERNAL", "BUS"], ) ext_trig_out = Instrument.control( "OUTP:TRIG?", "OUTP:TRIG %d", """ A boolean property that controls whether the trigger out signal is active (True) or not (False). This signal is output from the Ext Trig connector on the rear panel in Burst and Wobbel mode. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, )
class ITC503(Instrument): """Represents the Oxford Intelligent Temperature Controller 503. .. code-block:: python itc = ITC503("GPIB::24") # Default channel for the ITC503 itc.control_mode = "RU" # Set the control mode to remote itc.heater_gas_mode = "AUTO" # Turn on auto heater and flow itc.auto_pid = True # Turn on auto-pid print(itc.temperature_setpoint) # Print the current set-point itc.temperature_setpoint = 300 # Change the set-point to 300 K itc.wait_for_temperature() # Wait for the temperature to stabilize print(itc.temperature_1) # Print the temperature at sensor 1 """ def __init__(self, adapter, name="Oxford ITC503", clear_buffer=True, min_temperature=0, max_temperature=1677.7, **kwargs): if isinstance(adapter, (int, str)): kwargs.setdefault('read_termination', '\r') kwargs.setdefault('send_end', True) adapter = OxfordInstrumentsAdapter( adapter, asrl={ 'baud_rate': 9600, 'data_bits': 8, 'parity': 0, 'stop_bits': 20, }, preprocess_reply=lambda v: v[1:], **kwargs, ) super().__init__( adapter=adapter, name=name, includeSCPI=False, ) # Clear the buffer in order to prevent communication problems if clear_buffer: self.adapter.connection.clear() self.temperature_setpoint_values = [min_temperature, max_temperature] class FLOW_CONTROL_STATUS(IntFlag): """ IntFlag class for decoding the flow control status. Contains the following flags: === ====================== ============================================== bit flag meaning === ====================== ============================================== 4 HEATER_ERROR_SIGN Sign of heater-error; True means negative 3 TEMPERATURE_ERROR_SIGN Sign of temperature-error; True means negative 2 SLOW_VALVE_ACTION Slow valve action occurring 1 COOLDOWN_TERMINATION Cooldown-termination occurring 0 FAST_COOLDOWN Fast-cooldown occurring === ====================== ============================================== """ HEATER_ERROR_SIGN = 16 TEMPERATURE_ERROR_SIGN = 8 SLOW_VALVE_ACTION = 4 COOLDOWN_TERMINATION = 2 FAST_COOLDOWN = 1 version = Instrument.measurement( "V", """ A string property that returns the version of the IPS. """, preprocess_reply=lambda v: v, ) control_mode = Instrument.control( "X", "C%d", """ A string property that sets the ITC in `local` or `remote` and `locked` or `unlocked`, locking the LOC/REM button. Allowed values are: ===== ================= value state ===== ================= LL local & locked RL remote & locked LU local & unlocked RU remote & unlocked ===== ================= """, preprocess_reply=lambda v: v[5:6], cast=int, validator=strict_discrete_set, values={ "LL": 0, "RL": 1, "LU": 2, "RU": 3 }, map_values=True, ) heater_gas_mode = Instrument.control( "X", "A%d", """ A string property that sets the heater and gas flow control to `auto` or `manual`. Allowed values are: ====== ======================= value state ====== ======================= MANUAL heater & gas manual AM heater auto, gas manual MA heater manual, gas auto AUTO heater & gas auto ====== ======================= """, preprocess_reply=lambda v: v[3:4], cast=int, validator=strict_discrete_set, values={ "MANUAL": 0, "AM": 1, "MA": 2, "AUTO": 3 }, map_values=True, ) heater = Instrument.control( "R5", "O%f", """ A floating point property that represents the heater output power as a percentage of the maximum voltage. Can be set if the heater is in manual mode. Valid values are in range 0 [off] to 99.9 [%]. """, validator=truncated_range, values=[0, 99.9]) heater_voltage = Instrument.measurement( "R6", """ A floating point property that represents the heater output power in volts. For controlling the heater, use the :class:`ITC503.heater` property. """, ) gasflow = Instrument.control( "R7", "G%f", """ A floating point property that controls gas flow when in manual mode. The value is expressed as a percentage of the maximum gas flow. Valid values are in range 0 [off] to 99.9 [%]. """, validator=truncated_range, values=[0, 99.9]) proportional_band = Instrument.control( "R8", "P%f", """ A floating point property that controls the proportional band for the PID controller in Kelvin. Can be set if the PID controller is in manual mode. Valid values are 0 [K] to 1677.7 [K]. """, validator=truncated_range, values=[0, 1677.7]) integral_action_time = Instrument.control( "R9", "I%f", """ A floating point property that controls the integral action time for the PID controller in minutes. Can be set if the PID controller is in manual mode. Valid values are 0 [min.] to 140 [min.]. """, validator=truncated_range, values=[0, 140]) derivative_action_time = Instrument.control( "R10", "D%f", """ A floating point property that controls the derivative action time for the PID controller in minutes. Can be set if the PID controller is in manual mode. Valid values are 0 [min.] to 273 [min.]. """, validator=truncated_range, values=[0, 273]) auto_pid = Instrument.control( "X", "L%d", """ A boolean property that sets the Auto-PID mode on (True) or off (False). """, preprocess_reply=lambda v: v[12:13], cast=int, validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True, ) sweep_status = Instrument.control( "X", "S%d", """ An integer property that sets the sweep status. Values are: ========= ========================================= value meaning ========= ========================================= 0 Sweep not running 1 Start sweep / sweeping to first set-point 2P - 1 Sweeping to set-point P 2P Holding at set-point P ========= ========================================= """, preprocess_reply=lambda v: v[7:9], cast=int, validator=strict_range, values=[0, 32]) temperature_setpoint = Instrument.control( "R0", "T%f", """ A floating point property that controls the temperature set-point of the ITC in kelvin. """, validator=truncated_range, values=[ 0, 1677.7 ], # Kelvin, 0 - 1677.7K is the maximum range of the instrument dynamic=True, ) temperature_1 = Instrument.measurement( "R1", """ Reads the temperature of the sensor 1 in Kelvin. """, ) temperature_2 = Instrument.measurement( "R2", """ Reads the temperature of the sensor 2 in Kelvin. """, ) temperature_3 = Instrument.measurement( "R3", """ Reads the temperature of the sensor 3 in Kelvin. """, ) temperature_error = Instrument.measurement( "R4", """ Reads the difference between the set-point and the measured temperature in Kelvin. Positive when set-point is larger than measured. """, ) front_panel_display = Instrument.setting( "F%d", """ A string property that controls what value is displayed on the front panel of the ITC. Valid values are: 'temperature setpoint', 'temperature 1', 'temperature 2', 'temperature 3', 'temperature error', 'heater', 'heater voltage', 'gasflow', 'proportional band', 'integral action time', 'derivative action time', 'channel 1 freq/4', 'channel 2 freq/4', 'channel 3 freq/4'. """, validator=strict_discrete_set, map=True, values={ "temperature setpoint": 0, "temperature 1": 1, "temperature 2": 2, "temperature 3": 3, "temperature error": 4, "heater": 5, "heater voltage": 6, "gasflow": 7, "proportional band": 8, "integral action time": 9, "derivative action time": 10, "channel 1 freq/4": 11, "channel 2 freq/4": 12, "channel 3 freq/4": 13, }, ) x_pointer = Instrument.setting( "x%d", """ An integer property to set pointers into tables for loading and examining values in the table. The significance and valid values for the pointer depends on what property is to be read or set. """, validator=strict_range, values=[0, 128]) y_pointer = Instrument.setting( "y%d", """ An integer property to set pointers into tables for loading and examining values in the table. The significance and valid values for the pointer depends on what property is to be read or set. """, validator=strict_range, values=[0, 128]) pointer = Instrument.setting( "$x%d\r$y%d", """ A tuple property to set pointers into tables for loading and examining values in the table, of format (x, y). The significance and valid values for the pointer depends on what property is to be read or set. The value for x and y can be in the range 0 to 128. """, validator=pointer_validator, values=[0, 128]) sweep_table = Instrument.control( "r", "s%f", """ A property that controls values in the sweep table. Relies on :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or :class:`ITC503.pointer`) to point at the location in the table that is to be set or read. The x-pointer selects the step of the sweep (1 to 16); the y-pointer selects the parameter: ========= ======================= y-pointer parameter ========= ======================= 1 set-point temperature 2 sweep-time to set-point 3 hold-time at set-point ========= ======================= """, ) auto_pid_table = Instrument.control( "q", "p%f", """ A property that controls values in the auto-pid table. Relies on :class:`ITC503.x_pointer` and :class:`ITC503.y_pointer` (or :class:`ITC503.pointer`) to point at the location in the table that is to be set or read. The x-pointer selects the table entry (1 to 16); the y-pointer selects the parameter: ========= ======================= y-pointer parameter ========= ======================= 1 upper temperature limit 2 proportional band 3 integral action time 4 derivative action time ========= ======================= """, ) target_voltage_table = Instrument.control( "t", "v%f", """ A property that controls values in the target heater voltage table. Relies on the :class:`ITC503.x_pointer` to select the entry in the table that is to be set or read (1 to 64). """, ) gasflow_configuration_parameter = Instrument.control( "d", "c%f", """ A property that controls the gas flow configuration parameters. Relies on the :class:`ITC503.x_pointer` to select which parameter is set or read: ========= ===================================== x-pointer parameter ========= ===================================== 1 valve gearing 2 target table & features configuration 3 gas flow scaling 4 temperature error sensitivity 5 heater voltage error sensitivity 6 minimum gas valve in auto ========= ===================================== """, ) gasflow_control_status = Instrument.measurement( "m", """ A property that reads the gas-flow control status. Returns the status in the form of a :class:`ITC503.FLOW_CONTROL_STATUS` IntFlag. """, cast=int, get_process=lambda v: ITC503.FLOW_CONTROL_STATUS(v), ) target_voltage = Instrument.measurement( "n", """ A float property that reads the current heater target voltage with which the actual heater voltage is being compared. Only valid if gas-flow in auto mode. """, ) valve_scaling = Instrument.measurement( "o", """ A float property that reads the valve scaling parameter. Only valid if gas-flow in auto mode. """, ) def wait_for_temperature( self, error=0.01, timeout=3600, check_interval=0.5, stability_interval=10, thermalize_interval=300, should_stop=lambda: False, ): """ Wait for the ITC to reach the set-point temperature. :param error: The maximum error in Kelvin under which the temperature is considered at set-point :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is None, no timeout will be used. :param check_interval: The time between temperature queries to the ITC. :param stability_interval: The time over which the temperature_error is to be below error to be considered stable. :param thermalize_interval: The time to wait after stabilizing for the system to thermalize. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ number_of_intervals = int(stability_interval / check_interval) stable_intervals = 0 attempt = 0 t0 = time() while True: temp_error = self.temperature_error if abs(temp_error) < error: stable_intervals += 1 else: stable_intervals = 0 attempt += 1 if stable_intervals >= number_of_intervals: break if timeout is not None and (time() - t0) > timeout: raise TimeoutError( "Timeout expired while waiting for the Oxford ITC305 to " "reach the set-point temperature") if should_stop(): return sleep(check_interval) if attempt == 0: return t1 = time() + thermalize_interval while time() < t1: sleep(check_interval) if should_stop(): return return def program_sweep(self, temperatures, sweep_time, hold_time, steps=None): """ Program a temperature sweep in the controller. Stops any running sweep. After programming the sweep, it can be started using OxfordITC503.sweep_status = 1. :param temperatures: An array containing the temperatures for the sweep :param sweep_time: The time (or an array of times) to sweep to a set-point in minutes (between 0 and 1339.9). :param hold_time: The time (or an array of times) to hold at a set-point in minutes (between 0 and 1339.9). :param steps: The number of steps in the sweep, if given, the temperatures, sweep_time and hold_time will be interpolated into (approximately) equal segments """ # Check if in remote control if not self.control_mode.startswith("R"): raise AttributeError("Oxford ITC503 not in remote control mode") # Stop sweep if running to be able to write the program self.sweep_status = 0 # Convert input np.ndarrays temperatures = numpy.array(temperatures, ndmin=1) sweep_time = numpy.array(sweep_time, ndmin=1) hold_time = numpy.array(hold_time, ndmin=1) # Make steps array if steps is None: steps = temperatures.size steps = numpy.linspace(1, steps, steps) # Create interpolated arrays interpolator = numpy.round( numpy.linspace(1, steps.size, temperatures.size)) temperatures = numpy.interp(steps, interpolator, temperatures) interpolator = numpy.round( numpy.linspace(1, steps.size, sweep_time.size)) sweep_time = numpy.interp(steps, interpolator, sweep_time) interpolator = numpy.round( numpy.linspace(1, steps.size, hold_time.size)) hold_time = numpy.interp(steps, interpolator, hold_time) # Pad with zeros to wipe unused steps (total 16) of the sweep program padding = 16 - temperatures.size temperatures = numpy.pad(temperatures, (0, padding), 'constant', constant_values=temperatures[-1]) sweep_time = numpy.pad(sweep_time, (0, padding), 'constant') hold_time = numpy.pad(hold_time, (0, padding), 'constant') # Setting the arrays to the controller for line, (setpoint, sweep, hold) in \ enumerate(zip(temperatures, sweep_time, hold_time), 1): self.pointer = (line, 1) self.sweep_table = setpoint self.pointer = (line, 2) self.sweep_table = sweep self.pointer = (line, 3) self.sweep_table = hold def wipe_sweep_table(self): """ Wipe the currently programmed sweep table. """ self.write("w")
class Agilent33220A(Instrument): """Represents the Agilent 33220A Arbitrary Waveform Generator. .. code-block:: python # Default channel for the Agilent 33220A wfg = Agilent33220A("GPIB::10") wfg.shape = "SINUSOID" # Sets a sine waveform wfg.frequency = 4.7e3 # Sets the frequency to 4.7 kHz wfg.amplitude = 1 # Set amplitude of 1 V wfg.offset = 0 # Set the amplitude to 0 V wfg.burst = True # Enable burst mode wfg.burst_ncycles = 10e3 # A burst will consist of 10 cycles wfg.burst_mode = "TRIGGERED" # A burst will be applied on a trigger wfg.trigger_source = "BUS" # A burst will be triggered on TRG* wfg.output = True # Enable output of waveform generator wfg.trigger() # Trigger a burst wfg.wait_for_trigger() # Wait until the triggering is finished wfg.beep() # "beep" wfg.check_errors() # Get the error queue """ def __init__(self, adapter, **kwargs): super(Agilent33220A, self).__init__(adapter, "Agilent 33220A Arbitrary Waveform generator", **kwargs) shape = Instrument.control( "FUNC?", "FUNC %s", """ A string property that controls the output waveform. Can be set to: SIN<USOID>, SQU<ARE>, RAMP, PULS<E>, NOIS<E>, DC, USER. """, validator=string_validator, values=[ "SINUSOID", "SIN", "SQUARE", "SQU", "RAMP", "PULSE", "PULS", "NOISE", "NOIS", "DC", "USER" ], ) frequency = Instrument.control( "FREQ?", "FREQ %s", """ A floating point property that controls the frequency of the output waveform in Hz, from 1e-6 (1 uHz) to 20e+6 (20 MHz), depending on the specified function. Can be set. """, validator=strict_range, values=[1e-6, 5e+6], ) amplitude = Instrument.control( "VOLT?", "VOLT %f", """ A floating point property that controls the voltage amplitude of the output waveform in V, from 10e-3 V to 10 V. Can be set. """, validator=strict_range, values=[10e-3, 10], ) amplitude_unit = Instrument.control( "VOLT:UNIT?", "VOLT:UNIT %s", """ A string property that controls the units of the amplitude. Valid values are Vpp (default), Vrms, and dBm. Can be set. """, validator=string_validator, values=["VPP", "VRMS", "DBM"], ) offset = Instrument.control( "VOLT:OFFS?", "VOLT:OFFS %f", """ A floating point property that controls the voltage offset of the output waveform in V, from 0 V to 4.995 V, depending on the set voltage amplitude (maximum offset = (10 - voltage) / 2). Can be set. """, validator=strict_range, values=[-4.995, +4.995], ) voltage_high = Instrument.control( "VOLT:HIGH?", "VOLT:HIGH %f", """ A floating point property that controls the upper voltage of the output waveform in V, from -4.990 V to 5 V (must be higher than low voltage). Can be set. """, validator=strict_range, values=[-4.99, 5], ) voltage_low = Instrument.control( "VOLT:LOW?", "VOLT:LOW %f", """ A floating point property that controls the lower voltage of the output waveform in V, from -5 V to 4.990 V (must be lower than high voltage). Can be set. """, validator=strict_range, values=[-5, 4.99], ) square_dutycycle = Instrument.control( "FUNC:SQU:DCYC?", "FUNC:SQU:DCYC %f", """ A floating point property that controls the duty cycle of a square waveform function in percent. Can be set. """, validator=strict_range, values=[20, 80], ) ramp_symmetry = Instrument.control( "FUNC:RAMP:SYMM?", "FUNC:RAMP:SYMM %f", """ A floating point property that controls the symmetry percentage for the ramp waveform. Can be set. """, validator=strict_range, values=[0, 100], ) pulse_period = Instrument.control( "PULS:PER?", "PULS:PER %f", """ A floating point property that controls the period of a pulse waveform function in seconds, ranging from 200 ns to 2000 s. Can be set and overwrites the frequency for *all* waveforms. If the period is shorter than the pulse width + the edge time, the edge time and pulse width will be adjusted accordingly. """, validator=strict_range, values=[200e-9, 2e3], ) pulse_hold = Instrument.control( "FUNC:PULS:HOLD?", "FUNC:PULS:HOLD %s", """ A string property that controls if either the pulse width or the duty cycle is retained when changing the period or frequency of the waveform. Can be set to: WIDT<H> or DCYC<LE>. """, validator=string_validator, values=["WIDT", "WIDTH", "DCYC", "DCYCLE"], ) pulse_width = Instrument.control( "FUNC:PULS:WIDT?", "FUNC:PULS:WIDT %f", """ A floating point property that controls the width of a pulse waveform function in seconds, ranging from 20 ns to 2000 s, within a set of restrictions depending on the period. Can be set. """, validator=strict_range, values=[20e-9, 2e3], ) pulse_dutycycle = Instrument.control( "FUNC:PULS:DCYC?", "FUNC:PULS:DCYC %f", """ A floating point property that controls the duty cycle of a pulse waveform function in percent. Can be set. """, validator=strict_range, values=[0, 100], ) pulse_transition = Instrument.control( "FUNC:PULS:TRAN?", "FUNC:PULS:TRAN %f", """ A floating point property that controls the the edge time in seconds for both the rising and falling edges. It is defined as the time between 0.1 and 0.9 of the threshold. Valid values are between 5 ns to 100 ns. Can be set. """, validator=strict_range, values=[5e-9, 100e-9], ) output = Instrument.control( "OUTP?", "OUTP %d", """ A boolean property that turns on (True) or off (False) the output of the function generator. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) burst_state = Instrument.control( "BURS:STAT?", "BURS:STAT %d", """ A boolean property that controls whether the burst mode is on (True) or off (False). Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) burst_mode = Instrument.control( "BURS:MODE?", "BURS:MODE %s", """ A string property that controls the burst mode. Valid values are: TRIG<GERED>, GAT<ED>. This setting can be set. """, validator=string_validator, values=["TRIG", "TRIGGERED", "GAT", "GATED"], ) burst_ncycles = Instrument.control( "BURS:NCYC?", "BURS:NCYC %d", """ An integer property that sets the number of cycles to be output when a burst is triggered. Valid values are 1 to 50000. This can be set. """, validator=strict_discrete_set, values=range(1, 50001), ) def trigger(self): """ Send a trigger signal to the function generator. """ self.write("*TRG;*WAI") def wait_for_trigger(self, timeout=3600, should_stop=lambda: False): """ Wait until the triggering has finished or timeout is reached. :param timeout: The maximum time the waiting is allowed to take. If timeout is exceeded, a TimeoutError is raised. If timeout is set to zero, no timeout will be used. :param should_stop: Optional function (returning a bool) to allow the waiting to be stopped before its end. """ self.write("*OPC?") t0 = time() while True: try: ready = bool(self.read()) except VisaIOError: ready = False if ready: return if timeout != 0 and time() - t0 > timeout: raise TimeoutError( "Timeout expired while waiting for the Agilent 33220A" + " to finish the triggering.") if should_stop: return trigger_source = Instrument.control( "TRIG:SOUR?", "TRIG:SOUR %s", """ A string property that controls the trigger source. Valid values are: IMM<EDIATE> (internal), EXT<ERNAL> (rear input), BUS (via trigger command). This setting can be set. """, validator=string_validator, values=["IMM", "IMMEDIATE", "EXT", "EXTERNAL", "BUS"], ) trigger_state = Instrument.control( "OUTP:TRIG?", "OUTP:TRIG %d", """ A boolean property that controls whether the output is triggered (True) or not (False). Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) remote_local_state = Instrument.setting( "SYST:COMM:RLST %s", """ A string property that controls the remote/local state of the function generator. Valid values are: LOC<AL>, REM<OTE>, RWL<OCK>. This setting can only be set. """, validator=string_validator, values=["LOC", "LOCAL", "REM", "REMOTE", "RWL", "RWLOCK"], ) def check_errors(self): """ Read all errors from the instrument. """ errors = [] while True: err = self.values("SYST:ERR?") if int(err[0]) != 0: errmsg = "Agilent 33220A: %s: %s" % (err[0], err[1]) log.error(errmsg + '\n') errors.append(errmsg) else: break return errors beeper_state = Instrument.control( "SYST:BEEP:STAT?", "SYST:BEEP:STAT %d", """ A boolean property that controls the state of the beeper. Can be set. """, validator=strict_discrete_set, map_values=True, values={ True: 1, False: 0 }, ) def beep(self): """ Causes a system beep. """ self.write("SYST:BEEP")
class HP34401A(Instrument): """ Represents the HP 34401A instrument. """ ############# # Mappings # ############# ONOFF = ["ON", "OFF"] ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'} ################## # Configuration # ################## display = Instrument.setting( "DISP:ENABLE %s", "Instrument display (ON/OFF)", validator=strict_discrete_set, map_values=True, values=ONOFF_MAPPING ) id = Instrument.measurement( "*IDN?", """ Reads the instrument identification """ ) voltage_dc = Instrument.measurement("MEAS:VOLT:DC? DEF,DEF", "DC voltage, in Volts") voltage_ac = Instrument.measurement("MEAS:VOLT:AC? DEF,DEF", "AC voltage, in Volts") current_dc = Instrument.measurement("MEAS:CURR:DC? DEF,DEF", "DC current, in Amps") current_ac = Instrument.measurement("MEAS:CURR:AC? DEF,DEF", "AC current, in Amps") resistance = Instrument.measurement("MEAS:RES? DEF,DEF", "Resistance, in Ohms") resistance_4w = Instrument.measurement("MEAS:FRES? DEF,DEF", "Four-wires (remote sensing) resistance, in Ohms") def __init__(self, resourceName, **kwargs): super(HP34401A, self).__init__( resourceName, "HP 34401A", **kwargs ) def display_text(self, text): """ Write string on display """ self.write("DISP:TEXT '%s'" % text) def display_text_clear(self): """ Clear string from display """ self.write("DISP:TEXT:CLE") def reset(self): """ Resets the instrument and clears the queue. """ self.write("*RST;*CLS;*SRE 0;*ESE 0;:STAT:PRES;") def check_errors(self): """ Read all errors from the instrument. """ errors = [] while True: err = self.values("SYST:ERR?") if int(err[0]) != 0: errmsg = "Agilent 34401A: {0}: {1}".format(err[0], err[1]) log.error(errmsg + '\n') errors.append(errmsg) else: break return errors
class Keithley2306(Instrument): """ Represents the Keithley 2306 Dual Channel Battery/Charger Simulator. """ def __init__(self, resourceName, **kwargs): super().__init__( resourceName, "Keithley 2306", **kwargs ) self.ch1 = BatteryChannel(self, 1) self.ch2 = Channel(self, 2) self.relay1 = Relay(self, 1) self.relay2 = Relay(self, 2) self.relay3 = Relay(self, 3) self.relay4 = Relay(self, 4) display_enabled = Instrument.control( ":DISP:ENAB?", ":DISP:ENAB %d", """A boolean property that controls whether the display is enabled, takes values True or False. """, validator=strict_discrete_set, values={True: 1, False: 0}, map_values=True ) display_brightness = Instrument.control( ":DISP:BRIG?", ":DISP:BRIG %g", """A floating point property that controls the display brightness, takes values beteween 0.0 and 1.0. A blank display is 0.0, 1/4 brightness is for values less or equal to 0.25, otherwise 1/2 brightness for values less than or equal to 0.5, otherwise 3/4 brightness for values less than or equal to 0.75, otherwise full brightness. """, validator=truncated_range, values=[0, 1], ) display_channel = Instrument.control( ":DISP:CHAN?", ":DISP:CHAN %d", """An integer property that controls the display channel, takes values 1 or 2. """, validator=strict_discrete_set, values=[1, 2], ) display_text_data = Instrument.control( ":DISP:TEXT:DATA?", ":DISP:TEXT:DATA \"%s\"", """A string property that control text to be displayed, takes strings up to 32 characters. """, get_process=lambda v: v.replace('"', '') ) display_text_enabled = Instrument.control( ":DISP:TEXT:STAT?", ":DISP:TEXT:STAT %d", """A boolean property that controls whether display text is enabled, takes values True or False. """, validator=strict_discrete_set, values={True: 1, False: 0}, map_values=True, ) both_channels_enabled = Instrument.setting( ":BOTHOUT%s", """A boolean setting that controls whether both channel outputs are enabled, takes values of True or False. """, validator=strict_discrete_set, values={True: "ON", False: "OFF"}, map_values=True, ) def ch(self, channel_number): if channel_number == 1: return self.ch1 elif channel_number == 2: return self.ch2 else: raise ValueError("Invalid channel number. Must be 1 or 2.") def relay(self, relay_number): if relay_number == 1: return self.relay1 elif relay_number == 2: return self.relay2 elif relay_number == 3: return self.relay3 elif relay_number == 4: return self.relay4 else: raise ValueError("Invalid relay number. Must be 1, 2, 3, or 4")
class AnritsuMS9710C(Instrument): """Anritsu MS9710C Optical Spectrum Analyzer.""" ############# # Mappings # ############# ONOFF = ["ON", "OFF"] ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'} ###################### # Status Registers # ###################### ese2 = Instrument.control( "ESE2?", "ESE2 %d", "Extended Event Status Enable Register 2", get_process=int ) esr2 = Instrument.control( "ESR2?", "ESR2 %d", "Extended Event Status Register 2", get_process=_int_or_neg_one ) ########### # Modes # ########### measure_mode = Instrument.measurement( "MOD?", "Returns the current Measure Mode the OSA is in.", values={None: 0, "SINGLE": 1.0, "AUTO": 2.0, "POWER": 3.0}, map_values=True ) #################################### # Spectrum Parameters - Wavelength # #################################### wavelength_center = Instrument.control('CNT?', 'CNT %g', "Center Wavelength of Spectrum Scan in nm.") wavelength_span = Instrument.control('SPN?', 'SPN %g', "Wavelength Span of Spectrum Scan in nm.") wavelength_start = Instrument.control('STA?', 'STA %g', "Wavelength Start of Spectrum Scan in nm.") wavelength_stop = Instrument.control('STO?', 'STO %g', "Wavelength Stop of Spectrum Scan in nm.") wavelength_marker_value = Instrument.control( 'MKV?', 'MKV %s', "Wavelength Marker Value (wavelength or freq.?)", validator=strict_discrete_set, values=["WL", "FREQ"] ) wavelength_value_in = Instrument.control( 'WDP?', 'WDP %s', "Wavelength value in Vacuum or Air", validator=strict_discrete_set, values=["VACUUM", "AIR"] ) level_scale = Instrument.measurement( 'LVS?', "Current Level Scale", values=["LOG", "LIN"] ) level_log = Instrument.control( "LOG?", "LOG %f", "Level Log Scale (/div)", validator=truncated_range, values=[0.1, 10.0] ) level_lin = Instrument.control( "LIN?", "LIN %f", "Level Linear Scale (/div)", validator=truncated_range, values=[1e-12, 1] ) level_opt_attn = Instrument.control( "ATT?", "ATT %s", "Optical Attenuation Status (ON/OFF)", validator=strict_discrete_set, values=ONOFF ) resolution = Instrument.control( "RES?", "RES %f", "Resolution (nm)", validator=truncated_discrete_set, values=[0.05, 0.07, 0.1, 0.2, 0.5, 1.0] ) resolution_actual = Instrument.control( "ARES?", "ARES %s", "Resolution Actual (ON/OFF)", validator=strict_discrete_set, values=ONOFF, map_values=True ) resolution_vbw = Instrument.control( "VBW?", "VBW %s", "Video Bandwidth Resolution", validator=strict_discrete_set, values=["1MHz", "100kHz", "10kHz", "1kHz", "100Hz", "10Hz"] ) average_point = Instrument.control( "AVT?", "AVT %d", "Number of averages to take on each point (2-1000), or OFF", validator=truncated_range_or_off, values=[["OFF"], [2, 1000]] ) average_sweep = Instrument.control( "AVS?", "AVS %d", "Number of averages to make on a sweep (2-1000) or OFF", validator=truncated_range_or_off, values=[["OFF"], [2, 1000]] ) sampling_points = Instrument.control( "MPT?", "MPT %d", "Number of sampling points", validator=truncated_discrete_set, values=[51, 101, 251, 501, 1001, 2001, 5001], get_process=lambda v: int(v) ) ##################################### # Analysis Peak Search Parameters # ##################################### peak_search = Instrument.control( "PKS?", "PKS %s", "Peak Search Mode", validator=strict_discrete_set, values=["PEAK", "NEXT", "LAST", "LEFT", "RIGHT"] ) dip_search = Instrument.control( "DPS?", "DPS %s", "Dip Search Mode", validator=strict_discrete_set, values=["DIP", "NEXT", "LAST", "LEFT", "RIGHT"] ) analysis = Instrument.control( "ANA?", "ANA %s", "Analysis Control" ) analysis_result = Instrument.measurement( "ANAR?", "Read back anaysis result from current scan." ) ########################## # Data Memory Commands # ########################## data_memory_a_size = Instrument.measurement( 'DBA?', "Returns the number of points sampled in data memory register A." ) data_memory_b_size = Instrument.measurement( 'DBB?', "Returns the number of points sampled in data memory register B." ) data_memory_a_condition = Instrument.measurement( "DCA?", """Returns the data condition of data memory register A. Starting wavelength, and a sampling point (l1, l2, n).""" ) data_memory_b_condition = Instrument.measurement( "DCB?", """Returns the data condition of data memory register B. Starting wavelength, and a sampling point (l1, l2, n).""" ) data_memory_a_values = Instrument.measurement( "DMA?", "Reads the binary data from memory register A." ) data_memory_b_values = Instrument.measurement( "DMA?", "Reads the binary data from memory register B." ) data_memory_select = Instrument.control( "MSL?", "MSL %s", "Memory Data Select.", validator=strict_discrete_set, values=["A", "B"] ) ########################### # Trace Marker Commands # ########################### trace_marker_center = Instrument.setting( "TMC %s", "Trace Marker at Center. Set to 1 or True to initiate command", map_values=True, values={True: ''} ) trace_marker = Instrument.control( "TMK?", "TMK %f", "Sets the trace marker with a wavelength. Returns the trace wavelength and power.", get_process=_parse_trace_peak ) def __init__(self, adapter, **kwargs): """Constructor.""" self.analysis_mode = None super(AnritsuMS9710C, self).__init__(adapter, "Anritsu MS9710C Optical Spectrum Analyzer", **kwargs) @property def wavelengths(self): """Return a numpy array of the current wavelengths of scans.""" return np.linspace( self.wavelength_start, self.wavelength_stop, self.sampling_points ) def read_memory(self, slot="A"): """Read the scan saved in a memory slot.""" cond_attr = "data_memory_{}_condition".format(slot.lower()) data_attr = "data_memory_{}_values".format(slot.lower()) scan = getattr(self, cond_attr) wavelengths = np.linspace(scan[0], scan[1], int(scan[2])) power = np.fromstring(getattr(self, data_attr), sep="\r\n") return wavelengths, power def wait(self, n=3, delay=1): """Query OPC Command and waits for appropriate response.""" log.info("Wait for OPC") res = self.adapter.ask("*OPC?") n_attempts = n while(res == ''): log.debug("Empty OPC Repsonse. {} remaining".format(n_attempts)) if n_attempts == 0: break n_attempts -= 1 sleep(delay) res = self.adapter.read().strip() log.debug(res) def wait_for_sweep(self, n=20, delay=0.5): """Wait for a sweep to stop. This is performed by checking bit 1 of the ESR2. """ log.debug("Waiting for spectrum sweep") while(self.esr2 != 3 and n > 0): log.debug("Wait for sweep [{}]".format(n)) # log.debug("ESR2: {}".format(esr2)) sleep(delay) n -= 1 if n <= 0: log.warning("Sweep Timeout Occurred ({} s)".format(int(delay * n))) def single_sweep(self, **kwargs): """Perform a single sweep and wait for completion.""" log.debug("Performing a Spectrum Sweep") self.clear() self.write('SSI') self.wait_for_sweep(**kwargs) def center_at_peak(self, **kwargs): """Center the spectrum at the measured peak.""" self.write("PKC") self.wait(**kwargs) def measure_peak(self): """Measure the peak and return the trace marker.""" self.peak_search = "PEAK" return self.trace_marker
class Agilent5313xA(Instrument): """ Represents the HP/Agilent/Keysight 53131A and 53132A counter. Implemented measurements: Frequency """ ############# # Mappings # ############# ONOFF = ["ON", "OFF"] ONOFF_MAPPING = {True: 'ON', False: 'OFF', 1: 'ON', 0: 'OFF'} id = Instrument.measurement("*IDN?", """ Reads the instrument identification """) options = Instrument.measurement("*OPT?", """ Reads the installed options """) val = Instrument.measurement(":READ?", "Read current measured value.") fetch_frequency = Instrument.measurement("FETCH:FREQ?", "Read current frequency.") fetch_period = Instrument.measurement("FETCH:PERIOD?", "Read current period.") display = Instrument.setting("DISP:ENABLE %s", "Instrument display (ON/OFF)", validator=strict_discrete_set, map_values=True, values=ONOFF_MAPPING) display_menu_off = Instrument.setting( ":DISP:MENU %s", "Clear active menu item. Set to 1 or True to initiate command", map_values=True, values={True: 'OFF'}) hcopy = Instrument.control( ":HCOP:CONT?", ":HCOP:CONT %d", """ Enables or disables printing results. This property can be set (True, False).""", validator=strict_discrete_set, cast=bool, values=ONOFF_MAPPING, ) reference = Instrument.setting( ":ROSC:SOURCE %s", "Control reference oscillator. Can be set to INT / EXT", validator=strict_discrete_set, values=["INT", "EXT"]) reference_autocheck_off = Instrument.setting( ":ROSC:EXT:CHECK %s", "Disable check of external reference source. Set to 1 or True to initiate command", map_values=True, values={True: 'OFF'}) cal_interpolator_auto = Instrument.setting( ":DIAG:CAL:INT:AUTO %s", """ Disable automatic interpolater calibration. The most recent calibration values are used in the calculation of frequency. Set to (ON/OFF) """, validator=strict_discrete_set, map_values=True, values=ONOFF_MAPPING) format = Instrument.setting( ":FORM %s", "Sets a data format for transferring numeric information. ASCII or REAL", validator=strict_discrete_set, values=["ASCII", "REAL"]) measure_freq = Instrument.setting(":FREQ %d", "Set channel to measure frequency on.", validator=strict_discrete_set, values=[1, 2, 3]) cont_measurements = Instrument.control( ":INIT:CONT?", ":INIT:CONT %d", """ Sets the enable for continuously initiated measurements. This property can be set (True, False).""", validator=strict_discrete_set, cast=bool, values=ONOFF_MAPPING, ) func_frequency = Instrument.setting(":FUNC 'FREQ %d'", "Set channel to measure frequency on.", validator=strict_discrete_set, values=[1, 2, 3]) func_period = Instrument.setting(":FUNC 'PER %d'", "Set channel to measure period on.", validator=strict_discrete_set, values=[1, 2, 3]) def __init__(self, adapter, delay=0.02, **kwargs): super(Agilent5313xA, self).__init__(adapter, "HP/Agilent/Keysight 5313xA Counter", **kwargs) self.ch1 = Channel(self, 1) self.ch2 = Channel(self, 2) self.ch3 = Channel3(self, 3) def reset(self): """ Resets the instrument and clears the queue. """ self.write("*RST;*CLS;*SRE 0;*ESE 0;:STAT:PRES;") def measure_freq_simple(self, freq, resolution, channel): """ Configure measure frequency on channel. """ if 0 < channel <= 3: self.write(":MEASURE:FREQ? {0}Hz {1}, (@{2})".format( freq, resolution, channel)) def configure_freq(self, channel): """ Configure measure frequency on channel. """ if 0 < channel <= 3: self.write(":CONF:FREQ (@{0})".format(channel)) def freq_exp_set(self, frequency): """ Set expected frequency. """ self.write(":FREQ:EXP1 {0}".format(frequency)) def measure_t_interval(self): """ Time Interval from CH1 to CH2. """ self.write("MEAS:TINT? (@1),(@2)") def arming_auto(self): """ Enable the time arming mode. """ self.write(":FREQ:ARM:STAR:SOUR IMM") self.write(":FREQ:ARM:STOP:SOUR TIM") def arming_time(self, time): """ Enable the time arming mode. """ self.write(":FREQ:ARM:STAR:SOUR IMM") self.write(":FREQ:ARM:STOP:SOUR TIM") self.write((":FREQ:ARM:STOP:TIM %.1f" % time).lstrip('0')) def postproc_disable(self): """ Disable all post processing. """ self.write(":CALC:MATH:STATE OFF") self.write(":CALC2:LIM:STATE OFF") self.write(":CALC3:AVER:STATE OFF") def cal_read(self): """ Ask for calibration data. Returned data is binary. """ self.write(":CAL:DATA?") return self.adapter.read_raw() def cal_write(self, data): """ Write calibration data. Input data is binary. """ self.adapter.write_raw(":CAL:DATA ".encode('utf-8') + data) def trigger_level_set(self, level): """ Set trigger level. """ self.write((":EVENT1:LEVEL %.2f" % level).lstrip('0')) def measure_start(self): """ Start measurement. """ self.write("INIT") def trigger_set_fetc(self): """ Define the Trigger command. This means the command FETC? does not need to be sent for every measurement, decreasing the number of bytes transferred over the bus """ self.write("*DDT #15FETC?") def check_errors(self): """ Read all errors from the instrument. """ errors = [] while True: err = self.values("SYST:ERR?") if int(err[0]) != 0: errmsg = "Agilent 5313xA: {0}: {1}".format(err[0], err[1]) log.error(errmsg + '\n') errors.append(errmsg) else: break return errors
class Keithley2700(Instrument, KeithleyBuffer): """ Represents the Keithely 2700 Multimeter/Switch System and provides a high-level interface for interacting with the instrument. .. code-block:: python keithley = Keithley2700("GPIB::1") """ CLIST_VALUES = list(range(101, 300)) # Routing commands closed_channels = Instrument.control( "ROUTe:MULTiple:CLOSe?", "ROUTe:MULTiple:CLOSe %s", """ Parameter that controls the opened and closed channels. All mentioned channels are closed, other channels will be opened. """, validator=clist_validator, values=CLIST_VALUES, check_get_errors=True, check_set_errors=True, separator=None, get_process=lambda v: [ int(vv) for vv in (v.strip(" ()@,").split(",")) if not vv == "" ], ) open_channels = Instrument.setting( "ROUTe:MULTiple:OPEN %s", """ A parameter that opens the specified list of channels. Can only be set. """, validator=clist_validator, values=CLIST_VALUES, check_set_errors=True ) def get_state_of_channels(self, channels): """ Get the open or closed state of the specified channels :param channels: a list of channel numbers, or single channel number """ clist = clist_validator(channels, self.CLIST_VALUES) state = self.ask("ROUTe:MULTiple:STATe? %s" % clist) return state def open_all_channels(self): """ Open all channels of the Keithley 2700. """ self.write(":ROUTe:OPEN:ALL") def __init__(self, adapter, **kwargs): super(Keithley2700, self).__init__( adapter, "Keithley 2700 MultiMeter/Switch System", **kwargs ) self.check_errors() self.determine_valid_channels() def determine_valid_channels(self): """ Determine what cards are installed into the Keithley 2700 and from that determine what channels are valid. """ self.CLIST_VALUES.clear() self.cards = {slot: card for slot, card in enumerate(self.options, 1)} for slot, card in self.cards.items(): if card == "none": continue elif card == "7709": """The 7709 is a 6(rows) x 8(columns) matrix card, with two additional switches (49 & 50) that allow row 1 and 2 to be connected to the DMM backplane (input and sense respectively). """ channels = range(1, 51) else: log.warning( "Card type %s at slot %s is not yet implemented." % (card, slot) ) continue channels = [100 * slot + ch for ch in channels] self.CLIST_VALUES.extend(channels) def close_rows_to_columns(self, rows, columns, slot=None): """ Closes (connects) the channels between column(s) and row(s) of the 7709 connection matrix. Only one of the parameters 'rows' or 'columns' can be "all" :param rows: row number or list of numbers; can also be "all" :param columns: column number or list of numbers; can also be "all" :param slot: slot number (1 or 2) of the 7709 card to be used """ channels = self.channels_from_rows_columns(rows, columns, slot) self.closed_channels = channels def open_rows_to_columns(self, rows, columns, slot=None): """ Opens (disconnects) the channels between column(s) and row(s) of the 7709 connection matrix. Only one of the parameters 'rows' or 'columns' can be "all" :param rows: row number or list of numbers; can also be "all" :param columns: column number or list of numbers; can also be "all" :param slot: slot number (1 or 2) of the 7709 card to be used """ channels = self.channels_from_rows_columns(rows, columns, slot) self.open_channels = channels def channels_from_rows_columns(self, rows, columns, slot=None): """ Determine the channel numbers between column(s) and row(s) of the 7709 connection matrix. Returns a list of channel numbers. Only one of the parameters 'rows' or 'columns' can be "all" :param rows: row number or list of numbers; can also be "all" :param columns: column number or list of numbers; can also be "all" :param slot: slot number (1 or 2) of the 7709 card to be used """ if slot is not None and self.cards[slot] != "7709": raise ValueError("No 7709 card installed in slot %g" % slot) if isinstance(rows, str) and isinstance(columns, str): raise ValueError("Only one parameter can be 'all'") elif isinstance(rows, str) and rows == "all": rows = list(range(1, 7)) elif isinstance(columns, str) and columns == "all": columns = list(range(1, 9)) if isinstance(rows, (list, tuple, np.ndarray)) and \ isinstance(columns, (list, tuple, np.ndarray)): if len(rows) != len(columns): raise ValueError("The length of the rows and columns do not match") # Flatten (were necessary) the arrays new_rows = [] new_columns = [] for row, column in zip(rows, columns): if isinstance(row, int) and isinstance(column, int): new_rows.append(row) new_columns.append(column) elif isinstance(row, (list, tuple, np.ndarray)) and isinstance(column, int): new_columns.extend(len(row) * [column]) new_rows.extend(list(row)) elif isinstance(column, (list, tuple, np.ndarray)) and isinstance(row, int): new_columns.extend(list(column)) new_rows.extend(len(column) * [row]) rows = new_rows columns = new_columns # Determine channel number from rows and columns number. rows = np.array(rows, ndmin=1) columns = np.array(columns, ndmin=1) channels = (rows - 1) * 8 + columns if slot is not None: channels += 100 * slot return channels # system, some taken from Keithley 2400 def beep(self, frequency, duration): """ Sounds a system beep. :param frequency: A frequency in Hz between 65 Hz and 2 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.write(":SYST:BEEP %g, %g" % (frequency, duration)) def triad(self, base_frequency, duration): """ Sounds a musical triad using the system beep. :param base_frequency: A frequency in Hz between 65 Hz and 1.3 MHz :param duration: A time in seconds between 0 and 7.9 seconds """ self.beep(base_frequency, duration) time.sleep(duration) self.beep(base_frequency * 5.0 / 4.0, duration) time.sleep(duration) self.beep(base_frequency * 6.0 / 4.0, duration) @property def error(self): """ Returns a tuple of an error code and message from a single error. """ err = self.values(":system:error?") if len(err) < 2: err = self.read() # Try reading again code = err[0] message = err[1].replace('"', '') return (code, message) def check_errors(self): """ Logs any system errors reported by the instrument. """ code, message = self.error while code != 0: t = time.time() log.info("Keithley 2700 reported error: %d, %s" % (code, message)) print(code, message) code, message = self.error if (time.time() - t) > 10: log.warning("Timed out for Keithley 2700 error retrieval.") def reset(self): """ Resets the instrument and clears the queue. """ self.write("status:queue:clear;*RST;:stat:pres;:*CLS;") options = Instrument.measurement( "*OPT?", """Property that lists the installed cards in the Keithley 2700. Returns a dict with the integer card numbers on the position.""", cast=False ) ########### # DISPLAY # ########### text_enabled = Instrument.control( "DISP:TEXT:STAT?", "DISP:TEXT:STAT %d", """ A boolean property that controls whether a text message can be shown on the display of the Keithley 2700. """, values={True: 1, False: 0}, map_values=True, ) display_text = Instrument.control( "DISP:TEXT:DATA?", "DISP:TEXT:DATA '%s'", """ A string property that controls the text shown on the display of the Keithley 2700. Text can be up to 12 ASCII characters and must be enabled to show. """, validator=text_length_validator, values=12, cast=str, separator="NO_SEPARATOR", get_process=lambda v: v.strip("'\""), ) def display_closed_channels(self): """ Show the presently closed channels on the display of the Keithley 2700. """ # Get the closed channels and make a string of the list channels = self.closed_channels channel_string = " ".join([ str(channel % 100) for channel in channels ]) # Prepend "Closed: " or "C: " to the string, depending on the length str_length = 12 if len(channel_string) < str_length - 8: channel_string = "Closed: " + channel_string elif len(channel_string) < str_length - 3: channel_string = "C: " + channel_string # enable displaying text-messages self.text_enabled = True # write the string to the display self.display_text = channel_string
class SR570(Instrument): def __init__(self, resourceName, **kwargs): super(SR570, self).__init__( resourceName, "Stanford Research Systems SR570 Lock-in amplifier", **kwargs) SENSITIVITIES = [ 1e-12, 2e-12, 5e-12, 10e-12, 20e-12, 50e-12, 100e-12, 200e-12, 500e-12, 1e-9, 2e-9, 5e-9, 10e-9, 20e-9, 50e-9, 100e-9, 200e-9, 500e-9, 1e-6, 2e-6, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6, 500e-6, 1e-3 ] FREQUENCIES = [ 0.03, 0.1, 0.3, 1, 3, 10, 30, 100, 300, 1e3, 3e3, 1e4, 3e4, 1e5, 3e5, 1e6 ] FILT_TYPES = [ '6dB Highpass', '12dB Highpass', '6dB Bandpass', '6dB Lowpass', '12dB Lowpass', 'none' ] BIAS_LIMITS = [-5, 5] OFFSET_CURRENTS = [ 1e-12, 2e-12, 5e-12, 10e-12, 20e-12, 50e-12, 100e-12, 200e-12, 500e-12, 1e-9, 2e-9, 5e-9, 10e-9, 20e-9, 50e-9, 100e-9, 200e-9, 500e-9, 1e-6, 2e-6, 5e-6, 10e-6, 20e-6, 50e-6, 100e-6, 200e-6, 500e-6, 1e-3, 2e-3, 5e-3 ] GAIN_MODES = ['Low Noise', 'High Bandwidth', 'Low Drift'] sensitivity = Instrument.setting( "SENS %d", """ A floating point value that sets the sensitivity of the amplifier, which takes discrete values in a 1-2-5 sequence. Values are truncated to the closest allowed value if not exact. Allowed values range from 1 pA/V to 1 mA/V.""", validators=truncated_discrete_set, values=SENSITIVITIES, map_values=True) filter_type = Instrument.setting("FLTT %d", """ A string that sets the filter type. Allowed values are: {}""".format(FILT_TYPES), validators=truncated_discrete_set, values=FILT_TYPES, map_values=True) low_freq = Instrument.setting( "LFRQ %d", """ A floating point value that sets the lowpass frequency of the amplifier, which takes a discrete value in a 1-3 sequence. Values are truncated to the closest allowed value if not exact. Allowed values range from 0.03 Hz to 1 MHz.""", validators=truncated_discrete_set, values=FREQUENCIES, map_values=True) high_freq = Instrument.setting( "HFRQ %d", """ A floating point value that sets the highpass frequency of the amplifier, which takes a discrete value in a 1-3 sequence. Values are truncated to the closest allowed value if not exact. Allowed values range from 0.03 Hz to 1 MHz.""", validators=truncated_discrete_set, values=FREQUENCIES, map_values=True) bias_level = Instrument.setting( "BSLV %g", """ A floating point value in V that sets the bias voltage level of the amplifier, in the [-5V,+5V] limits. The values are up to 1 mV precision level.""", validators=truncated_range, values=BIAS_LIMITS, set_process=lambda v: int(1000 * v)) offset_current = Instrument.setting( "BSLV %f", """ A floating point value in A that sets the absolute value of the offset current of the amplifier, in the [1pA,5mA] limits. The offset current takes discrete values in a 1-2-5 sequence. Values are truncated to the closest allowed value if not exact. """, validators=truncated_discrete_set, values=OFFSET_CURRENTS, map_values=True) offset_current_sign = Instrument.setting( "IOSN %d", """ An string that sets the offset current sign. Allowed values are: 'positive' and 'negative'. """, validators=strict_discrete_set, values={ 'positive': 1, 'negative': 0 }, map_values=True) gain_mode = Instrument.setting("GNMD %d", """ A string that sets the gain mode. Allowed values are: {}""".format(GAIN_MODES), validators=truncated_discrete_set, values=GAIN_MODES, map_values=True) invert_signal_sign = Instrument.setting( "INVT %d", """ An boolean sets the signal invert sense. Allowed values are: True (inverted) and False (not inverted). """, validators=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) bias_enabled = Instrument.setting( "BSON %d", """ Boolean that turns the bias on or off. Allowed values are: True (bias on) and False (bias off)""", validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) offset_current_enabled = Instrument.setting( "IOON %d", """ Boolean that turns the offset current on or off. Allowed values are: True (current on) and False (current off).""", validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) front_blanked = Instrument.setting( "BLNK %d", """ Boolean that blanks(True) or un-blanks (False) the front panel""", validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) signal_inverted = Instrument.setting( "INVT %d", """ Boolean that inverts the signal if True""", validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) #################### # Methods # #################### def enable_bias(self): """Turns the bias voltage on""" self.bias_enabled = True def disable_bias(self): """Turns the bias voltage off""" self.bias_enabled = False def enable_offset_current(self): """"Enables the offset current """ self.offset_current_enabled = True def disable_offset_current(self): """"Disables the offset current """ self.offset_current_enabled = False def clear_overload(self): """"Reset the filter capacitors to clear an overload condition""" self.write("ROLD") def blank_front(self): """"Blanks the frontend output of the device""" self.front_blanked = True def unblank_front(self): """Un-blanks the frontend output of the device""" self.front_blanked = False
class HP3478A(Instrument): """ Represents the Hewlett Packard 3748A 5 1/2 digit multimeter and provides a high-level interface for interacting with the instrument. """ def __init__(self, resourceName, **kwargs): kwargs.setdefault('read_termination', '\r\n') kwargs.setdefault('send_end', True) super(HP3478A, self).__init__( resourceName, "Hewlett-Packard HP3478A", includeSCPI=False, **kwargs ) # Definitions for different specifics of this instrument MODES = {"DCV": "F1", "ACV": "F2", "R2W": "F3", "R4W": "F4", "DCI": "F5", "ACI": "F6", "Rext": "F7", } INV_MODES = {v: k for k, v in MODES.items()} RANGES = {"DCV": {3E-2: "R-2", 3E-1: "R-1", 3: "R0", 30: "R1", 300: "R2", "auto": "RA"}, "ACV": {3E-1: "R-1", 3: "R0", 30: "R1", 300: "R2", "auto": "RA"}, "R2W": {30: "R1", 300: "R2", 3E3: "R3", 3E4: "R4", 3E5: "R5", 3E6: "R6", 3E7: "R7", "auto": "RA"}, "R4W": {30: "R1", 300: "R2", 3E3: "R3", 3E4: "R4", 3E5: "R5", 3E6: "R6", 3E7: "R7", "auto": "RA"}, "DCI": {3E-1: "R-1", 3: "R0", "auto": "RA"}, "ACI": {3E-1: "R-1", 3: "R0", "auto": "RA"}, "Rext": {3E7: "R7", "auto": "RA"}, } TRIGGERS = { "auto": "T1", "internal": "T1", "external": "T2", "single": "T3", "hold": "T4", "fast": "T5", } class ERRORS(IntFlag): """Enum element for errror bit decoding """ AD_LINK = 32 # AD link error AD_SELFCHK = 16 # AD self check error AD_SLOPE = 8 # AD slope error ROM = 4 # Control ROM error RAM = 2 # RAM selftest failed CALIBRATION = 1 # Calibration checksum error or cal range issue NO_ERR = 0 # Should be obvious class SRQ(IntFlag): """Enum element for SRQ mask bit decoding """ Power_on = 128 Calibration = 32 Front_panel_button = 16 Internal_error = 8 Syntax_error = 4 Data_ready = 1 def get_status(self): """Method to read the status bytes from the instrument :return current_status: a byte array representing the instrument status :rtype current_status: bytes """ self.write("B") current_status = self.adapter.read_bytes(5) return current_status # decoder functions @classmethod def decode_status(cls, status_bytes, field=None): """Method to handle the decoding of the status bytes into something meaningfull :param status_bytes: list of bytes to be decoded :param field: name of field to be returned :return ret_val: int status value """ ret_val = Status(Status_bytes(*status_bytes)) if field is None: return ret_val.b elif field == "SRQ": return cls.SRQ(getattr(ret_val.B, "byte3")) else: return getattr(ret_val.b, field) @classmethod def decode_mode(cls, function): """Method to decode current mode :param function: int indicating the measurement function selected :return cur_mode: string with the current measurement mode :rtype cur_mode: str """ cur_mode = cls.INV_MODES["F" + str(function)] return cur_mode @classmethod def decode_range(cls, range_undecoded, function): """Method to decode current range :param range_undecoded: int to be decoded :param function: int indicating the measurement function selected :return cur_range: float value repesenting the active measurment range :rtype cur_range: float """ cur_mode = cls.INV_MODES["F" + str(function)] if cur_mode == "DCV": correction_factor = 3 elif cur_mode in ["ACV", "ACI", "DCI"]: correction_factor = 2 else: correction_factor = 0 cur_range = 3 * math.pow(10, range_undecoded - correction_factor) return cur_range @staticmethod def decode_trigger(status_bytes): """Method to decode trigger mode :param status_bytes: list of bytes to be decoded :return trigger_mode: string with the current trigger mode :rtype trigger_mode: str """ cur_stat = Status(Status_bytes(*status_bytes)) i_trig = cur_stat.b.int_trig e_trig = cur_stat.b.ext_trig if i_trig == 0: if e_trig == 0: trigger_mode = "hold" else: trigger_mode = "external" else: trigger_mode = "internal" return trigger_mode # commands/properties for instrument control @property def active_connectors(self): """Return selected connectors ("front"/"back"), based on front-panel selector switch """ selection = self.status.front_rear if selection == 1: return "front" else: return "back" @property def auto_range_enabled(self): """ Property describing the auto-ranging status ====== ============================================ Value Status ====== ============================================ True auto-range function activated False manual range selection / auto-range disabled ====== ============================================ The range can be set with the :py:attr:`range` property """ selection = self.status.auto_range return bool(selection) @property def auto_zero_enabled(self): """ Return auto-zero status, this property can be set ====== ================== Value Status ====== ================== True auto-zero active False auto-zero disabled ====== ================== """ selection = self.status.auto_zero return bool(selection) @auto_zero_enabled.setter 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) @property def calibration_enabled(self): """Return calibration enable switch setting, based on front-panel selector switch ====== =================== Value Status ====== =================== True calbration possible False calibration locked ====== =================== """ selection = self.status.cal_enable return bool(selection) def check_errors(self): """ Method to read the error status register :return error_status: one byte with the error status register content :rtype error_status: int """ # Read the error status reigster only one time for this method, as # the manual states that reading the error status register also clears it. current_errors = self.error_status if current_errors != 0: log.error("HP3478A error detected: %s", self.ERRORS(current_errors)) return self.ERRORS(current_errors) error_status = Instrument.measurement( "E", """Checks the error status register """, cast=int, ) def display_reset(self): """ Reset the display of the instrument. """ self.write("D1") display_text = Instrument.setting( "D2%s", """Displays up to 12 upper-case ASCII characters on the display. """, set_process=(lambda x: str.upper(x[0:12])), ) display_text_no_symbol = Instrument.setting( "D3%s", """Displays up to 12 upper-case ASCII characters on the display and disables all symbols on the display. """, set_process=(lambda x: str.upper(x[0:12])), ) measure_ACI = Instrument.measurement( MODES["ACI"], """ Returns the measured value for AC current as a float in A. """, ) measure_ACV = Instrument.measurement( MODES["ACV"], """ Returns the measured value for AC Voltage as a float in V. """, ) measure_DCI = Instrument.measurement( MODES["DCI"], """ Returns the measured value for DC current as a float in A. """, ) measure_DCV = Instrument.measurement( MODES["DCV"], """ Returns the measured value for DC Voltage as a float in V. """, ) measure_R2W = Instrument.measurement( MODES["R2W"], """ Returns the measured value for 2-wire resistance as a float in Ohm. """, ) measure_R4W = Instrument.measurement( MODES["R4W"], """ Returns the measured value for 4-wire resistance as a float in Ohm. """, ) measure_Rext = Instrument.measurement( MODES["Rext"], """ Returns the measured value for extended resistance mode (>30M, 2-wire) resistance as a float in Ohm. """, ) @property def mode(self): """Return current selected measurement mode, this propery can be set. Allowed values are ==== ============================================================== Mode Function ==== ============================================================== ACI AC current ACV AC voltage DCI DC current DCV DC voltage R2W 2-wire resistance R4W 4-wire resistance Rext extended resistance method (requires additional 10 M resistor) ==== ============================================================== """ current_mode = self.decode_mode(self.status.function) return current_mode @mode.setter def mode(self, value): mode_set = self.MODES[strict_discrete_set(value, self.MODES)] self.write(mode_set) @property def range(self): """Returns the current measurement range, this property can be set. Valid values are : ==== ======================================= Mode Range ==== ======================================= ACI 0.3, 3, auto ACV 0.3, 3, 30, 300, auto DCI 0.3, 3, auto DCV 0.03, 0.3, 3, 30, 300, auto R2W 30, 300, 3000, 3E4, 3E5, 3E6, 3E7, auto R4W 30, 300, 3000, 3E4, 3E5, 3E6, 3E7, auto Rext 3E7, auto ==== ======================================= """ current_range = self.decode_range(self.status.range, self.status.function) return current_range @range.setter 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) @property def resolution(self): """Returns current selected resolution, this property can be set. Possible values are 3,4 or 5 (for 3 1/2, 4 1/2 or 5 1/2 digits of resolution) """ number_of_digit = 6 - self.status.digits return number_of_digit @resolution.setter def resolution(self, value): resolution_string = "N" + str(strict_discrete_set(value, [3, 4, 5])) self.write(resolution_string) @property def status(self): """ Returns an object representing the current status of the unit. """ current_status = self.decode_status(self.get_status()) return current_status @property def SRQ_mask(self): """Return current SRQ mask, this property can be set, bit assigment for SRQ: ========= ========================== Bit (dec) Description ========= ========================== 1 SRQ when Data ready 4 SRQ when Syntax error 8 SRQ when internal error 16 front panel SQR button 32 SRQ by invalid calibration ========= ========================== """ mask = self.decode_status(self.get_status(), "SRQ") return mask @SRQ_mask.setter def SRQ_mask(self, value): mask_str = "M" + format(strict_range(value, [0, 63]), "2o") self.write(mask_str) @property def trigger(self): """Return current selected trigger mode, this property can be set Possibe values are: ======== =========================================== Value Meaning ======== =========================================== auto automatic trigger (internal) internal automatic trigger (internal) external external trigger (connector on back or GET) hold holds the measurement fast fast trigger for AC measurements ======== =========================================== """ trigger = self.decode_trigger(self.get_status()) return trigger @trigger.setter def trigger(self, value): trig_set = self.TRIGGERS[strict_discrete_set(value, self.TRIGGERS)] self.write(trig_set) # Functions using low-level access via instrument.adapter.connection methods def GPIB_trigger(self): """ Initate trigger via low-level GPIB-command (aka GET - group execute trigger) """ self.adapter.connection.assert_trigger() def reset(self): """ Initatiates a reset (like a power-on reset) of the HP3478A """ self.adapter.connection.clear() def shutdown(self): """ provides a way to gracefully close the connection to the HP3478A """ self.adapter.connection.clear() self.adapter.connection.close() super().shutdown()
class SciMag(Instrument): """ Represents the Scientific Magnetics (Twickenham Scientific Instruments) Superconducting Magnet Controller SciMag S-11-52-13. Provides a high-level interface for interacting with the instrument. WARNING: Use at your own risk! Faulty operation of a superconducting magnet can lead to severe damage or injury! Read the manual: https://www.twicksci.co.uk/manuals/pdf/smc552+.pdf """ _RAMP_RATE_MAX = 5 _VOLTAGE_LIMIT_MAX = 1 pause = Instrument.setting( "P%d", """A boolean property that controls the pause-status, which takes the values True (pause) and False (continue) """, validator=strict_discrete_set, values={ True: 1, False: 0 }, map_values=True) unit = Instrument.setting( "T%d", """Sets the units displayed and can take the values "A"mpere or "T"esla """, validator=strict_discrete_set, values={ "A": 0, "T": 1 }, map_values=True) reverse_switch_on = Instrument.setting( "D%d", """Sets the direction of the reversing switch, if installed. Forward if switch is off; Reverse if switch is on. Takes boolean value. True = Switch is on, Field is reverse. WARNING: Change only if Magnet has been ramped to Zero! """, validator=strict_discrete_set, values={ False: 0, True: 1 }, map_values=True) ramp_target = Instrument.setting( "R%d", """Sets Ramp Target. Can take the values "Z"ero, "L"ower, "U"pper. """, validator=strict_discrete_set, values={ "Z": 0, "L": 1, "U": 2 }, map_values=True) persistent_mode_heater = Instrument.setting("H%d", """Sets the persistent mode. Can take the values "O"ff, "C"onditional on, "U"nconditional on, "R"eset persistent current mode to zero. WARNING: Unconditional on will turn on the heater without any interlocks! Use at own risk. Standard mode to switch heater on is Conditional. Simplified Commands are "True" (Conditional on) and "False" (Off). """, validator=strict_discrete_set, values={ "O": 0, "C": 1, "U": 2, "R": 9, False: 0, True: 1 }, map_values=True) ramp_rate = Instrument.setting( "A%08.5f", """Sets the Ramp rate in A/s.""", validator=strict_range, values=[0, _RAMP_RATE_MAX], ) external_trip = Instrument.setting("X%d", """Sets the External trip: False = Off, True = Simple trip On, "Auto" = Auto-rampdown On.""", validator=strict_discrete_set, values={ False: 0, True: 1, "Auto": 4 }, map_values=True) terminal_voltage_limit = Instrument.setting( "Y%04.1f", """Sets the terminal voltage limit to nn.n V.""", validator=strict_range, values=[0, _VOLTAGE_LIMIT_MAX]) heater_current_output = Instrument.setting( "W%03d", """Sets the Heater current output to nnn mA.""", validator=strict_range, values=[0, 255]) calibrate_tesla_per_amp = Instrument.setting( "C%08.6f", """Calibrate the output to Tesla per Amp value of the superconducting magnet. The Value is assumed to lie between 0.01 and 0.5 T/A and will be set to zero if a number outside this range is entered. """, validator=strict_range, values=[0.01, 0.5], ) lower_setpoint = Instrument.setting("L%08.4f", """Sets the lower setpoint. Unit depends on the unit set in the magnet controller. Unit can be "T" or "A" and must be set before. """, validator=strict_range, values=[0, 200]) upper_setpoint = Instrument.setting("U%08.4f", """Sets the upper setpoint. Unit depends on the unit set in the magnet controller. Unit can be "T" or "A" and must be set before. """, validator=strict_range, values=[0, 200]) output_parameters_amps = Instrument.measurement( "G", """Returns dict of output parameters.""", preprocess_reply=extract_value_output_parameters_amps) output_parameters_tesla = Instrument.measurement( "N", """Returns dict of output parameters.""", preprocess_reply=extract_value_output_parameters_tesla) magnet_status = Instrument.measurement( "J", "Returns dict of magnet status.", preprocess_reply=extract_value_magnet_status) current_status = Instrument.measurement( "K", "Returns dict of current status.", preprocess_reply=extract_value_current_status) operating_parameters = Instrument.measurement( "O", "Returns dict of operation parameters", preprocess_reply=extract_value_operating_parameters) set_point_status = Instrument.measurement( "S", "Returns dict of set point status.", preprocess_reply=extract_value_set_point_status) def __init__(self, resourceName, ramp_rate_max=5, voltage_limit_max=1, field_max=10, **kwargs): super().__init__(resourceName, "Scientific Magnetics SMC", includeSCPI=False, **kwargs) self._RAMP_RATE_MAX = ramp_rate_max # A/s self._VOLTAGE_LIMIT_MAX = voltage_limit_max # V self._T_MAX = field_max # T @property def tesla(self) -> float: """ High-Level property for setting and getting the field in the solenoid. If installed, the setter-funktion toggles the reversal-switch automatically at zero-field. """ self.unit = "T" value = self.output_parameters_tesla["field"] return value @tesla.setter def tesla(self, value: float): self.unit = "T" if value == 0: self.ramp_target = "Z" elif abs(value) < self._T_MAX: # 1. Get actual direction if self.operating_parameters["switch_direction"] == 0: sign = +1 elif self.operating_parameters["switch_direction"] == 1: sign = -1 else: sign = 0 # 2. Check, if value and sign are both positive or both negative if value * sign > 0: self.upper_setpoint = abs(value) elif value * sign < 0: self.ramp_target = "Z" # At zero-field: switch direction. while not (self.current_status["ramp_status"] == 1): time.sleep(1) if value > 0: self.reverse_switch_on = False elif value < 0: self.reverse_switch_on = True self.upper_setpoint = abs(value) self.ramp_target = "U" else: log.warning( "Intended field was: %08.4f T.\nAllowed maximum field is: %08.4f T" % (value, self._T_MAX)) raise UserWarning( "Intended field value exceeded range of the solenoid. Value was NOT set." ) def shutdown(self): """Brings the instrument to a safe and stable state. """ self.isShutdown = True log.info("Shutting down %s" % self.name) self.persistent_mode_heater = True self.ramp_target = "Z" self.pause = False
class KeysightDSOX1102G(Instrument): """ Represents the Keysight DSOX1102G Oscilloscope interface for interacting with the instrument. Refer to the Keysight DSOX1102G Oscilloscope Programmer's Guide for further details about using the lower-level methods to interact directly with the scope. .. code-block:: python scope = KeysightDSOX1102G(resource) scope.autoscale() ch1_data_array, ch1_preamble = scope.download_data(source="channel1", points=2000) # ... scope.shutdown() Known issues: - The digitize command will be completed before the operation is. May lead to VI_ERROR_TMO (timeout) occuring when sending commands immediately after digitize. Current fix: if deemed necessary, add delay between digitize and follow-up command to scope. """ BOOLS = {True: 1, False: 0} def __init__(self, adapter, **kwargs): super(KeysightDSOX1102G, self).__init__(adapter, "Keysight DSOX1102G Oscilloscope", **kwargs) # Account for setup time for timebase_mode, waveform_points_mode self.adapter.connection.timeout = 6000 self.ch1 = Channel(self, 1) self.ch2 = Channel(self, 2) ################# # Channel setup # ################# def autoscale(self): """ Autoscale displayed channels. """ self.write(":autoscale") ################## # Timebase Setup # ################## @property def timebase(self): """ Read timebase setup as a dict containing the following keys: - "REF": position on screen of timebase reference (str) - "MAIN:RANG": full-scale timebase range (float) - "POS": interval between trigger and reference point (float) - "MODE": mode (str)""" return self._timebase() timebase_mode = Instrument.control( ":TIMebase:MODE?", ":TIMebase:MODE %s", """ A string parameter that sets the current time base. Can be "main", "window", "xy", or "roll".""", validator=strict_discrete_set, values={ "main": "MAIN", "window": "WIND", "xy": "XY", "roll": "ROLL" }, map_values=True) timebase_offset = Instrument.control( ":TIMebase:POSition?", ":TIMebase:REFerence CENTer;:TIMebase:POSition %f", """ A float parameter that sets the time interval in seconds between the trigger event and the reference position (at center of screen by default).""") timebase_range = Instrument.control( ":TIMebase:RANGe?", ":TIMebase:RANGe %f", """ A float parameter that sets the full-scale horizontal time in seconds for the main window.""") timebase_scale = Instrument.control( ":TIMebase:SCALe?", ":TIMebase:SCALe %f", """ A float parameter that sets the horizontal scale (units per division) in seconds for the main window.""") ############### # Acquisition # ############### acquisition_type = Instrument.control( ":ACQuire:TYPE?", ":ACQuire:TYPE %s", """ A string parameter that sets the type of data acquisition. Can be "normal", "average", "hresolution", or "peak".""", validator=strict_discrete_set, values={ "normal": "NORM", "average": "AVER", "hresolution": "HRES", "peak": "PEAK" }, map_values=True) acquisition_mode = Instrument.control( ":ACQuire:MODE?", ":ACQuire:MODE %s", """ A string parameter that sets the acquisition mode. Can be "realtime" or "segmented".""", validator=strict_discrete_set, values={ "realtime": "RTIM", "segmented": "SEGM" }, map_values=True) def run(self): """ Starts repetitive acquisitions. This is the same as pressing the Run key on the front panel.""" self.write(":run") def stop(self): """ Stops the acquisition. This is the same as pressing the Stop key on the front panel.""" self.write(":stop") def single(self): """ Causes the instrument to acquire a single trigger of data. This is the same as pressing the Single key on the front panel. """ self.write(":single") _digitize = Instrument.setting( ":DIGitize %s", """ Acquire waveforms according to the settings of the :ACQuire commands and specified source, as a string parameter that can take the following values: "channel1", "channel2", "function", "math", "fft", "abus", or "ext". """, validator=strict_discrete_set, values={ "channel1": "CHAN1", "channel2": "CHAN2", "function": "FUNC", "math": "MATH", "fft": "FFT", "abus": "ABUS", "ext": "EXT" }, map_values=True) def digitize(self, source: str): """ Acquire waveforms according to the settings of the :ACQuire commands. Ensure a delay between the digitize operation and further commands, as timeout may be reached before digitize has completed. :param source: "channel1", "channel2", "function", "math", "fft", "abus", or "ext".""" self._digitize = source waveform_points_mode = Instrument.control( ":waveform:points:mode?", ":waveform:points:mode %s", """ A string parameter that sets the data record to be transferred with the waveform_data method. Can be "normal", "maximum", or "raw".""", validator=strict_discrete_set, values={ "normal": "NORM", "maximum": "MAX", "raw": "RAW" }, map_values=True) waveform_points = Instrument.control( ":waveform:points?", ":waveform:points %d", """ An integer parameter that sets the number of waveform points to be transferred with the waveform_data method. Can be any of the following values: 100, 250, 500, 1000, 2 000, 5 000, 10 000, 20 000, 50 000, 62 500. Note that the oscilloscope may provide less than the specified nb of points. """, validator=strict_discrete_set, values=[100, 250, 500, 1000, 2000, 5000, 10000, 20000, 50000, 62500]) waveform_source = Instrument.control( ":waveform:source?", ":waveform:source %s", """ A string parameter that selects the analog channel, function, or reference waveform to be used as the source for the waveform methods. Can be "channel1", "channel2", "function", "fft", "wmemory1", "wmemory2", or "ext".""", validator=strict_discrete_set, values={ "channel1": "CHAN1", "channel2": "CHAN2", "function": "FUNC", "fft": "FFT", "wmemory1": "WMEM1", "wmemory2": "WMEM2", "ext": "EXT" }, map_values=True) waveform_format = Instrument.control( ":waveform:format?", ":waveform:format %s", """ A string parameter that controls how the data is formatted when sent from the oscilloscope. Can be "ascii", "word" or "byte". Words are transmitted in big endian by default.""", validator=strict_discrete_set, values={ "ascii": "ASC", "word": "WORD", "byte": "BYTE" }, map_values=True) @property def waveform_preamble(self): """ Get preamble information for the selected waveform source as a dict with the following keys: - "format": byte, word, or ascii (str) - "type": normal, peak detect, or average (str) - "points": nb of data points transferred (int) - "count": always 1 (int) - "xincrement": time difference between data points (float) - "xorigin": first data point in memory (float) - "xreference": data point associated with xorigin (int) - "yincrement": voltage difference between data points (float) - "yorigin": voltage at center of screen (float) - "yreference": data point associated with yorigin (int)""" return self._waveform_preamble() @property def waveform_data(self): """ Get the binary block of sampled data points transmitted using the IEEE 488.2 arbitrary block data format.""" # Other waveform formats raise UnicodeDecodeError self.waveform_format = "ascii" data = self.values(":waveform:data?") # Strip header from first data element data[0] = float(data[0][10:]) return data ################ # System Setup # ################ @property def system_setup(self): """ A string parameter that sets up the oscilloscope. Must be in IEEE 488.2 format. It is recommended to only set a string previously obtained from this command.""" return self.ask(":system:setup?") @system_setup.setter def system_setup(self, setup_string): self.write(":system:setup " + setup_string) def ch(self, channel_number): if channel_number == 1: return self.ch1 elif channel_number == 2: return self.ch2 else: raise ValueError("Invalid channel number. Must be 1 or 2.") def clear_status(self): """ Clear device status. """ self.write("*CLS") def factory_reset(self): """ Factory default setup, no user settings remain unchanged. """ self.write("*RST") def default_setup(self): """ Default setup, some user settings (like preferences) remain unchanged. """ self.write(":SYSTem:PRESet") def timebase_setup(self, mode=None, offset=None, horizontal_range=None, scale=None): """ Set up timebase. Unspecified parameters are not modified. Modifying a single parameter might impact other parameters. Refer to oscilloscope documentation and make multiple consecutive calls to channel_setup if needed. :param mode: Timebase mode, can be "main", "window", "xy", or "roll". :param offset: Offset in seconds between trigger and center of screen. :param horizontal_range: Full-scale range in seconds. :param scale: Units-per-division in seconds.""" if mode is not None: self.timebase_mode = mode if offset is not None: self.timebase_offset = offset if horizontal_range is not None: self.timebase_range = horizontal_range if scale is not None: self.timebase_scale = scale def download_image(self, format_="png", color_palette="color"): """ Get image of oscilloscope screen in bytearray of specified file format. :param format_: "bmp", "bmp8bit", or "png" :param color_palette: "color" or "grayscale" """ query = f":DISPlay:DATA? {format_}, {color_palette}" # Using binary_values query because default interface does not support binary transfer img = self.binary_values(query, header_bytes=10, dtype=np.uint8) return bytearray(img) def download_data(self, source, points=62500): """ Get data from specified source of oscilloscope. Returned objects are a np.ndarray of data values (no temporal axis) and a dict of the waveform preamble, which can be used to build the corresponding time values for all data points. Multimeter will be stopped for proper acquisition. :param source: measurement source, can be "channel1", "channel2", "function", "fft", "wmemory1", "wmemory2", or "ext". :param points: integer number of points to acquire. Note that oscilloscope may return less points than specified, this is not an issue of this library. Can be 100, 250, 500, 1000, 2000, 5000, 10000, 20000, 50000, or 62500. :return data_ndarray, waveform_preamble_dict: see waveform_preamble property for dict format. """ # TODO: Consider downloading from multiple sources at the same time. self.waveform_source = source self.waveform_points_mode = "normal" self.waveform_points = points preamble = self.waveform_preamble data_bytes = self.waveform_data return np.array(data_bytes), preamble def _timebase(self): """ Reads setup data from timebase and converts it to a more convenient dict of values. """ tb_setup_raw = self.ask(":timebase?").strip("\n") # tb_setup_raw hat the following format: # :TIM:MODE MAIN;REF CENT;MAIN:RANG +1.00E-03;POS +0.0E+00 # Cut out the ":TIM:" at beginning and split string tb_setup_splitted = tb_setup_raw[5:].split(";") # Create dict of setup parameters tb_setup = dict(map(lambda v: v.split(" "), tb_setup_splitted)) # Convert values to specific type to_str = ["MODE", "REF"] to_float = ["MAIN:RANG", "POS"] for key in tb_setup: if key in to_str: tb_setup[key] = str(tb_setup[key]) elif key in to_float: tb_setup[key] = float(tb_setup[key]) return tb_setup def _waveform_preamble(self): """ Reads waveform preamble and converts it to a more convenient dict of values. """ vals = self.values(":waveform:preamble?") # Get values to dict vals_dict = dict( zip([ "format", "type", "points", "count", "xincrement", "xorigin", "xreference", "yincrement", "yorigin", "yreference" ], vals)) # Map element values format_map = {0: "BYTE", 1: "WORD", 4: "ASCII"} type_map = {0: "NORMAL", 1: "PEAK DETECT", 2: "AVERAGE", 3: "HRES"} vals_dict["format"] = format_map[int(vals_dict["format"])] vals_dict["type"] = type_map[int(vals_dict["type"])] # Correct types to_int = ["points", "count", "xreference", "yreference"] to_float = ["xincrement", "xorigin", "yincrement", "yorigin"] for key in vals_dict: if key in to_int: vals_dict[key] = int(vals_dict[key]) elif key in to_float: vals_dict[key] = float(vals_dict[key]) return vals_dict
class Axis(object): """ Represents a single open loop axis of the Attocube ANC350 :param axis: axis identifier, integer from 1 to 7 :param controller: ANC300Controller instance used for the communication """ serial_nr = Instrument.measurement("getser", "Serial number of the axis") voltage = Instrument.control( "getv", "setv %.3f", """ Amplitude of the stepping voltage in volts from 0 to 150 V. This property can be set. """, validator=strict_range, values=[0, 150]) frequency = Instrument.control( "getf", "setf %.3f", """ Frequency of the stepping motion in Hertz from 1 to 10000 Hz. This property can be set. """, validator=strict_range, values=[1, 10000], cast=int) mode = Instrument.control( "getm", "setm %s", """ Axis mode. This can be 'gnd', 'inp', 'cap', 'stp', 'off', 'stp+', 'stp-'. Available modes depend on the actual axis model""", validator=strict_discrete_set, values=['gnd', 'inp', 'cap', 'stp', 'off', 'stp+', 'stp-']) offset_voltage = Instrument.control( "geta", "seta %.3f", """ Offset voltage in Volts from 0 to 150 V. This property can be set. """, validator=strict_range, values=[0, 150]) pattern_up = Instrument.control( "getpu", "setpu %s", """ step up pattern of the piezo drive. 256 values ranging from 0 to 255 representing the the sequence of output voltages within one step of the piezo drive. This property can be set, the set value needs to be an array with 256 integer values. """, validator=truncated_int_array_strict_length, values=[256, [0, 255]], set_process=lambda a: " ".join("%d" % v for v in a), separator='\r\n', cast=int) pattern_down = Instrument.control( "getpd", "setpd %s", """ step down pattern of the piezo drive. 256 values ranging from 0 to 255 representing the the sequence of output voltages within one step of the piezo drive. This property can be set, the set value needs to be an array with 256 integer values. """, validator=truncated_int_array_strict_length, values=[256, [0, 255]], set_process=lambda a: " ".join("%d" % v for v in a), separator='\r\n', cast=int) output_voltage = Instrument.measurement("geto", """ Output voltage in volts.""") capacity = Instrument.measurement( "getc", """ Saved capacity value in nF of the axis.""") stepu = Instrument.setting( "stepu %d", """ Step upwards for N steps. Mode must be 'stp' and N must be positive.""", validator=strict_range, values=[0, inf]) stepd = Instrument.setting( "stepd %d", """ Step downwards for N steps. Mode must be 'stp' and N must be positive.""", validator=strict_range, values=[0, inf]) def __init__(self, controller, axis): self.axis = str(axis) self.controller = controller def _add_axis_id(self, command): """ add axis id to a command string at the correct position after the initial command, but before a potential value :param command: command string :returns: command string with added axis id """ cmdparts = command.split() cmdparts.insert(1, self.axis) return ' '.join(cmdparts) def ask(self, command, **kwargs): return self.controller.ask(self._add_axis_id(command), **kwargs) def write(self, command, **kwargs): return self.controller.write(self._add_axis_id(command), **kwargs) def values(self, command, **kwargs): return self.controller.values(self._add_axis_id(command), **kwargs) def stop(self): """ Stop any motion of the axis """ self.write('stop') def move(self, steps, gnd=True): """ Move 'steps' steps in the direction given by the sign of the argument. This method will change the mode of the axis automatically and ground the axis on the end if 'gnd' is True. The method returns only when the movement is finished. :param steps: finite integer value of steps to be performed. A positive sign corresponds to upwards steps, a negative sign to downwards steps. :param gnd: bool, flag to decide if the axis should be grounded after completion of the movement """ self.mode = 'stp' # perform the movement if steps > 0: self.stepu = steps elif steps < 0: self.stepd = abs(steps) else: pass # do not set stepu/d to 0 since it triggers a continous move # wait for the move to finish self.write('stepw') if gnd: self.mode = 'gnd' def measure_capacity(self): """ Obtains a new measurement of the capacity. The mode of the axis returns to 'gnd' after the measurement. :returns capacity: the freshly measured capacity in nF. """ self.mode = 'cap' # wait for the measurement to finish self.ask('capw') return self.capacity
class RohdeSMB100A(Instrument): """ Instrument code to control a Rohde & Schwarz SMB100A RF Signal Generator. .. code-block:: python from pymeasure.instruments.rohdeschwarz import RohdeSMB100A sig = RohdeSMB100A("GPIB0::29", read_termination = '\\n', write_termination = '\\n', timeout=None) # reset instrument to clear previous settings. sig.reset() # set freq and power units sig.freq_unit = "MHz" sig.power_unit = "DBM" # set frequency and power levels sig.power_level = -20 #dBm sig.fixed_freq = 700 #MHz # turn on signal sig.output = "ON" # perform any measurements needed. # turn off signal sig.output = "OFF" """ def __init__(self, resourceName, **kwargs): super().__init__(resourceName, "Rohde & Schwarz SMB100A RF Signal Generator", **kwargs) self.freq_unit = "GHz" ####### # POWER ####### # RF power level power_level = Instrument.control( ":POW?", ":POW %s", """ Sets the RF power level in dBm applied to DUT. If a level offset is included, the output power level is adjusted accordingly. The power level is not checked to see if the instrument can support it because it depends on the option installed. When setting power level, please refer the operation manual to ensure that your instrument can support it at your operating frequency. Use :meth:`~.RohdeSMB100A.get_instrument_options` method to identify the options installed in the instrument. .. code-block:: python sig.power_level = -5 """, check_set_errors=True, check_get_errors=True) # RF power offset power_offset = Instrument.control(":POW:OFFS?", ":POW:OFFS %s", """ Sets the RF offset power level in dB. Specifies the constant level offset of a downstream attenuator/amplifier. If level offset is entered, the level entered with :meth:`~.RohdeSMB100A.power_level` no longer corresponds to the RF output level at connector. .. code-block:: python sig.power_offset = 5 """, validator=truncated_discrete_set, values=np.arange(-100, 100, 0.01), check_set_errors=True, check_get_errors=True) ########### # FREQUENCY ########### # fixed frequency @property def fixed_freq(self): """ Sets the fixed frequency of the RF signal. The range of allowed frequencies depends on the instrument option, which can be checked using :meth:`~.RohdeSMB100A.get_instrument_options`. .. code-block:: python sig.fixed_freq = 1 #GHz (default) To change default frequency units, change the object variable `freq_unit`. For example, to set frequency to 1 MHz, .. code-block:: python sig.freq_unit = "MHz" sig.fixed_freq = 1 """ value = self.ask(":FREQ?") return value @fixed_freq.setter def fixed_freq(self, value): self.write(":FREQ {}{}".format(value, self.freq_unit)) ######## # OUTPUT ######## output = Instrument.setting(":OUTP %s", """ Turns the RF output on and off. - Values: :code:`ON`, :code:`OFF` .. code-block:: python sig.output = "ON" """, validator=strict_discrete_set, values={'OFF', 'ON'}, map_values=False, check_set_errors=True, check_get_errors=True) ####### # UNITS ####### power_unit = Instrument.control("UNIT:POW?", "UNIT:POW %s", """ Defines the default unit for power parameters. This setting affects the GUI, as well as all remote control commands that determine power values. - Values: :code:`V`, :code:`DBUV`, :code:`DBM` .. code-block:: python sig.power_unit = "DBM" # default setting """, validator=strict_discrete_set, values={'V', 'DBUV', 'DBM'}, map_values=False, check_set_errors=True, check_get_errors=True) ######### # GENERAL ######### get_instrument_options = Instrument.measurement( "*OPT?", """ Get the options installed on instrument. .. code-block:: python sig.get_instrument_options """)