mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-11-09 11:40:02 +00:00
Updated view
This commit is contained in:
commit
9777ba3570
5 changed files with 566 additions and 111 deletions
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from serial.tools.list_ports import comports
|
from serial.tools.list_ports import comports
|
||||||
from PyQt6.QtTest import QTest
|
from PyQt6.QtTest import QTest
|
||||||
from PyQt6 import QtSerialPort
|
from PyQt6 import QtSerialPort
|
||||||
|
@ -24,17 +25,41 @@ class AutoTMController(ModuleController):
|
||||||
for device in self.module.model.available_devices:
|
for device in self.module.model.available_devices:
|
||||||
logger.debug("Found device: %s", device)
|
logger.debug("Found device: %s", device)
|
||||||
|
|
||||||
def connect(self, device: str) -> None:
|
def handle_connection(self, device: str) -> None:
|
||||||
"""Connect to the specified device.
|
"""Connect or disconnect to the specified device based on if there already is a connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
device (str): The device port to connect to."""
|
device (str): The device port to connect to.
|
||||||
|
|
||||||
|
@TODO: If the user actually want to connect to another device while already connected to one,
|
||||||
|
this would have to be handled differently. But this doesn't really make sense in the current implementation.
|
||||||
|
"""
|
||||||
logger.debug("Connecting to device %s", device)
|
logger.debug("Connecting to device %s", device)
|
||||||
|
# If the user has already connected to a device, close the previous connection
|
||||||
|
if self.module.model.serial is not None:
|
||||||
|
if self.module.model.serial.isOpen():
|
||||||
|
logger.debug("Closing previous connection")
|
||||||
|
serial = self.module.model.serial
|
||||||
|
serial.close()
|
||||||
|
self.module.model.serial = serial
|
||||||
|
else:
|
||||||
|
self.open_connection(device)
|
||||||
|
# This is just for the first time the user connects to the device
|
||||||
|
else:
|
||||||
|
self.open_connection(device)
|
||||||
|
|
||||||
|
def open_connection(self, device: str) -> None:
|
||||||
|
"""Open a connection to the specified device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device (str): The device port to connect to.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.module.model.serial = QtSerialPort.QSerialPort(
|
serial = QtSerialPort.QSerialPort(
|
||||||
device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read
|
device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read
|
||||||
)
|
)
|
||||||
self.module.model.serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite)
|
serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite)
|
||||||
|
self.module.model.serial = serial
|
||||||
|
|
||||||
logger.debug("Connected to device %s", device)
|
logger.debug("Connected to device %s", device)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -77,10 +102,13 @@ class AutoTMController(ModuleController):
|
||||||
return
|
return
|
||||||
|
|
||||||
if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY:
|
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" % (
|
error = (
|
||||||
|
"Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz"
|
||||||
|
% (
|
||||||
MIN_FREQUENCY / 1e6,
|
MIN_FREQUENCY / 1e6,
|
||||||
MAX_FREQUENCY / 1e6,
|
MAX_FREQUENCY / 1e6,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
@ -92,12 +120,15 @@ class AutoTMController(ModuleController):
|
||||||
stop_frequency,
|
stop_frequency,
|
||||||
frequency_step,
|
frequency_step,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Print the command 'f<start>f<stop>f<step>' to the serial connection
|
||||||
|
command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step)
|
||||||
|
self.module.model.frequency_sweep_start = time.time()
|
||||||
|
confirmation = self.send_command(command)
|
||||||
|
if confirmation:
|
||||||
# We create the frequency sweep spinner dialog
|
# We create the frequency sweep spinner dialog
|
||||||
self.module.model.clear_data_points()
|
self.module.model.clear_data_points()
|
||||||
self.module.view.create_frequency_sweep_spinner_dialog()
|
self.module.view.create_frequency_sweep_spinner_dialog()
|
||||||
# Print the command 'f<start>f<stop>f<step>' to the serial connection
|
|
||||||
command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step)
|
|
||||||
self.send_command(command)
|
|
||||||
|
|
||||||
def on_ready_read(self) -> None:
|
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."""
|
||||||
|
@ -108,7 +139,10 @@ class AutoTMController(ModuleController):
|
||||||
# logger.debug("Received data: %s", text)
|
# 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
|
# 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
|
# 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")
|
text = text[1:].split("r")
|
||||||
frequency = float(text[0])
|
frequency = float(text[0])
|
||||||
return_loss, phase = map(float, text[1].split("p"))
|
return_loss, phase = map(float, text[1].split("p"))
|
||||||
|
@ -116,24 +150,46 @@ class AutoTMController(ModuleController):
|
||||||
# If the text starts with 'r' and no calibration is active we know that the data is a measurement
|
# 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:
|
elif text.startswith("r") and self.module.model.active_calibration == None:
|
||||||
logger.debug("Measurement finished")
|
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()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
|
self.module.model.frequency_sweep_stop = time.time()
|
||||||
|
self.module.view.add_info_text(
|
||||||
|
"Frequency sweep finished in %.2f seconds"
|
||||||
|
% (
|
||||||
|
self.module.model.frequency_sweep_stop
|
||||||
|
- self.module.model.frequency_sweep_start
|
||||||
|
)
|
||||||
|
)
|
||||||
# If the text starts with 'r' and a short calibration is active we know that the data is a short calibration
|
# 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")
|
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.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
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
|
# 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")
|
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.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
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
|
# 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")
|
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.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
# If the text starts with 'i' we know that the data is an info message
|
# If the text starts with 'i' we know that the data is an info message
|
||||||
|
@ -167,7 +223,9 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Starting next voltage sweep: %s", command)
|
logger.debug("Starting next voltage sweep: %s", command)
|
||||||
self.send_command(command)
|
self.send_command(command)
|
||||||
|
|
||||||
def on_short_calibration(self, start_frequency: float, stop_frequency: float) -> None:
|
def on_short_calibration(
|
||||||
|
self, start_frequency: float, stop_frequency: float
|
||||||
|
) -> None:
|
||||||
"""This method is called when the short calibration button is pressed.
|
"""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.
|
It starts a frequency sweep in the specified range and then starts a short calibration.
|
||||||
"""
|
"""
|
||||||
|
@ -175,7 +233,9 @@ class AutoTMController(ModuleController):
|
||||||
self.module.model.init_short_calibration()
|
self.module.model.init_short_calibration()
|
||||||
self.start_frequency_sweep(start_frequency, stop_frequency)
|
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.
|
"""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.
|
It starts a frequency sweep in the specified range and then starts an open calibration.
|
||||||
"""
|
"""
|
||||||
|
@ -183,7 +243,9 @@ class AutoTMController(ModuleController):
|
||||||
self.module.model.init_open_calibration()
|
self.module.model.init_open_calibration()
|
||||||
self.start_frequency_sweep(start_frequency, stop_frequency)
|
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.
|
"""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.
|
It starts a frequency sweep in the specified range and then loads a calibration.
|
||||||
"""
|
"""
|
||||||
|
@ -205,13 +267,19 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Calculating calibration")
|
logger.debug("Calculating calibration")
|
||||||
# First we check if the short and open calibration data points are available
|
# First we check if the short and open calibration data points are available
|
||||||
if self.module.model.short_calibration == None:
|
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
|
return
|
||||||
if self.module.model.open_calibration == None:
|
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
|
return
|
||||||
if self.module.model.load_calibration == None:
|
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
|
return
|
||||||
|
|
||||||
# Then we calculate the calibration
|
# Then we calculate the calibration
|
||||||
|
@ -226,7 +294,9 @@ class AutoTMController(ModuleController):
|
||||||
e_00s = []
|
e_00s = []
|
||||||
e_11s = []
|
e_11s = []
|
||||||
delta_es = []
|
delta_es = []
|
||||||
for gamma_s, gamma_o, gamma_l in zip(measured_gamma_short, measured_gamma_open, measured_gamma_load):
|
for gamma_s, gamma_o, gamma_l in zip(
|
||||||
|
measured_gamma_short, measured_gamma_open, measured_gamma_load
|
||||||
|
):
|
||||||
# This is the solution from
|
# This is the solution from
|
||||||
A = np.array(
|
A = np.array(
|
||||||
[
|
[
|
||||||
|
@ -257,15 +327,21 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Exporting calibration")
|
logger.debug("Exporting calibration")
|
||||||
# First we check if the short and open calibration data points are available
|
# First we check if the short and open calibration data points are available
|
||||||
if self.module.model.short_calibration == None:
|
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
|
return
|
||||||
|
|
||||||
if self.module.model.open_calibration == None:
|
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
|
return
|
||||||
|
|
||||||
if self.module.model.load_calibration == None:
|
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
|
return
|
||||||
|
|
||||||
# Then we export the different calibrations as a json file
|
# Then we export the different calibrations as a json file
|
||||||
|
@ -316,7 +392,9 @@ class AutoTMController(ModuleController):
|
||||||
return
|
return
|
||||||
|
|
||||||
if tuning_voltage < 0 or matching_voltage < 0:
|
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)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
@ -368,7 +446,12 @@ class AutoTMController(ModuleController):
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
|
||||||
if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0 or voltage_resolution < 0:
|
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"
|
error = "Could not generate LUT. Start frequency, stop frequency, frequency step and voltage resolution must be positive"
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
|
@ -395,14 +478,18 @@ class AutoTMController(ModuleController):
|
||||||
)
|
)
|
||||||
|
|
||||||
# We create the lookup table
|
# We create the lookup table
|
||||||
LUT = LookupTable(start_frequency, stop_frequency, frequency_step, voltage_resolution)
|
LUT = LookupTable(
|
||||||
|
start_frequency, stop_frequency, frequency_step, voltage_resolution
|
||||||
|
)
|
||||||
|
|
||||||
LUT.started_frequency = start_frequency
|
LUT.started_frequency = start_frequency
|
||||||
self.module.model.LUT = LUT
|
self.module.model.LUT = LUT
|
||||||
|
|
||||||
# We write the first command to the serial connection
|
# We write the first command to the serial connection
|
||||||
command = "s%s" % (start_frequency)
|
command = "s%s" % (start_frequency)
|
||||||
self.send_command(command)
|
confirmation = self.send_command(command)
|
||||||
|
if not confirmation:
|
||||||
|
return
|
||||||
|
|
||||||
def switch_to_preamp(self) -> None:
|
def switch_to_preamp(self) -> None:
|
||||||
"""This method is used to send the command 'cp' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'Preamp'.
|
"""This method is used to send the command 'cp' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'Preamp'.
|
||||||
|
@ -411,7 +498,6 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Switching to preamp")
|
logger.debug("Switching to preamp")
|
||||||
self.send_command("cp")
|
self.send_command("cp")
|
||||||
|
|
||||||
|
|
||||||
def switch_to_atm(self) -> None:
|
def switch_to_atm(self) -> None:
|
||||||
"""This method is used to send the command 'ca' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'ATM.
|
"""This method is used to send the command 'ca' to the atm system. This switches the signal pathway of the atm system to 'RX' to 'ATM.
|
||||||
In this state the atm system can be used to measure the reflection coefficient of the probecoils.
|
In this state the atm system can be used to measure the reflection coefficient of the probecoils.
|
||||||
|
@ -419,22 +505,57 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Switching to atm")
|
logger.debug("Switching to atm")
|
||||||
self.send_command("ca")
|
self.send_command("ca")
|
||||||
|
|
||||||
def send_command(self, command : str) -> None:
|
def send_command(self, command: str) -> bool:
|
||||||
"""This method is used to send a command to the active serial connection.
|
"""This method is used to send a command to the active serial connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
command (str): The command that should be send to the atm system.
|
command (str): The command that should be send to the atm system.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the command was send successfully, False otherwise.
|
||||||
"""
|
"""
|
||||||
logger.debug("Sending command %s", command)
|
logger.debug("Sending command %s", command)
|
||||||
|
timeout = 10000 # ms
|
||||||
|
|
||||||
|
if self.module.model.serial is None:
|
||||||
|
logger.error("Could not send command. No serial connection")
|
||||||
|
self.module.view.add_error_text(
|
||||||
|
"Could not send command. No serial connection"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.module.model.serial.isOpen() == False:
|
||||||
|
logger.error("Could not send command. Serial connection is not open")
|
||||||
|
self.module.view.add_error_text(
|
||||||
|
"Could not send command. Serial connection is not open"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.module.model.serial.write(command.encode("utf-8"))
|
self.module.model.serial.write(command.encode("utf-8"))
|
||||||
# Wait for 0.5 seconds
|
# Wait for the confirmation of the command ('c') to be read with a timeout of 1 second
|
||||||
QTest.qWait(500)
|
|
||||||
# Make sure that the command is being send
|
if not self.module.model.serial.waitForReadyRead(timeout):
|
||||||
QApplication.processEvents()
|
logger.error("Could not send command. Timeout")
|
||||||
except AttributeError:
|
self.module.view.add_error_text("Could not send command. Timeout")
|
||||||
logger.error("Could not send command. No device connected.")
|
return False
|
||||||
self.module.view.add_error_text("Could not send command. No device connected.")
|
|
||||||
|
confirmation = self.module.model.serial.readLine().data().decode("utf-8")
|
||||||
|
logger.debug("Confirmation: %s", confirmation)
|
||||||
|
|
||||||
|
if confirmation == "c":
|
||||||
|
logger.debug("Command send successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error("Could not send command. No confirmation received")
|
||||||
|
self.module.view.add_error_text(
|
||||||
|
"Could not send command. No confirmation received"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Could not send command. %s", e)
|
||||||
|
self.module.view.add_error_text("Could not send command. %s" % e)
|
||||||
|
|
||||||
def homing(self) -> None:
|
def homing(self) -> None:
|
||||||
"""This method is used to send the command 'h' to the atm system.
|
"""This method is used to send the command 'h' to the atm system.
|
||||||
|
@ -442,5 +563,3 @@ class AutoTMController(ModuleController):
|
||||||
"""
|
"""
|
||||||
logger.debug("Homing")
|
logger.debug("Homing")
|
||||||
self.send_command("h")
|
self.send_command("h")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import cmath
|
import cmath
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import logging
|
import logging
|
||||||
|
from scipy.signal import find_peaks
|
||||||
from PyQt6.QtCore import pyqtSignal
|
from PyQt6.QtCore import pyqtSignal
|
||||||
from PyQt6.QtSerialPort import QSerialPort
|
from PyQt6.QtSerialPort import QSerialPort
|
||||||
from nqrduck.module.module_model import ModuleModel
|
from nqrduck.module.module_model import ModuleModel
|
||||||
|
@ -12,7 +13,7 @@ class S11Data:
|
||||||
# Conversion factors - the data is generally sent and received in mV
|
# Conversion factors - the data is generally sent and received in mV
|
||||||
# These values are used to convert the data to dB and degrees
|
# These values are used to convert the data to dB and degrees
|
||||||
CENTER_POINT_MAGNITUDE = 900 # mV
|
CENTER_POINT_MAGNITUDE = 900 # mV
|
||||||
CENTER_POINT_PHASE = 900 # mV
|
CENTER_POINT_PHASE = 0 # mV
|
||||||
MAGNITUDE_SLOPE = 30 # dB/mV
|
MAGNITUDE_SLOPE = 30 # dB/mV
|
||||||
PHASE_SLOPE = 10 # deg/mV
|
PHASE_SLOPE = 10 # deg/mV
|
||||||
|
|
||||||
|
@ -32,9 +33,18 @@ class S11Data:
|
||||||
) / self.MAGNITUDE_SLOPE
|
) / self.MAGNITUDE_SLOPE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def phase_deg(self):
|
def phase_deg(self, phase_correction=True):
|
||||||
"""Returns the absolute value of the phase in degrees"""
|
"""Returns the absolute value of the phase in degrees
|
||||||
return (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
|
|
||||||
|
Keyword Arguments:
|
||||||
|
phase_correction {bool} -- If True, the phase correction is applied. (default: {False})
|
||||||
|
"""
|
||||||
|
|
||||||
|
phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
|
||||||
|
if phase_correction:
|
||||||
|
phase_deg = self.phase_correction(self.frequency, phase_deg)
|
||||||
|
|
||||||
|
return phase_deg
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def phase_rad(self):
|
def phase_rad(self):
|
||||||
|
@ -51,6 +61,86 @@ class S11Data:
|
||||||
for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad)
|
for loss_db, phase_rad in zip(self.return_loss_db, self.phase_rad)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def phase_correction(
|
||||||
|
self, frequency_data: np.array, phase_data: np.array
|
||||||
|
) -> np.array:
|
||||||
|
"""This method fixes the phase sign of the phase data.
|
||||||
|
The AD8302 can only measure the absolute value of the phase.
|
||||||
|
Therefore we need to correct the phase sign. This can be done via the slope of the phase.
|
||||||
|
If the slope is negative, the phase is positive and vice versa.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frequency_data (np.array): The frequency data.
|
||||||
|
phase_data (np.array): The phase data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
np.array: The corrected phase data.
|
||||||
|
"""
|
||||||
|
# First we apply a moving average filter to the phase data
|
||||||
|
WINDOW_SIZE = 5
|
||||||
|
phase_data_filtered = (
|
||||||
|
np.convolve(phase_data, np.ones(WINDOW_SIZE), "same") / WINDOW_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fix transient response
|
||||||
|
phase_data_filtered[: WINDOW_SIZE // 2] = phase_data[: WINDOW_SIZE // 2]
|
||||||
|
phase_data_filtered[-WINDOW_SIZE // 2 :] = phase_data[-WINDOW_SIZE // 2 :]
|
||||||
|
|
||||||
|
# Now we find the peaks and valleys of the data
|
||||||
|
HEIGHT = 100
|
||||||
|
distance = len(phase_data_filtered) / 10
|
||||||
|
|
||||||
|
peaks, _ = find_peaks(phase_data_filtered, distance=distance, height=HEIGHT)
|
||||||
|
|
||||||
|
valleys, _ = find_peaks(
|
||||||
|
180 - phase_data_filtered, distance=distance, height=HEIGHT
|
||||||
|
)
|
||||||
|
|
||||||
|
# Determine if the first point is a peak or a valley
|
||||||
|
if phase_data_filtered[0] > phase_data_filtered[1]:
|
||||||
|
peaks = np.insert(peaks, 0, 0)
|
||||||
|
else:
|
||||||
|
valleys = np.insert(valleys, 0, 0)
|
||||||
|
|
||||||
|
# Determine if the last point is a peak or a valley
|
||||||
|
if phase_data_filtered[-1] > phase_data_filtered[-2]:
|
||||||
|
peaks = np.append(peaks, len(phase_data_filtered) - 1)
|
||||||
|
else:
|
||||||
|
valleys = np.append(valleys, len(phase_data_filtered) - 1)
|
||||||
|
|
||||||
|
frequency_peaks = frequency_data[peaks]
|
||||||
|
frequency_valleys = frequency_data[valleys]
|
||||||
|
|
||||||
|
# Combine the peaks and valleys
|
||||||
|
frequency_peaks_valleys = np.sort(
|
||||||
|
np.concatenate((frequency_peaks, frequency_valleys))
|
||||||
|
)
|
||||||
|
peaks_valleys = np.sort(np.concatenate((peaks, valleys)))
|
||||||
|
|
||||||
|
# Now we can determine the slope of the phase
|
||||||
|
# For this we compare the phase of our peaks_valleys array to the next point
|
||||||
|
# If the phase is increasing, the slope is positive, if it is decreasing, the slope is negative
|
||||||
|
phase_slope = np.zeros(len(peaks_valleys) - 1)
|
||||||
|
for i in range(len(peaks_valleys) - 1):
|
||||||
|
phase_slope[i] = (
|
||||||
|
phase_data_filtered[peaks_valleys[i + 1]]
|
||||||
|
- phase_data_filtered[peaks_valleys[i]]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now we can determine the sign of the phase
|
||||||
|
# If the slope is negative, the phase is positive and vice versa
|
||||||
|
phase_sign = np.sign(phase_slope) * -1
|
||||||
|
|
||||||
|
# Now we can correct the phase for the different sections
|
||||||
|
phase_data_corrected = np.zeros(len(phase_data))
|
||||||
|
for i in range(len(peaks_valleys) - 1):
|
||||||
|
phase_data_corrected[peaks_valleys[i] : peaks_valleys[i + 1]] = (
|
||||||
|
phase_data_filtered[peaks_valleys[i] : peaks_valleys[i + 1]]
|
||||||
|
* phase_sign[i]
|
||||||
|
)
|
||||||
|
|
||||||
|
return phase_data_corrected
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
"frequency": self.frequency.tolist(),
|
"frequency": self.frequency.tolist(),
|
||||||
|
@ -148,6 +238,7 @@ class AutoTMModel(ModuleModel):
|
||||||
self.data_points = []
|
self.data_points = []
|
||||||
self.active_calibration = None
|
self.active_calibration = None
|
||||||
self.calibration = None
|
self.calibration = None
|
||||||
|
self.serial = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_devices(self):
|
def available_devices(self):
|
||||||
|
@ -265,3 +356,21 @@ class AutoTMModel(ModuleModel):
|
||||||
@LUT.setter
|
@LUT.setter
|
||||||
def LUT(self, value):
|
def LUT(self, value):
|
||||||
self._LUT = value
|
self._LUT = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frequency_sweep_start(self):
|
||||||
|
"""The timestamp for when the frequency sweep has been started. This is used for timing of the frequency sweep."""
|
||||||
|
return self._frequency_sweep_start
|
||||||
|
|
||||||
|
@frequency_sweep_start.setter
|
||||||
|
def frequency_sweep_start(self, value):
|
||||||
|
self._frequency_sweep_start = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frequency_sweep_end(self):
|
||||||
|
"""The timestamp for when the frequency sweep has been ended. This is used for timing of the frequency sweep."""
|
||||||
|
return self._frequency_sweep_end
|
||||||
|
|
||||||
|
@frequency_sweep_end.setter
|
||||||
|
def frequency_sweep_end(self, value):
|
||||||
|
self._frequency_sweep_end = value
|
||||||
|
|
|
@ -97,14 +97,114 @@
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Mechanical</string>
|
<string>Mechanical</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="homingButton">
|
<layout class="QGridLayout" name="gridLayout_4" rowstretch="0,0,0,0,0,0">
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QPushButton" name="homematcherButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Homing</string>
|
<string>Home</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_17">
|
||||||
|
<property name="text">
|
||||||
|
<string>Step Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="stepsizeBox">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-1000</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>500</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label_18">
|
||||||
|
<property name="text">
|
||||||
|
<string>Tuning Stepper:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QPushButton" name="decreasetunerButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>-</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<widget class="QPushButton" name="increasetunerButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>+</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label_19">
|
||||||
|
<property name="text">
|
||||||
|
<string>Matching Stepper:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QPushButton" name="decreasematcherButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>-</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="2">
|
||||||
|
<widget class="QPushButton" name="increasematcherButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>+</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QPushButton" name="hometunerButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Home</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label_16">
|
||||||
|
<property name="text">
|
||||||
|
<string>Stepper Control:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="starpositionButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Start Position</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="elecTab">
|
<widget class="QWidget" name="elecTab">
|
||||||
|
@ -257,7 +357,11 @@
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QLineEdit" name="startEdit"/>
|
<widget class="QLineEdit" name="startEdit">
|
||||||
|
<property name="text">
|
||||||
|
<string>80</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<widget class="QLabel" name="label_8">
|
<widget class="QLabel" name="label_8">
|
||||||
|
@ -267,7 +371,11 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="stopEdit"/>
|
<widget class="QLineEdit" name="stopEdit">
|
||||||
|
<property name="text">
|
||||||
|
<string>100</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
<item row="0" column="2">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
|
|
|
@ -18,6 +18,10 @@ from PyQt6.QtWidgets import (
|
||||||
from PyQt6.QtCore import pyqtSlot, Qt
|
from PyQt6.QtCore import pyqtSlot, Qt
|
||||||
from nqrduck.module.module_view import ModuleView
|
from nqrduck.module.module_view import ModuleView
|
||||||
from nqrduck.contrib.mplwidget import MplWidget
|
from nqrduck.contrib.mplwidget import MplWidget
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
from nqrduck.assets.icons import Logos
|
||||||
|
>>>>>>> c21dc155fa5b50d6dce64605a6d86007ea8086c7
|
||||||
from nqrduck.assets.animations import DuckAnimations
|
from nqrduck.assets.animations import DuckAnimations
|
||||||
from .widget import Ui_Form
|
from .widget import Ui_Form
|
||||||
|
|
||||||
|
@ -33,7 +37,7 @@ class AutoTMView(ModuleView):
|
||||||
self._ui_form.setupUi(self)
|
self._ui_form.setupUi(self)
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
|
|
||||||
self.frequency_sweep_spinner = self.FrequencySweepSpinner()
|
self.frequency_sweep_spinner = self.FrequencySweepSpinner(self)
|
||||||
self.frequency_sweep_spinner.hide()
|
self.frequency_sweep_spinner.hide()
|
||||||
|
|
||||||
# Disable the connectButton while no devices are selected
|
# Disable the connectButton while no devices are selected
|
||||||
|
@ -43,7 +47,9 @@ class AutoTMView(ModuleView):
|
||||||
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
|
# 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.module.model.available_devices_changed.connect(
|
||||||
|
self.on_available_devices_changed
|
||||||
|
)
|
||||||
|
|
||||||
# Connect the serial changed signal to the on_serial_changed slot
|
# Connect the serial changed signal to the on_serial_changed slot
|
||||||
self.module.model.serial_changed.connect(self.on_serial_changed)
|
self.module.model.serial_changed.connect(self.on_serial_changed)
|
||||||
|
@ -79,23 +85,35 @@ class AutoTMView(ModuleView):
|
||||||
)
|
)
|
||||||
|
|
||||||
# On clicking of the calibration button call the on_calibration_button_clicked method
|
# 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._ui_form.calibrationButton.clicked.connect(
|
||||||
|
self.on_calibration_button_clicked
|
||||||
|
)
|
||||||
|
|
||||||
# On clicking of the switchpreampButton call the switch_preamp method
|
# On clicking of the switchpreampButton call the switch_preamp method
|
||||||
self._ui_form.switchpreampButton.clicked.connect(self.module.controller.switch_to_preamp)
|
self._ui_form.switchpreampButton.clicked.connect(
|
||||||
|
self.module.controller.switch_to_preamp
|
||||||
|
)
|
||||||
|
|
||||||
# On clicking of the switchATMButton call the switch_atm method
|
# On clicking of the switchATMButton call the switch_atm method
|
||||||
self._ui_form.switchATMButton.clicked.connect(self.module.controller.switch_to_atm)
|
self._ui_form.switchATMButton.clicked.connect(
|
||||||
|
self.module.controller.switch_to_atm
|
||||||
|
)
|
||||||
|
|
||||||
# On clicking of the homingButton call the homing method
|
# On clicking of the homingButton call the homing method
|
||||||
self._ui_form.homingButton.clicked.connect(self.module.controller.homing)
|
self._ui_form.starpositionButton.clicked.connect(self.module.controller.homing)
|
||||||
|
|
||||||
# Connect the measurement finished signal to the plot_measurement slot
|
# Connect the measurement finished signal to the plot_measurement slot
|
||||||
self.module.model.measurement_finished.connect(self.plot_measurement)
|
self.module.model.measurement_finished.connect(self.plot_measurement)
|
||||||
|
|
||||||
# Add a vertical layout to the info box
|
# Add a vertical layout to the info box
|
||||||
self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout())
|
self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout())
|
||||||
self._ui_form.scrollAreaWidgetContents.layout().setAlignment(Qt.AlignmentFlag.AlignTop)
|
self._ui_form.scrollAreaWidgetContents.layout().setAlignment(
|
||||||
|
Qt.AlignmentFlag.AlignTop
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add button Icons
|
||||||
|
self._ui_form.startButton.setIcon(Logos.Play_16x16())
|
||||||
|
self._ui_form.startButton.setIconSize(self._ui_form.startButton.size())
|
||||||
|
|
||||||
self.init_plot()
|
self.init_plot()
|
||||||
self.init_labels()
|
self.init_labels()
|
||||||
|
@ -125,7 +143,7 @@ class AutoTMView(ModuleView):
|
||||||
It opens the calibration window.
|
It opens the calibration window.
|
||||||
"""
|
"""
|
||||||
logger.debug("Calibration button clicked")
|
logger.debug("Calibration button clicked")
|
||||||
self.calibration_window = self.CalibrationWindow(self.module)
|
self.calibration_window = self.CalibrationWindow(self.module, self)
|
||||||
self.calibration_window.show()
|
self.calibration_window.show()
|
||||||
|
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
|
@ -148,7 +166,7 @@ class AutoTMView(ModuleView):
|
||||||
"""
|
"""
|
||||||
logger.debug("Connect button clicked")
|
logger.debug("Connect button clicked")
|
||||||
selected_device = self._ui_form.portBox.currentText()
|
selected_device = self._ui_form.portBox.currentText()
|
||||||
self.module.controller.connect(selected_device)
|
self.module.controller.handle_connection(selected_device)
|
||||||
|
|
||||||
@pyqtSlot(QSerialPort)
|
@pyqtSlot(QSerialPort)
|
||||||
def on_serial_changed(self, serial: QSerialPort) -> None:
|
def on_serial_changed(self, serial: QSerialPort) -> None:
|
||||||
|
@ -157,11 +175,16 @@ class AutoTMView(ModuleView):
|
||||||
Args:
|
Args:
|
||||||
serial (serial.Serial): The current serial connection."""
|
serial (serial.Serial): The current serial connection."""
|
||||||
logger.debug("Updating serial connection label")
|
logger.debug("Updating serial connection label")
|
||||||
if serial:
|
if serial.isOpen():
|
||||||
self._ui_form.connectionLabel.setText(serial.portName())
|
self._ui_form.connectionLabel.setText(serial.portName())
|
||||||
self.add_info_text("Connected to device %s" % serial.portName())
|
self.add_info_text("Connected to device %s" % serial.portName())
|
||||||
|
# Change the connectButton to a disconnectButton
|
||||||
|
self._ui_form.connectButton.setText("Disconnect")
|
||||||
else:
|
else:
|
||||||
self._ui_form.connectionLabel.setText("Disconnected")
|
self._ui_form.connectionLabel.setText("Disconnected")
|
||||||
|
self.add_info_text("Disconnected from device")
|
||||||
|
self._ui_form.connectButton.setText("Connect")
|
||||||
|
|
||||||
logger.debug("Updated serial connection label")
|
logger.debug("Updated serial connection label")
|
||||||
|
|
||||||
def plot_measurement(self, data: "S11Data") -> None:
|
def plot_measurement(self, data: "S11Data") -> None:
|
||||||
|
@ -188,17 +211,19 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
# Calibration for visualization happens here.
|
# Calibration for visualization happens here.
|
||||||
if self.module.model.calibration is not None:
|
if self.module.model.calibration is not None:
|
||||||
|
|
||||||
calibration = self.module.model.calibration
|
calibration = self.module.model.calibration
|
||||||
e_00 = calibration[0]
|
e_00 = calibration[0]
|
||||||
e11 = calibration[1]
|
e11 = calibration[1]
|
||||||
delta_e = calibration[2]
|
delta_e = calibration[2]
|
||||||
|
|
||||||
gamma_corr = [
|
gamma_corr = [
|
||||||
(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i]) for i, data_point in enumerate(gamma)
|
(data_point - e_00[i]) / (data_point * e11[i] - delta_e[i])
|
||||||
|
for i, data_point in enumerate(gamma)
|
||||||
]
|
]
|
||||||
|
|
||||||
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")
|
magnitude_ax.plot(frequency, return_loss_db_corr, color="red")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -206,8 +231,7 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
self.phase_ax.set_ylabel("|Phase (deg)|")
|
self.phase_ax.set_ylabel("|Phase (deg)|")
|
||||||
self.phase_ax.plot(frequency, phase, color="orange", linestyle="--")
|
self.phase_ax.plot(frequency, phase, color="orange", linestyle="--")
|
||||||
self.phase_ax.set_ylim(-180, 180)
|
# self.phase_ax.invert_yaxis()
|
||||||
self.phase_ax.invert_yaxis()
|
|
||||||
|
|
||||||
magnitude_ax.set_xlabel("Frequency (MHz)")
|
magnitude_ax.set_xlabel("Frequency (MHz)")
|
||||||
magnitude_ax.set_ylabel("S11 (dB)")
|
magnitude_ax.set_ylabel("S11 (dB)")
|
||||||
|
@ -234,7 +258,9 @@ class AutoTMView(ModuleView):
|
||||||
text_label = QLabel(text)
|
text_label = QLabel(text)
|
||||||
text_label.setStyleSheet("font-size: 25px;")
|
text_label.setStyleSheet("font-size: 25px;")
|
||||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
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().setValue(
|
||||||
|
self._ui_form.scrollArea.verticalScrollBar().maximum()
|
||||||
|
)
|
||||||
|
|
||||||
def add_error_text(self, text: str) -> None:
|
def add_error_text(self, text: str) -> None:
|
||||||
"""Adds text to the error text box.
|
"""Adds text to the error text box.
|
||||||
|
@ -242,17 +268,30 @@ class AutoTMView(ModuleView):
|
||||||
Args:
|
Args:
|
||||||
text (str): Text to add to the error text box.
|
text (str): Text to add to the error text box.
|
||||||
"""
|
"""
|
||||||
|
message_widget = QWidget()
|
||||||
|
message_widget.setLayout(QHBoxLayout())
|
||||||
|
|
||||||
|
error_icon = QLabel()
|
||||||
|
error_icon.setPixmap(
|
||||||
|
Logos.Error_16x16().pixmap(Logos.Error_16x16().availableSizes()[0])
|
||||||
|
)
|
||||||
# Add a timestamp to the text
|
# Add a timestamp to the text
|
||||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||||
text = "[%s] %s ERROR:" % (timestamp, text)
|
text = "[%s] %s" % (timestamp, text)
|
||||||
text_label = QLabel(text)
|
text_label = QLabel(text)
|
||||||
text_label.setStyleSheet("font-size: 25px; color: red;")
|
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())
|
message_widget.layout().addWidget(error_icon)
|
||||||
|
message_widget.layout().addWidget(text_label)
|
||||||
|
|
||||||
|
self._ui_form.scrollAreaWidgetContents.layout().addWidget(message_widget)
|
||||||
|
self._ui_form.scrollArea.verticalScrollBar().setValue(
|
||||||
|
self._ui_form.scrollArea.verticalScrollBar().maximum()
|
||||||
|
)
|
||||||
|
|
||||||
def create_frequency_sweep_spinner_dialog(self) -> None:
|
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 = self.FrequencySweepSpinner(self)
|
||||||
self.frequency_sweep_spinner.show()
|
self.frequency_sweep_spinner.show()
|
||||||
|
|
||||||
def view_lut(self) -> None:
|
def view_lut(self) -> None:
|
||||||
|
@ -264,19 +303,24 @@ class AutoTMView(ModuleView):
|
||||||
class FrequencySweepSpinner(QDialog):
|
class FrequencySweepSpinner(QDialog):
|
||||||
"""This class implements a spinner dialog that is shown during a frequency sweep."""
|
"""This class implements a spinner dialog that is shown during a frequency sweep."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, parent=None):
|
||||||
super().__init__()
|
super().__init__(parent)
|
||||||
self.setWindowTitle("Frequency sweep")
|
self.setWindowTitle("Frequency sweep")
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
|
self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
|
||||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
self.spinner_movie = DuckAnimations.DuckKick128x128()
|
self.spinner_movie = DuckAnimations.DuckKick128x128()
|
||||||
|
=======
|
||||||
|
self.spinner_movie = DuckAnimations.DuckSleep128x128()
|
||||||
|
>>>>>>> c21dc155fa5b50d6dce64605a6d86007ea8086c7
|
||||||
self.spinner_label = QLabel(self)
|
self.spinner_label = QLabel(self)
|
||||||
self.spinner_label.setMovie(self.spinner_movie)
|
self.spinner_label.setMovie(self.spinner_movie)
|
||||||
|
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.layout.addWidget(QLabel("Performing frequency sweep..."))
|
||||||
self.layout.addWidget(self.spinner_label)
|
self.layout.addWidget(self.spinner_label)
|
||||||
|
|
||||||
self.spinner_movie.start()
|
self.spinner_movie.start()
|
||||||
|
@ -294,13 +338,19 @@ class AutoTMView(ModuleView):
|
||||||
# Create table widget
|
# Create table widget
|
||||||
self.table_widget = QTableWidget()
|
self.table_widget = QTableWidget()
|
||||||
self.table_widget.setColumnCount(3)
|
self.table_widget.setColumnCount(3)
|
||||||
self.table_widget.setHorizontalHeaderLabels(["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"])
|
self.table_widget.setHorizontalHeaderLabels(
|
||||||
|
["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"]
|
||||||
|
)
|
||||||
LUT = self.module.model.LUT
|
LUT = self.module.model.LUT
|
||||||
for row, frequency in enumerate(LUT.data.keys()):
|
for row, frequency in enumerate(LUT.data.keys()):
|
||||||
self.table_widget.insertRow(row)
|
self.table_widget.insertRow(row)
|
||||||
self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency)))
|
self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency)))
|
||||||
self.table_widget.setItem(row, 1, QTableWidgetItem(str(LUT.data[frequency][0])))
|
self.table_widget.setItem(
|
||||||
self.table_widget.setItem(row, 2, QTableWidgetItem(str(LUT.data[frequency][1])))
|
row, 1, QTableWidgetItem(str(LUT.data[frequency][0]))
|
||||||
|
)
|
||||||
|
self.table_widget.setItem(
|
||||||
|
row, 2, QTableWidgetItem(str(LUT.data[frequency][1]))
|
||||||
|
)
|
||||||
|
|
||||||
# Add table widget to main layout
|
# Add table widget to main layout
|
||||||
main_layout.addWidget(self.table_widget)
|
main_layout.addWidget(self.table_widget)
|
||||||
|
@ -321,11 +371,11 @@ class AutoTMView(ModuleView):
|
||||||
matching_voltage = str(self.module.model.LUT.data[frequency][0])
|
matching_voltage = str(self.module.model.LUT.data[frequency][0])
|
||||||
self.module.controller.set_voltages(tuning_voltage, matching_voltage)
|
self.module.controller.set_voltages(tuning_voltage, matching_voltage)
|
||||||
|
|
||||||
class CalibrationWindow(QWidget):
|
class CalibrationWindow(QDialog):
|
||||||
def __init__(self, module, parent=None):
|
def __init__(self, module, parent=None):
|
||||||
super().__init__()
|
super().__init__(parent)
|
||||||
self.module = module
|
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
|
self.module = module
|
||||||
self.setWindowTitle("Calibration")
|
self.setWindowTitle("Calibration")
|
||||||
|
|
||||||
# Add vertical main layout
|
# Add vertical main layout
|
||||||
|
@ -344,6 +394,7 @@ class AutoTMView(ModuleView):
|
||||||
frequency_layout.addWidget(stop_edit)
|
frequency_layout.addWidget(stop_edit)
|
||||||
unit_label = QLabel("MHz")
|
unit_label = QLabel("MHz")
|
||||||
frequency_layout.addWidget(unit_label)
|
frequency_layout.addWidget(unit_label)
|
||||||
|
frequency_layout.addStretch()
|
||||||
|
|
||||||
# Add horizontal layout for the calibration type
|
# Add horizontal layout for the calibration type
|
||||||
type_layout = QHBoxLayout()
|
type_layout = QHBoxLayout()
|
||||||
|
@ -353,7 +404,9 @@ class AutoTMView(ModuleView):
|
||||||
short_layout = QVBoxLayout()
|
short_layout = QVBoxLayout()
|
||||||
short_button = QPushButton("Short")
|
short_button = QPushButton("Short")
|
||||||
short_button.clicked.connect(
|
short_button.clicked.connect(
|
||||||
lambda: self.module.controller.on_short_calibration(start_edit.text(), stop_edit.text())
|
lambda: self.module.controller.on_short_calibration(
|
||||||
|
start_edit.text(), stop_edit.text()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# Short plot widget
|
# Short plot widget
|
||||||
self.short_plot = MplWidget()
|
self.short_plot = MplWidget()
|
||||||
|
@ -365,7 +418,9 @@ class AutoTMView(ModuleView):
|
||||||
open_layout = QVBoxLayout()
|
open_layout = QVBoxLayout()
|
||||||
open_button = QPushButton("Open")
|
open_button = QPushButton("Open")
|
||||||
open_button.clicked.connect(
|
open_button.clicked.connect(
|
||||||
lambda: self.module.controller.on_open_calibration(start_edit.text(), stop_edit.text())
|
lambda: self.module.controller.on_open_calibration(
|
||||||
|
start_edit.text(), stop_edit.text()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# Open plot widget
|
# Open plot widget
|
||||||
self.open_plot = MplWidget()
|
self.open_plot = MplWidget()
|
||||||
|
@ -377,7 +432,9 @@ class AutoTMView(ModuleView):
|
||||||
load_layout = QVBoxLayout()
|
load_layout = QVBoxLayout()
|
||||||
load_button = QPushButton("Load")
|
load_button = QPushButton("Load")
|
||||||
load_button.clicked.connect(
|
load_button.clicked.connect(
|
||||||
lambda: self.module.controller.on_load_calibration(start_edit.text(), stop_edit.text())
|
lambda: self.module.controller.on_load_calibration(
|
||||||
|
start_edit.text(), stop_edit.text()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# Load plot widget
|
# Load plot widget
|
||||||
self.load_plot = MplWidget()
|
self.load_plot = MplWidget()
|
||||||
|
@ -405,9 +462,15 @@ class AutoTMView(ModuleView):
|
||||||
self.setLayout(main_layout)
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
# Connect the calibration finished signals to the on_calibration_finished slot
|
# Connect the calibration finished signals to the on_calibration_finished slot
|
||||||
self.module.model.short_calibration_finished.connect(self.on_short_calibration_finished)
|
self.module.model.short_calibration_finished.connect(
|
||||||
self.module.model.open_calibration_finished.connect(self.on_open_calibration_finished)
|
self.on_short_calibration_finished
|
||||||
self.module.model.load_calibration_finished.connect(self.on_load_calibration_finished)
|
)
|
||||||
|
self.module.model.open_calibration_finished.connect(
|
||||||
|
self.on_open_calibration_finished
|
||||||
|
)
|
||||||
|
self.module.model.load_calibration_finished.connect(
|
||||||
|
self.on_load_calibration_finished
|
||||||
|
)
|
||||||
|
|
||||||
def on_short_calibration_finished(self, short_calibration: "S11Data") -> None:
|
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)
|
||||||
|
@ -418,7 +481,9 @@ class AutoTMView(ModuleView):
|
||||||
def on_load_calibration_finished(self, load_calibration: "S11Data") -> None:
|
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:
|
def on_calibration_finished(
|
||||||
|
self, type: str, widget: MplWidget, data: "S11Data"
|
||||||
|
) -> None:
|
||||||
"""This method is called when a calibration has finished.
|
"""This method is called when a calibration has finished.
|
||||||
It plots the calibration data on the given widget.
|
It plots the calibration data on the given widget.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -62,9 +62,51 @@ class Ui_Form(object):
|
||||||
self.mechTab.setObjectName("mechTab")
|
self.mechTab.setObjectName("mechTab")
|
||||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.mechTab)
|
self.verticalLayout = QtWidgets.QVBoxLayout(self.mechTab)
|
||||||
self.verticalLayout.setObjectName("verticalLayout")
|
self.verticalLayout.setObjectName("verticalLayout")
|
||||||
self.homingButton = QtWidgets.QPushButton(parent=self.mechTab)
|
self.gridLayout_4 = QtWidgets.QGridLayout()
|
||||||
self.homingButton.setObjectName("homingButton")
|
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||||
self.verticalLayout.addWidget(self.homingButton)
|
self.homematcherButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.homematcherButton.setObjectName("homematcherButton")
|
||||||
|
self.gridLayout_4.addWidget(self.homematcherButton, 5, 1, 1, 1)
|
||||||
|
self.label_17 = QtWidgets.QLabel(parent=self.mechTab)
|
||||||
|
self.label_17.setObjectName("label_17")
|
||||||
|
self.gridLayout_4.addWidget(self.label_17, 1, 0, 1, 1)
|
||||||
|
self.stepsizeBox = QtWidgets.QSpinBox(parent=self.mechTab)
|
||||||
|
self.stepsizeBox.setMinimum(-1000)
|
||||||
|
self.stepsizeBox.setMaximum(1000)
|
||||||
|
self.stepsizeBox.setProperty("value", 500)
|
||||||
|
self.stepsizeBox.setObjectName("stepsizeBox")
|
||||||
|
self.gridLayout_4.addWidget(self.stepsizeBox, 1, 1, 1, 1)
|
||||||
|
self.label_18 = QtWidgets.QLabel(parent=self.mechTab)
|
||||||
|
self.label_18.setObjectName("label_18")
|
||||||
|
self.gridLayout_4.addWidget(self.label_18, 2, 0, 1, 3)
|
||||||
|
self.decreasetunerButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.decreasetunerButton.setObjectName("decreasetunerButton")
|
||||||
|
self.gridLayout_4.addWidget(self.decreasetunerButton, 3, 0, 1, 1)
|
||||||
|
self.increasetunerButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.increasetunerButton.setObjectName("increasetunerButton")
|
||||||
|
self.gridLayout_4.addWidget(self.increasetunerButton, 3, 2, 1, 1)
|
||||||
|
self.label_19 = QtWidgets.QLabel(parent=self.mechTab)
|
||||||
|
self.label_19.setObjectName("label_19")
|
||||||
|
self.gridLayout_4.addWidget(self.label_19, 4, 0, 1, 3)
|
||||||
|
self.decreasematcherButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.decreasematcherButton.setObjectName("decreasematcherButton")
|
||||||
|
self.gridLayout_4.addWidget(self.decreasematcherButton, 5, 0, 1, 1)
|
||||||
|
self.increasematcherButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.increasematcherButton.setObjectName("increasematcherButton")
|
||||||
|
self.gridLayout_4.addWidget(self.increasematcherButton, 5, 2, 1, 1)
|
||||||
|
self.hometunerButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.hometunerButton.setObjectName("hometunerButton")
|
||||||
|
self.gridLayout_4.addWidget(self.hometunerButton, 3, 1, 1, 1)
|
||||||
|
self.label_16 = QtWidgets.QLabel(parent=self.mechTab)
|
||||||
|
self.label_16.setObjectName("label_16")
|
||||||
|
self.gridLayout_4.addWidget(self.label_16, 0, 0, 1, 3)
|
||||||
|
self.verticalLayout.addLayout(self.gridLayout_4)
|
||||||
|
self.starpositionButton = QtWidgets.QPushButton(parent=self.mechTab)
|
||||||
|
self.starpositionButton.setObjectName("starpositionButton")
|
||||||
|
self.verticalLayout.addWidget(self.starpositionButton)
|
||||||
|
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
|
||||||
|
self.verticalLayout.addItem(spacerItem)
|
||||||
|
self.verticalLayout.setStretch(1, 1)
|
||||||
self.typeTab.addTab(self.mechTab, "")
|
self.typeTab.addTab(self.mechTab, "")
|
||||||
self.elecTab = QtWidgets.QWidget()
|
self.elecTab = QtWidgets.QWidget()
|
||||||
self.elecTab.setObjectName("elecTab")
|
self.elecTab.setObjectName("elecTab")
|
||||||
|
@ -215,7 +257,17 @@ class Ui_Form(object):
|
||||||
self.label_10.setText(_translate("Form", "Connected to:"))
|
self.label_10.setText(_translate("Form", "Connected to:"))
|
||||||
self.connectButton.setText(_translate("Form", "Connect"))
|
self.connectButton.setText(_translate("Form", "Connect"))
|
||||||
self.titletypeLabel.setText(_translate("Form", "T&M Type:"))
|
self.titletypeLabel.setText(_translate("Form", "T&M Type:"))
|
||||||
self.homingButton.setText(_translate("Form", "Homing"))
|
self.homematcherButton.setText(_translate("Form", "Home"))
|
||||||
|
self.label_17.setText(_translate("Form", "Step Size:"))
|
||||||
|
self.label_18.setText(_translate("Form", "Tuning Stepper:"))
|
||||||
|
self.decreasetunerButton.setText(_translate("Form", "-"))
|
||||||
|
self.increasetunerButton.setText(_translate("Form", "+"))
|
||||||
|
self.label_19.setText(_translate("Form", "Matching Stepper:"))
|
||||||
|
self.decreasematcherButton.setText(_translate("Form", "-"))
|
||||||
|
self.increasematcherButton.setText(_translate("Form", "+"))
|
||||||
|
self.hometunerButton.setText(_translate("Form", "Home"))
|
||||||
|
self.label_16.setText(_translate("Form", "Stepper Control:"))
|
||||||
|
self.starpositionButton.setText(_translate("Form", "Start Position"))
|
||||||
self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical"))
|
self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical"))
|
||||||
self.label_4.setText(_translate("Form", "Voltage Resolution"))
|
self.label_4.setText(_translate("Form", "Voltage Resolution"))
|
||||||
self.label_2.setText(_translate("Form", "Voltage Tuning"))
|
self.label_2.setText(_translate("Form", "Voltage Tuning"))
|
||||||
|
@ -233,7 +285,9 @@ class Ui_Form(object):
|
||||||
self.switchATMButton.setText(_translate("Form", "ATM"))
|
self.switchATMButton.setText(_translate("Form", "ATM"))
|
||||||
self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical"))
|
self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical"))
|
||||||
self.titlefrequencyLabel.setText(_translate("Form", "Frequency Sweep:"))
|
self.titlefrequencyLabel.setText(_translate("Form", "Frequency Sweep:"))
|
||||||
|
self.startEdit.setText(_translate("Form", "80"))
|
||||||
self.label_8.setText(_translate("Form", "MHz"))
|
self.label_8.setText(_translate("Form", "MHz"))
|
||||||
|
self.stopEdit.setText(_translate("Form", "100"))
|
||||||
self.label_6.setText(_translate("Form", "MHz"))
|
self.label_6.setText(_translate("Form", "MHz"))
|
||||||
self.label_7.setText(_translate("Form", "Stop Frequency:"))
|
self.label_7.setText(_translate("Form", "Stop Frequency:"))
|
||||||
self.label_5.setText(_translate("Form", "Start Frequency:"))
|
self.label_5.setText(_translate("Form", "Start Frequency:"))
|
||||||
|
|
Loading…
Reference in a new issue