mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-12-30 11:28:07 +00:00
Implemented basic voltage sweep.
This commit is contained in:
parent
224417687b
commit
dcf8bdeb85
5 changed files with 551 additions and 195 deletions
|
@ -5,15 +5,16 @@ from serial.tools.list_ports import comports
|
|||
from PyQt6 import QtSerialPort
|
||||
from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot
|
||||
from nqrduck.module.module_controller import ModuleController
|
||||
from .model import S11Data
|
||||
from .model import S11Data, LookupTable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutoTMController(ModuleController):
|
||||
BAUDRATE = 115200
|
||||
|
||||
def find_devices(self) -> None:
|
||||
"""Scan for available serial devices and add them to the model as available devices. """
|
||||
"""Scan for available serial devices and add them to the model as available devices."""
|
||||
logger.debug("Scanning for available serial devices")
|
||||
ports = comports()
|
||||
self.module.model.available_devices = [port.device for port in ports]
|
||||
|
@ -21,34 +22,36 @@ class AutoTMController(ModuleController):
|
|||
for device in self.module.model.available_devices:
|
||||
logger.debug("Found device: %s", device)
|
||||
|
||||
def connect(self, device : str) -> None:
|
||||
"""Connect to the specified device.
|
||||
|
||||
def connect(self, device: str) -> None:
|
||||
"""Connect to the specified device.
|
||||
|
||||
Args:
|
||||
device (str): The device port to connect to."""
|
||||
logger.debug("Connecting to device %s", device)
|
||||
try:
|
||||
self.module.model.serial = QtSerialPort.QSerialPort(device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read)
|
||||
self.module.model.serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite)
|
||||
self.module.model.serial = QtSerialPort.QSerialPort(
|
||||
device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read
|
||||
)
|
||||
self.module.model.serial.open(
|
||||
QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite
|
||||
)
|
||||
|
||||
|
||||
|
||||
logger.debug("Connected to device %s", device)
|
||||
except Exception as e:
|
||||
logger.error("Could not connect to device %s: %s", device, e)
|
||||
|
||||
def start_frequency_sweep(self, start_frequency : str, stop_frequency : str) -> None:
|
||||
""" This starts a frequency sweep on the device in the specified range.
|
||||
def start_frequency_sweep(self, start_frequency: str, stop_frequency: str) -> None:
|
||||
"""This starts a frequency sweep on the device in the specified range.
|
||||
The minimum start and stop frequency are specific to the AD4351 based frequency generator.
|
||||
|
||||
|
||||
Args:
|
||||
start_frequency (str): The start frequency in MHz.
|
||||
stop_frequency (str): The stop frequency in MHz.
|
||||
|
||||
"""
|
||||
FREQUENCY_STEP = 50000 # Hz
|
||||
MIN_FREQUENCY = 35e6 # Hz
|
||||
MAX_FREQUENCY = 300e6 # Hz
|
||||
FREQUENCY_STEP = 50000 # Hz
|
||||
MIN_FREQUENCY = 35e6 # Hz
|
||||
MAX_FREQUENCY = 300e6 # Hz
|
||||
|
||||
try:
|
||||
start_frequence = start_frequency.replace(",", ".")
|
||||
|
@ -66,42 +69,52 @@ class AutoTMController(ModuleController):
|
|||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
|
||||
if start_frequency < 0 or stop_frequency < 0:
|
||||
error = "Could not start frequency sweep. Start and stop frequency must be positive"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
|
||||
if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY:
|
||||
error = "Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz" % (MIN_FREQUENCY / 1e6, MAX_FREQUENCY / 1e6)
|
||||
error = (
|
||||
"Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz"
|
||||
% (MIN_FREQUENCY / 1e6, MAX_FREQUENCY / 1e6)
|
||||
)
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
logger.debug("Starting frequency sweep from %s to %s with step size %s", start_frequency, stop_frequency, FREQUENCY_STEP)
|
||||
logger.debug(
|
||||
"Starting frequency sweep from %s to %s with step size %s",
|
||||
start_frequency,
|
||||
stop_frequency,
|
||||
FREQUENCY_STEP,
|
||||
)
|
||||
# We create the frequency sweep spinner dialog
|
||||
self.module.model.clear_data_points()
|
||||
self.module.view.create_frequency_sweep_spinner_dialog()
|
||||
# Print the command 'f<start>f<stop>' to the serial connection
|
||||
# Print the command 'f<start>f<stop>f<step>' to the serial connection
|
||||
try:
|
||||
command = "f%sf%sf%s" % (start_frequency, stop_frequency, FREQUENCY_STEP)
|
||||
self.module.model.serial.write(command.encode('utf-8'))
|
||||
self.module.model.serial.write(command.encode("utf-8"))
|
||||
except AttributeError:
|
||||
logger.error("Could not start frequency sweep. No device connected.")
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
|
||||
|
||||
def on_ready_read(self) -> None:
|
||||
"""This method is called when data is received from the serial connection. """
|
||||
"""This method is called when data is received from the serial connection."""
|
||||
serial = self.module.model.serial
|
||||
while serial.canReadLine():
|
||||
text = serial.readLine().data().decode()
|
||||
text = text.rstrip('\r\n')
|
||||
text = text.rstrip("\r\n")
|
||||
# logger.debug("Received data: %s", text)
|
||||
# If the text starts with 'f' and the frequency sweep spinner is visible we know that the data is a data point
|
||||
# then we have the data for the return loss and the phase at a certain frequency
|
||||
if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible():
|
||||
if (
|
||||
text.startswith("f")
|
||||
and self.module.view.frequency_sweep_spinner.isVisible()
|
||||
):
|
||||
text = text[1:].split("r")
|
||||
frequency = float(text[0])
|
||||
return_loss, phase = map(float, text[1].split("p"))
|
||||
|
@ -109,24 +122,38 @@ class AutoTMController(ModuleController):
|
|||
# If the text starts with 'r' and no calibration is active we know that the data is a measurement
|
||||
elif text.startswith("r") and self.module.model.active_calibration == None:
|
||||
logger.debug("Measurement finished")
|
||||
self.module.model.measurement = S11Data(self.module.model.data_points.copy())
|
||||
self.module.model.measurement = S11Data(
|
||||
self.module.model.data_points.copy()
|
||||
)
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
# If the text starts with 'r' and a short calibration is active we know that the data is a short calibration
|
||||
elif text.startswith("r") and self.module.model.active_calibration == "short":
|
||||
elif (
|
||||
text.startswith("r") and self.module.model.active_calibration == "short"
|
||||
):
|
||||
logger.debug("Short calibration finished")
|
||||
self.module.model.short_calibration = S11Data(self.module.model.data_points.copy())
|
||||
self.module.model.short_calibration = S11Data(
|
||||
self.module.model.data_points.copy()
|
||||
)
|
||||
self.module.model.active_calibration = None
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
# If the text starts with 'r' and an open calibration is active we know that the data is an open calibration
|
||||
elif text.startswith("r") and self.module.model.active_calibration == "open":
|
||||
elif (
|
||||
text.startswith("r") and self.module.model.active_calibration == "open"
|
||||
):
|
||||
logger.debug("Open calibration finished")
|
||||
self.module.model.open_calibration = S11Data(self.module.model.data_points.copy())
|
||||
self.module.model.open_calibration = S11Data(
|
||||
self.module.model.data_points.copy()
|
||||
)
|
||||
self.module.model.active_calibration = None
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
# If the text starts with 'r' and a load calibration is active we know that the data is a load calibration
|
||||
elif text.startswith("r") and self.module.model.active_calibration == "load":
|
||||
elif (
|
||||
text.startswith("r") and self.module.model.active_calibration == "load"
|
||||
):
|
||||
logger.debug("Load calibration finished")
|
||||
self.module.model.load_calibration = S11Data(self.module.model.data_points.copy())
|
||||
self.module.model.load_calibration = S11Data(
|
||||
self.module.model.data_points.copy()
|
||||
)
|
||||
self.module.model.active_calibration = None
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
# If the text starts with 'i' we know that the data is an info message
|
||||
|
@ -137,8 +164,32 @@ class AutoTMController(ModuleController):
|
|||
elif text.startswith("e"):
|
||||
text = "ATM Error: " + text[1:]
|
||||
self.module.view.add_info_text(text)
|
||||
# If the text starts with 'v' we know that the data is a voltage sweep result
|
||||
elif text.startswith("v"):
|
||||
text = text[1:]
|
||||
text = text.split("t")
|
||||
matching_voltage = float(text[0])
|
||||
tuning_voltage = float(text[1])
|
||||
# Now we add the datapoint to the current LUT
|
||||
LUT = self.module.model.LUT
|
||||
logger.debug(
|
||||
"Received voltage sweep result: %s %s",
|
||||
matching_voltage,
|
||||
tuning_voltage,
|
||||
)
|
||||
LUT.add_voltages(matching_voltage, tuning_voltage)
|
||||
|
||||
def on_short_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||
# Start the next voltage sweep if there are more voltages to sweep
|
||||
if LUT.is_incomplete():
|
||||
next_frequency = LUT.get_next_frequency()
|
||||
command = "s%s" % next_frequency
|
||||
LUT.started_frequency = next_frequency
|
||||
logger.debug("Starting next voltage sweep: %s", command)
|
||||
serial.write(command.encode("utf-8"))
|
||||
|
||||
def on_short_calibration(
|
||||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the short calibration button is pressed.
|
||||
It starts a frequency sweep in the specified range and then starts a short calibration.
|
||||
"""
|
||||
|
@ -146,7 +197,9 @@ class AutoTMController(ModuleController):
|
|||
self.module.model.init_short_calibration()
|
||||
self.start_frequency_sweep(start_frequency, stop_frequency)
|
||||
|
||||
def on_open_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||
def on_open_calibration(
|
||||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the open calibration button is pressed.
|
||||
It starts a frequency sweep in the specified range and then starts an open calibration.
|
||||
"""
|
||||
|
@ -154,7 +207,9 @@ class AutoTMController(ModuleController):
|
|||
self.module.model.init_open_calibration()
|
||||
self.start_frequency_sweep(start_frequency, stop_frequency)
|
||||
|
||||
def on_load_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||
def on_load_calibration(
|
||||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the load calibration button is pressed.
|
||||
It starts a frequency sweep in the specified range and then loads a calibration.
|
||||
"""
|
||||
|
@ -169,7 +224,7 @@ class AutoTMController(ModuleController):
|
|||
@TODO: Make calibration useful. Right now the calibration does not work for the probe coils. It completly messes up the S11 data.
|
||||
For 50 Ohm reference loads the calibration makes the S11 data usable - one then gets a flat line at -50 dB.
|
||||
The problem is probably two things:
|
||||
1. The ideal values for open, short and load should be measured with a VNA and then be loaded for the calibration.
|
||||
1. The ideal values for open, short and load should be measured with a VNA and then be loaded for the calibration.
|
||||
The ideal values are probably not -1, 1 and 0 but will also show frequency dependent behaviour.
|
||||
2 The AD8302 chip only returns the absolute value of the phase. One would probably need to calculate the phase with various algorithms found in the literature.
|
||||
Though Im not sure if these proposed algorithms would work for the AD8302 chip.
|
||||
|
@ -177,15 +232,21 @@ class AutoTMController(ModuleController):
|
|||
logger.debug("Calculating calibration")
|
||||
# First we check if the short and open calibration data points are available
|
||||
if self.module.model.short_calibration == None:
|
||||
logger.error("Could not calculate calibration. No short calibration data points available.")
|
||||
logger.error(
|
||||
"Could not calculate calibration. No short calibration data points available."
|
||||
)
|
||||
return
|
||||
if self.module.model.open_calibration == None:
|
||||
logger.error("Could not calculate calibration. No open calibration data points available.")
|
||||
logger.error(
|
||||
"Could not calculate calibration. No open calibration data points available."
|
||||
)
|
||||
return
|
||||
if self.module.model.load_calibration == None:
|
||||
logger.error("Could not calculate calibration. No load calibration data points available.")
|
||||
logger.error(
|
||||
"Could not calculate calibration. No load calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
# Then we calculate the calibration
|
||||
ideal_gamma_short = -1
|
||||
ideal_gamma_open = 1
|
||||
|
@ -198,8 +259,10 @@ class AutoTMController(ModuleController):
|
|||
E_Ds = []
|
||||
E_Ss = []
|
||||
E_ts = []
|
||||
for gamma_s, gamma_o, gamma_l in zip(measured_gamma_short, measured_gamma_open, measured_gamma_load):
|
||||
# This is the solution from
|
||||
for gamma_s, gamma_o, gamma_l in zip(
|
||||
measured_gamma_short, measured_gamma_open, measured_gamma_load
|
||||
):
|
||||
# This is the solution from
|
||||
# A = np.array([
|
||||
# [1, ideal_gamma_short * gamma_s, -ideal_gamma_short],
|
||||
# [1, ideal_gamma_open * gamma_o, -ideal_gamma_open],
|
||||
|
@ -212,7 +275,7 @@ class AutoTMController(ModuleController):
|
|||
# e_00, e11, delta_e = np.linalg.lstsq(A, B, rcond=None)[0]
|
||||
|
||||
E_D = gamma_l
|
||||
E_ = (2 * gamma_l - (gamma_s + gamma_o)) / (gamma_s - gamma_o)
|
||||
E_ = (2 * gamma_l - (gamma_s + gamma_o)) / (gamma_s - gamma_o)
|
||||
E_S = (2 * (gamma_o + gamma_l) * (gamma_s + gamma_l)) / (gamma_s - gamma_o)
|
||||
|
||||
E_Ds.append(E_D)
|
||||
|
@ -240,22 +303,28 @@ class AutoTMController(ModuleController):
|
|||
logger.debug("Exporting calibration")
|
||||
# First we check if the short and open calibration data points are available
|
||||
if self.module.model.short_calibration == None:
|
||||
logger.error("Could not export calibration. No short calibration data points available.")
|
||||
logger.error(
|
||||
"Could not export calibration. No short calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if self.module.model.open_calibration == None:
|
||||
logger.error("Could not export calibration. No open calibration data points available.")
|
||||
logger.error(
|
||||
"Could not export calibration. No open calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if self.module.model.load_calibration == None:
|
||||
logger.error("Could not export calibration. No load calibration data points available.")
|
||||
logger.error(
|
||||
"Could not export calibration. No load calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
# Then we export the different calibrations as a json file
|
||||
data = {
|
||||
"short": self.module.model.short_calibration.to_json(),
|
||||
"open": self.module.model.open_calibration.to_json(),
|
||||
"load": self.module.model.load_calibration.to_json()
|
||||
"load": self.module.model.load_calibration.to_json(),
|
||||
}
|
||||
|
||||
with open(filename, "w") as f:
|
||||
|
@ -277,16 +346,16 @@ class AutoTMController(ModuleController):
|
|||
self.module.model.open_calibration = S11Data.from_json(data["open"])
|
||||
self.module.model.load_calibration = S11Data.from_json(data["load"])
|
||||
|
||||
def set_voltages(self, tuning_voltage : str, matching_voltage : str) -> None:
|
||||
def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None:
|
||||
"""This method is called when the set voltages button is pressed.
|
||||
It writes the specified tuning and matching voltage to the serial connection.
|
||||
|
||||
|
||||
Args:
|
||||
tuning_voltage (str): The tuning voltage in V.
|
||||
matching_voltage (str): The matching voltage in V.
|
||||
"""
|
||||
logger.debug("Setting voltages")
|
||||
MAX_VOLTAGE = 5 # V
|
||||
MAX_VOLTAGE = 5 # V
|
||||
try:
|
||||
tuning_voltage = tuning_voltage.replace(",", ".")
|
||||
matching_voltage = matching_voltage.replace(",", ".")
|
||||
|
@ -297,22 +366,103 @@ class AutoTMController(ModuleController):
|
|||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
|
||||
if tuning_voltage < 0 or matching_voltage < 0:
|
||||
error = "Could not set voltages. Tuning and matching voltage must be positive"
|
||||
error = (
|
||||
"Could not set voltages. Tuning and matching voltage must be positive"
|
||||
)
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
|
||||
if tuning_voltage > MAX_VOLTAGE or matching_voltage > MAX_VOLTAGE:
|
||||
error = "Could not set voltages. Tuning and matching voltage must be between 0 and 5 V"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
logger.debug("Setting tuning voltage to %s V and matching voltage to %s V", tuning_voltage, matching_voltage)
|
||||
|
||||
logger.debug(
|
||||
"Setting tuning voltage to %s V and matching voltage to %s V",
|
||||
tuning_voltage,
|
||||
matching_voltage,
|
||||
)
|
||||
try:
|
||||
command = "v%sv%s" % (matching_voltage, tuning_voltage)
|
||||
self.module.model.serial.write(command.encode('utf-8'))
|
||||
self.module.model.serial.write(command.encode("utf-8"))
|
||||
except AttributeError:
|
||||
logger.error("Could not set voltages. No device connected.")
|
||||
|
||||
def generate_lut(
|
||||
self,
|
||||
start_frequency: str,
|
||||
stop_frequency: str,
|
||||
frequency_step: str,
|
||||
voltage_resolution: str,
|
||||
) -> None:
|
||||
"""This method is called when the generate LUT button is pressed.
|
||||
It generates a lookup table for the specified frequency range and voltage resolution.
|
||||
|
||||
Args:
|
||||
start_frequency (str): The start frequency in Hz.
|
||||
stop_frequency (str): The stop frequency in Hz.
|
||||
frequency_step (str): The frequency step in Hz.
|
||||
voltage_resolution (str): The voltage resolution in V.
|
||||
"""
|
||||
logger.debug("Generating LUT")
|
||||
try:
|
||||
start_frequency = start_frequency.replace(",", ".")
|
||||
stop_frequency = stop_frequency.replace(",", ".")
|
||||
frequency_step = frequency_step.replace(",", ".")
|
||||
voltage_resolution = voltage_resolution.replace(",", ".")
|
||||
start_frequency = float(start_frequency)
|
||||
stop_frequency = float(stop_frequency)
|
||||
frequency_step = float(frequency_step)
|
||||
voltage_resolution = float(voltage_resolution)
|
||||
except ValueError:
|
||||
error = "Could not generate LUT. Start frequency, stop frequency, frequency step and voltage resolution must be floats"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
if (
|
||||
start_frequency < 0
|
||||
or stop_frequency < 0
|
||||
or frequency_step < 0
|
||||
or voltage_resolution < 0
|
||||
):
|
||||
error = "Could not generate LUT. Start frequency, stop frequency, frequency step and voltage resolution must be positive"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
if start_frequency > stop_frequency:
|
||||
error = "Could not generate LUT. Start frequency must be smaller than stop frequency"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
if frequency_step > (stop_frequency - start_frequency):
|
||||
error = "Could not generate LUT. Frequency step must be smaller than the frequency range"
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"Generating LUT from %s MHz to %s MHz with a frequency step of %s MHz and a voltage resolution of %s V",
|
||||
start_frequency,
|
||||
stop_frequency,
|
||||
frequency_step,
|
||||
voltage_resolution,
|
||||
)
|
||||
|
||||
# We create the lookup table
|
||||
LUT = LookupTable(
|
||||
start_frequency, stop_frequency, frequency_step, voltage_resolution
|
||||
)
|
||||
|
||||
LUT.started_frequency = start_frequency
|
||||
self.module.model.LUT = LUT
|
||||
|
||||
# We write the first command to the serial connection
|
||||
command = "s%s" % (start_frequency)
|
||||
self.module.model.serial.write(command.encode("utf-8"))
|
||||
|
|
|
@ -7,55 +7,57 @@ from nqrduck.module.module_model import ModuleModel
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class S11Data:
|
||||
|
||||
class S11Data:
|
||||
# Conversion factors - the data is generally sent and received in mV
|
||||
# These values are used to convert the data to dB and degrees
|
||||
CENTER_POINT_MAGNITUDE = 900 # mV
|
||||
CENTER_POINT_PHASE = 1800 # mV
|
||||
MAGNITUDE_SLOPE = 30 # dB/mV
|
||||
PHASE_SLOPE = 10 # deg/mV
|
||||
CENTER_POINT_MAGNITUDE = 900 # mV
|
||||
CENTER_POINT_PHASE = 1800 # mV
|
||||
MAGNITUDE_SLOPE = 30 # dB/mV
|
||||
PHASE_SLOPE = 10 # deg/mV
|
||||
|
||||
def __init__(self, data_points : list) -> None:
|
||||
def __init__(self, data_points: list) -> None:
|
||||
self.frequency = np.array([data_point[0] for data_point in data_points])
|
||||
self.return_loss_mv = np.array([data_point[1] for data_point in data_points])
|
||||
self.phase_mv = np.array([data_point[2] for data_point in data_points])
|
||||
|
||||
|
||||
@property
|
||||
def millivolts(self):
|
||||
return self.frequency, self.return_loss_mv, self.phase_mv
|
||||
|
||||
|
||||
@property
|
||||
def return_loss_db(self):
|
||||
return (self.return_loss_mv - self.CENTER_POINT_MAGNITUDE) / self.MAGNITUDE_SLOPE
|
||||
return (
|
||||
self.return_loss_mv - self.CENTER_POINT_MAGNITUDE
|
||||
) / self.MAGNITUDE_SLOPE
|
||||
|
||||
@property
|
||||
def phase_deg(self):
|
||||
"""Returns the absolute value of the phase in degrees"""
|
||||
return (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
|
||||
|
||||
|
||||
@property
|
||||
def phase_rad(self):
|
||||
return self.phase_deg * cmath.pi / 180
|
||||
|
||||
|
||||
@property
|
||||
def gamma(self):
|
||||
"""Complex reflection coefficient"""
|
||||
if len(self.return_loss_db) != len(self.phase_rad):
|
||||
raise ValueError("return_loss_db and phase_rad must be the same length")
|
||||
|
||||
return [cmath.rect(10 ** (-loss_db / 20), phase_rad) for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad)]
|
||||
return [
|
||||
cmath.rect(10 ** (-loss_db / 20), phase_rad)
|
||||
for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad)
|
||||
]
|
||||
|
||||
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"frequency": self.frequency.tolist(),
|
||||
"return_loss_mv": self.return_loss_mv.tolist(),
|
||||
"phase_mv": self.phase_mv.tolist()
|
||||
"phase_mv": self.phase_mv.tolist(),
|
||||
}
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
f = json["frequency"]
|
||||
|
@ -64,8 +66,74 @@ class S11Data:
|
|||
data = [(f[i], rl[i], p[i]) for i in range(len(f))]
|
||||
return cls(data)
|
||||
|
||||
class AutoTMModel(ModuleModel):
|
||||
|
||||
class LookupTable:
|
||||
"""This class is used to store a lookup table for tuning and matching of electrical probeheads."""
|
||||
|
||||
data = dict()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_frequency: float,
|
||||
stop_frequency: float,
|
||||
frequency_step: float,
|
||||
voltage_resolution: float,
|
||||
) -> None:
|
||||
self.start_frequency = start_frequency
|
||||
self.stop_frequency = stop_frequency
|
||||
self.frequency_step = frequency_step
|
||||
self.voltage_resolution = voltage_resolution
|
||||
|
||||
# This is the frequency at which the tuning and matching process was started
|
||||
self.started_frequency = None
|
||||
|
||||
self.init_voltages()
|
||||
|
||||
def init_voltages(self) -> None:
|
||||
"""Initialize the lookup table with default values."""
|
||||
for frequency in np.arange(
|
||||
self.start_frequency, self.stop_frequency, self.frequency_step
|
||||
):
|
||||
self.started_frequency = frequency
|
||||
self.add_voltages(None, None)
|
||||
|
||||
def is_incomplete(self) -> bool:
|
||||
"""This method returns True if the lookup table is incomplete,
|
||||
i.e. if there are frequencies for which no the tuning or matching voltage is none.
|
||||
|
||||
Returns:
|
||||
bool: True if the lookup table is incomplete, False otherwise.
|
||||
"""
|
||||
return any(
|
||||
[
|
||||
tuning_voltage is None or matching_voltage is None
|
||||
for tuning_voltage, matching_voltage in self.data.values()
|
||||
]
|
||||
)
|
||||
|
||||
def get_next_frequency(self) -> float:
|
||||
"""This method returns the next frequency for which the tuning and matching voltage is not yet set.
|
||||
|
||||
Returns:
|
||||
float: The next frequency for which the tuning and matching voltage is not yet set.
|
||||
"""
|
||||
|
||||
for frequency, (tuning_voltage, matching_voltage) in self.data.items():
|
||||
if tuning_voltage is None or matching_voltage is None:
|
||||
return frequency
|
||||
|
||||
return None
|
||||
|
||||
def add_voltages(self, tuning_voltage: float, matching_voltage: float) -> None:
|
||||
"""Add a tuning and matching voltage for the last started frequency to the lookup table.
|
||||
|
||||
Args:
|
||||
tuning_voltage (float): The tuning voltage for the given frequency.
|
||||
matching_voltage (float): The matching voltage for the given frequency."""
|
||||
self.data[self.started_frequency] = (tuning_voltage, matching_voltage)
|
||||
|
||||
|
||||
class AutoTMModel(ModuleModel):
|
||||
available_devices_changed = pyqtSignal(list)
|
||||
serial_changed = pyqtSignal(QSerialPort)
|
||||
data_points_changed = pyqtSignal(list)
|
||||
|
@ -92,8 +160,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def serial(self):
|
||||
"""The serial property is used to store the current serial connection.
|
||||
"""
|
||||
"""The serial property is used to store the current serial connection."""
|
||||
return self._serial
|
||||
|
||||
@serial.setter
|
||||
|
@ -101,7 +168,9 @@ class AutoTMModel(ModuleModel):
|
|||
self._serial = value
|
||||
self.serial_changed.emit(value)
|
||||
|
||||
def add_data_point(self, frequency: float, return_loss: float, phase : float) -> None:
|
||||
def add_data_point(
|
||||
self, frequency: float, return_loss: float, phase: float
|
||||
) -> None:
|
||||
"""Add a data point to the model. These data points are our intermediate data points read in via the serial connection.
|
||||
They will be saved in the according properties later on.
|
||||
"""
|
||||
|
@ -118,7 +187,7 @@ class AutoTMModel(ModuleModel):
|
|||
"""The measurement property is used to store the current measurement.
|
||||
This is the measurement that is shown in the main S11 plot"""
|
||||
return self._measurement
|
||||
|
||||
|
||||
@measurement.setter
|
||||
def measurement(self, value):
|
||||
"""The measurement value is a tuple of three lists: frequency, return loss and phase."""
|
||||
|
@ -130,7 +199,7 @@ class AutoTMModel(ModuleModel):
|
|||
@property
|
||||
def active_calibration(self):
|
||||
return self._active_calibration
|
||||
|
||||
|
||||
@active_calibration.setter
|
||||
def active_calibration(self, value):
|
||||
self._active_calibration = value
|
||||
|
@ -183,9 +252,16 @@ class AutoTMModel(ModuleModel):
|
|||
@property
|
||||
def calibration(self):
|
||||
return self._calibration
|
||||
|
||||
|
||||
@calibration.setter
|
||||
def calibration(self, value):
|
||||
logger.debug("Setting calibration")
|
||||
self._calibration = value
|
||||
|
||||
|
||||
@property
|
||||
def LUT(self):
|
||||
return self._LUT
|
||||
|
||||
@LUT.setter
|
||||
def LUT(self, value):
|
||||
self._LUT = value
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>705</height>
|
||||
<height>745</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -91,7 +91,7 @@
|
|||
<item>
|
||||
<widget class="QTabWidget" name="typeTab">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="mechTab">
|
||||
<attribute name="title">
|
||||
|
@ -111,44 +111,105 @@
|
|||
<attribute name="title">
|
||||
<string>Electrical</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="matchingBox"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="setvoltagesButton">
|
||||
<property name="text">
|
||||
<string>Set Voltages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="tuningBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Voltage Matching</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0,0,0,0,0">
|
||||
<item row="7" column="1">
|
||||
<widget class="QDoubleSpinBox" name="stopfrequencyBox"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="font">
|
||||
<font>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Voltage Tuning</string>
|
||||
<string>Set Voltages:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="font">
|
||||
<font>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Generate LUT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="tuningBox"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Start Frequency (MHz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Voltage Resolution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Voltage Matching</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="matchingBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Voltage Tuning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Stop Frequency (MHz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="generateLUTButton">
|
||||
<property name="text">
|
||||
<string>Start Voltage Sweep</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QDoubleSpinBox" name="resolutionBox"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QDoubleSpinBox" name="startfrequencyBox"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="setvoltagesButton">
|
||||
<property name="text">
|
||||
<string>Set Voltages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Frequency Step (MHz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QDoubleSpinBox" name="frequencystepBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
@ -247,7 +308,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>297</width>
|
||||
<height>169</height>
|
||||
<height>68</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
|
@ -3,7 +3,17 @@ from datetime import datetime
|
|||
from pathlib import Path
|
||||
from PyQt6.QtGui import QMovie
|
||||
from PyQt6.QtSerialPort import QSerialPort
|
||||
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication, QHBoxLayout, QLineEdit, QPushButton, QDialog, QFileDialog
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget,
|
||||
QLabel,
|
||||
QVBoxLayout,
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QDialog,
|
||||
QFileDialog,
|
||||
)
|
||||
from PyQt6.QtCore import pyqtSlot, Qt
|
||||
from nqrduck.module.module_view import ModuleView
|
||||
from nqrduck.contrib.mplwidget import MplWidget
|
||||
|
@ -13,7 +23,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class AutoTMView(ModuleView):
|
||||
|
||||
def __init__(self, module):
|
||||
super().__init__(module)
|
||||
|
||||
|
@ -26,57 +35,69 @@ class AutoTMView(ModuleView):
|
|||
self._ui_form.connectButton.setDisabled(True)
|
||||
|
||||
# On clicking of the refresh button scan for available usb devices
|
||||
self._ui_form.refreshButton.clicked.connect(
|
||||
self.module.controller.find_devices)
|
||||
self._ui_form.refreshButton.clicked.connect(self.module.controller.find_devices)
|
||||
|
||||
# Connect the available devices changed signal to the on_available_devices_changed slot
|
||||
self.module.model.available_devices_changed.connect(
|
||||
self.on_available_devices_changed)
|
||||
self.on_available_devices_changed
|
||||
)
|
||||
|
||||
# Connect the serial changed signal to the on_serial_changed slot
|
||||
self.module.model.serial_changed.connect(self.on_serial_changed)
|
||||
|
||||
# On clicking of the connect button call the connect method
|
||||
self._ui_form.connectButton.clicked.connect(
|
||||
self.on_connect_button_clicked)
|
||||
self._ui_form.connectButton.clicked.connect(self.on_connect_button_clicked)
|
||||
|
||||
# On clicking of the start button call the start_frequency_sweep method
|
||||
self._ui_form.startButton.clicked.connect(lambda: self.module.controller.start_frequency_sweep(
|
||||
self._ui_form.startEdit.text(),
|
||||
self._ui_form.stopEdit.text()
|
||||
))
|
||||
self._ui_form.startButton.clicked.connect(
|
||||
lambda: self.module.controller.start_frequency_sweep(
|
||||
self._ui_form.startEdit.text(), self._ui_form.stopEdit.text()
|
||||
)
|
||||
)
|
||||
|
||||
# On clicking of the generateLUTButton call the generate_lut method
|
||||
self._ui_form.generateLUTButton.clicked.connect(
|
||||
lambda: self.module.controller.generate_lut(
|
||||
self._ui_form.startfrequencyBox.text(),
|
||||
self._ui_form.stopfrequencyBox.text(),
|
||||
self._ui_form.frequencystepBox.text(),
|
||||
self._ui_form.resolutionBox.text(),
|
||||
)
|
||||
)
|
||||
|
||||
# On clicking of the setvoltagesButton call the set_voltages method
|
||||
self._ui_form.setvoltagesButton.clicked.connect(lambda: self.module.controller.set_voltages(
|
||||
self._ui_form.tuningBox.text(),
|
||||
self._ui_form.matchingBox.text()
|
||||
))
|
||||
self._ui_form.setvoltagesButton.clicked.connect(
|
||||
lambda: self.module.controller.set_voltages(
|
||||
self._ui_form.tuningBox.text(), self._ui_form.matchingBox.text()
|
||||
)
|
||||
)
|
||||
|
||||
# On clicking of the calibration button call the on_calibration_button_clicked method
|
||||
self._ui_form.calibrationButton.clicked.connect(
|
||||
self.on_calibration_button_clicked)
|
||||
self.on_calibration_button_clicked
|
||||
)
|
||||
|
||||
# Connect the measurement finished signal to the plot_measurement slot
|
||||
self.module.model.measurement_finished.connect(self.plot_measurement)
|
||||
|
||||
# Add a vertical layout to the info box
|
||||
self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout())
|
||||
self._ui_form.scrollAreaWidgetContents.layout(
|
||||
).setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
self._ui_form.scrollAreaWidgetContents.layout().setAlignment(
|
||||
Qt.AlignmentFlag.AlignTop
|
||||
)
|
||||
|
||||
self.init_plot()
|
||||
self.init_labels()
|
||||
|
||||
def init_labels(self) -> None:
|
||||
"""Makes some of the labels bold for better readability.
|
||||
"""
|
||||
"""Makes some of the labels bold for better readability."""
|
||||
self._ui_form.titleconnectionLabel.setStyleSheet("font-weight: bold;")
|
||||
self._ui_form.titlefrequencyLabel.setStyleSheet("font-weight: bold;")
|
||||
self._ui_form.titletypeLabel.setStyleSheet("font-weight: bold;")
|
||||
self._ui_form.titleinfoLabel.setStyleSheet("font-weight: bold;")
|
||||
|
||||
def init_plot(self) -> None:
|
||||
"""Initialize the S11 plot. """
|
||||
"""Initialize the S11 plot."""
|
||||
ax = self._ui_form.S11Plot.canvas.ax
|
||||
ax.set_xlabel("Frequency (MHz)")
|
||||
ax.set_ylabel("S11 (dB)")
|
||||
|
@ -87,7 +108,7 @@ class AutoTMView(ModuleView):
|
|||
self._ui_form.S11Plot.canvas.draw()
|
||||
|
||||
def on_calibration_button_clicked(self) -> None:
|
||||
"""This method is called when the calibration button is clicked.
|
||||
"""This method is called when the calibration button is clicked.
|
||||
It opens the calibration window.
|
||||
"""
|
||||
logger.debug("Calibration button clicked")
|
||||
|
@ -96,7 +117,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
@pyqtSlot(list)
|
||||
def on_available_devices_changed(self, available_devices: list) -> None:
|
||||
"""Update the available devices list in the view. """
|
||||
"""Update the available devices list in the view."""
|
||||
logger.debug("Updating available devices list")
|
||||
self._ui_form.portBox.clear()
|
||||
self._ui_form.portBox.addItems(available_devices)
|
||||
|
@ -109,7 +130,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
@pyqtSlot()
|
||||
def on_connect_button_clicked(self) -> None:
|
||||
"""This method is called when the connect button is clicked.
|
||||
"""This method is called when the connect button is clicked.
|
||||
It calls the connect method of the controller with the currently selected device.
|
||||
"""
|
||||
logger.debug("Connect button clicked")
|
||||
|
@ -118,7 +139,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
@pyqtSlot(QSerialPort)
|
||||
def on_serial_changed(self, serial: QSerialPort) -> None:
|
||||
"""Update the serial 'connectionLabel' according to the current serial connection.
|
||||
"""Update the serial 'connectionLabel' according to the current serial connection.
|
||||
|
||||
Args:
|
||||
serial (serial.Serial): The current serial connection."""
|
||||
|
@ -131,12 +152,12 @@ class AutoTMView(ModuleView):
|
|||
logger.debug("Updated serial connection label")
|
||||
|
||||
def plot_measurement(self, data: "S11Data") -> None:
|
||||
"""Update the S11 plot with the current data points.
|
||||
"""Update the S11 plot with the current data points.
|
||||
|
||||
Args:
|
||||
data_points (list): List of data points to plot.
|
||||
data_points (list): List of data points to plot.
|
||||
|
||||
@TODO: implement proper calibration. See the controller class for more information.
|
||||
@TODO: implement proper calibration. See the controller class for more information.
|
||||
"""
|
||||
frequency = data.frequency
|
||||
return_loss_db = data.return_loss_db
|
||||
|
@ -165,14 +186,17 @@ class AutoTMView(ModuleView):
|
|||
if self.module.model.calibration is not None:
|
||||
# Calibration test:
|
||||
import cmath
|
||||
|
||||
calibration = self.module.model.calibration
|
||||
E_D = calibration[0]
|
||||
E_S = calibration[1]
|
||||
E_t = calibration[2]
|
||||
|
||||
# gamma_corr = [(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(gamma)]
|
||||
gamma_corr = [(data_point - E_D[i]) / (E_S[i] * (data_point - E_D[i]) + E_t[i])
|
||||
for i, data_point in enumerate(gamma)]
|
||||
gamma_corr = [
|
||||
(data_point - E_D[i]) / (E_S[i] * (data_point - E_D[i]) + E_t[i])
|
||||
for i, data_point in enumerate(gamma)
|
||||
]
|
||||
""" fig, ax = plt.subplots()
|
||||
ax.plot([g.real for g in gamma_corr], [g.imag for g in gamma_corr])
|
||||
ax.set_aspect('equal')
|
||||
|
@ -181,8 +205,9 @@ class AutoTMView(ModuleView):
|
|||
ax.set_xlabel("Real")
|
||||
ax.set_ylabel("Imaginary")
|
||||
plt.show() """
|
||||
return_loss_db_corr = [-20 *
|
||||
cmath.log10(abs(g + 1e-12)) for g in gamma_corr]
|
||||
return_loss_db_corr = [
|
||||
-20 * cmath.log10(abs(g + 1e-12)) for g in gamma_corr
|
||||
]
|
||||
magnitude_ax.plot(frequency, return_loss_db_corr, color="red")
|
||||
|
||||
phase_ax.set_ylabel("|Phase (deg)|")
|
||||
|
@ -204,10 +229,10 @@ class AutoTMView(ModuleView):
|
|||
QApplication.processEvents()
|
||||
|
||||
def add_info_text(self, text: str) -> None:
|
||||
""" Adds text to the info text box.
|
||||
"""Adds text to the info text box.
|
||||
|
||||
Args:
|
||||
text (str): Text to add to the info text box.
|
||||
text (str): Text to add to the info text box.
|
||||
"""
|
||||
# Add a timestamp to the text
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
@ -216,13 +241,14 @@ class AutoTMView(ModuleView):
|
|||
text_label.setStyleSheet("font-size: 25px;")
|
||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
||||
self._ui_form.scrollArea.verticalScrollBar().setValue(
|
||||
self._ui_form.scrollArea.verticalScrollBar().maximum())
|
||||
self._ui_form.scrollArea.verticalScrollBar().maximum()
|
||||
)
|
||||
|
||||
def add_error_text(self, text: str) -> None:
|
||||
""" Adds text to the error text box.
|
||||
"""Adds text to the error text box.
|
||||
|
||||
Args:
|
||||
text (str): Text to add to the error text box.
|
||||
text (str): Text to add to the error text box.
|
||||
"""
|
||||
# Add a timestamp to the text
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
@ -231,10 +257,11 @@ class AutoTMView(ModuleView):
|
|||
text_label.setStyleSheet("font-size: 25px; color: red;")
|
||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
||||
self._ui_form.scrollArea.verticalScrollBar().setValue(
|
||||
self._ui_form.scrollArea.verticalScrollBar().maximum())
|
||||
self._ui_form.scrollArea.verticalScrollBar().maximum()
|
||||
)
|
||||
|
||||
def create_frequency_sweep_spinner_dialog(self) -> None:
|
||||
"""Creates a frequency sweep spinner dialog. """
|
||||
"""Creates a frequency sweep spinner dialog."""
|
||||
self.frequency_sweep_spinner = self.FrequencySweepSpinner()
|
||||
self.frequency_sweep_spinner.show()
|
||||
|
||||
|
@ -259,7 +286,6 @@ class AutoTMView(ModuleView):
|
|||
self.spinner_movie.start()
|
||||
|
||||
class CalibrationWindow(QWidget):
|
||||
|
||||
def __init__(self, module, parent=None):
|
||||
super().__init__()
|
||||
self.module = module
|
||||
|
@ -290,10 +316,11 @@ class AutoTMView(ModuleView):
|
|||
# Add vertical layout for short calibration
|
||||
short_layout = QVBoxLayout()
|
||||
short_button = QPushButton("Short")
|
||||
short_button.clicked.connect(lambda: self.module.controller.on_short_calibration(
|
||||
float(start_edit.text()),
|
||||
float(stop_edit.text())
|
||||
))
|
||||
short_button.clicked.connect(
|
||||
lambda: self.module.controller.on_short_calibration(
|
||||
float(start_edit.text()), float(stop_edit.text())
|
||||
)
|
||||
)
|
||||
# Short plot widget
|
||||
self.short_plot = MplWidget()
|
||||
short_layout.addWidget(self.short_plot)
|
||||
|
@ -303,10 +330,11 @@ class AutoTMView(ModuleView):
|
|||
# Add vertical layout for open calibration
|
||||
open_layout = QVBoxLayout()
|
||||
open_button = QPushButton("Open")
|
||||
open_button.clicked.connect(lambda: self.module.controller.on_open_calibration(
|
||||
float(start_edit.text()),
|
||||
float(stop_edit.text())
|
||||
))
|
||||
open_button.clicked.connect(
|
||||
lambda: self.module.controller.on_open_calibration(
|
||||
float(start_edit.text()), float(stop_edit.text())
|
||||
)
|
||||
)
|
||||
# Open plot widget
|
||||
self.open_plot = MplWidget()
|
||||
open_layout.addWidget(self.open_plot)
|
||||
|
@ -316,10 +344,11 @@ class AutoTMView(ModuleView):
|
|||
# Add vertical layout for load calibration
|
||||
load_layout = QVBoxLayout()
|
||||
load_button = QPushButton("Load")
|
||||
load_button.clicked.connect(lambda: self.module.controller.on_load_calibration(
|
||||
float(start_edit.text()),
|
||||
float(stop_edit.text())
|
||||
))
|
||||
load_button.clicked.connect(
|
||||
lambda: self.module.controller.on_load_calibration(
|
||||
float(start_edit.text()), float(stop_edit.text())
|
||||
)
|
||||
)
|
||||
# Load plot widget
|
||||
self.load_plot = MplWidget()
|
||||
load_layout.addWidget(self.load_plot)
|
||||
|
@ -347,26 +376,28 @@ class AutoTMView(ModuleView):
|
|||
|
||||
# Connect the calibration finished signals to the on_calibration_finished slot
|
||||
self.module.model.short_calibration_finished.connect(
|
||||
self.on_short_calibration_finished)
|
||||
self.on_short_calibration_finished
|
||||
)
|
||||
self.module.model.open_calibration_finished.connect(
|
||||
self.on_open_calibration_finished)
|
||||
self.on_open_calibration_finished
|
||||
)
|
||||
self.module.model.load_calibration_finished.connect(
|
||||
self.on_load_calibration_finished)
|
||||
self.on_load_calibration_finished
|
||||
)
|
||||
|
||||
def on_short_calibration_finished(self, short_calibration: "S11Data") -> None:
|
||||
self.on_calibration_finished(
|
||||
"short", self.short_plot, short_calibration)
|
||||
self.on_calibration_finished("short", self.short_plot, short_calibration)
|
||||
|
||||
def on_open_calibration_finished(self, open_calibration: "S11Data") -> None:
|
||||
self.on_calibration_finished(
|
||||
"open", self.open_plot, open_calibration)
|
||||
self.on_calibration_finished("open", self.open_plot, open_calibration)
|
||||
|
||||
def on_load_calibration_finished(self, load_calibration: "S11Data") -> None:
|
||||
self.on_calibration_finished(
|
||||
"load", self.load_plot, load_calibration)
|
||||
self.on_calibration_finished("load", self.load_plot, load_calibration)
|
||||
|
||||
def on_calibration_finished(self, type: str, widget: MplWidget, data: "S11Data") -> None:
|
||||
"""This method is called when a calibration has finished.
|
||||
def on_calibration_finished(
|
||||
self, type: str, widget: MplWidget, data: "S11Data"
|
||||
) -> None:
|
||||
"""This method is called when a calibration has finished.
|
||||
It plots the calibration data on the given widget.
|
||||
"""
|
||||
frequency = data.frequency
|
||||
|
@ -403,7 +434,7 @@ class AutoTMView(ModuleView):
|
|||
self.module.controller.export_calibration(filename)
|
||||
|
||||
def on_import_button_clicked(self) -> None:
|
||||
"""This method is called when the import button is clicked. """
|
||||
"""This method is called when the import button is clicked."""
|
||||
filedialog = QFileDialog()
|
||||
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
|
||||
filedialog.setNameFilter("calibration files (*.cal)")
|
||||
|
@ -414,7 +445,7 @@ class AutoTMView(ModuleView):
|
|||
self.module.controller.import_calibration(filename)
|
||||
|
||||
def on_apply_button_clicked(self) -> None:
|
||||
"""This method is called when the apply button is clicked. """
|
||||
"""This method is called when the apply button is clicked."""
|
||||
self.module.controller.calculate_calibration()
|
||||
# Close the calibration window
|
||||
self.close()
|
||||
|
|
|
@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets
|
|||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(1280, 705)
|
||||
Form.resize(1280, 745)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
|
@ -70,28 +70,60 @@ class Ui_Form(object):
|
|||
self.elecTab.setObjectName("elecTab")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.elecTab)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.matchingBox.setObjectName("matchingBox")
|
||||
self.gridLayout_3.addWidget(self.matchingBox, 1, 1, 1, 1)
|
||||
self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab)
|
||||
self.setvoltagesButton.setObjectName("setvoltagesButton")
|
||||
self.gridLayout_3.addWidget(self.setvoltagesButton, 2, 0, 1, 2)
|
||||
self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.stopfrequencyBox.setObjectName("stopfrequencyBox")
|
||||
self.gridLayout_3.addWidget(self.stopfrequencyBox, 7, 1, 1, 1)
|
||||
self.label_9 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
font = QtGui.QFont()
|
||||
font.setBold(True)
|
||||
self.label_9.setFont(font)
|
||||
self.label_9.setObjectName("label_9")
|
||||
self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1)
|
||||
self.label_11 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
font = QtGui.QFont()
|
||||
font.setBold(True)
|
||||
self.label_11.setFont(font)
|
||||
self.label_11.setObjectName("label_11")
|
||||
self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1)
|
||||
self.tuningBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.tuningBox.setObjectName("tuningBox")
|
||||
self.gridLayout_3.addWidget(self.tuningBox, 0, 1, 1, 1)
|
||||
self.label_3 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout_3.addWidget(self.label_3, 1, 0, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout_3.addWidget(self.label_2, 0, 0, 1, 1)
|
||||
self.gridLayout_3.addWidget(self.tuningBox, 1, 1, 1, 1)
|
||||
self.label_12 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_12.setObjectName("label_12")
|
||||
self.gridLayout_3.addWidget(self.label_12, 6, 0, 1, 1)
|
||||
self.label_4 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout_3.addWidget(self.label_4, 3, 0, 1, 1)
|
||||
self.gridLayout_3.addWidget(self.label_4, 5, 0, 1, 1)
|
||||
self.label_3 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1)
|
||||
self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.matchingBox.setObjectName("matchingBox")
|
||||
self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1)
|
||||
self.label_13 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_13.setObjectName("label_13")
|
||||
self.gridLayout_3.addWidget(self.label_13, 7, 0, 1, 1)
|
||||
self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab)
|
||||
self.generateLUTButton.setObjectName("generateLUTButton")
|
||||
self.gridLayout_3.addWidget(self.generateLUTButton, 9, 0, 1, 2)
|
||||
self.resolutionBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.resolutionBox.setObjectName("resolutionBox")
|
||||
self.gridLayout_3.addWidget(self.resolutionBox, 3, 1, 1, 1)
|
||||
self.gridLayout_3.setRowStretch(3, 1)
|
||||
self.gridLayout_3.addWidget(self.resolutionBox, 5, 1, 1, 1)
|
||||
self.startfrequencyBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.startfrequencyBox.setObjectName("startfrequencyBox")
|
||||
self.gridLayout_3.addWidget(self.startfrequencyBox, 6, 1, 1, 1)
|
||||
self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab)
|
||||
self.setvoltagesButton.setObjectName("setvoltagesButton")
|
||||
self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2)
|
||||
self.label_14 = QtWidgets.QLabel(parent=self.elecTab)
|
||||
self.label_14.setObjectName("label_14")
|
||||
self.gridLayout_3.addWidget(self.label_14, 8, 0, 1, 1)
|
||||
self.frequencystepBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab)
|
||||
self.frequencystepBox.setObjectName("frequencystepBox")
|
||||
self.gridLayout_3.addWidget(self.frequencystepBox, 8, 1, 1, 1)
|
||||
self.typeTab.addTab(self.elecTab, "")
|
||||
self.verticalLayout_2.addWidget(self.typeTab)
|
||||
self.titlefrequencyLabel = QtWidgets.QLabel(parent=Form)
|
||||
|
@ -140,7 +172,7 @@ class Ui_Form(object):
|
|||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollArea.setObjectName("scrollArea")
|
||||
self.scrollAreaWidgetContents = QtWidgets.QWidget()
|
||||
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 297, 169))
|
||||
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 297, 68))
|
||||
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
|
||||
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
|
||||
self.verticalLayout_2.addWidget(self.scrollArea)
|
||||
|
@ -159,7 +191,7 @@ class Ui_Form(object):
|
|||
self.horizontalLayout_2.setStretch(1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
self.typeTab.setCurrentIndex(0)
|
||||
self.typeTab.setCurrentIndex(1)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
|
@ -173,10 +205,16 @@ class Ui_Form(object):
|
|||
self.titletypeLabel.setText(_translate("Form", "T&M Type:"))
|
||||
self.pushButton.setText(_translate("Form", "Homing"))
|
||||
self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical"))
|
||||
self.setvoltagesButton.setText(_translate("Form", "Set Voltages"))
|
||||
self.label_9.setText(_translate("Form", "Set Voltages:"))
|
||||
self.label_11.setText(_translate("Form", "Generate LUT:"))
|
||||
self.label_12.setText(_translate("Form", "Start Frequency (MHz)"))
|
||||
self.label_4.setText(_translate("Form", "Voltage Resolution"))
|
||||
self.label_3.setText(_translate("Form", "Voltage Matching"))
|
||||
self.label_2.setText(_translate("Form", "Voltage Tuning"))
|
||||
self.label_4.setText(_translate("Form", "Voltage Resolution"))
|
||||
self.label_13.setText(_translate("Form", "Stop Frequency (MHz)"))
|
||||
self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep"))
|
||||
self.setvoltagesButton.setText(_translate("Form", "Set Voltages"))
|
||||
self.label_14.setText(_translate("Form", "Frequency Step (MHz)"))
|
||||
self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical"))
|
||||
self.titlefrequencyLabel.setText(_translate("Form", "Frequency Sweep:"))
|
||||
self.label_8.setText(_translate("Form", "MHz"))
|
||||
|
|
Loading…
Reference in a new issue