Changed to S11Data structure.

This commit is contained in:
jupfi 2023-08-09 11:57:34 +02:00
parent 720e3ac9f1
commit 711a04f945
3 changed files with 117 additions and 39 deletions

View file

@ -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 = []

View file

@ -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

View file

@ -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()