#!/usr/bin/env python
"""
Provides support for the Teledyne-Lecroy Oscilloscopes that use the
MAUI interface.
Development follows the IEEE 488.2 Command Reference from the MAUI
Oscilloscopes Remote Control and Automation Manual, document number
maui-remote-control-automation_10mar20.pdf
Where possible, commands are sent using the enum_property, ... that
are usually used for SCPI classes, even though, this is not an SCPI
class.
"""
# IMPORTS #####################################################################
from enum import Enum
from instruments.abstract_instruments import Oscilloscope
from instruments.optional_dep_finder import numpy
from instruments.units import ureg as u
from instruments.util_fns import assume_units, enum_property, bool_property, ProxyList
# CLASSES #####################################################################
# pylint: disable=too-many-lines,arguments-differ
[docs]
class MAUI(Oscilloscope):
"""
Medium to high-end Teledyne-Lecroy Oscilloscopes are shipped with
the MAUI user interface. This class can be used to communicate with
these instruments.
By default, the IEEE 488.2 commands are used. However, commands
based on MAUI's `app` definition can be submitted too using the
appropriate send / query commands.
Your scope must be set up to communicate via LXI (VXI11) to be used
with pyvisa. Make sure that either the pyvisa-py or the NI-VISA
backend is installed. Please see the pyvisa documentation for more
information.
This class inherits from: `Oscilloscope`
Example usage (more examples below):
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> # start the trigger in automatic mode
>>> inst.run()
>>> print(inst.trigger_state) # print the trigger state
<TriggerState.auto: 'AUTO'>
>>> # set timebase to 20 ns per division
>>> inst.time_div = u.Quantity(20, u.ns)
>>> # call the first oscilloscope channel
>>> channel = inst.channel[0]
>>> channel.trace = True # turn the trace on
>>> channel.coupling = channel.Coupling.dc50 # coupling to 50 Ohm
>>> channel.scale = u.Quantity(1, u.V) # vertical scale to 1V/division
>>> # transfer a waveform into xdat and ydat:
>>> xdat, ydat = channel.read_waveform()
"""
# CONSTANTS #
# number of horizontal and vertical divisions on the scope
# HOR_DIVS = 10
# VERT_DIVS = 8
def __init__(self, filelike):
super().__init__(filelike)
# turn off command headers -> for SCPI like behavior
self.sendcmd("COMM_HEADER OFF")
# constants
self._number_channels = 4
self._number_functions = 2
self._number_measurements = 6
# ENUMS #
[docs]
class MeasurementParameters(Enum):
"""
Enum containing valid measurement parameters that only require
one or more sources. Only single source parameters are currently
implemented.
"""
amplitude = "AMPL"
area = "AREA"
base = "BASE"
delay = "DLY"
duty_cycle = "DUTY"
fall_time_80_20 = "FALL82"
fall_time_90_10 = "FALL"
frequency = "FREQ"
maximum = "MAX"
minimum = "MIN"
mean = "MEAN"
none = "NULL"
overshoot_pos = "OVSP"
overshoot_neg = "OVSN"
peak_to_peak = "PKPK"
period = "PER"
phase = "PHASE"
rise_time_20_80 = "RISE28"
rise_time_10_90 = "RISE"
rms = "RMS"
stdev = "SDEV"
top = "TOP"
width_50_pos = "WID"
width_50_neg = "WIDN"
[docs]
class TriggerState(Enum):
"""
Enum containing valid trigger state for the oscilloscope.
"""
auto = "AUTO"
normal = "NORM"
single = "SINGLE"
stop = "STOP"
[docs]
class TriggerType(Enum):
"""Enum containing valid trigger state.
Availability depends on oscilloscope options. Please consult
your manual. Only simple types are currently included.
.. warning:: Some of the trigger types are untested and might
need further parameters in order to be appropriately set.
"""
dropout = "DROPOUT"
edge = "EDGE"
glitch = "GLIT"
interval = "INTV"
pattern = "PA"
runt = "RUNT"
slew_rate = "SLEW"
width = "WIDTH"
qualified = "TEQ"
tv = "TV"
[docs]
class TriggerSource(Enum):
"""Enum containing valid trigger sources.
This is an enum for the default values.
.. note:: This class is initialized like this for four channels,
which is the default setting. If you change the number of
channels, `TriggerSource` will be recreated using the
routine `_create_trigger_source_enum`. This will make
further channels available to you or remove channels that
are not present in your setup.
"""
c0 = "C1"
c1 = "C2"
c2 = "C3"
c3 = "C4"
ext = "EX"
ext5 = "EX5"
ext10 = "EX10"
etm10 = "ETM10"
line = "LINE"
def _create_trigger_source_enum(self):
"""Create an Enum for the trigger source class.
Needs to be dynamically generated, in case channel number
changes!
.. note:: Not all trigger sources are available on all scopes.
Please consult the manual for your oscilloscope.
"""
names = ["ext", "ext5", "ext10", "etm10", "line"]
values = ["EX", "EX5", "EX10", "ETM10", "LINE"]
# now add the channels
for it in range(self._number_channels):
names.append(f"c{it}")
values.append(f"C{it + 1}") # to send to scope
# create and store the enum
self.TriggerSource = Enum("TriggerSource", zip(names, values))
# CLASSES #
[docs]
class DataSource(Oscilloscope.DataSource):
"""
Class representing a data source (channel, math, ref) on a MAUI
oscilloscope.
.. warning:: This class should NOT be manually created by the
user. It is designed to be initialized by the `MAUI` class.
"""
# PROPERTIES #
@property
def name(self):
return self._name
# METHODS #
trace = bool_property(
command="TRA",
doc="""
Gets/Sets if a given trace is turned on or off.
Example usage:
>>> import instruments as ik
>>> address = "TCPIP0::192.168.0.10::INSTR"
>>> inst = inst = ik.teledyne.MAUI.open_visa(address)
>>> channel = inst.channel[0]
>>> channel.trace = False
""",
)
[docs]
class Channel(DataSource, Oscilloscope.Channel):
"""
Class representing a channel on a MAUI oscilloscope.
.. warning:: This class should NOT be manually created by the
user. It is designed to be initialized by the `MAUI` class.
"""
def __init__(self, parent, idx):
self._parent = parent
self._idx = idx + 1 # 1-based
# Initialize as a data source with name C{}.
super().__init__(self._parent, f"C{self._idx}")
# ENUMS #
[docs]
class Coupling(Enum):
"""
Enum containing valid coupling modes for the oscilloscope
channel. 1 MOhm and 50 Ohm are included.
"""
ac1M = "A1M"
dc1M = "D1M"
dc50 = "D50"
ground = "GND"
coupling = enum_property(
"CPL",
Coupling,
doc="""
Gets/sets the coupling for the specified channel.
Example usage:
>>> import instruments as ik
>>> address = "TCPIP0::192.168.0.10::INSTR"
>>> inst = inst = ik.teledyne.MAUI.open_visa(address)
>>> channel = inst.channel[0]
>>> channel.coupling = channel.Coupling.dc50
""",
)
# PROPERTIES #
@property
def offset(self):
"""
Sets/gets the vertical offset of the specified input
channel.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> channel = inst.channel[0] # set up channel
>>> channel.offset = u.Quantity(-1, u.V)
"""
return u.Quantity(float(self.query("OFST?")), u.V)
@offset.setter
def offset(self, newval):
newval = assume_units(newval, "V").to(u.V).magnitude
self.sendcmd(f"OFST {newval}")
@property
def scale(self):
"""
Sets/Gets the vertical scale of the channel.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> channel = inst.channel[0] # set up channel
>>> channel.scale = u.Quantity(20, u.mV)
"""
return u.Quantity(float(self.query("VDIV?")), u.V)
@scale.setter
def scale(self, newval):
newval = assume_units(newval, "V").to(u.V).magnitude
self.sendcmd(f"VDIV {newval}")
# METHODS #
[docs]
def sendcmd(self, cmd):
"""
Wraps commands sent from property factories in this class
with identifiers for the specified channel.
:param str cmd: Command to send to the instrument
"""
self._parent.sendcmd(f"C{self._idx}:{cmd}")
[docs]
def query(self, cmd, size=-1):
"""
Executes the given query. Wraps commands sent from property
factories in this class with identifiers for the specified
channel.
:param str cmd: String containing the query to
execute.
:param int size: Number of bytes to be read. Default is read
until termination character is found.
:return: The result of the query as returned by the
connected instrument.
:rtype: `str`
"""
return self._parent.query(f"C{self._idx}:{cmd}", size=size)
[docs]
class Math(DataSource):
"""
Class representing a function on a MAUI oscilloscope.
.. warning:: This class should NOT be manually created by the
user. It is designed to be initialized by the `MAUI` class.
"""
def __init__(self, parent, idx):
self._parent = parent
self._idx = idx + 1 # 1-based
# Initialize as a data source with name C{}.
super().__init__(self._parent, f"F{self._idx}")
# CLASSES #
[docs]
class Operators:
"""
Sets the operator for a given channel.
Most operators need a source `src`. If the source is given
as an integer, it is assume that the a signal channel is
requested. If you want to select another math channel for
example, you will need to specify the source as a tuple:
Example: `src=('f', 0)` would represent the first function
channel (called F1 in the MAUI manual). A channel could be
selected by calling `src=('c', 1)`, which would request the
second channel (oscilloscope channel 2). Please consult the
oscilloscope manual / the math setup itself for further
possibilities.
.. note:: Your oscilloscope might not have all functions
that are described here. Also: Not all possibilities are
currently implemented. However, extension of this
functionality should be simple when following the given
structure
"""
def __init__(self, parent):
self._parent = parent
# PROPERTIES #
@property
def current_setting(self):
"""
Gets the current setting and returns it as the full
command, as sent to the scope when setting an operator.
"""
return self._parent.query("DEF?")
# METHODS - OPERATORS #
[docs]
def absolute(self, src):
"""
Absolute of wave form.
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
send_str = f"'ABS({src_str})'"
self._send_operator(send_str)
[docs]
def average(self, src, average_type="summed", sweeps=1000):
"""
Average of wave form.
:param int,tuple src: Source, see info above
:param str average_type: `summed` or `continuous`
:param int sweeps: In summed mode, how many sweeps to
collect. In `continuous` mode the weight of each
sweep is equal to 1/`1`sweeps`
"""
src_str = _source(src)
avgtp_str = "SUMMED"
if average_type == "continuous":
avgtp_str = "CONTINUOUS"
send_str = "'AVG({})',AVERAGETYPE,{},SWEEPS,{}".format(
src_str, avgtp_str, sweeps
)
self._send_operator(send_str)
[docs]
def derivative(self, src, vscale=1e6, voffset=0, autoscale=True):
"""
Derivative of waveform using subtraction of adjacent
samples. If vscale and voffset are unitless, V/s are
assumed.
:param int,tuple src: Source, see info above
:param float vscale: vertical units to display (V/s)
:param float voffset: vertical offset (V/s)
:param bool autoscale: auto scaling of vscale, voffset?
"""
src_str = _source(src)
vscale = assume_units(vscale, u.V / u.s).to(u.V / u.s).magnitude
voffset = assume_units(voffset, u.V / u.s).to(u.V / u.s).magnitude
autoscale_str = "OFF"
if autoscale:
autoscale_str = "ON"
send_str = (
"'DERI({})',VERSCALE,{},VEROFFSET,{},"
"ENABLEAUTOSCALE,{}".format(src_str, vscale, voffset, autoscale_str)
)
self._send_operator(send_str)
[docs]
def difference(self, src1, src2, vscale_variable=False):
"""
Difference between two sources, `src1`-`src2`.
:param int,tuple src1: Source 1, see info above
:param int,tuple src2: Source 2, see info above
:param bool vscale_variable: Horizontal and vertical
scale for addition and subtraction must be
identical. Allow for variable vertical scale in
result?
"""
src1_str = _source(src1)
src2_str = _source(src2)
opt_str = "FALSE"
if vscale_variable:
opt_str = "TRUE"
send_str = "'{}-{}',VERSCALEVARIABLE,{}".format(
src1_str, src2_str, opt_str
)
self._send_operator(send_str)
[docs]
def envelope(self, src, sweeps=1000, limit_sweeps=True):
"""
Highest and lowest Y values at each X in N sweeps.
:param int,tuple src: Source, see info above
:param int sweeps: Number of sweeps
:param bool limit_sweeps: Limit the number of sweeps?
"""
src_str = _source(src)
send_str = "'EXTR({})',SWEEPS,{},LIMITNUMSWEEPS,{}".format(
src_str, sweeps, limit_sweeps
)
self._send_operator(send_str)
[docs]
def eres(self, src, bits=0.5):
"""
Smoothing function defined by extra bits of resolution.
:param int,tuple src: Source, see info above
:param float bits: Number of bits. Possible values are
(0.5, 1.0, 1.5, 2.0, 2.5, 3.0). If not in list,
default to 0.5.
"""
src_str = _source(src)
bits_possible = (0.5, 1.0, 1.5, 2.0, 2.5, 3.0)
if bits not in bits_possible:
bits = 0.5
send_str = f"'ERES({src_str})',BITS,{bits}"
self._send_operator(send_str)
[docs]
def fft(
self, src, type="powerspectrum", window="vonhann", suppress_dc=True
):
"""
Fast Fourier Transform of signal.
:param int,tuple src: Source, see info above
:param str type: Type of power spectrum. Possible
options are: ['real', 'imaginary', 'magnitude',
'phase', 'powerspectrum', 'powerdensity'].
Default: 'powerspectrum'
:param str window: Window. Possible options are:
['blackmanharris', 'flattop', 'hamming',
'rectangular', 'vonhann']. Default: 'vonhann'
:param bool suppress_dc: Supress DC?
"""
src_str = _source(src)
type_possible = [
"real",
"imaginary",
"magnitude",
"phase",
"powerspectrum",
"powerdensity",
]
if type not in type_possible:
type = "powerspectrum"
window_possible = [
"blackmanharris",
"flattop",
"hamming",
"rectangular",
"vonhann",
]
if window not in window_possible:
window = "vonhann"
if suppress_dc:
opt = "ON"
else:
opt = "OFF"
send_str = "'FFT({})',TYPE,{},WINDOW,{},SUPPRESSDC,{}".format(
src_str, type, window, opt
)
self._send_operator(send_str)
[docs]
def floor(self, src, sweeps=1000, limit_sweeps=True):
"""
Lowest vertical value at each X value in N sweeps.
:param int,tuple src: Source, see info above
:param int sweeps: Number of sweeps
:param bool limit_sweeps: Limit the number of sweeps?
"""
src_str = _source(src)
send_str = "'FLOOR({})',SWEEPS,{},LIMITNUMSWEEPS,{}".format(
src_str, sweeps, limit_sweeps
)
self._send_operator(send_str)
[docs]
def integral(self, src, multiplier=1, adder=0, vscale=1e-3, voffset=0):
"""
Integral of waveform.
:param int,tuple src: Source, see info above
:param float multiplier: 0 to 1e15
:param float adder: 0 to 1e15
:param float vscale: vertical units to display (Wb)
:param float voffset: vertical offset (Wb)
"""
src_str = _source(src)
vscale = assume_units(vscale, u.Wb).to(u.Wb).magnitude
voffset = assume_units(voffset, u.Wb).to(u.Wb).magnitude
send_str = (
"'INTG({}),MULTIPLIER,{},ADDER,{},VERSCALE,{},"
"VEROFFSET,{}".format(src_str, multiplier, adder, vscale, voffset)
)
self._send_operator(send_str)
[docs]
def invert(self, src):
"""
Inversion of waveform (-waveform).
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
self._send_operator(f"'-{src_str}'")
[docs]
def product(self, src1, src2):
"""
Product of two sources, `src1`*`src2`.
:param int,tuple src1: Source 1, see info above
:param int,tuple src2: Source 2, see info above
"""
src1_str = _source(src1)
src2_str = _source(src2)
send_str = f"'{src1_str}*{src2_str}'"
self._send_operator(send_str)
[docs]
def ratio(self, src1, src2):
"""
Ratio of two sources, `src1`/`src2`.
:param int,tuple src1: Source 1, see info above
:param int,tuple src2: Source 2, see info above
"""
src1_str = _source(src1)
src2_str = _source(src2)
send_str = f"'{src1_str}/{src2_str}'"
self._send_operator(send_str)
[docs]
def reciprocal(self, src):
"""
Reciprocal of waveform (1/waveform).
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
self._send_operator(f"'1/{src_str}'")
[docs]
def rescale(self, src, multiplier=1, adder=0):
"""
Rescales the waveform (w) in the style.
multiplier * w + adder
:param int,tuple src: Source, see info above
:param float multiplier: multiplier
:param float adder: addition in V or assuming V
"""
src_str = _source(src)
adder = assume_units(adder, u.V).to(u.V).magnitude
send_str = "'RESC({})',MULTIPLIER,{},ADDER,{}".format(
src_str, multiplier, adder
)
self._send_operator(send_str)
[docs]
def sinx(self, src):
"""
Sin(x)/x interpolation to produce 10x output samples.
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
self._send_operator(f"'SINX({src_str})'")
[docs]
def square(self, src):
"""
Square of the input waveform.
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
self._send_operator(f"'SQR({src_str})'")
[docs]
def square_root(self, src):
"""
Square root of the input waveform.
:param int,tuple src: Source, see info above
"""
src_str = _source(src)
self._send_operator(f"'SQRT({src_str})'")
[docs]
def sum(self, src1, src2):
"""
Product of two sources, `src1`+`src2`.
:param int,tuple src1: Source 1, see info above
:param int,tuple src2: Source 2, see info above
"""
src1_str = _source(src1)
src2_str = _source(src2)
send_str = f"'{src1_str}+{src2_str}'"
self._send_operator(send_str)
[docs]
def trend(self, src, vscale=1, center=0, autoscale=True):
"""
Trend of the values of a paramter
:param float vscale: vertical units to display (V)
:param float center: center (V)
"""
src_str = _source(src)
vscale = assume_units(vscale, u.V).to(u.V).magnitude
center = assume_units(center, u.V).to(u.V).magnitude
if autoscale:
auto_str = "ON"
else:
auto_str = "OFF"
send_str = (
"'TREND({})',VERSCALE,{},CENTER,{},"
"AUTOFINDSCALE,{}".format(src_str, vscale, center, auto_str)
)
self._send_operator(send_str)
[docs]
def roof(self, src, sweeps=1000, limit_sweeps=True):
"""
Highest vertical value at each X value in N sweeps.
:param int,tuple src: Source, see info above
:param int sweeps: Number of sweeps
:param bool limit_sweeps: Limit the number of sweeps?
"""
src_str = _source(src)
send_str = "'ROOF({})',SWEEPS,{},LIMITNUMSWEEPS,{}".format(
src_str, sweeps, limit_sweeps
)
self._send_operator(send_str)
def _send_operator(self, cmd):
"""
Set the operator in the scope.
"""
self._parent.sendcmd("{},{}".format("DEFINE EQN", cmd))
# PROPERTIES #
@property
def operator(self):
"""Get an operator object to set use to do math.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> channel = inst.channel[0] # set up channel
>>> # set up the first math function
>>> function = inst.math[0]
>>> function.trace = True # turn the trace on
>>> # set function to average the first oscilloscope channel
>>> function.operator.average(0)
"""
return self.Operators(self)
# METHODS #
[docs]
def clear_sweeps(self):
"""Clear the sweeps in a measurement."""
self._parent.clear_sweeps() # re-implemented because handy
[docs]
def sendcmd(self, cmd):
"""
Wraps commands sent from property factories in this class
with identifiers for the specified channel.
:param str cmd: Command to send to the instrument
"""
self._parent.sendcmd(f"F{self._idx}:{cmd}")
[docs]
def query(self, cmd, size=-1):
"""
Executes the given query. Wraps commands sent from property
factories in this class with identifiers for the specified
channel.
:param str cmd: String containing the query to
execute.
:param int size: Number of bytes to be read. Default is read
until termination character is found.
:return: The result of the query as returned by the
connected instrument.
:rtype: `str`
"""
return self._parent.query(f"F{self._idx}:{cmd}", size=size)
[docs]
class Measurement:
"""
Class representing a measurement on a MAUI oscilloscope.
.. warning:: This class should NOT be manually created by the
user. It is designed to be initialized by the `MAUI` class.
"""
def __init__(self, parent, idx):
self._parent = parent
self._idx = idx + 1 # 1-based
# CLASSES #
[docs]
class State(Enum):
"""
Enum class for Measurement Parameters. Required to turn it
on or off.
"""
statistics = "CUST,STAT"
histogram_icon = "CUST,HISTICON"
both = "CUST,BOTH"
off = "CUST,OFF"
# PROPERTIES #
measurement_state = enum_property(
command="PARM",
enum=State,
doc="""
Sets / Gets the measurement state. Valid values are
'statistics', 'histogram_icon', 'both', 'off'.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> msr1 = inst.measurement[0] # set up first measurement
>>> msr1.measurement_state = msr1.State.both # set to `both`
""",
)
@property
def statistics(self):
"""
Gets the statistics for the selected parameter. The scope
must be in `My_Measure` mode.
:return tuple: (average, low, high, sigma, sweeps)
:return type: (float, float, float, float, float)
"""
ret_str = self.query(f"PAST? CUST, P{self._idx}").rstrip().split(",")
# parse the return string -> put into dictionary:
ret_dict = {
ret_str[it]: ret_str[it + 1] for it in range(0, len(ret_str), 2)
}
try:
stats = (
float(ret_dict["AVG"]),
float(ret_dict["LOW"]),
float(ret_dict["HIGH"]),
float(ret_dict["SIGMA"]),
float(ret_dict["SWEEPS"]),
)
except ValueError: # some statistics did not return
raise ValueError(
"Some statistics did not return useful "
"values. The return string is {}. Please "
"ensure that statistics is properly turned "
"on.".format(ret_str)
)
return stats
# METHODS #
[docs]
def delete(self):
"""
Deletes the given measurement parameter.
"""
self.sendcmd(f"PADL {self._idx}")
[docs]
def set_parameter(self, param, src):
"""
Sets a given parameter that should be measured on this
given channel.
:param `inst.MeasurementParameters` param: The parameter
to set from the given enum list.
:param int,tuple src: Source, either as an integer if a
channel is requested (e.g., src=0 for Channel 1) or as
a tuple in the form, e.g., ('F', 1). Here 'F' refers
to a mathematical function and 1 would take the second
mathematical function `F2`.
:raises AttributeError: The chosen parameter is invalid.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> msr1 = inst.measurement[0] # set up first measurement
>>> # setup to measure the 10 - 90% rise time on first channel
>>> msr1.set_parameter(inst.MeasurementParameters.rise_time_10_90, 0)
"""
if not isinstance(param, self._parent.MeasurementParameters):
raise AttributeError(
"Parameter must be selected from {}.".format(
self._parent.MeasurementParameters
)
)
send_str = f"PACU {self._idx},{param.value},{_source(src)}"
self.sendcmd(send_str)
[docs]
def sendcmd(self, cmd):
"""
Wraps commands sent from property factories in this class
with identifiers for the specified channel.
:param str cmd: Command to send to the instrument
"""
self._parent.sendcmd(cmd)
[docs]
def query(self, cmd, size=-1):
"""
Executes the given query. Wraps commands sent from property
factories in this class with identifiers for the specified
channel.
:param str cmd: String containing the query to
execute.
:param int size: Number of bytes to be read. Default is read
until termination character is found.
:return: The result of the query as returned by the
connected instrument.
:rtype: `str`
"""
return self._parent.query(cmd, size=size)
# PROPERTIES #
@property
def channel(self):
"""
Gets an iterator or list for easy Pythonic access to the various
channel objects on the oscilloscope instrument.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> channel = inst.channel[0] # get first channel
"""
return ProxyList(self, self.Channel, range(self.number_channels))
@property
def math(self):
"""
Gets an iterator or list for easy Pythonic access to the various
math data sources objects on the oscilloscope instrument.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> math = inst.math[0] # get first math function
"""
return ProxyList(self, self.Math, range(self.number_functions))
@property
def measurement(self):
"""
Gets an iterator or list for easy Pythonic access to the various
measurement data sources objects on the oscilloscope instrument.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> msr = inst.measurement[0] # get first measurement parameter
"""
return ProxyList(self, self.Measurement, range(self.number_measurements))
@property
def ref(self):
raise NotImplementedError
# PROPERTIES
@property
def number_channels(self):
"""
Sets/Gets the number of channels available on the specific
oscilloscope. Defaults to 4.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.number_channel = 2 # for a oscilloscope with 2 channels
>>> inst.number_channel
2
"""
return self._number_channels
@number_channels.setter
def number_channels(self, newval):
self._number_channels = newval
# create new trigger source enum
self._create_trigger_source_enum()
@property
def number_functions(self):
"""
Sets/Gets the number of functions available on the specific
oscilloscope. Defaults to 2.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.number_functions = 4 # for a oscilloscope with 4 math functions
>>> inst.number_functions
4
"""
return self._number_functions
@number_functions.setter
def number_functions(self, newval):
self._number_functions = newval
@property
def number_measurements(self):
"""
Sets/Gets the number of measurements available on the specific
oscilloscope. Defaults to 6.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.number_measurements = 4 # for a oscilloscope with 4 measurements
>>> inst.number_measurements
4
"""
return self._number_measurements
@number_measurements.setter
def number_measurements(self, newval):
self._number_measurements = newval
@property
def self_test(self):
"""
Runs an oscilloscope's internal self test and returns the
result. The self-test includes testing the hardware of all
channels, the timebase and the trigger circuits.
Hardware failures are identified by a unique binary code in the
returned <status> number. A status of 0 indicates that no
failures occurred.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.self_test()
"""
# increase timeout x 10 to allow for enough time to test
self.timeout *= 10
retval = self.query("*TST?")
self.timeout /= 10
return retval
@property
def show_id(self):
"""
Gets the scope information and returns it. The response
comprises manufacturer, oscilloscope model, serial number,
and firmware revision level.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.show_id()
"""
return self.query("*IDN?")
@property
def show_options(self):
"""
Gets and returns oscilloscope options: installed software or
hardware that is additional to the standard instrument
configuration. The response consists of a series of response
fields listing all the installed options.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.show_options()
"""
return self.query("*OPT?")
@property
def time_div(self):
"""
Sets/Gets the time per division, modifies the timebase setting.
Unitful.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.time_div = u.Quantity(200, u.ns)
"""
return u.Quantity(float(self.query("TDIV?")), u.s)
@time_div.setter
def time_div(self, newval):
newval = assume_units(newval, "s").to(u.s).magnitude
self.sendcmd(f"TDIV {newval}")
# TRIGGER PROPERTIES
trigger_state = enum_property(
command="TRMD",
enum=TriggerState,
doc="""
Sets / Gets the trigger state. Valid values are are defined
in `TriggerState` enum class.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.trigger_state = inst.TriggerState.normal
""",
)
@property
def trigger_delay(self):
"""
Sets/Gets the trigger offset with respect to time zero (i.e.,
a horizontal shift). Unitful.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.trigger_delay = u.Quantity(60, u.ns)
"""
return u.Quantity(float(self.query("TRDL?")), u.s)
@trigger_delay.setter
def trigger_delay(self, newval):
newval = assume_units(newval, "s").to(u.s).magnitude
self.sendcmd(f"TRDL {newval}")
@property
def trigger_source(self):
"""Sets / Gets the trigger source.
.. note:: The `TriggerSource` class is dynamically generated
when the number of channels is switched. The above shown class
is only the default! Channels are added and removed, as
required.
.. warning:: If a trigger type is currently set on the
oscilloscope that is not implemented in this class,
setting the source will fail. The oscilloscope is set up
such that the the trigger type and source are set at the
same time. However, for convenience, these two properties
are split apart here.
:return: Trigger source.
:rtype: Member of `TriggerSource` class.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.trigger_source = inst.TriggerSource.ext # external trigger
"""
retval = self.query("TRIG_SELECT?").split(",")[2]
return self.TriggerSource(retval)
@trigger_source.setter
def trigger_source(self, newval):
curr_trig_typ = self.trigger_type
cmd = f"TRIG_SELECT {curr_trig_typ.value},SR,{newval.value}"
self.sendcmd(cmd)
@property
def trigger_type(self):
"""Sets / Gets the trigger type.
.. warning:: If a trigger source is currently set on the
oscilloscope that is not implemented in this class,
setting the source will fail. The oscilloscope is set up
such that the the trigger type and source are set at the
same time. However, for convenience, these two properties
are split apart here.
:return: Trigger type.
:rtype: Member of `TriggerType` enum class.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.trigger_type = inst.TriggerType.edge # trigger on edge
"""
retval = self.query("TRIG_SELECT?").split(",")[0]
return self.TriggerType(retval)
@trigger_type.setter
def trigger_type(self, newval):
curr_trig_src = self.trigger_source
cmd = f"TRIG_SELECT {newval.value},SR,{curr_trig_src.value}"
self.sendcmd(cmd)
# METHODS #
[docs]
def clear_sweeps(self):
"""Clears the sweeps in a measurement.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.clear_sweeps()
"""
self.sendcmd("CLEAR_SWEEPS")
[docs]
def force_trigger(self):
"""Forces a trigger event to occur on the attached oscilloscope.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.force_trigger()
"""
self.sendcmd("ARM")
[docs]
def run(self):
"""Enables the trigger for the oscilloscope and sets it to auto.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.run()
"""
self.trigger_state = self.TriggerState.auto
[docs]
def stop(self):
"""Disables the trigger for the oscilloscope.
Example:
>>> import instruments as ik
>>> import instruments.units as u
>>> inst = ik.teledyne.MAUI.open_visa("TCPIP0::192.168.0.10::INSTR")
>>> inst.stop()
"""
self.sendcmd("STOP")
# STATICS #
def _source(src):
"""Stich the source together properly and return it."""
if isinstance(src, int):
return f"C{src + 1}"
elif isinstance(src, tuple) and len(src) == 2:
return f"{src[0].upper()}{int(src[1]) + 1}"
else:
raise ValueError(
"An invalid source was specified. "
"Source must be an integer or a tuple of "
"length 2."
)