import ctypes as ct
import logging
from enum import IntEnum
from time import sleep
from typing import TYPE_CHECKING, Any
import numpy as np
import qcodes.validators as vals
from qcodes.instrument import Instrument, InstrumentBaseKWArgs
from qcodes.parameters import ArrayParameter, Parameter, ParameterWithSetpoints
if TYPE_CHECKING:
from typing_extensions import Unpack
log = logging.getLogger(__name__)
class TraceParameter(Parameter):
"""
A parameter that used a flag on the instrument to keeps track of if it's
value has been synced to the instrument. It is intended that this
type of parameter is synced using an external method which resets the flag.
This is most likely used similar to a ``ManualParameter``
I.e. calling set/get will not communicate with the instrument.
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
def set_raw(self, value: Any) -> None:
if not isinstance(self.instrument, SignalHoundUSBSA124B):
raise RuntimeError(
"TraceParameter only works with 'SignalHound_USB_SA124B'"
)
self.instrument._parameters_synced = False
class ExternalRefParameter(TraceParameter):
"""
Parameter that handles the fact that external reference can only be
enabled but not disabled.
From the manual:
Once a device has successfully switched to an external reference it
must remain using it until the device is closed, and it is undefined
behavior to disconnect the reference input from the reference BNC port.
"""
def set_raw(self, value: bool) -> None:
if self.get_latest() is True and value is False:
raise RuntimeError(
"Signal Hound does not support disabling "
"external reference. To switch back to internal "
"reference close the device and start again."
)
super().set_raw(value)
class ScaleParameter(TraceParameter):
"""
Parameter that handles changing the unit when the scale is changed.
"""
def set_raw(self, value: str) -> None:
if not isinstance(self.instrument, SignalHoundUSBSA124B):
raise RuntimeError(
"ScaleParameter only works with 'SignalHound_USB_SA124B'"
)
if value in ("log-scale", "log-full-scale"):
unit = "dBm"
elif value in ("lin-scale", "lin-full-scale"):
unit = "mV"
else:
raise RuntimeError("Unsupported scale")
self.instrument.trace.unit = unit
self.instrument.power.unit = unit
super().set_raw(value)
class SweepTraceParameter(TraceParameter):
"""
An extension to TraceParameter that keeps track of the trace setpoints in
addition to the functionality of `TraceParameter`
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
def set_raw(self, value: Any) -> None:
if not isinstance(self.instrument, SignalHoundUSBSA124B):
raise RuntimeError(
"SweepTraceParameter only works with 'SignalHound_USB_SA124B'"
)
self.instrument._trace_updated = False
super().set_raw(value)
class FrequencySweep(ArrayParameter):
"""
Hardware controlled parameter class for SignalHound_USB_SA124B.
Instrument returns an array of powers for different frequencies
Args:
name: parameter name
instrument: instrument the parameter belongs to
sweep_len: Number of steps in sweep
start_freq: Starting frequency
stepsize: Size of a frequency step
Methods:
get(): executes a sweep and returns magnitude and phase arrays
"""
def __init__(
self,
name: str,
instrument: "SignalHoundUSBSA124B",
sweep_len: int,
start_freq: float,
stepsize: float,
**kwargs: Any,
) -> None:
super().__init__(
name,
shape=(sweep_len,),
instrument=instrument,
unit="dBm",
label="Magnitude",
setpoint_units=("Hz",),
setpoint_labels=("Frequency",),
setpoint_names=("frequency",),
**kwargs,
)
self.set_sweep(sweep_len, start_freq, stepsize)
def set_sweep(self, sweep_len: int, start_freq: float, stepsize: float) -> None:
"""
Set the setpoints of the Array parameter representing a frequency
sweep.
Args:
sweep_len: Number of points in the sweep
start_freq: Starting frequency of the sweep
stepsize: Size of step between individual points
"""
if not isinstance(self.instrument, SignalHoundUSBSA124B):
raise RuntimeError(
"'FrequencySweep' is only implemented for 'SignalHound_USB_SA124B'"
)
end_freq = start_freq + stepsize * (sweep_len - 1)
freq_points = tuple(np.linspace(start_freq, end_freq, sweep_len))
self.setpoints = (freq_points,)
self.shape = (sweep_len,)
self.instrument._trace_updated = True
def get_raw(self) -> np.ndarray:
if self.instrument is None:
raise RuntimeError("No instrument is attached to 'FrequencySweep'")
if not isinstance(self.instrument, SignalHoundUSBSA124B):
raise RuntimeError(
"'FrequencySweep' is only implemented for 'SignalHound_USB_SA124B'"
)
if not self.instrument._trace_updated:
raise RuntimeError("trace not updated, run configure to update")
data = self.instrument._get_sweep_data()
sleep(2 * self.instrument.sleep_time.get())
return data
[docs]
class SignalHoundUSBSA124B(Instrument):
"""
QCoDeS driver for the SignalHound USB SA124B
The driver needs Signal Hounds software
`Spike <https://signalhound.com/spike/>`_ installed to function.
In addition, you may need to install Microsoft Visual Studio C++
Redistributable for the driver to function within QCoDeS.
At the time of writing the current version of Spike (3.2.3) uses
`Microsoft Visual Studio C++ Redistributable 2012
<https://www.microsoft.com/en-us/download/details.aspx?id=30679>`_
"""
dll_path = "C:\\Program Files\\Signal Hound\\Spike\\sa_api.dll"
def __init__(
self,
name: str,
dll_path: str | None = None,
**kwargs: "Unpack[InstrumentBaseKWArgs]",
):
"""
Args:
name: Name of the instrument.
dll_path: Path to ``sa_api.dll`` Defaults to the default dll within
Spike installation
**kwargs: kwargs are forwarded to base class.
"""
super().__init__(name, **kwargs)
self._parameters_synced = False
self._trace_updated = False
log.info("Initializing instrument SignalHound USB 124B")
self.dll = ct.CDLL(dll_path or self.dll_path)
self._set_ctypes_argtypes()
self.hf = Constants
self.frequency: SweepTraceParameter = self.add_parameter(
"frequency",
label="Frequency",
unit="Hz",
initial_value=5e9,
vals=vals.Numbers(),
parameter_class=SweepTraceParameter,
docstring="Center frequency for sweep. "
"This is the set center, the actual "
"center may be subject to round off "
"compared to this value",
)
"""
Center frequency for sweep.
This is the set center, the actual center may be subject to round off compared to this value
"""
self.span: SweepTraceParameter = self.add_parameter(
"span",
label="Span",
unit="Hz",
initial_value=0.25e6,
vals=vals.Numbers(),
parameter_class=SweepTraceParameter,
docstring="Width of frequency span. "
"This is the set span, the actual "
"span may be subject to round off "
"compared to this value",
)
"""Width of frequency span.
This is the set span, the actual span may be subject to round off compared to this value
"""
self.npts: Parameter = self.add_parameter(
"npts",
label="Number of Points",
get_cmd=self._get_npts,
set_cmd=False,
docstring="Number of points in frequency sweep.",
)
"""Number of points in frequency sweep."""
self.avg: Parameter = self.add_parameter(
"avg",
label="Averages",
initial_value=1,
get_cmd=None,
set_cmd=None,
vals=vals.Ints(),
docstring="Number of averages to perform. "
"Averages are performed in software by "
"acquiring multiple sweeps",
)
"""Number of averages to perform.
Averages are performed in software by acquiring multiple sweeps"""
self.ref_lvl: TraceParameter = self.add_parameter(
"ref_lvl",
label="Reference power",
unit="dBm",
initial_value=0,
vals=vals.Numbers(max_value=20),
parameter_class=TraceParameter,
docstring="Setting reference level will "
"automatically select gain and attenuation "
"optimal for measuring at and below "
"this level",
)
"""
Setting reference level will automatically select gain and attenuation
optimal for measuring at and below this level
"""
self.external_reference: ExternalRefParameter = self.add_parameter(
"external_reference",
initial_value=False,
vals=vals.Bool(),
parameter_class=ExternalRefParameter,
docstring="Use an external 10 MHz reference source. "
"Note that Signal Hound does not support "
"disabling external ref. To disable close "
"the connection and restart.",
)
"""
Use an external 10 MHz reference source.
Note that Signal Hound does not support disabling external ref.
To disable close the connection and restart.
"""
self.device_type: Parameter = self.add_parameter(
"device_type", set_cmd=False, get_cmd=self._get_device_type
)
"""Parameter device_type"""
self.device_mode: Parameter = self.add_parameter(
"device_mode",
get_cmd=lambda: "sweeping",
set_cmd=False,
docstring="The driver currently only "
"supports sweeping mode. "
"It is therefor not possible "
"to set this parameter to anything else",
)
"""
The driver currently only supports sweeping mode.
It is therefor not possible to set this parameter to anything else
"""
self.acquisition_mode: Parameter = self.add_parameter(
"acquisition_mode",
get_cmd=lambda: "average",
set_cmd=False,
docstring="The driver only supports averaging "
"mode it is therefor not possible to set "
"this parameter to anything else",
)
"""
The driver only supports averaging mode.
It is therefor not possible to set
this parameter to anything else
"""
self.rbw: TraceParameter = self.add_parameter(
"rbw",
label="Resolution Bandwidth",
unit="Hz",
initial_value=1e3,
vals=vals.Numbers(0.1, 250e3),
parameter_class=TraceParameter,
docstring="Resolution Bandwidth (RBW) is "
"the bandwidth of "
"spectral energy represented in each "
"frequency bin",
)
"""Resolution Bandwidth (RBW)
is the bandwidth of spectral energy represented in each frequency bin
"""
self.vbw: TraceParameter = self.add_parameter(
"vbw",
label="Video Bandwidth",
unit="Hz",
initial_value=1e3,
vals=vals.Numbers(),
parameter_class=TraceParameter,
docstring="The video bandwidth (VBW) is applied "
"after the signal has been converted to "
"frequency domain as power, voltage, "
"or log units. It is implemented as a "
"simple rectangular window, averaging the "
"amplitude readings for each frequency "
"bin over several overlapping FFTs. "
"For best performance use RBW as the VBW.",
)
"""
The video bandwidth (VBW)
is applied after the signal has been converted to frequency domain as power,
voltage, or log units. It is implemented as a simple rectangular window,
averaging the amplitude readings for each frequency bin over several overlapping FFTs.
For best performance use RBW as the VBW.
"""
self.reject_image: TraceParameter = self.add_parameter(
"reject_image",
label="Reject image",
unit="",
initial_value=True,
parameter_class=TraceParameter,
get_cmd=None,
docstring="Apply software filter to remove undersampling mirroring",
vals=vals.Bool(),
)
"""Apply software filter to remove undersampling mirroring"""
self.sleep_time: Parameter = self.add_parameter(
"sleep_time",
label="Sleep time",
unit="s",
initial_value=0.1,
get_cmd=None,
set_cmd=None,
docstring="Time to sleep before and after "
"getting data from the instrument",
vals=vals.Numbers(0),
)
"""Time to sleep before and after getting data from the instrument"""
# We don't know the correct values of
# the sweep parameters yet so we supply
# some defaults. The correct will be set when we call configure below
self.trace: FrequencySweep = self.add_parameter(
name="trace",
sweep_len=1,
start_freq=1,
stepsize=1,
parameter_class=FrequencySweep,
)
"""Parameter trace"""
self.power: Parameter = self.add_parameter(
"power",
label="Power",
unit="dBm",
get_cmd=self._get_power_at_freq,
set_cmd=False,
docstring="The maximum power in a window of 250 kHz "
"around the specified frequency with "
"Resolution bandwidth set to 1 kHz."
"The integration window is specified by "
"the VideoBandWidth (set by vbw)",
)
"""
The maximum power in a window of 250 kHz around the specified frequency with Resolution bandwidth set to 1 kHz.
The integration window is specified by the VideoBandWidth (set by vbw)
"""
# scale is defined after the trace and power parameter so that
# it can change the units of those in it's set method when the
# scale changes
self.scale: ScaleParameter = self.add_parameter(
"scale",
initial_value="log-scale",
vals=vals.Enum(
"log-scale", "lin-scale", "log-full-scale", "lin-full-scale"
),
parameter_class=ScaleParameter,
)
"""Parameter scale"""
self.frequency_axis: Parameter = self.add_parameter(
"frequency_axis",
label="Frequency",
unit="Hz",
get_cmd=self._get_freq_axis,
set_cmd=False,
vals=vals.Arrays(shape=(self.npts,)),
snapshot_value=False,
)
"""Parameter frequency_axis"""
self.freq_sweep: ParameterWithSetpoints = self.add_parameter(
"freq_sweep",
label="Power",
unit="depends on mode",
get_cmd=self._get_sweep_data,
set_cmd=False,
parameter_class=ParameterWithSetpoints,
vals=vals.Arrays(shape=(self.npts,)),
setpoints=(self.frequency_axis,),
snapshot_value=False,
)
"""Parameter freq_sweep"""
self.openDevice()
self.configure()
self.connect_message()
def _set_ctypes_argtypes(self) -> None:
"""
Set the expected argtypes for function calls in the sa_api dll
These should match the function signatures defined in the sa-api
header files included with the signal hound sdk
"""
self.dll.saConfigCenterSpan.argtypes = [ct.c_int, ct.c_double, ct.c_double]
self.dll.saConfigAcquisition.argtypes = [ct.c_int, ct.c_int, ct.c_int]
self.dll.saConfigLevel.argtypes = [ct.c_int, ct.c_double]
self.dll.saSetTimebase.argtypes = [ct.c_int, ct.c_int]
self.dll.saConfigSweepCoupling.argtypes = [
ct.c_int,
ct.c_double,
ct.c_double,
ct.c_bool,
]
self.dll.saInitiate.argtypes = [ct.c_int, ct.c_int, ct.c_int]
self.dll.saOpenDevice.argtypes = [ct.POINTER(ct.c_int)]
self.dll.saCloseDevice.argtypes = [ct.c_int]
self.dll.saPreset.argtypes = [ct.c_int]
self.dll.saGetDeviceType.argtypes = [ct.c_int, ct.POINTER(ct.c_int)]
self.dll.saQuerySweepInfo.argtypes = [
ct.c_int,
ct.POINTER(ct.c_int),
ct.POINTER(ct.c_double),
ct.POINTER(ct.c_double),
]
self.dll.saGetSweep_32f.argtypes = [
ct.c_int,
ct.POINTER(ct.c_float),
ct.POINTER(ct.c_float),
]
self.dll.saGetSerialNumber.argtypes = [ct.c_int, ct.POINTER(ct.c_int)]
self.dll.saGetFirmwareString.argtypes = [ct.c_int, ct.c_char_p]
def _get_npts(self) -> int:
if not self._parameters_synced:
self.sync_parameters()
sweep_info = self.QuerySweep()
sweep_len = sweep_info[0]
return sweep_len
def _update_trace(self) -> None:
"""
Private method to sync changes of the
frequency axis to the setpoints of the
trace parameter. This also set the units
of power and trace.
"""
sweep_info = self.QuerySweep()
self.npts.cache.set(sweep_info[0])
self.trace.set_sweep(*sweep_info)
[docs]
def sync_parameters(self) -> None:
"""
Sync parameters sets the configuration of the instrument using the
parameters specified in the Qcodes instrument.
Sync parameters consists of five parts
1. Center span configuration (freqs and span)
2. Acquisition configuration
lin-scale/log-scale
avg/max power
3. Configuring the external 10MHz reference
4. Configuration of the mode that is being used
5. Acquisition mode. At the moment only `sweeping` is implemented
This does not currently implement Configuration of the tracking
generator used in VNA mode
"""
# 1. CenterSpan Configuration
center = ct.c_double(self.frequency())
span = ct.c_double(self.span())
log.info("Setting device CenterSpan configuration.")
err = self.dll.saConfigCenterSpan(self.deviceHandle, center, span)
self.check_for_error(err, "saConfigCenterSpan")
# 2. Acquisition configuration
detectorVals = {
"min-max": ct.c_int(self.hf.sa_MIN_MAX),
"average": ct.c_int(self.hf.sa_AVERAGE),
}
scaleVals = {
"log-scale": ct.c_int(self.hf.sa_LOG_SCALE),
"lin-scale": ct.c_int(self.hf.sa_LIN_SCALE),
"log-full-scale": ct.c_int(self.hf.sa_LOG_FULL_SCALE),
"lin-full-scale": ct.c_int(self.hf.sa_LIN_FULL_SCALE),
}
detector = detectorVals[self.acquisition_mode()]
scale = scaleVals[self.scale()]
err = self.dll.saConfigAcquisition(self.deviceHandle, detector, scale)
self.check_for_error(err, "saConfigAcquisition")
# 3. Reference Level configuration
log.info("Setting device reference level configuration.")
err = self.dll.saConfigLevel(self.deviceHandle, ct.c_double(self.ref_lvl()))
self.check_for_error(err, "saConfigLevel")
# 4. External Reference configuration
if self.external_reference():
external = self.hf.sa_REF_EXTERNAL_IN
log.info("Setting reference frequency from external source.")
err = self.dll.saSetTimebase(self.deviceHandle, external)
self.check_for_error(err, "saSetTimebase")
reject_var = ct.c_bool(self.reject_image())
log.info("Setting device Sweeping configuration.")
err = self.dll.saConfigSweepCoupling(
self.deviceHandle,
ct.c_double(self.rbw()),
ct.c_double(self.vbw()),
reject_var,
)
self.check_for_error(err, "saConfigSweepCoupling")
modeOpts = {
"sweeping": self.hf.sa_SWEEPING,
"real_time": self.hf.sa_REAL_TIME, # not implemented
"IQ": self.hf.sa_IQ, # not implemented
"idle": self.hf.sa_IDLE,
}
mode = modeOpts[self.device_mode()]
# the third argument to saInitiate is a flag that is
# currently not used
err = self.dll.saInitiate(self.deviceHandle, mode, 0)
extrainfo: str | None = None
if err == saStatus.saInvalidParameterErr:
extrainfo = """
In real-time mode, this value may be returned if the span
limits defined in the API header are broken. Also in
real-time mode, this error will be returned if the
resolution bandwidth is outside the limits defined in
the API header.
In time-gate analysis mode this error will be returned if
span limits defined in the API header are broken. Also in
time gate analysis, this error is returned if the
bandwidth provided require more samples for processing
than is allowed in the gate length. To fix this
increase rbw/vbw.
"""
elif err == saStatus.saBandwidthErr:
extrainfo = "RBW is larger than your span. (Sweep Mode)!"
self.check_for_error(err, "saInitiate", extrainfo)
self._parameters_synced = True
[docs]
def openDevice(self) -> None:
"""
Opens connection to the instrument
"""
log.info("Opening Device")
self.deviceHandle = ct.c_int(0)
deviceHandlePnt = ct.pointer(self.deviceHandle)
err = self.dll.saOpenDevice(deviceHandlePnt)
self.check_for_error(err, "saOpenDevice")
self.device_type()
[docs]
def close(self) -> None:
"""
Close connection to the instrument.
"""
log.info(f"Closing Device with handle num: {self.deviceHandle.value}")
try:
self.abort()
log.info("Running acquistion aborted.")
except Exception as e:
# it's ok to catch any exception here
# as we are tearing down the instrument we might
# as well try to continue
log.warning(f"Could not abort acquisition: {e}")
err = self.dll.saCloseDevice(self.deviceHandle)
self.check_for_error(err, "saCloseDevice")
log.info(f"Closed Device with handle num: {self.deviceHandle.value}")
super().close()
[docs]
def abort(self) -> None:
"""
Abort any running acquisition.
"""
log.info("Stopping acquisition")
err = self.dll.saAbort(self.deviceHandle)
extrainfo: str | None = None
if err == saStatus.saDeviceNotConfiguredErr:
extrainfo = (
"Device was already idle! Did you call abort "
"without ever calling initiate()"
)
self.check_for_error(err, "saAbort", extrainfo)
[docs]
def preset(self) -> None:
"""
Like close but performs a hardware reset before closing the
connection.
"""
log.warning("Performing hardware-reset of device!")
err = self.dll.saPreset(self.deviceHandle)
self.check_for_error(err, "saPreset")
super().close()
def _get_device_type(self) -> str:
"""
Returns the model string of the Spectrum Analyzer.
"""
log.info("Querying device for model information")
devType = ct.c_int32(0)
devTypePnt = ct.pointer(devType)
err = self.dll.saGetDeviceType(self.deviceHandle, devTypePnt)
self.check_for_error(err, "saGetDeviceType")
if devType.value == self.hf.saDeviceTypeNone:
dev = "No device"
elif devType.value == self.hf.saDeviceTypeSA44:
dev = "sa44"
elif devType.value == self.hf.saDeviceTypeSA44B:
dev = "sa44B"
elif devType.value == self.hf.saDeviceTypeSA124A:
dev = "sa124A"
elif devType.value == self.hf.saDeviceTypeSA124B:
dev = "sa124B"
else:
raise ValueError("Unknown device type!")
return dev
########################################################################
[docs]
def QuerySweep(self) -> tuple[int, float, float]:
"""
Queries the sweep for information on the parameters that defines the
x axis of the sweep
Returns:
number of points in sweep, start frequency and step size
"""
sweep_len = ct.c_int(0)
start_freq = ct.c_double(0)
stepsize = ct.c_double(0)
err = self.dll.saQuerySweepInfo(
self.deviceHandle,
ct.pointer(sweep_len),
ct.pointer(start_freq),
ct.pointer(stepsize),
)
self.check_for_error(err, "saQuerySweepInfo")
return sweep_len.value, start_freq.value, stepsize.value
def _get_sweep_data(self) -> np.ndarray:
"""
This function performs a sweep over the configured ranges.
The result of the sweep is returned along with the sweep points
Returns:
datamin numpy array
"""
if not self._parameters_synced:
self.sync_parameters()
sweep_len, _, _ = self.QuerySweep()
data = np.zeros(sweep_len)
Navg = self.avg()
for i in range(Navg):
datamin = np.zeros((sweep_len), dtype=np.float32)
datamax = np.zeros((sweep_len), dtype=np.float32)
minarr = datamin.ctypes.data_as(ct.POINTER(ct.c_float))
maxarr = datamax.ctypes.data_as(ct.POINTER(ct.c_float))
sleep(self.sleep_time.get()) # Added extra sleep for updating issue
err = self.dll.saGetSweep_32f(self.deviceHandle, minarr, maxarr)
self.check_for_error(err, "saGetSweep_32f")
data += datamin
return data / Navg
def _get_power_at_freq(self) -> float:
"""
Returns the maximum power in a window of 250 kHz
around the specified frequency with Resolution bandwidth set to 1 kHz.
The integration window is specified by the VideoBandWidth (set by vbw)
"""
original_span = self.span()
original_rbw = self.rbw()
needs_reset = False
if not (original_span == 0.25e6 and original_rbw == 1e3):
needs_reset = True
self.span(0.25e6)
self.rbw(1e3)
if not self._parameters_synced:
# call configure to update both
# the parameters on the device and the
# setpoints and units
self.configure()
data = self._get_sweep_data()
max_power = np.max(data)
if needs_reset:
self.span(original_span)
self.rbw(original_rbw)
self.configure()
sleep(2 * self.sleep_time.get())
return max_power
[docs]
@staticmethod
def check_for_error(err: int, source: str, extrainfo: str | None = None) -> None:
if err != saStatus.saNoError:
err_str = saStatus(err).name
if err > 0:
msg = (
f"During call of {source} the following"
f"Warning: {err_str} was raised"
)
if extrainfo is not None:
msg = msg + f"\n Extra info: {extrainfo}"
log.warning(msg)
else:
msg = (
f"During call of {source} the following Error: "
f"{err_str} was raised"
)
if extrainfo is not None:
msg = msg + f"\n Extra info: {extrainfo}"
raise OSError(msg)
else:
msg = "Call to {source} was successful"
if extrainfo is not None:
msg = msg + f"\n Extra info: {extrainfo}"
log.info(msg)
[docs]
def get_idn(self) -> dict[str, str | None]:
output: dict[str, str | None] = {}
output["vendor"] = "Signal Hound"
output["model"] = self._get_device_type()
serialnumber = ct.c_int32()
err = self.dll.saGetSerialNumber(self.deviceHandle, ct.pointer(serialnumber))
self.check_for_error(err, "saGetSerialNumber")
output["serial"] = str(serialnumber.value)
fw_version = (ct.c_char * 17)()
# the manual says that this must be at least 16 char
# but not clear if that includes a termination zero so
# make it 17 just in case
err = self.dll.saGetFirmwareString(self.deviceHandle, fw_version)
self.check_for_error(err, "saGetFirmwareString")
output["firmware"] = fw_version.value.decode("ascii")
return output
def _get_freq_axis(self) -> np.ndarray:
if not self._parameters_synced:
self.sync_parameters()
sweep_len, start_freq, stepsize = self.QuerySweep()
end_freq = start_freq + stepsize * (sweep_len - 1)
freq_points = np.linspace(start_freq, end_freq, sweep_len)
return freq_points
class Constants:
"""
These constants are defined in sa_api.h as part of the the Signal Hound
SDK
"""
SA_MAX_DEVICES = 8
saDeviceTypeNone = 0
saDeviceTypeSA44 = 1
saDeviceTypeSA44B = 2
saDeviceTypeSA124A = 3
saDeviceTypeSA124B = 4
sa44_MIN_FREQ = 1.0
sa124_MIN_FREQ = 100.0e3
sa44_MAX_FREQ = 4.4e9
sa124_MAX_FREQ = 13.0e9
sa_MIN_SPAN = 1.0
sa_MAX_REF = 20
sa_MAX_ATTEN = 3
sa_MAX_GAIN = 2
sa_MIN_RBW = 0.1
sa_MAX_RBW = 6.0e6
sa_MIN_RT_RBW = 100.0
sa_MAX_RT_RBW = 10000.0
sa_MIN_IQ_BANDWIDTH = 100.0
sa_MAX_IQ_DECIMATION = 128
sa_IQ_SAMPLE_RATE = 486111.111
sa_IDLE = -1
sa_SWEEPING = 0x0
sa_REAL_TIME = 0x1
sa_IQ = 0x2
sa_AUDIO = 0x3
sa_TG_SWEEP = 0x4
sa_MIN_MAX = 0x0
sa_AVERAGE = 0x1
sa_LOG_SCALE = 0x0
sa_LIN_SCALE = 0x1
sa_LOG_FULL_SCALE = 0x2
sa_LIN_FULL_SCALE = 0x3
sa_AUTO_ATTEN = -1
sa_AUTO_GAIN = -1
sa_LOG_UNITS = 0x0
sa_VOLT_UNITS = 0x1
sa_POWER_UNITS = 0x2
sa_BYPASS = 0x3
sa_AUDIO_AM = 0x0
sa_AUDIO_FM = 0x1
sa_AUDIO_USB = 0x2
sa_AUDIO_LSB = 0x3
sa_AUDIO_CW = 0x4
TG_THRU_0DB = 0x1
TG_THRU_20DB = 0x2
sa_REF_UNUSED = 0
sa_REF_INTERNAL_OUT = 1
sa_REF_EXTERNAL_IN = 2
class saStatus(IntEnum):
saUnknownErr = -666
saFrequencyRangeErr = 99
saInvalidDetectorErr = -95
saInvalidScaleErr = -94
saBandwidthErr = -91
saExternalReferenceNotFound = -89
# Device specific errors
saOvenColdErr = -20
# Data errors
saInternetErr = -12
saUSBCommErr = -11
# General configuration errors
saTrackingGeneratorNotFound = -10
saDeviceNotIdleErr = -9
saDeviceNotFoundErr = -8
saInvalidModeErr = -7
saNotConfiguredErr = -6
saDeviceNotConfiguredErr = -6 # Added because key error raised
saTooManyDevicesErr = -5
saInvalidParameterErr = -4
saDeviceNotOpenErr = -3
saInvalidDeviceErr = -2
saNullPtrErr = -1
# No error
saNoError = 0
# Warnings
saNoCorrections = 1
saCompressionWarning = 2
saParameterClamped = 3
saBandwidthClamped = 4