mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-12-21 15:20:27 +00:00
Changed to S11Data structure.
This commit is contained in:
parent
720e3ac9f1
commit
711a04f945
3 changed files with 117 additions and 39 deletions
|
@ -54,31 +54,42 @@ class AutoTMController(ModuleController):
|
|||
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():
|
||||
text = text[1:].split("r")
|
||||
frequency = float(text[0])
|
||||
return_loss, phase = map(float, text[1].split("p"))
|
||||
self.module.model.add_data_point(frequency, return_loss, phase)
|
||||
# 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.view.plot_data()
|
||||
self.module.model.measurement = 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":
|
||||
logger.debug("Short calibration finished")
|
||||
self.module.model.short_calibration = 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":
|
||||
logger.debug("Open calibration finished")
|
||||
self.module.model.open_calibration = 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":
|
||||
logger.debug("Load calibration finished")
|
||||
self.module.model.load_calibration = self.module.model.data_points.copy()
|
||||
self.module.model.active_calibration = None
|
||||
self.module.view.frequency_sweep_spinner.hide()
|
||||
else:
|
||||
# If the text starts with 'i' we know that the data is an info message
|
||||
elif text.startswith("i"):
|
||||
text = "ATM Info: " + text[1:]
|
||||
self.module.view.add_info_text(text)
|
||||
# If the text starts with 'e' we know that the data is an error message
|
||||
elif text.startswith("e"):
|
||||
text = "ATM Error: " + text[1:]
|
||||
self.module.view.add_info_text(text)
|
||||
|
||||
def on_short_calibration(self, start_frequency : float, stop_frequency : float) -> None:
|
||||
|
@ -133,9 +144,9 @@ class AutoTMController(ModuleController):
|
|||
ideal_gamma_open = 1
|
||||
ideal_gamma_load = 0
|
||||
|
||||
short_calibration = [10 **(-returnloss_s[1] /6 / 24 / 20) for returnloss_s in self.module.model.short_calibration]
|
||||
open_calibration = [10 **(-returnloss_o[1] / 6 / 24 / 20) for returnloss_o in self.module.model.open_calibration]
|
||||
load_calibration = [10 **(-returnloss_l[1] / 6 / 24 / 20) for returnloss_l in self.module.model.load_calibration]
|
||||
short_calibration = [10 **(-returnloss_s[1]) for returnloss_s in self.module.model.short_calibration]
|
||||
open_calibration = [10 **(-returnloss_o[1]) for returnloss_o in self.module.model.open_calibration]
|
||||
load_calibration = [10 **(-returnloss_l[1]) for returnloss_l in self.module.model.load_calibration]
|
||||
|
||||
e_00s = []
|
||||
e11s = []
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import serial
|
||||
import cmath
|
||||
import numpy as np
|
||||
import logging
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtSerialPort import QSerialPort
|
||||
|
@ -6,14 +7,51 @@ from nqrduck.module.module_model import ModuleModel
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
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 = 900 # mV
|
||||
MAGNITUDE_SLOPE = 30 # dB/mV
|
||||
PHASE_SLOPE = 10 # deg/mV
|
||||
|
||||
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) / self.MAGNITUDE_SLOPE
|
||||
|
||||
@property
|
||||
def phase_deg(self):
|
||||
return (self.phase_mv - self.CENTER_POINT) / self.PHASE_SLOPE
|
||||
|
||||
@property
|
||||
def phase_rad(self):
|
||||
return self.phase_deg * cmath.pi / 180
|
||||
|
||||
@property
|
||||
def gamma(self):
|
||||
"""Complex reflection coefficient"""
|
||||
return cmath.rect(10 ** (-self.return_loss_db / 20), self.phase_rad)
|
||||
|
||||
class AutoTMModel(ModuleModel):
|
||||
|
||||
available_devices_changed = pyqtSignal(list)
|
||||
serial_changed = pyqtSignal(QSerialPort)
|
||||
data_points_changed = pyqtSignal(list)
|
||||
|
||||
short_calibration_finished = pyqtSignal(list)
|
||||
open_calibration_finished = pyqtSignal(list)
|
||||
load_calibration_finished = pyqtSignal(list)
|
||||
short_calibration_finished = pyqtSignal(S11Data)
|
||||
open_calibration_finished = pyqtSignal(S11Data)
|
||||
load_calibration_finished = pyqtSignal(S11Data)
|
||||
measurement_finished = pyqtSignal(S11Data)
|
||||
|
||||
def __init__(self, module) -> None:
|
||||
super().__init__(module)
|
||||
|
@ -31,6 +69,8 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def serial(self):
|
||||
"""The serial property is used to store the current serial connection.
|
||||
"""
|
||||
return self._serial
|
||||
|
||||
@serial.setter
|
||||
|
@ -39,7 +79,9 @@ class AutoTMModel(ModuleModel):
|
|||
self.serial_changed.emit(value)
|
||||
|
||||
def add_data_point(self, frequency: float, return_loss: float, phase : float) -> None:
|
||||
"""Add a data point to the model."""
|
||||
"""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.
|
||||
"""
|
||||
self.data_points.append((frequency, return_loss, phase))
|
||||
self.data_points_changed.emit(self.data_points)
|
||||
|
||||
|
@ -48,6 +90,20 @@ class AutoTMModel(ModuleModel):
|
|||
self.data_points.clear()
|
||||
self.data_points_changed.emit(self.data_points)
|
||||
|
||||
@property
|
||||
def measurement(self):
|
||||
"""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."""
|
||||
self._measurement = S11Data(value)
|
||||
self.measurement_finished.emit(self._measurement)
|
||||
|
||||
# Calibration properties
|
||||
|
||||
@property
|
||||
def active_calibration(self):
|
||||
return self._active_calibration
|
||||
|
@ -63,8 +119,8 @@ class AutoTMModel(ModuleModel):
|
|||
@short_calibration.setter
|
||||
def short_calibration(self, value):
|
||||
logger.debug("Setting short calibration")
|
||||
self._short_calibration = value
|
||||
self.short_calibration_finished.emit(value)
|
||||
self._short_calibration = S11Data(value)
|
||||
self.short_calibration_finished.emit(self._short_calibration)
|
||||
|
||||
def init_short_calibration(self):
|
||||
"""This method is called when a frequency sweep has been started for a short calibration in this way the module knows that the next data points are for a short calibration."""
|
||||
|
@ -78,8 +134,8 @@ class AutoTMModel(ModuleModel):
|
|||
@open_calibration.setter
|
||||
def open_calibration(self, value):
|
||||
logger.debug("Setting open calibration")
|
||||
self._open_calibration = value
|
||||
self.open_calibration_finished.emit(value)
|
||||
self._open_calibration = S11Data(value)
|
||||
self.open_calibration_finished.emit(self._open_calibration)
|
||||
|
||||
def init_open_calibration(self):
|
||||
"""This method is called when a frequency sweep has been started for an open calibration in this way the module knows that the next data points are for an open calibration."""
|
||||
|
@ -93,8 +149,8 @@ class AutoTMModel(ModuleModel):
|
|||
@load_calibration.setter
|
||||
def load_calibration(self, value):
|
||||
logger.debug("Setting load calibration")
|
||||
self._load_calibration = value
|
||||
self.load_calibration_finished.emit(value)
|
||||
self._load_calibration = S11Data(value)
|
||||
self.load_calibration_finished.emit(self._load_calibration)
|
||||
|
||||
def init_load_calibration(self):
|
||||
"""This method is called when a frequency sweep has been started for a load calibration in this way the module knows that the next data points are for a load calibration."""
|
||||
|
@ -109,3 +165,4 @@ class AutoTMModel(ModuleModel):
|
|||
def calibration(self, value):
|
||||
logger.debug("Setting calibration")
|
||||
self._calibration = value
|
||||
|
|
@ -45,10 +45,14 @@ class AutoTMView(ModuleView):
|
|||
# On clicking of the calibration button call the on_calibration_button_clicked method
|
||||
self._ui_form.calibrationButton.clicked.connect(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.init_plot()
|
||||
self.init_labels()
|
||||
|
||||
|
@ -115,15 +119,15 @@ class AutoTMView(ModuleView):
|
|||
self._ui_form.connectionLabel.setText("Disconnected")
|
||||
logger.debug("Updated serial connection label")
|
||||
|
||||
def plot_data(self) -> None:
|
||||
def plot_measurement(self, data : "S11Data") -> None:
|
||||
"""Update the S11 plot with the current data points.
|
||||
|
||||
Args:
|
||||
data_points (list): List of data points to plot.
|
||||
"""
|
||||
x = [data_point[0] for data_point in self.module.model.data_points]
|
||||
y = [(data_point[1] - 900) / 30 for data_point in self.module.model.data_points]
|
||||
phase = [(data_point[2] - 900) / 10 for data_point in self.module.model.data_points]
|
||||
frequency = data.frequency
|
||||
return_loss_db = data.return_loss_db
|
||||
phase = data.phase_deg
|
||||
|
||||
# Calibration test:
|
||||
#calibration = self.module.model.calibration
|
||||
|
@ -143,7 +147,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
phase_ax = self._ui_form.S11Plot.canvas.ax.twinx()
|
||||
phase_ax.set_ylabel("Phase (deg)")
|
||||
phase_ax.plot(x, phase, color="orange", linestyle="--")
|
||||
phase_ax.plot(frequency, phase, color="orange", linestyle="--")
|
||||
phase_ax.set_ylim(-180, 180)
|
||||
phase_ax.invert_yaxis()
|
||||
|
||||
|
@ -153,7 +157,7 @@ class AutoTMView(ModuleView):
|
|||
magnitude_ax.set_ylabel("S11 (dB)")
|
||||
magnitude_ax.set_title("S11")
|
||||
magnitude_ax.grid(True)
|
||||
magnitude_ax.plot(x, y)
|
||||
magnitude_ax.plot(frequency, return_loss_db, color="blue")
|
||||
# make the y axis go down instead of up
|
||||
magnitude_ax.invert_yaxis()
|
||||
|
||||
|
@ -294,33 +298,39 @@ class AutoTMView(ModuleView):
|
|||
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 : list) -> None:
|
||||
def on_short_calibration_finished(self, short_calibration : "S11Data") -> None:
|
||||
self.on_calibration_finished("short", self.short_plot, short_calibration)
|
||||
|
||||
def on_open_calibration_finished(self, open_calibration : list) -> None:
|
||||
def on_open_calibration_finished(self, open_calibration : "S11Data") -> None:
|
||||
self.on_calibration_finished("open", self.open_plot, open_calibration)
|
||||
|
||||
def on_load_calibration_finished(self, load_calibration : list) -> None:
|
||||
def on_load_calibration_finished(self, load_calibration : "S11Data") -> None:
|
||||
self.on_calibration_finished("load", self.load_plot, load_calibration)
|
||||
|
||||
def on_calibration_finished(self, type : str, widget: MplWidget, data :list) -> None:
|
||||
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.
|
||||
"""
|
||||
x = [data_point[0] for data_point in data]
|
||||
magnitude = [data_point[1] for data_point in data]
|
||||
phase = [data_point[2] for data_point in data]
|
||||
ax = widget.canvas.ax
|
||||
ax.clear()
|
||||
ax.set_xlabel("Frequency (MHz)")
|
||||
ax.set_ylabel("S11 (dB)")
|
||||
ax.set_title("S11")
|
||||
ax.grid(True)
|
||||
ax.plot(x, magnitude, label="Magnitude")
|
||||
ax.plot(x, phase, label="Phase")
|
||||
ax.legend()
|
||||
frequency = data.frequency
|
||||
return_loss_db = data.return_loss_db
|
||||
phase = data.phase_deg
|
||||
|
||||
phase_ax = widget.canvas.ax.twinx()
|
||||
phase_ax.set_ylabel("Phase (deg)")
|
||||
phase_ax.plot(frequency, phase, color="orange", linestyle="--")
|
||||
phase_ax.set_ylim(-180, 180)
|
||||
phase_ax.invert_yaxis()
|
||||
|
||||
magnitude_ax = widget.canvas.ax
|
||||
magnitude_ax.clear()
|
||||
magnitude_ax.set_xlabel("Frequency (MHz)")
|
||||
magnitude_ax.set_ylabel("S11 (dB)")
|
||||
magnitude_ax.set_title("S11")
|
||||
magnitude_ax.grid(True)
|
||||
magnitude_ax.plot(frequency, return_loss_db, color="blue")
|
||||
# make the y axis go down instead of up
|
||||
ax.invert_yaxis()
|
||||
magnitude_ax.invert_yaxis()
|
||||
|
||||
widget.canvas.draw()
|
||||
widget.canvas.flush_events()
|
||||
|
||||
|
|
Loading…
Reference in a new issue