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') text = text.rstrip('\r\n')
# 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
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"))
self.module.model.add_data_point(frequency, return_loss, phase) 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: elif text.startswith("r") and self.module.model.active_calibration == None:
logger.debug("Measurement finished") 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() 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") logger.debug("Short calibration finished")
self.module.model.short_calibration = self.module.model.data_points.copy() self.module.model.short_calibration = 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
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 = self.module.model.data_points.copy() self.module.model.open_calibration = 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
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 = self.module.model.data_points.copy() self.module.model.load_calibration = 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()
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) self.module.view.add_info_text(text)
def on_short_calibration(self, start_frequency : float, stop_frequency : float) -> None: 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_open = 1
ideal_gamma_load = 0 ideal_gamma_load = 0
short_calibration = [10 **(-returnloss_s[1] /6 / 24 / 20) for returnloss_s in self.module.model.short_calibration] short_calibration = [10 **(-returnloss_s[1]) 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] open_calibration = [10 **(-returnloss_o[1]) 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] load_calibration = [10 **(-returnloss_l[1]) for returnloss_l in self.module.model.load_calibration]
e_00s = [] e_00s = []
e11s = [] e11s = []

View file

@ -1,4 +1,5 @@
import serial import cmath
import numpy as np
import logging import logging
from PyQt6.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt6.QtSerialPort import QSerialPort from PyQt6.QtSerialPort import QSerialPort
@ -6,14 +7,51 @@ from nqrduck.module.module_model import ModuleModel
logger = logging.getLogger(__name__) 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): class AutoTMModel(ModuleModel):
available_devices_changed = pyqtSignal(list) available_devices_changed = pyqtSignal(list)
serial_changed = pyqtSignal(QSerialPort) serial_changed = pyqtSignal(QSerialPort)
data_points_changed = pyqtSignal(list) data_points_changed = pyqtSignal(list)
short_calibration_finished = pyqtSignal(list) short_calibration_finished = pyqtSignal(S11Data)
open_calibration_finished = pyqtSignal(list) open_calibration_finished = pyqtSignal(S11Data)
load_calibration_finished = pyqtSignal(list) load_calibration_finished = pyqtSignal(S11Data)
measurement_finished = pyqtSignal(S11Data)
def __init__(self, module) -> None: def __init__(self, module) -> None:
super().__init__(module) super().__init__(module)
@ -31,6 +69,8 @@ class AutoTMModel(ModuleModel):
@property @property
def serial(self): def serial(self):
"""The serial property is used to store the current serial connection.
"""
return self._serial return self._serial
@serial.setter @serial.setter
@ -39,7 +79,9 @@ class AutoTMModel(ModuleModel):
self.serial_changed.emit(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.""" """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.append((frequency, return_loss, phase))
self.data_points_changed.emit(self.data_points) self.data_points_changed.emit(self.data_points)
@ -48,6 +90,20 @@ class AutoTMModel(ModuleModel):
self.data_points.clear() self.data_points.clear()
self.data_points_changed.emit(self.data_points) 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 @property
def active_calibration(self): def active_calibration(self):
return self._active_calibration return self._active_calibration
@ -63,8 +119,8 @@ class AutoTMModel(ModuleModel):
@short_calibration.setter @short_calibration.setter
def short_calibration(self, value): def short_calibration(self, value):
logger.debug("Setting short calibration") logger.debug("Setting short calibration")
self._short_calibration = value self._short_calibration = S11Data(value)
self.short_calibration_finished.emit(value) self.short_calibration_finished.emit(self._short_calibration)
def init_short_calibration(self): 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.""" """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 @open_calibration.setter
def open_calibration(self, value): def open_calibration(self, value):
logger.debug("Setting open calibration") logger.debug("Setting open calibration")
self._open_calibration = value self._open_calibration = S11Data(value)
self.open_calibration_finished.emit(value) self.open_calibration_finished.emit(self._open_calibration)
def init_open_calibration(self): 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.""" """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 @load_calibration.setter
def load_calibration(self, value): def load_calibration(self, value):
logger.debug("Setting load calibration") logger.debug("Setting load calibration")
self._load_calibration = value self._load_calibration = S11Data(value)
self.load_calibration_finished.emit(value) self.load_calibration_finished.emit(self._load_calibration)
def init_load_calibration(self): 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.""" """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): def calibration(self, value):
logger.debug("Setting calibration") logger.debug("Setting calibration")
self._calibration = value 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 # 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)
# 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 # 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)
self.init_plot() self.init_plot()
self.init_labels() self.init_labels()
@ -115,15 +119,15 @@ class AutoTMView(ModuleView):
self._ui_form.connectionLabel.setText("Disconnected") self._ui_form.connectionLabel.setText("Disconnected")
logger.debug("Updated serial connection label") 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. """Update the S11 plot with the current data points.
Args: Args:
data_points (list): List of data points to plot. data_points (list): List of data points to plot.
""" """
x = [data_point[0] for data_point in self.module.model.data_points] frequency = data.frequency
y = [(data_point[1] - 900) / 30 for data_point in self.module.model.data_points] return_loss_db = data.return_loss_db
phase = [(data_point[2] - 900) / 10 for data_point in self.module.model.data_points] phase = data.phase_deg
# Calibration test: # Calibration test:
#calibration = self.module.model.calibration #calibration = self.module.model.calibration
@ -143,7 +147,7 @@ class AutoTMView(ModuleView):
phase_ax = self._ui_form.S11Plot.canvas.ax.twinx() phase_ax = self._ui_form.S11Plot.canvas.ax.twinx()
phase_ax.set_ylabel("Phase (deg)") 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.set_ylim(-180, 180)
phase_ax.invert_yaxis() phase_ax.invert_yaxis()
@ -153,7 +157,7 @@ class AutoTMView(ModuleView):
magnitude_ax.set_ylabel("S11 (dB)") magnitude_ax.set_ylabel("S11 (dB)")
magnitude_ax.set_title("S11") magnitude_ax.set_title("S11")
magnitude_ax.grid(True) 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 # make the y axis go down instead of up
magnitude_ax.invert_yaxis() 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.open_calibration_finished.connect(self.on_open_calibration_finished)
self.module.model.load_calibration_finished.connect(self.on_load_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) 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) 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) 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. """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.
""" """
x = [data_point[0] for data_point in data] frequency = data.frequency
magnitude = [data_point[1] for data_point in data] return_loss_db = data.return_loss_db
phase = [data_point[2] for data_point in data] phase = data.phase_deg
ax = widget.canvas.ax
ax.clear() phase_ax = widget.canvas.ax.twinx()
ax.set_xlabel("Frequency (MHz)") phase_ax.set_ylabel("Phase (deg)")
ax.set_ylabel("S11 (dB)") phase_ax.plot(frequency, phase, color="orange", linestyle="--")
ax.set_title("S11") phase_ax.set_ylim(-180, 180)
ax.grid(True) phase_ax.invert_yaxis()
ax.plot(x, magnitude, label="Magnitude")
ax.plot(x, phase, label="Phase") magnitude_ax = widget.canvas.ax
ax.legend() 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 # make the y axis go down instead of up
ax.invert_yaxis() magnitude_ax.invert_yaxis()
widget.canvas.draw() widget.canvas.draw()
widget.canvas.flush_events() widget.canvas.flush_events()