mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-12-21 23:30: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')
|
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 = []
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue