from __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
from qcodes.instrument import (
InstrumentBaseKWArgs,
InstrumentChannel,
VisaInstrument,
VisaInstrumentKWArgs,
)
from qcodes.parameters import (
Parameter,
ParameterWithSetpoints,
ParamRawDataType,
create_on_off_val_mapping,
)
from qcodes.validators import Arrays, Bool, Enum, Ints, Numbers
if TYPE_CHECKING:
from collections.abc import Callable
from typing_extensions import Unpack
class FrequencyAxis(Parameter):
def __init__(
self,
start: Parameter,
stop: Parameter,
npts: Parameter,
*args: Any,
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self._start: Parameter = start
self._stop: Parameter = stop
self._npts: Parameter = npts
def get_raw(self) -> ParamRawDataType:
start_val = self._start()
stop_val = self._stop()
npts_val = self._npts()
assert start_val is not None
assert stop_val is not None
assert npts_val is not None
return np.linspace(start_val, stop_val, npts_val)
class Trace(ParameterWithSetpoints):
def __init__(
self,
number: int,
*args: Any,
get_data: Callable[[int], ParamRawDataType],
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
# the parameter classes should ideally be generic in instrument
# and root instrument classes so we can specialize here.
# for now we have to ignore a type error from pyright
self.instrument: (
KeysightN9030BSpectrumAnalyzerMode | KeysightN9030BPhaseNoiseMode
)
self.root_instrument: KeysightN9030B
self.number = number
self.get_data = get_data
def get_raw(self) -> ParamRawDataType:
return self.get_data(self.number)
[docs]
class KeysightN9030BSpectrumAnalyzerMode(InstrumentChannel):
"""
Spectrum Analyzer Mode for Keysight N9030B instrument.
"""
def __init__(
self,
parent: KeysightN9030B,
name: str,
*arg: Any,
additional_wait: int = 1,
**kwargs: Unpack[InstrumentBaseKWArgs],
):
super().__init__(parent, name, *arg, **kwargs)
self._additional_wait = additional_wait
self._min_freq = -8e7
self._valid_max_freq: dict[str, float] = {
"503": 3.7e9,
"508": 8.5e9,
"513": 13.8e9,
"526": 27e9,
"544": 44.5e9,
}
opt: str | None = None
for hw_opt_for_max_freq in self._valid_max_freq:
if hw_opt_for_max_freq in self.root_instrument.options():
opt = hw_opt_for_max_freq
assert opt is not None
self._max_freq = self._valid_max_freq[opt]
# Frequency Parameters
self.start: Parameter = self.add_parameter(
name="start",
unit="Hz",
get_cmd=":SENSe:FREQuency:STARt?",
set_cmd=self._set_start,
get_parser=float,
vals=Numbers(self._min_freq, self._max_freq - 10),
docstring="Start Frequency",
)
"""Start Frequency"""
self.stop: Parameter = self.add_parameter(
name="stop",
unit="Hz",
get_cmd=":SENSe:FREQuency:STOP?",
set_cmd=self._set_stop,
get_parser=float,
vals=Numbers(self._min_freq + 10, self._max_freq),
docstring="Stop Frequency",
)
"""Stop Frequency"""
self.center: Parameter = self.add_parameter(
name="center",
unit="Hz",
get_cmd=":SENSe:FREQuency:CENTer?",
set_cmd=self._set_center,
get_parser=float,
vals=Numbers(self._min_freq + 5, self._max_freq - 5),
docstring="Sets and gets center frequency",
)
"""Sets and gets center frequency"""
self.span: Parameter = self.add_parameter(
name="span",
unit="Hz",
get_cmd=":SENSe:FREQuency:SPAN?",
set_cmd=self._set_span,
get_parser=float,
vals=Numbers(10, self._max_freq - self._min_freq),
docstring="Changes span of frequency",
)
"""Changes span of frequency"""
self.npts: Parameter = self.add_parameter(
name="npts",
get_cmd=":SENSe:SWEep:POINts?",
set_cmd=":SENSe:SWEep:POINts {}",
get_parser=int,
vals=Ints(1, 20001),
docstring="Number of points for the sweep",
)
"""Number of points for the sweep"""
# Amplitude/Input Parameters
self.mech_attenuation: Parameter = self.add_parameter(
name="mech_attenuation",
unit="dB",
get_cmd=":SENS:POW:ATT?",
set_cmd=":SENS:POW:ATT {}",
get_parser=int,
vals=Ints(0, 70),
docstring="Internal mechanical attenuation",
)
"""Internal mechanical attenuation"""
self.preamp: Parameter = self.add_parameter(
name="preamp",
get_cmd=":SENS:POW:GAIN:BAND?",
set_cmd=":SENS:POW:GAIN:BAND {}",
vals=Enum("LOW", "FULL"),
docstring="Preamplifier selection",
)
"""Preamplifier selection"""
self.preamp_enabled: Parameter = self.add_parameter(
name="preamp_enabled",
get_cmd=":SENS:POW:GAIN:STAT?",
set_cmd=":SENS:POW:GAIN:STAT {}",
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
docstring="Preamplifier state",
)
"""Preamplifier state"""
# Resolution parameters
self.res_bw: Parameter = self.add_parameter(
name="res_bw",
unit="Hz",
get_cmd=":SENS:BAND:RES?",
set_cmd=":SENS:BAND:RES {}",
get_parser=float,
vals=Numbers(1, 8e6),
docstring="Resolution Bandwidth",
)
"""Resolution Bandwidth"""
self.video_bw: Parameter = self.add_parameter(
name="video_bw",
unit="Hz",
get_cmd=":SENS:BAND:VID?",
set_cmd=":SENS:BAND:VID {}",
get_parser=float,
vals=Numbers(1, 50e6),
docstring="Video Filter Bandwidth",
)
"""Video Filter Bandwidth"""
self.res_bw_type: Parameter = self.add_parameter(
name="res_bw_type",
get_cmd=":SENS:BAND:TYPE?",
set_cmd=":SENS:BAND:TYPE {}",
vals=Enum("DB3", "DB6", "IMP", "NOISE"),
docstring=(
"The instrument provides four ways of specifying the "
"bandwidth of a Gaussian filter:\n"
" 1. The -3 dB bandwidth of the filter (DB3)\n"
" 2. The -6 dB bandwidth of the filter (DB6)\n"
" 3. The equivalent Noise bandwidth of the filter, "
"which is defined as the bandwidth of a rectangular "
"filter with the same peak gain which would pass the "
"same power for noise signals\n"
" 4. The equivalent Impulse bandwidth of the filter, "
"which is defined as the bandwidth of a rectangular "
"filter with the same peak gain which would pass the "
"same power for impulsive (narrow pulsed) signals."
),
)
"""
The instrument provides four ways of specifying the bandwidth
of a Gaussian filter:
1. The -3 dB bandwidth of the filter (DB3)
2. The -6 dB bandwidth of the filter (DB6)
3. The equivalent Noise bandwidth of the filter,
which is defined as the bandwidth of a rectangular
filter with the same peak gain which would pass the
same power for noise signals
4. The equivalent Impulse bandwidth of the filter,
which is defined as the bandwidth of a rectangular filter
with the same peak gain which would pass the same power
for impulsive (narrow pulsed) signals.
"""
# Input parameters
self.detector: Parameter = self.add_parameter(
name="detector",
get_cmd=":SENS:DET:TRAC?",
set_cmd=":SENS:DET:TRAC {}",
vals=Enum("NORM", "AVER", "POS", "SAMP", "NEG"),
docstring="Detector type",
)
"""Detector type"""
self.average_type: Parameter = self.add_parameter(
name="average_type",
get_cmd=":SENS:AVER:TYPE?",
set_cmd=":SENS:AVER:TYPE {}",
vals=Enum("LOG", "RMS", "SCAL"),
docstring=(
"Lets you control the way averaging is done. The averaging processes "
"affected are:\n"
" 1. Trace averaging\n"
" 2. Average detector averages signals within the resolution BW\n"
" 3. Noise marker is corrected for average type\n"
" 4. VBW filtering (not affected if Average detector is used).\n"
"The averaging types are:"
" 1. LOG: Selects the logarithmic (decibel) scale for all filtering and "
"averaging processes. This scale is sometimes called 'Video' because it "
"is the most common display and analysis scale for the video signal "
"within a spectrum instrument. This scale is excellent for finding CW "
"signals near noise, but its response to noise-like signals is 2.506 dB "
"lower than the average power of those noise signals. This is compensated "
"for in the Marker Noise function.\n"
" 2. RMS: All filtering and averaging processes work on the power (the square "
"of the magnitude) of the signal, instead of its log or envelope voltage. This "
"scale is best for measuring the true time average power of complex signals. "
"This scale is sometimes called RMS because the resulting voltage is proportional "
"to the square root of the mean of the square of the voltage.\n"
" 3. SCAL: (Voltage) All filtering and averaging processes work on the voltage "
"of the envelope of the signal. This scale is good for observing rise and fall "
"behavior of AM or pulse-modulated signals such as radar and TDMA transmitters, "
"but its response to noise-like signals is 1.049 dB lower than the average power "
"of those noise signals. This is compensated for in the Marker Noise function."
),
)
"""
Lets you control the way averaging is done. The averaging
processes affected are:
1. Trace averaging
2. Average detector averages signals within the resolution BW
3. Noise marker is corrected for average type
4. VBW filtering (not affected if Average detector is used).
The averaging types are:
1. LOG: Selects the logarithmic (decibel) scale for all filtering
and averaging processes. This scale is sometimes called
'Video' because it is the most common display and analysis
scale for the video signal within a spectrum instrument.
This scale is excellent for finding CW signals near noise,
but its response to noise-like signals is 2.506 dB lower
than the average power of those noise signals.
This is compensated for in the Marker Noise function.
2. RMS: All filtering and averaging processes work on the power
(the square of the magnitude) of the signal, instead of its
log or envelope voltage. This scale is best for measuring
the true time average power of complex signals. This scale
is sometimes called RMS because the resulting voltage is
proportional to the square root of the mean of the square
of the voltage.
3. SCAL: (Voltage) All filtering and averaging processes
work on the voltage of the envelope of the signal.
This scale is good for observing rise and fall behavior
of AM or pulse-modulated signals such as radar and TDMA transmitters,
but its response to noise-like signals is 1.049 dB lower than
the average power of those noise signals. This is compensated
for in the Marker Noise function."""
# Sweep Parameters
self.sweep_time: Parameter = self.add_parameter(
name="sweep_time",
label="Sweep time",
get_cmd=":SENSe:SWEep:TIME?",
set_cmd=":SENSe:SWEep:TIME {}",
get_parser=float,
unit="s",
docstring="gets sweep time",
)
"""gets sweep time"""
self.auto_sweep_time_enabled: Parameter = self.add_parameter(
name="auto_sweep_time_enabled",
get_cmd=":SENSe:SWEep:TIME:AUTO?",
set_cmd=":SENSe:SWEep:TIME:AUTO {}",
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
docstring="enables auto sweep time",
)
"""enables auto sweep time"""
self.auto_sweep_type_enabled: Parameter = self.add_parameter(
name="auto_sweep_type_enabled",
get_cmd=":SENSe:SWEep:TYPE:AUTO?",
set_cmd=":SENSe:SWEep:TYPE:AUTO {}",
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
docstring="enables auto sweep type",
)
"""enables auto sweep type"""
self.sweep_type: Parameter = self.add_parameter(
name="sweep_type",
get_cmd=":SENSe:SWEep:TYPE?",
set_cmd=":SENSe:SWEep:TYPE {}",
val_mapping={
"fft": "FFT",
"sweep": "SWE",
},
docstring="Sets up sweep type. Possible options are 'fft' and 'sweep'.",
)
"""Sets up sweep type. Possible options are 'fft' and 'sweep'."""
# Array (Data) Parameters
self.freq_axis: FrequencyAxis = self.add_parameter(
name="freq_axis",
label="Frequency",
unit="Hz",
start=self.start,
stop=self.stop,
npts=self.npts,
vals=Arrays(shape=(self.npts.get_latest,)),
parameter_class=FrequencyAxis,
docstring="Creates frequency axis for the sweep from start, "
"stop and npts values.",
)
"""Creates frequency axis for the sweep from start, stop and npts values."""
self.trace: Trace = self.add_parameter(
name="trace",
label="Trace",
unit="dB",
number=1,
vals=Arrays(shape=(self.npts.get_latest,)),
setpoints=(self.freq_axis,),
get_data=self._get_data,
parameter_class=Trace,
docstring="Gets trace data.",
)
"""Gets trace data."""
def _set_start(self, val: float) -> None:
"""
Sets start frequency
"""
self.write(f":SENSe:FREQuency:STARt {val}")
self.update_trace()
start = self.start.cache.get()
if abs(val - start) >= 1:
self.log.warning(f"Start frequency rounded to {start}")
def _set_stop(self, val: float) -> None:
"""
Sets stop frequency
"""
self.write(f":SENSe:FREQuency:STOP {val}")
self.update_trace()
stop = self.stop.cache.get()
if abs(val - stop) >= 1:
self.log.warning(f"Stop frequency rounded to {stop}")
def _set_center(self, val: float) -> None:
"""
Sets center frequency and updates start and stop frequencies if they
change.
"""
self.write(f":SENSe:FREQuency:CENTer {val}")
self.update_trace()
def _set_span(self, val: float) -> None:
"""
Sets frequency span and updates start and stop frequencies if they
change.
"""
self.write(f":SENSe:FREQuency:SPAN {val}")
self.update_trace()
def _get_data(self, trace_num: int) -> ParamRawDataType:
"""
Gets data from the measurement.
"""
root_instr = self.root_instrument
# Check if we should run a new sweep
auto_sweep = root_instr.auto_sweep()
if auto_sweep:
# If we need to run a sweep, we need to set the timeout to take into account
# the sweep time
timeout = self.sweep_time() + self._additional_wait
with root_instr.timeout.set_to(timeout):
data = root_instr.visa_handle.query_binary_values(
f":READ:{root_instr.measurement()}{trace_num}?",
datatype="d",
is_big_endian=False,
)
else:
data = root_instr.visa_handle.query_binary_values(
f":FETC:{root_instr.measurement()}{trace_num}?",
datatype="d",
is_big_endian=False,
)
data = np.array(data).reshape((-1, 2))
return data[:, 1]
[docs]
def update_trace(self) -> None:
"""
Updates all frequency parameters together when one is changed
"""
self.start()
self.stop()
self.span()
self.center()
[docs]
def setup_swept_sa_sweep(self, start: float, stop: float, npts: int) -> None:
"""
Sets up the Swept SA measurement sweep for Spectrum Analyzer Mode.
"""
self.root_instrument.mode("SA")
if "SAN" in self.root_instrument.available_meas():
self.root_instrument.measurement("SAN")
else:
raise RuntimeError(
"Swept SA measurement is not available on your "
"Keysight N9030B instrument with Spectrum "
"Analyzer mode."
)
self.start(start)
self.stop(stop)
self.npts(npts)
[docs]
def autotune(self) -> None:
"""
Autotune quickly get to the most likely signal of interest, and
position it optimally on the display.
"""
self.write(":SENS:FREQuency:TUNE:IMMediate")
self.center()
[docs]
class KeysightN9030BPhaseNoiseMode(InstrumentChannel):
"""
Phase Noise Mode for Keysight N9030B instrument.
"""
def __init__(
self,
parent: KeysightN9030B,
name: str,
*arg: Any,
**kwargs: Unpack[InstrumentBaseKWArgs],
):
super().__init__(parent, name, *arg, **kwargs)
self._min_freq = 1
self._valid_max_freq: dict[str, float] = {
"503": 3699999995,
"508": 8499999995,
"513": 13799999995,
"526": 26999999995,
"544": 44499999995,
}
opt: str | None = None
for hw_opt_for_max_freq in self._valid_max_freq:
if hw_opt_for_max_freq in self.root_instrument.options():
opt = hw_opt_for_max_freq
assert opt is not None
self._max_freq = self._valid_max_freq[opt]
self.npts: Parameter = self.add_parameter(
name="npts",
get_cmd=":SENSe:LPLot:SWEep:POINts?",
set_cmd=":SENSe:LPLot:SWEep:POINts {}",
get_parser=int,
vals=Ints(601, 20001),
docstring="Number of points for the sweep",
)
"""Number of points for the sweep"""
self.start_offset: Parameter = self.add_parameter(
name="start_offset",
unit="Hz",
get_cmd=":SENSe:LPLot:FREQuency:OFFSet:STARt?",
set_cmd=self._set_start_offset,
get_parser=float,
vals=Numbers(self._min_freq, self._max_freq - 10),
docstring="start frequency offset for the plot",
)
"""start frequency offset for the plot"""
self.stop_offset: Parameter = self.add_parameter(
name="stop_offset",
unit="Hz",
get_cmd=":SENSe:LPLot:FREQuency:OFFSet:STOP?",
set_cmd=self._set_stop_offset,
get_parser=float,
vals=Numbers(self._min_freq + 99, self._max_freq),
docstring="stop frequency offset for the plot",
)
"""stop frequency offset for the plot"""
self.signal_tracking_enabled: Parameter = self.add_parameter(
name="signal_tracking_enabled",
get_cmd=":SENSe:FREQuency:CARRier:TRACk?",
set_cmd=":SENSe:FREQuency:CARRier:TRACk {}",
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
docstring="Gets/Sets signal tracking. When signal tracking is "
"enabled carrier signal is repeatedly realigned. Signal "
"Tracking assumes the new acquisition occurs repeatedly "
"without pause.",
)
"""
Gets/Sets signal tracking.
When signal tracking is enabled carrier signal is repeatedly
realigned. Signal Tracking assumes the new acquisition occurs
repeatedly without pause.
"""
self.freq_axis: FrequencyAxis = self.add_parameter(
name="freq_axis",
label="Frequency",
unit="Hz",
start=self.start_offset,
stop=self.stop_offset,
npts=self.npts,
vals=Arrays(shape=(self.npts.get_latest,)),
parameter_class=FrequencyAxis,
docstring="Creates frequency axis for the sweep from "
"start_offset, stop_offset and npts values.",
)
"""
Creates frequency axis for the sweep from start_offset,
stop_offset and npts values.
"""
self.trace: Trace = self.add_parameter(
name="trace",
label="Trace",
unit="dB",
number=3,
vals=Arrays(shape=(self.npts.get_latest,)),
setpoints=(self.freq_axis,),
get_data=self._get_data,
parameter_class=Trace,
docstring="Gets trace data",
)
"""Gets trace data"""
def _set_start_offset(self, val: float) -> None:
"""
Sets start offset for frequency in the plot
"""
stop_offset = self.stop_offset()
self.write(f":SENSe:LPLot:FREQuency:OFFSet:STARt {val}")
start_offset = self.start_offset()
if abs(val - start_offset) >= 1:
self.log.warning(
f"Could not set start offset to {val} setting it to {start_offset}"
)
if val >= stop_offset or abs(val - stop_offset) < 10:
self.log.warning(
f"Provided start frequency offset {val} Hz was "
f"greater than preset stop frequency offset "
f"{stop_offset} Hz. Provided start frequency "
f"offset {val} Hz is set and new stop freq offset"
f" is: {self.stop_offset()} Hz."
)
def _set_stop_offset(self, val: float) -> None:
"""
Sets stop offset for frequency in the plot
"""
start_offset = self.start_offset()
self.write(f":SENSe:LPLot:FREQuency:OFFSet:STOP {val}")
stop_offset = self.stop_offset()
if abs(val - stop_offset) >= 1:
self.log.warning(
f"Could not set stop offset to {val} setting it to {stop_offset}"
)
if val <= start_offset or abs(val - start_offset) < 10:
self.log.warning(
f"Provided stop frequency offset {val} Hz was "
f"less than preset start frequency offset "
f"{start_offset} Hz. Provided stop frequency "
f"offset {val} Hz is set and new start freq offset"
f" is: {self.start_offset()} Hz."
)
def _get_data(self, trace_num: int) -> ParamRawDataType:
"""
Gets data from the measurement.
"""
root_instr = self.root_instrument
measurement = root_instr.measurement()
raw_data = root_instr.visa_handle.query_binary_values(
f":READ:{measurement}1?",
datatype="d",
is_big_endian=False,
)
trace_res_details = np.array(raw_data)
if len(trace_res_details) != 7 or (
len(trace_res_details) >= 1 and trace_res_details[0] < -50
):
self.log.warning("Carrier(s) Incorrect or Missing!")
return -1 * np.ones(self.npts())
try:
data = root_instr.visa_handle.query_binary_values(
f":READ:{measurement}{trace_num}?",
datatype="d",
is_big_endian=False,
)
data = np.array(data).reshape((-1, 2))
except TimeoutError as e:
raise TimeoutError("Couldn't receive any data. Command timed out.") from e
return data[:, 1]
[docs]
def setup_log_plot_sweep(
self, start_offset: float, stop_offset: float, npts: int
) -> None:
"""
Sets up the Log Plot measurement sweep for Phase Noise Mode.
"""
self.root_instrument.mode("PNOISE")
if "LPL" in self.root_instrument.available_meas():
self.root_instrument.measurement("LPL")
else:
raise RuntimeError(
"Log Plot measurement is not available on your "
"Keysight N9030B instrument with Phase Noise "
"mode."
)
self.start_offset(start_offset)
self.stop_offset(stop_offset)
self.npts(npts)
[docs]
def autotune(self) -> None:
"""
On autotune, the measurement automatically searches for and tunes to
the strongest signal in the full span of the analyzer.
"""
self.write(":SENSe:FREQuency:CARRier:SEARch")
self.start_offset()
self.stop_offset()
[docs]
class KeysightN9030B(VisaInstrument):
"""
Driver for Keysight N9030B PXA signal analyzer. Keysight N9030B PXA
signal analyzer is part of Keysight X-Series Multi-touch Signal
Analyzers.
This driver allows Swept SA measurements in Spectrum Analyzer mode and
Log Plot measurements in Phase Noise mode of the instrument.
Args:
name
address
"""
default_terminator = "\n"
def __init__(
self, name: str, address: str, **kwargs: Unpack[VisaInstrumentKWArgs]
) -> None:
super().__init__(name, address, **kwargs)
self._min_freq: float
self._max_freq: float
self._additional_wait: float = 1
self.mode: Parameter = self.add_parameter(
name="mode",
get_cmd=":INSTrument:SELect?",
set_cmd=":INSTrument:SELect {}",
vals=Enum(*self.available_modes()),
docstring="Allows setting of different modes present and licensed "
"for the instrument.",
)
"""
Allows setting of different modes present and licensed
for the instrument.
"""
self.measurement: Parameter = self.add_parameter(
name="measurement",
get_cmd=":CONFigure?",
set_cmd=":CONFigure:{}",
vals=Enum("SAN", "LPL"),
docstring="Sets measurement type from among the available "
"measurement types.",
)
"""Sets measurement type from among the available measurement types."""
self.cont_meas: Parameter = self.add_parameter(
name="cont_meas",
initial_value=False,
get_cmd=":INITiate:CONTinuous?",
set_cmd=":INITiate:CONTinuous {}",
val_mapping=create_on_off_val_mapping(on_val=1, off_val=0),
docstring="Enables or disables continuous measurement.",
)
"""Enables or disables continuous measurement."""
# Set auto_sweep parameter
# If we want to return multiple traces per setpoint without sweeping
# multiple times, or return data on screen, then we can set this value false
self.auto_sweep: Parameter = self.add_parameter(
"auto_sweep",
label="Auto Sweep",
set_cmd=None,
get_cmd=None,
vals=Bool(),
initial_value=True,
)
"""Parameter auto_sweep"""
# Set binary format and don't allow change. There isn't much point to
# allow this value to be varied. Retained for backwards compatibility.
self.format: Parameter = self.add_parameter(
name="format",
get_cmd=lambda: "real64",
set_cmd=False,
docstring="Sets up format of data received",
)
"""Sets up format of data received"""
# Set default format on initialisation
self.write("FORM REAL,64")
self.write("FORM:BORD SWAP")
if "SA" in self.available_modes():
sa_mode = KeysightN9030BSpectrumAnalyzerMode(
self, name="sa", additional_wait=self._additional_wait
)
self.add_submodule("sa", sa_mode)
else:
self.log.info("Spectrum Analyzer mode is not available on this instrument.")
if "PNOISE" in self.available_modes():
pnoise_mode = KeysightN9030BPhaseNoiseMode(self, name="pn")
self.add_submodule("pn", pnoise_mode)
else:
self.log.info("Phase Noise mode is not available on this instrument.")
self.connect_message()
[docs]
def available_modes(self) -> tuple[str, ...]:
"""
Returns present and licensed modes for the instrument.
"""
available_modes = self.ask(":INSTrument:CATalog?")
av_modes = available_modes[1:-1].split(",")
modes: tuple[str, ...] = ()
for i, mode in enumerate(av_modes):
if i == 0:
modes = (*modes, mode.split(" ")[0])
else:
modes = (*modes, mode.split(" ")[1])
return modes
[docs]
def available_meas(self) -> tuple[str, ...]:
"""
Gives available measurement with a given mode for the instrument
"""
available_meas = self.ask(":CONFigure:CATalog?")
av_meas = available_meas[1:-1].split(",")
measurements: tuple[str, ...] = ()
for i, meas in enumerate(av_meas):
if i == 0:
measurements = (*measurements, meas)
else:
measurements = (*measurements, meas[1:])
return measurements
[docs]
def options(self) -> tuple[str, ...]:
"""
Returns installed options numbers.
"""
options_raw = self.ask("*OPT?")
return tuple(options_raw[1:-1].split(","))
[docs]
def reset(self) -> None:
"""
Reset the instrument by sending the RST command
"""
self.write("*RST")
[docs]
def abort(self) -> None:
"""
Aborts the measurement
"""
self.write(":ABORt")