From c993512c9b8af0fff7cc465ac4087272ced42070 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 10:27:13 +0100 Subject: [PATCH 01/31] Merge stuff. --- src/nqrduck_autotm/view.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 6fa4822..bcf0fe9 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -18,10 +18,7 @@ from PyQt6.QtWidgets import ( from PyQt6.QtCore import pyqtSlot, Qt from nqrduck.module.module_view import ModuleView from nqrduck.contrib.mplwidget import MplWidget -<<<<<<< HEAD -======= from nqrduck.assets.icons import Logos ->>>>>>> c21dc155fa5b50d6dce64605a6d86007ea8086c7 from nqrduck.assets.animations import DuckAnimations from .widget import Ui_Form @@ -310,12 +307,7 @@ class AutoTMView(ModuleView): self.setWindowFlag(Qt.WindowType.FramelessWindowHint) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) -<<<<<<< HEAD - - self.spinner_movie = DuckAnimations.DuckKick128x128() -======= self.spinner_movie = DuckAnimations.DuckSleep128x128() ->>>>>>> c21dc155fa5b50d6dce64605a6d86007ea8086c7 self.spinner_label = QLabel(self) self.spinner_label.setMovie(self.spinner_movie) From a6521ad5b28f79a0ccb85c2038e8a64b73ce8c6a Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 13:09:07 +0100 Subject: [PATCH 02/31] Changed UI for mech stepper LUT generation. --- src/nqrduck_autotm/model.py | 3 + src/nqrduck_autotm/resources/autotm_widget.ui | 364 ++++++++++-------- src/nqrduck_autotm/view.py | 9 +- src/nqrduck_autotm/widget.py | 310 ++++++++------- 4 files changed, 392 insertions(+), 294 deletions(-) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 8ecc223..0635845 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -139,6 +139,9 @@ class S11Data: * phase_sign[i] ) + # Murks: The last point is always wrong so just set it to the previous value + phase_data_corrected[-1] = phase_data_corrected[-2] + return phase_data_corrected def to_json(self): diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 2b2838b..7721d6d 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -7,7 +7,7 @@ 0 0 1280 - 862 + 1011 @@ -26,6 +26,7 @@ + 75 true @@ -76,10 +77,58 @@ + + + + + 75 + true + + + + T&M Settings: + + + + + + + + + + + + Stop Frequency (MHz) + + + + + + + + + + Start Frequency (MHz) + + + + + + + Frequency Step (MHz) + + + + + + + + + 75 true @@ -97,24 +146,65 @@ Mechanical - + - - + + Home - - + + + + + 75 + true + + - Step Size: + Stepper Control: + + + + Tuning + + + + + Matching + + + + + + + + - + + + + + + + + + + + + + + + Stepper: + + + + -1000 @@ -127,68 +217,50 @@ - - + + - Tuning Stepper: - - - - - - - - - - - - - - - + - - - - - - - Matching Stepper: + Step Size: - + - - + Absolute: - + - + + Go - - - - Home - - - - - - - Stepper Control: - - + + - + - Start Position + Saved Positions + + + + + + + Generate LUT + + + + + + + View LUT @@ -212,15 +284,8 @@ Electrical - - - - Voltage Resolution - - - - - + + @@ -229,70 +294,9 @@ - - - - - - - - - - - true - - - - Generate LUT: - - - - - - - - - - Start Voltage Sweep - - - - - - - Stop Frequency (MHz) - - - - - - - - true - - - - Set Voltages: - - - - - - - RF Switch: - - - - - - - View LUT - - - @@ -300,6 +304,13 @@ + + + + Voltage Resolution + + + @@ -307,34 +318,46 @@ - - + + + + + + + + 75 + true + + - Start Frequency (MHz) + Set Voltages: - - - - - + + - Frequency Step (MHz) + View LUT - - + + - Preamplifier + Start Voltage Sweep - - + + + + + 75 + true + + - ATM + Generate LUT: @@ -342,10 +365,42 @@ + + + + + 75 + true + + + + RF Switch: + + + + + + + + + ATM + + + + + + + Preamplifier + + + + + + 75 true @@ -363,17 +418,10 @@ - - + + - MHz - - - - - - - 100 + Stop Frequency: @@ -384,13 +432,6 @@ - - - - Stop Frequency: - - - @@ -398,6 +439,20 @@ + + + + 100 + + + + + + + MHz + + + @@ -425,6 +480,7 @@ + 75 true @@ -443,8 +499,8 @@ 0 0 - 297 - 68 + 285 + 83 diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index bcf0fe9..dfaf90c 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -72,7 +72,7 @@ class AutoTMView(ModuleView): ) # On clicking of the viewLUTButton call the view_lut method - self._ui_form.viewLUTButton.clicked.connect(self.view_lut) + self._ui_form.viewelLUTButton.clicked.connect(self.view_el_lut) # On clicking of the setvoltagesButton call the set_voltages method self._ui_form.setvoltagesButton.clicked.connect( @@ -97,7 +97,7 @@ class AutoTMView(ModuleView): ) # On clicking of the homingButton call the homing method - self._ui_form.starpositionButton.clicked.connect(self.module.controller.homing) + self._ui_form.homeButton.clicked.connect(self.module.controller.homing) # Connect the measurement finished signal to the plot_measurement slot self.module.model.measurement_finished.connect(self.plot_measurement) @@ -117,6 +117,7 @@ class AutoTMView(ModuleView): def init_labels(self) -> None: """Makes some of the labels bold for better readability.""" + self._ui_form.tmsettingsLabel.setStyleSheet("font-weight: bold;") self._ui_form.titleconnectionLabel.setStyleSheet("font-weight: bold;") self._ui_form.titlefrequencyLabel.setStyleSheet("font-weight: bold;") self._ui_form.titletypeLabel.setStyleSheet("font-weight: bold;") @@ -291,8 +292,8 @@ class AutoTMView(ModuleView): self.frequency_sweep_spinner = self.FrequencySweepSpinner(self) self.frequency_sweep_spinner.show() - def view_lut(self) -> None: - """Creates a new Dialog that shows the currently active LUT.""" + def view_el_lut(self) -> None: + """Creates a new Dialog that shows the currently active electrical LUT.""" logger.debug("View LUT") self.lut_window = self.LutWindow(self.module) self.lut_window.show() diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index d0a4ac4..28c26d2 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -1,6 +1,6 @@ # Form implementation generated from reading ui file '../Modules/nqrduck-autotm/src/nqrduck_autotm/resources/autotm_widget.ui' # -# Created by: PyQt6 UI code generator 6.4.2 +# Created by: PyQt6 UI code generator 6.5.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") - Form.resize(1280, 862) + Form.resize(1280, 1011) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -25,6 +25,7 @@ class Ui_Form(object): self.titleconnectionLabel = QtWidgets.QLabel(parent=Form) font = QtGui.QFont() font.setBold(True) + font.setWeight(75) self.titleconnectionLabel.setFont(font) self.titleconnectionLabel.setObjectName("titleconnectionLabel") self.verticalLayout_2.addWidget(self.titleconnectionLabel) @@ -50,9 +51,38 @@ class Ui_Form(object): self.connectButton = QtWidgets.QPushButton(parent=Form) self.connectButton.setObjectName("connectButton") self.verticalLayout_2.addWidget(self.connectButton) + self.tmsettingsLabel = QtWidgets.QLabel(parent=Form) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.tmsettingsLabel.setFont(font) + self.tmsettingsLabel.setObjectName("tmsettingsLabel") + self.verticalLayout_2.addWidget(self.tmsettingsLabel) + self.gridLayout_8 = QtWidgets.QGridLayout() + self.gridLayout_8.setObjectName("gridLayout_8") + self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.stopfrequencyBox.setObjectName("stopfrequencyBox") + self.gridLayout_8.addWidget(self.stopfrequencyBox, 1, 1, 1, 1) + self.label_13 = QtWidgets.QLabel(parent=Form) + self.label_13.setObjectName("label_13") + self.gridLayout_8.addWidget(self.label_13, 1, 0, 1, 1) + self.startfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.startfrequencyBox.setObjectName("startfrequencyBox") + self.gridLayout_8.addWidget(self.startfrequencyBox, 0, 1, 1, 1) + self.label_12 = QtWidgets.QLabel(parent=Form) + self.label_12.setObjectName("label_12") + self.gridLayout_8.addWidget(self.label_12, 0, 0, 1, 1) + self.label_14 = QtWidgets.QLabel(parent=Form) + self.label_14.setObjectName("label_14") + self.gridLayout_8.addWidget(self.label_14, 2, 0, 1, 1) + self.frequencystepBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.frequencystepBox.setObjectName("frequencystepBox") + self.gridLayout_8.addWidget(self.frequencystepBox, 2, 1, 1, 1) + self.verticalLayout_2.addLayout(self.gridLayout_8) self.titletypeLabel = QtWidgets.QLabel(parent=Form) font = QtGui.QFont() font.setBold(True) + font.setWeight(75) self.titletypeLabel.setFont(font) self.titletypeLabel.setObjectName("titletypeLabel") self.verticalLayout_2.addWidget(self.titletypeLabel) @@ -64,46 +94,58 @@ class Ui_Form(object): self.verticalLayout.setObjectName("verticalLayout") self.gridLayout_4 = QtWidgets.QGridLayout() self.gridLayout_4.setObjectName("gridLayout_4") - 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.homeButton = QtWidgets.QPushButton(parent=self.mechTab) + self.homeButton.setObjectName("homeButton") + self.gridLayout_4.addWidget(self.homeButton, 4, 1, 1, 1) + self.label_16 = QtWidgets.QLabel(parent=self.mechTab) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_16.setFont(font) + self.label_16.setObjectName("label_16") + self.gridLayout_4.addWidget(self.label_16, 0, 0, 1, 3) + self.stepperselectBox = QtWidgets.QComboBox(parent=self.mechTab) + self.stepperselectBox.setObjectName("stepperselectBox") + self.stepperselectBox.addItem("") + self.stepperselectBox.addItem("") + self.gridLayout_4.addWidget(self.stepperselectBox, 1, 1, 1, 1) + self.decreaseButton = QtWidgets.QPushButton(parent=self.mechTab) + self.decreaseButton.setObjectName("decreaseButton") + self.gridLayout_4.addWidget(self.decreaseButton, 4, 0, 1, 1) + self.increaseButton = QtWidgets.QPushButton(parent=self.mechTab) + self.increaseButton.setObjectName("increaseButton") + self.gridLayout_4.addWidget(self.increaseButton, 4, 2, 1, 1) + self.label_18 = QtWidgets.QLabel(parent=self.mechTab) + self.label_18.setObjectName("label_18") + self.gridLayout_4.addWidget(self.label_18, 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.gridLayout_4.addWidget(self.stepsizeBox, 2, 1, 1, 1) + self.label_17 = QtWidgets.QLabel(parent=self.mechTab) + self.label_17.setObjectName("label_17") + self.gridLayout_4.addWidget(self.label_17, 2, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(parent=self.mechTab) + self.label_20.setObjectName("label_20") + self.gridLayout_4.addWidget(self.label_20, 5, 0, 1, 1) + self.absoluteGoButton = QtWidgets.QPushButton(parent=self.mechTab) + self.absoluteGoButton.setObjectName("absoluteGoButton") + self.gridLayout_4.addWidget(self.absoluteGoButton, 5, 2, 1, 1) + self.absoluteposBox = QtWidgets.QSpinBox(parent=self.mechTab) + self.absoluteposBox.setObjectName("absoluteposBox") + self.gridLayout_4.addWidget(self.absoluteposBox, 5, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout_4) - self.starpositionButton = QtWidgets.QPushButton(parent=self.mechTab) - self.starpositionButton.setObjectName("starpositionButton") - self.verticalLayout.addWidget(self.starpositionButton) + self.positionButton = QtWidgets.QPushButton(parent=self.mechTab) + self.positionButton.setObjectName("positionButton") + self.verticalLayout.addWidget(self.positionButton) + self.mechLUTButton = QtWidgets.QPushButton(parent=self.mechTab) + self.mechLUTButton.setObjectName("mechLUTButton") + self.verticalLayout.addWidget(self.mechLUTButton) + self.viewmechLUTButton = QtWidgets.QPushButton(parent=self.mechTab) + self.viewmechLUTButton.setObjectName("viewmechLUTButton") + self.verticalLayout.addWidget(self.viewmechLUTButton) spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) self.verticalLayout.addItem(spacerItem) self.verticalLayout.setStretch(1, 1) @@ -112,77 +154,69 @@ class Ui_Form(object): self.elecTab.setObjectName("elecTab") self.gridLayout_3 = QtWidgets.QGridLayout(self.elecTab) self.gridLayout_3.setObjectName("gridLayout_3") - self.label_4 = QtWidgets.QLabel(parent=self.elecTab) - self.label_4.setObjectName("label_4") - self.gridLayout_3.addWidget(self.label_4, 5, 0, 1, 1) - self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.stopfrequencyBox.setObjectName("stopfrequencyBox") - self.gridLayout_3.addWidget(self.stopfrequencyBox, 7, 1, 1, 1) - self.label_2 = QtWidgets.QLabel(parent=self.elecTab) - self.label_2.setObjectName("label_2") - self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1) - self.tuningBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.tuningBox.setObjectName("tuningBox") - self.gridLayout_3.addWidget(self.tuningBox, 1, 1, 1, 1) - self.frequencystepBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.frequencystepBox.setObjectName("frequencystepBox") - self.gridLayout_3.addWidget(self.frequencystepBox, 8, 1, 1, 1) - self.label_11 = QtWidgets.QLabel(parent=self.elecTab) - font = QtGui.QFont() - font.setBold(True) - self.label_11.setFont(font) - self.label_11.setObjectName("label_11") - self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1) - self.startfrequencyBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.startfrequencyBox.setObjectName("startfrequencyBox") - self.gridLayout_3.addWidget(self.startfrequencyBox, 6, 1, 1, 1) - self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) - self.generateLUTButton.setObjectName("generateLUTButton") - self.gridLayout_3.addWidget(self.generateLUTButton, 9, 0, 1, 2) - self.label_13 = QtWidgets.QLabel(parent=self.elecTab) - self.label_13.setObjectName("label_13") - self.gridLayout_3.addWidget(self.label_13, 7, 0, 1, 1) - self.resolutionBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.resolutionBox.setObjectName("resolutionBox") - self.gridLayout_3.addWidget(self.resolutionBox, 5, 1, 1, 1) - self.label_9 = QtWidgets.QLabel(parent=self.elecTab) - font = QtGui.QFont() - font.setBold(True) - self.label_9.setFont(font) - self.label_9.setObjectName("label_9") - self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1) - self.label_15 = QtWidgets.QLabel(parent=self.elecTab) - self.label_15.setObjectName("label_15") - self.gridLayout_3.addWidget(self.label_15, 11, 0, 1, 1) - self.viewLUTButton = QtWidgets.QPushButton(parent=self.elecTab) - self.viewLUTButton.setObjectName("viewLUTButton") - self.gridLayout_3.addWidget(self.viewLUTButton, 10, 0, 1, 2) - self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab) - self.setvoltagesButton.setObjectName("setvoltagesButton") - self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2) - self.label_3 = QtWidgets.QLabel(parent=self.elecTab) - self.label_3.setObjectName("label_3") - self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) - self.label_12 = QtWidgets.QLabel(parent=self.elecTab) - self.label_12.setObjectName("label_12") - self.gridLayout_3.addWidget(self.label_12, 6, 0, 1, 1) self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) self.matchingBox.setObjectName("matchingBox") self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1) - self.label_14 = QtWidgets.QLabel(parent=self.elecTab) - self.label_14.setObjectName("label_14") - self.gridLayout_3.addWidget(self.label_14, 8, 0, 1, 1) - self.switchpreampButton = QtWidgets.QPushButton(parent=self.elecTab) - self.switchpreampButton.setObjectName("switchpreampButton") - self.gridLayout_3.addWidget(self.switchpreampButton, 12, 0, 1, 1) - self.switchATMButton = QtWidgets.QPushButton(parent=self.elecTab) - self.switchATMButton.setObjectName("switchATMButton") - self.gridLayout_3.addWidget(self.switchATMButton, 12, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(parent=self.elecTab) + self.label_2.setObjectName("label_2") + self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1) + self.resolutionBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) + self.resolutionBox.setObjectName("resolutionBox") + self.gridLayout_3.addWidget(self.resolutionBox, 5, 1, 1, 1) + self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab) + self.setvoltagesButton.setObjectName("setvoltagesButton") + self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2) + self.label_4 = QtWidgets.QLabel(parent=self.elecTab) + self.label_4.setObjectName("label_4") + self.gridLayout_3.addWidget(self.label_4, 5, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=self.elecTab) + self.label_3.setObjectName("label_3") + self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) + self.tuningBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) + self.tuningBox.setObjectName("tuningBox") + self.gridLayout_3.addWidget(self.tuningBox, 1, 1, 1, 1) + self.label_9 = QtWidgets.QLabel(parent=self.elecTab) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_9.setFont(font) + self.label_9.setObjectName("label_9") + self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1) + self.viewelLUTButton = QtWidgets.QPushButton(parent=self.elecTab) + self.viewelLUTButton.setObjectName("viewelLUTButton") + self.gridLayout_3.addWidget(self.viewelLUTButton, 10, 0, 1, 2) + self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) + self.generateLUTButton.setObjectName("generateLUTButton") + self.gridLayout_3.addWidget(self.generateLUTButton, 9, 0, 1, 2) + self.label_11 = QtWidgets.QLabel(parent=self.elecTab) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_11.setFont(font) + self.label_11.setObjectName("label_11") + self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1) self.typeTab.addTab(self.elecTab, "") self.verticalLayout_2.addWidget(self.typeTab) + self.rfswitchLabel = QtWidgets.QLabel(parent=Form) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.rfswitchLabel.setFont(font) + self.rfswitchLabel.setObjectName("rfswitchLabel") + self.verticalLayout_2.addWidget(self.rfswitchLabel) + self.gridLayout_7 = QtWidgets.QGridLayout() + self.gridLayout_7.setObjectName("gridLayout_7") + self.switchATMButton = QtWidgets.QPushButton(parent=Form) + self.switchATMButton.setObjectName("switchATMButton") + self.gridLayout_7.addWidget(self.switchATMButton, 0, 0, 1, 1) + self.switchpreampButton = QtWidgets.QPushButton(parent=Form) + self.switchpreampButton.setObjectName("switchpreampButton") + self.gridLayout_7.addWidget(self.switchpreampButton, 0, 1, 1, 1) + self.verticalLayout_2.addLayout(self.gridLayout_7) self.titlefrequencyLabel = QtWidgets.QLabel(parent=Form) font = QtGui.QFont() font.setBold(True) + font.setWeight(75) self.titlefrequencyLabel.setFont(font) self.titlefrequencyLabel.setObjectName("titlefrequencyLabel") self.verticalLayout_2.addWidget(self.titlefrequencyLabel) @@ -191,21 +225,21 @@ class Ui_Form(object): self.startEdit = QtWidgets.QLineEdit(parent=Form) self.startEdit.setObjectName("startEdit") self.gridLayout.addWidget(self.startEdit, 0, 1, 1, 1) - self.label_8 = QtWidgets.QLabel(parent=Form) - self.label_8.setObjectName("label_8") - self.gridLayout.addWidget(self.label_8, 1, 2, 1, 1) - self.stopEdit = QtWidgets.QLineEdit(parent=Form) - self.stopEdit.setObjectName("stopEdit") - self.gridLayout.addWidget(self.stopEdit, 1, 1, 1, 1) - self.label_6 = QtWidgets.QLabel(parent=Form) - self.label_6.setObjectName("label_6") - self.gridLayout.addWidget(self.label_6, 0, 2, 1, 1) self.label_7 = QtWidgets.QLabel(parent=Form) self.label_7.setObjectName("label_7") self.gridLayout.addWidget(self.label_7, 1, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(parent=Form) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 0, 2, 1, 1) self.label_5 = QtWidgets.QLabel(parent=Form) self.label_5.setObjectName("label_5") self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1) + self.stopEdit = QtWidgets.QLineEdit(parent=Form) + self.stopEdit.setObjectName("stopEdit") + self.gridLayout.addWidget(self.stopEdit, 1, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(parent=Form) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 1, 2, 1, 1) self.verticalLayout_2.addLayout(self.gridLayout) self.startButton = QtWidgets.QPushButton(parent=Form) self.startButton.setObjectName("startButton") @@ -219,6 +253,7 @@ class Ui_Form(object): self.titleinfoLabel = QtWidgets.QLabel(parent=Form) font = QtGui.QFont() font.setBold(True) + font.setWeight(75) self.titleinfoLabel.setFont(font) self.titleinfoLabel.setObjectName("titleinfoLabel") self.verticalLayout_2.addWidget(self.titleinfoLabel) @@ -226,7 +261,7 @@ class Ui_Form(object): self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QtWidgets.QWidget() - self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 297, 68)) + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 285, 83)) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.verticalLayout_2.addWidget(self.scrollArea) @@ -256,41 +291,44 @@ class Ui_Form(object): self.label.setText(_translate("Form", "Port:")) self.label_10.setText(_translate("Form", "Connected to:")) self.connectButton.setText(_translate("Form", "Connect")) - self.titletypeLabel.setText(_translate("Form", "T&M Type:")) - 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.label_4.setText(_translate("Form", "Voltage Resolution")) - self.label_2.setText(_translate("Form", "Voltage Tuning")) - self.label_11.setText(_translate("Form", "Generate LUT:")) - self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep")) + self.tmsettingsLabel.setText(_translate("Form", "T&M Settings:")) self.label_13.setText(_translate("Form", "Stop Frequency (MHz)")) - self.label_9.setText(_translate("Form", "Set Voltages:")) - self.label_15.setText(_translate("Form", "RF Switch:")) - self.viewLUTButton.setText(_translate("Form", "View LUT")) - self.setvoltagesButton.setText(_translate("Form", "Set Voltages")) - self.label_3.setText(_translate("Form", "Voltage Matching")) self.label_12.setText(_translate("Form", "Start Frequency (MHz)")) self.label_14.setText(_translate("Form", "Frequency Step (MHz)")) - self.switchpreampButton.setText(_translate("Form", "Preamplifier")) - self.switchATMButton.setText(_translate("Form", "ATM")) + self.titletypeLabel.setText(_translate("Form", "T&M Type:")) + self.homeButton.setText(_translate("Form", "Home")) + self.label_16.setText(_translate("Form", "Stepper Control:")) + self.stepperselectBox.setItemText(0, _translate("Form", "Tuning")) + self.stepperselectBox.setItemText(1, _translate("Form", "Matching")) + self.decreaseButton.setText(_translate("Form", "-")) + self.increaseButton.setText(_translate("Form", "+")) + self.label_18.setText(_translate("Form", "Stepper:")) + self.label_17.setText(_translate("Form", "Step Size:")) + self.label_20.setText(_translate("Form", "Absolute:")) + self.absoluteGoButton.setText(_translate("Form", "Go")) + self.positionButton.setText(_translate("Form", "Saved Positions")) + self.mechLUTButton.setText(_translate("Form", "Generate LUT")) + self.viewmechLUTButton.setText(_translate("Form", "View LUT")) + self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical")) + self.label_2.setText(_translate("Form", "Voltage Tuning")) + self.setvoltagesButton.setText(_translate("Form", "Set Voltages")) + self.label_4.setText(_translate("Form", "Voltage Resolution")) + self.label_3.setText(_translate("Form", "Voltage Matching")) + self.label_9.setText(_translate("Form", "Set Voltages:")) + self.viewelLUTButton.setText(_translate("Form", "View LUT")) + self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep")) + self.label_11.setText(_translate("Form", "Generate LUT:")) self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical")) + self.rfswitchLabel.setText(_translate("Form", "RF Switch:")) + self.switchATMButton.setText(_translate("Form", "ATM")) + self.switchpreampButton.setText(_translate("Form", "Preamplifier")) self.titlefrequencyLabel.setText(_translate("Form", "Frequency Sweep:")) self.startEdit.setText(_translate("Form", "80")) - self.label_8.setText(_translate("Form", "MHz")) - self.stopEdit.setText(_translate("Form", "100")) - self.label_6.setText(_translate("Form", "MHz")) self.label_7.setText(_translate("Form", "Stop Frequency:")) + self.label_6.setText(_translate("Form", "MHz")) self.label_5.setText(_translate("Form", "Start Frequency:")) + self.stopEdit.setText(_translate("Form", "100")) + self.label_8.setText(_translate("Form", "MHz")) self.startButton.setText(_translate("Form", "Start Sweep")) self.calibrationButton.setText(_translate("Form", "Calibrate")) self.pushButton_3.setText(_translate("Form", "T&M Settings")) From 7541a20cf3d8aecc98c3d2944e18611e4baf9b19 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 13:28:10 +0100 Subject: [PATCH 03/31] Updated voltage sweep functionality. --- src/nqrduck_autotm/controller.py | 30 +++++--- src/nqrduck_autotm/model.py | 2 - src/nqrduck_autotm/resources/autotm_widget.ui | 76 ++++++++++--------- src/nqrduck_autotm/view.py | 18 +++-- src/nqrduck_autotm/widget.py | 58 +++++++------- 5 files changed, 96 insertions(+), 88 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 279c7b2..8369f51 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -222,6 +222,17 @@ class AutoTMController(ModuleController): LUT.started_frequency = next_frequency logger.debug("Starting next voltage sweep: %s", command) self.send_command(command) + else: + logger.debug("Voltage sweep finished") + self.module.view.el_LUT_spinner.hide() + self.module.model.voltage_sweep_stop = time.time() + self.module.view.add_info_text( + "Voltage sweep finished in %.2f seconds" + % ( + self.module.model.voltage_sweep_stop + - self.module.model.voltage_sweep_start + ) + ) def on_short_calibration( self, start_frequency: float, stop_frequency: float @@ -419,7 +430,6 @@ class AutoTMController(ModuleController): start_frequency: str, stop_frequency: str, frequency_step: str, - voltage_resolution: str, ) -> None: """This method is called when the generate LUT button is pressed. It generates a lookup table for the specified frequency range and voltage resolution. @@ -428,20 +438,17 @@ class AutoTMController(ModuleController): start_frequency (str): The start frequency in Hz. stop_frequency (str): The stop frequency in Hz. frequency_step (str): The frequency step in Hz. - voltage_resolution (str): The voltage resolution in V. """ logger.debug("Generating LUT") try: start_frequency = start_frequency.replace(",", ".") stop_frequency = stop_frequency.replace(",", ".") frequency_step = frequency_step.replace(",", ".") - voltage_resolution = voltage_resolution.replace(",", ".") start_frequency = float(start_frequency) stop_frequency = float(stop_frequency) frequency_step = float(frequency_step) - voltage_resolution = float(voltage_resolution) except ValueError: - error = "Could not generate LUT. Start frequency, stop frequency, frequency step and voltage resolution must be floats" + error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be floats" logger.error(error) self.module.view.add_info_text(error) return @@ -450,9 +457,8 @@ class AutoTMController(ModuleController): 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 must be positive" logger.error(error) self.module.view.add_info_text(error) return @@ -470,16 +476,15 @@ class AutoTMController(ModuleController): return logger.debug( - "Generating LUT from %s MHz to %s MHz with a frequency step of %s MHz and a voltage resolution of %s V", + "Generating LUT from %s MHz to %s MHz with a frequency step of %s MHz", start_frequency, stop_frequency, frequency_step, - voltage_resolution, ) # We create the lookup table LUT = LookupTable( - start_frequency, stop_frequency, frequency_step, voltage_resolution + start_frequency, stop_frequency, frequency_step ) LUT.started_frequency = start_frequency @@ -487,6 +492,9 @@ class AutoTMController(ModuleController): # We write the first command to the serial connection command = "s%s" % (start_frequency) + self.module.view.create_el_LUT_spinner_dialog() + # For timing of the voltage sweep + self.module.model.voltage_sweep_start = time.time() confirmation = self.send_command(command) if not confirmation: return @@ -544,7 +552,7 @@ class AutoTMController(ModuleController): logger.debug("Confirmation: %s", confirmation) if confirmation == "c": - logger.debug("Command send successfully") + logger.debug("Command sent successfully") return True else: logger.error("Could not send command. No confirmation received") diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 0635845..9ce0fb1 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -170,12 +170,10 @@ class LookupTable: start_frequency: float, stop_frequency: float, frequency_step: float, - voltage_resolution: float, ) -> None: self.start_frequency = start_frequency self.stop_frequency = stop_frequency self.frequency_step = frequency_step - self.voltage_resolution = voltage_resolution # This is the frequency at which the tuning and matching process was started self.started_frequency = None diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 7721d6d..4e6734a 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -93,7 +93,11 @@ - + + + 80.299999999999997 + + @@ -103,7 +107,11 @@ - + + + 80.000000000000000 + + @@ -120,7 +128,11 @@ - + + + 0.100000000000000 + + @@ -140,7 +152,7 @@ - 0 + 1 @@ -283,9 +295,13 @@ Electrical - - - + + + + + Start Voltage Sweep + + @@ -294,20 +310,19 @@ - - + + - - - - Set Voltages + + + + + 75 + true + - - - - - Voltage Resolution + Generate LUT: @@ -318,9 +333,6 @@ - - - @@ -334,32 +346,22 @@ - + View LUT - - + + - Start Voltage Sweep + Set Voltages - - - - - 75 - true - - - - Generate LUT: - - + + diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index dfaf90c..9c2bb1c 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -34,7 +34,7 @@ class AutoTMView(ModuleView): self._ui_form.setupUi(self) self.widget = widget - self.frequency_sweep_spinner = self.FrequencySweepSpinner(self) + self.frequency_sweep_spinner = self.LoadingSpinner(self) self.frequency_sweep_spinner.hide() # Disable the connectButton while no devices are selected @@ -67,7 +67,6 @@ class AutoTMView(ModuleView): self._ui_form.startfrequencyBox.text(), self._ui_form.stopfrequencyBox.text(), self._ui_form.frequencystepBox.text(), - self._ui_form.resolutionBox.text(), ) ) @@ -289,21 +288,26 @@ class AutoTMView(ModuleView): def create_frequency_sweep_spinner_dialog(self) -> None: """Creates a frequency sweep spinner dialog.""" - self.frequency_sweep_spinner = self.FrequencySweepSpinner(self) + self.frequency_sweep_spinner = self.LoadingSpinner("Performing frequency sweep ...", self) self.frequency_sweep_spinner.show() + def create_el_LUT_spinner_dialog(self) -> None: + """Creates a electrical LUT spinner dialog.""" + self.el_LUT_spinner = self.LoadingSpinner("Generating electrical LUT ...", self) + self.el_LUT_spinner.show() + def view_el_lut(self) -> None: """Creates a new Dialog that shows the currently active electrical LUT.""" logger.debug("View LUT") self.lut_window = self.LutWindow(self.module) self.lut_window.show() - class FrequencySweepSpinner(QDialog): + class LoadingSpinner(QDialog): """This class implements a spinner dialog that is shown during a frequency sweep.""" - def __init__(self, parent=None): + def __init__(self, text : str, parent=None): super().__init__(parent) - self.setWindowTitle("Frequency sweep") + self.setWindowTitle("Loading") self.setModal(True) self.setWindowFlag(Qt.WindowType.FramelessWindowHint) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) @@ -313,7 +317,7 @@ class AutoTMView(ModuleView): self.spinner_label.setMovie(self.spinner_movie) self.layout = QVBoxLayout(self) - self.layout.addWidget(QLabel("Performing frequency sweep...")) + self.layout.addWidget(QLabel(text)) self.layout.addWidget(self.spinner_label) self.spinner_movie.start() diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 28c26d2..51f93bc 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -61,12 +61,14 @@ class Ui_Form(object): self.gridLayout_8 = QtWidgets.QGridLayout() self.gridLayout_8.setObjectName("gridLayout_8") self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.stopfrequencyBox.setProperty("value", 80.3) self.stopfrequencyBox.setObjectName("stopfrequencyBox") self.gridLayout_8.addWidget(self.stopfrequencyBox, 1, 1, 1, 1) self.label_13 = QtWidgets.QLabel(parent=Form) self.label_13.setObjectName("label_13") self.gridLayout_8.addWidget(self.label_13, 1, 0, 1, 1) self.startfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.startfrequencyBox.setProperty("value", 80.0) self.startfrequencyBox.setObjectName("startfrequencyBox") self.gridLayout_8.addWidget(self.startfrequencyBox, 0, 1, 1, 1) self.label_12 = QtWidgets.QLabel(parent=Form) @@ -76,6 +78,7 @@ class Ui_Form(object): self.label_14.setObjectName("label_14") self.gridLayout_8.addWidget(self.label_14, 2, 0, 1, 1) self.frequencystepBox = QtWidgets.QDoubleSpinBox(parent=Form) + self.frequencystepBox.setProperty("value", 0.1) self.frequencystepBox.setObjectName("frequencystepBox") self.gridLayout_8.addWidget(self.frequencystepBox, 2, 1, 1, 1) self.verticalLayout_2.addLayout(self.gridLayout_8) @@ -154,27 +157,25 @@ class Ui_Form(object): self.elecTab.setObjectName("elecTab") self.gridLayout_3 = QtWidgets.QGridLayout(self.elecTab) self.gridLayout_3.setObjectName("gridLayout_3") - self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.matchingBox.setObjectName("matchingBox") - self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1) + self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) + self.generateLUTButton.setObjectName("generateLUTButton") + self.gridLayout_3.addWidget(self.generateLUTButton, 8, 0, 1, 2) self.label_2 = QtWidgets.QLabel(parent=self.elecTab) self.label_2.setObjectName("label_2") self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1) - self.resolutionBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.resolutionBox.setObjectName("resolutionBox") - self.gridLayout_3.addWidget(self.resolutionBox, 5, 1, 1, 1) - self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab) - self.setvoltagesButton.setObjectName("setvoltagesButton") - self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2) - self.label_4 = QtWidgets.QLabel(parent=self.elecTab) - self.label_4.setObjectName("label_4") - self.gridLayout_3.addWidget(self.label_4, 5, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(parent=self.elecTab) - self.label_3.setObjectName("label_3") - self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) self.tuningBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) self.tuningBox.setObjectName("tuningBox") self.gridLayout_3.addWidget(self.tuningBox, 1, 1, 1, 1) + self.label_11 = QtWidgets.QLabel(parent=self.elecTab) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_11.setFont(font) + self.label_11.setObjectName("label_11") + self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=self.elecTab) + self.label_3.setObjectName("label_3") + self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) self.label_9 = QtWidgets.QLabel(parent=self.elecTab) font = QtGui.QFont() font.setBold(True) @@ -184,17 +185,13 @@ class Ui_Form(object): self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1) self.viewelLUTButton = QtWidgets.QPushButton(parent=self.elecTab) self.viewelLUTButton.setObjectName("viewelLUTButton") - self.gridLayout_3.addWidget(self.viewelLUTButton, 10, 0, 1, 2) - self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) - self.generateLUTButton.setObjectName("generateLUTButton") - self.gridLayout_3.addWidget(self.generateLUTButton, 9, 0, 1, 2) - self.label_11 = QtWidgets.QLabel(parent=self.elecTab) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_11.setFont(font) - self.label_11.setObjectName("label_11") - self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1) + self.gridLayout_3.addWidget(self.viewelLUTButton, 9, 0, 1, 2) + self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab) + self.setvoltagesButton.setObjectName("setvoltagesButton") + self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2) + self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) + self.matchingBox.setObjectName("matchingBox") + self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1) self.typeTab.addTab(self.elecTab, "") self.verticalLayout_2.addWidget(self.typeTab) self.rfswitchLabel = QtWidgets.QLabel(parent=Form) @@ -280,7 +277,7 @@ class Ui_Form(object): self.horizontalLayout_2.setStretch(1, 1) self.retranslateUi(Form) - self.typeTab.setCurrentIndex(0) + self.typeTab.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): @@ -310,14 +307,13 @@ class Ui_Form(object): self.mechLUTButton.setText(_translate("Form", "Generate LUT")) self.viewmechLUTButton.setText(_translate("Form", "View LUT")) self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical")) + self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep")) self.label_2.setText(_translate("Form", "Voltage Tuning")) - self.setvoltagesButton.setText(_translate("Form", "Set Voltages")) - self.label_4.setText(_translate("Form", "Voltage Resolution")) + self.label_11.setText(_translate("Form", "Generate LUT:")) self.label_3.setText(_translate("Form", "Voltage Matching")) self.label_9.setText(_translate("Form", "Set Voltages:")) self.viewelLUTButton.setText(_translate("Form", "View LUT")) - self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep")) - self.label_11.setText(_translate("Form", "Generate LUT:")) + self.setvoltagesButton.setText(_translate("Form", "Set Voltages")) self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical")) self.rfswitchLabel.setText(_translate("Form", "RF Switch:")) self.switchATMButton.setText(_translate("Form", "ATM")) From 44cce2c5dae69e8e41cbd1d7c7c3078cb9a15e7d Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 14:07:32 +0100 Subject: [PATCH 04/31] Emitting LUT data --- src/nqrduck_autotm/controller.py | 4 +++- src/nqrduck_autotm/resources/autotm_widget.ui | 2 +- src/nqrduck_autotm/resources/duck_kick.gif | Bin 27758 -> 0 bytes src/nqrduck_autotm/view.py | 1 + src/nqrduck_autotm/widget.py | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) delete mode 100644 src/nqrduck_autotm/resources/duck_kick.gif diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 8369f51..1e558de 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -232,7 +232,9 @@ class AutoTMController(ModuleController): self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start ) - ) + ) + # Now we emit the signal that the LUT is finished with the LUT as an argument + self.module.nqrduck_signal.emit("LUT_finished", LUT) def on_short_calibration( self, start_frequency: float, stop_frequency: float diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 4e6734a..faf37f5 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -95,7 +95,7 @@ - 80.299999999999997 + 80.200000000000003 diff --git a/src/nqrduck_autotm/resources/duck_kick.gif b/src/nqrduck_autotm/resources/duck_kick.gif deleted file mode 100644 index 0b7b08e932043faae13cdef74e609d1f8683d2eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27758 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 diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 9c2bb1c..fcd9124 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -363,6 +363,7 @@ class AutoTMView(ModuleView): """This method is called when the Test LUT button is clicked. It sets all of the voltages from the lut with a small delay. One can then view the matching on a seperate VNA. """ + # This should be in the controller for frequency in self.module.model.LUT.data.keys(): tuning_voltage = str(self.module.model.LUT.data[frequency][1]) matching_voltage = str(self.module.model.LUT.data[frequency][0]) diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 51f93bc..8037f90 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -61,7 +61,7 @@ class Ui_Form(object): self.gridLayout_8 = QtWidgets.QGridLayout() self.gridLayout_8.setObjectName("gridLayout_8") self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) - self.stopfrequencyBox.setProperty("value", 80.3) + self.stopfrequencyBox.setProperty("value", 80.1) self.stopfrequencyBox.setObjectName("stopfrequencyBox") self.gridLayout_8.addWidget(self.stopfrequencyBox, 1, 1, 1, 1) self.label_13 = QtWidgets.QLabel(parent=Form) From 0e2905b895c0e180edec293a314d5fe3f8b0d5c7 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 15:34:46 +0100 Subject: [PATCH 05/31] Refactored Serial communication. --- src/nqrduck_autotm/controller.py | 209 ++++++++++++++++--------------- 1 file changed, 110 insertions(+), 99 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 1e558de..2777ad7 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -130,111 +130,122 @@ class AutoTMController(ModuleController): self.module.model.clear_data_points() self.module.view.create_frequency_sweep_spinner_dialog() + def process_frequency_sweep_data(self, text): + """This method is called when data is received from the serial connection during a frequency sweep. + It processes the data and adds it to the model. + """ + 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) + + def process_measurement_data(self): + """This method is called when data is received from the serial connection during a measurement. + It processes the data and adds it to the model. + """ + logger.debug("Measurement finished") + self.module.model.measurement = S11Data( + self.module.model.data_points.copy() + ) + self.finish_frequency_sweep() + + def process_calibration_data(self, calibration_type): + """This method is called when data is received from the serial connection during a calibration. + It processes the data and adds it to the model. + + Args: + calibration_type (str): The type of calibration that is being performed. + """ + logger.debug(f"{calibration_type.capitalize()} calibration finished") + setattr(self.module.model, f"{calibration_type}_calibration", + S11Data(self.module.model.data_points.copy())) + self.module.model.active_calibration = None + self.module.view.frequency_sweep_spinner.hide() + + def process_voltage_sweep_result(self, text): + """This method is called when data is received from the serial connection during a voltage sweep. + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. + """ + text = text[1:].split("t") + matching_voltage, tuning_voltage = map(float, text) + LUT = self.module.model.LUT + logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) + LUT.add_voltages(matching_voltage, tuning_voltage) + self.continue_or_finish_voltage_sweep(LUT) + + def finish_frequency_sweep(self): + """This method is called when a frequency sweep is finished. + It hides the frequency sweep spinner dialog and adds the data to the model. + """ + self.module.view.frequency_sweep_spinner.hide() + self.module.model.frequency_sweep_stop = time.time() + duration = self.module.model.frequency_sweep_stop - self.module.model.frequency_sweep_start + self.module.view.add_info_text(f"Frequency sweep finished in {duration:.2f} seconds") + + def continue_or_finish_voltage_sweep(self, LUT): + """This method is called when a voltage sweep is finished. + It checks if the voltage sweep is finished or if the next voltage sweep should be started. + + Args: + LUT (LookupTable): The lookup table that is being generated. + """ + if LUT.is_incomplete(): + # Start the next voltage sweep + self.start_next_voltage_sweep(LUT) + else: + # Finish voltage sweep + self.finish_voltage_sweep(LUT) + + def start_next_voltage_sweep(self, LUT): + """This method is called when a voltage sweep is finished. + It starts the next voltage sweep. + + Args: + LUT (LookupTable): The lookup table that is being generated. + """ + next_frequency = LUT.get_next_frequency() + command = f"s{next_frequency}" + LUT.started_frequency = next_frequency + logger.debug("Starting next voltage sweep: %s", command) + self.send_command(command) + + def finish_voltage_sweep(self, LUT): + """This method is called when a voltage sweep is finished. + It hides the voltage sweep spinner dialog and adds the data to the model. + + Args: + LUT (LookupTable): The lookup table that is being generated.""" + logger.debug("Voltage sweep finished") + self.module.view.el_LUT_spinner.hide() + self.module.model.voltage_sweep_stop = time.time() + duration = self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start + self.module.view.add_info_text(f"Voltage sweep finished in {duration:.2f} seconds") + self.module.nqrduck_signal.emit("LUT_finished", LUT) + def on_ready_read(self) -> None: """This method is called when data is received from the serial connection.""" serial = self.module.model.serial while serial.canReadLine(): - text = serial.readLine().data().decode() - text = text.rstrip("\r\n") + text = serial.readLine().data().decode().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.model.measurement = S11Data( - self.module.model.data_points.copy() - ) - 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 - elif ( - text.startswith("r") and self.module.model.active_calibration == "short" - ): - logger.debug("Short calibration finished") - self.module.model.short_calibration = S11Data( - 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 = S11Data( - 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 = S11Data( - self.module.model.data_points.copy() - ) - self.module.model.active_calibration = None - self.module.view.frequency_sweep_spinner.hide() - # 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) - # If the text starts with 'v' we know that the data is a voltage sweep result - elif text.startswith("v"): - text = text[1:] - text = text.split("t") - matching_voltage = float(text[0]) - tuning_voltage = float(text[1]) - # Now we add the datapoint to the current LUT - LUT = self.module.model.LUT - logger.debug( - "Received voltage sweep result: %s %s", - matching_voltage, - tuning_voltage, - ) - LUT.add_voltages(matching_voltage, tuning_voltage) - # Start the next voltage sweep if there are more voltages to sweep - if LUT.is_incomplete(): - next_frequency = LUT.get_next_frequency() - command = "s%s" % next_frequency - LUT.started_frequency = next_frequency - logger.debug("Starting next voltage sweep: %s", command) - self.send_command(command) - else: - logger.debug("Voltage sweep finished") - self.module.view.el_LUT_spinner.hide() - self.module.model.voltage_sweep_stop = time.time() - self.module.view.add_info_text( - "Voltage sweep finished in %.2f seconds" - % ( - self.module.model.voltage_sweep_stop - - self.module.model.voltage_sweep_start - ) - ) - # Now we emit the signal that the LUT is finished with the LUT as an argument - self.module.nqrduck_signal.emit("LUT_finished", LUT) + if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible(): + self.process_frequency_sweep_data(text) + elif text.startswith("r"): + if self.module.model.active_calibration is None: + self.process_measurement_data() + elif self.module.model.active_calibration in ["short", "open", "load"]: + self.process_calibration_data(self.module.model.active_calibration) + elif text.startswith("i"): + self.module.view.add_info_text("ATM Info: " + text[1:]) + elif text.startswith("e"): + self.module.view.add_info_text("ATM Error: " + text[1:]) + elif text.startswith("v"): + self.process_voltage_sweep_result(text) + def on_short_calibration( self, start_frequency: float, stop_frequency: float From 211670f2ceee281aaeecd9253f9e9d3e9e7fd358 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 15:46:05 +0100 Subject: [PATCH 06/31] Error handling LUT generation. --- src/nqrduck_autotm/controller.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 2777ad7..9736f31 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -501,16 +501,17 @@ class AutoTMController(ModuleController): ) LUT.started_frequency = start_frequency - self.module.model.LUT = LUT # We write the first command to the serial connection command = "s%s" % (start_frequency) - self.module.view.create_el_LUT_spinner_dialog() + # For timing of the voltage sweep self.module.model.voltage_sweep_start = time.time() confirmation = self.send_command(command) - if not confirmation: - return + # If the command was send successfully, we set the LUT + if confirmation: + self.module.model.LUT = LUT + self.module.view.create_el_LUT_spinner_dialog() 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'. From b3db05e279bf84d4142f19416931436574aff4c5 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 16:15:16 +0100 Subject: [PATCH 07/31] Updated button labels. --- src/nqrduck_autotm/resources/autotm_widget.ui | 2 +- src/nqrduck_autotm/widget.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index faf37f5..1b46085 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -299,7 +299,7 @@ - Start Voltage Sweep + Generate LUT diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 8037f90..5f4e4ae 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -61,7 +61,7 @@ class Ui_Form(object): self.gridLayout_8 = QtWidgets.QGridLayout() self.gridLayout_8.setObjectName("gridLayout_8") self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) - self.stopfrequencyBox.setProperty("value", 80.1) + self.stopfrequencyBox.setProperty("value", 80.2) self.stopfrequencyBox.setObjectName("stopfrequencyBox") self.gridLayout_8.addWidget(self.stopfrequencyBox, 1, 1, 1, 1) self.label_13 = QtWidgets.QLabel(parent=Form) @@ -307,7 +307,7 @@ class Ui_Form(object): self.mechLUTButton.setText(_translate("Form", "Generate LUT")) self.viewmechLUTButton.setText(_translate("Form", "View LUT")) self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical")) - self.generateLUTButton.setText(_translate("Form", "Start Voltage Sweep")) + self.generateLUTButton.setText(_translate("Form", "Generate LUT")) self.label_2.setText(_translate("Form", "Voltage Tuning")) self.label_11.setText(_translate("Form", "Generate LUT:")) self.label_3.setText(_translate("Form", "Voltage Matching")) From 9bd185253275a6bf738d03ae426426fe4bf911bc Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 17:15:08 +0100 Subject: [PATCH 08/31] Implemented communication atm broadband module. --- src/nqrduck_autotm/controller.py | 32 +++++++++++++++++--- src/nqrduck_autotm/model.py | 52 ++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 9736f31..a65fd2f 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -8,7 +8,7 @@ from PyQt6 import QtSerialPort from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt from PyQt6.QtWidgets import QApplication from nqrduck.module.module_controller import ModuleController -from .model import S11Data, LookupTable +from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable logger = logging.getLogger(__name__) @@ -16,6 +16,25 @@ logger = logging.getLogger(__name__) class AutoTMController(ModuleController): BAUDRATE = 115200 + @pyqtSlot(str, object) + def process_signals(self, key: str, value: object) -> None: + logger.debug("Received signal: %s", key) + if key == "set_tune_and_match": + self.tune_and_match(value) + + def tune_and_match(self, frequency: float) -> None: + """ This method is called when this module already has a LUT table. It should then tune and match the probe coil to the specified frequency. + """ + if self.module.model.LUT is None: + logger.error("Could not tune and match. No LUT available.") + return + elif self.module.model.LUT.TYPE == "Electrical": + tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) + self.set_voltages(str(tunning_voltage), str(matching_voltage)) + + elif self.module.model.LUT.TYPE == "Mechanical": + pass + def find_devices(self) -> None: """Scan for available serial devices and add them to the model as available devices.""" logger.debug("Scanning for available serial devices") @@ -436,7 +455,11 @@ class AutoTMController(ModuleController): ) command = "v%sv%s" % (matching_voltage, tuning_voltage) - self.send_command(command) + confirmation = self.send_command(command) + if confirmation: + logger.debug("Voltages set successfully") + # Emit nqrduck signal that T&M was successful + self.module.nqrduck_signal.emit("confirm_tune_and_match", None) def generate_lut( self, @@ -482,7 +505,8 @@ class AutoTMController(ModuleController): self.module.view.add_info_text(error) return - if frequency_step > (stop_frequency - start_frequency): + # - 0.1 is to prevent float errors + if frequency_step - 0.1 > (stop_frequency - start_frequency): error = "Could not generate LUT. Frequency step must be smaller than the frequency range" logger.error(error) self.module.view.add_info_text(error) @@ -496,7 +520,7 @@ class AutoTMController(ModuleController): ) # We create the lookup table - LUT = LookupTable( + LUT = ElectricalLookupTable( start_frequency, stop_frequency, frequency_step ) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 9ce0fb1..1e407ca 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -178,16 +178,6 @@ class LookupTable: # This is the frequency at which the tuning and matching process was started self.started_frequency = None - self.init_voltages() - - def init_voltages(self) -> None: - """Initialize the lookup table with default values.""" - for frequency in np.arange( - self.start_frequency, self.stop_frequency, self.frequency_step - ): - self.started_frequency = frequency - self.add_voltages(None, None) - def is_incomplete(self) -> bool: """This method returns True if the lookup table is incomplete, i.e. if there are frequencies for which no the tuning or matching voltage is none. @@ -214,6 +204,33 @@ class LookupTable: return frequency return None + + def get_entry_number(self, frequency: float) -> int: + """This method returns the entry number of the given frequency. + + Args: + frequency (float): The frequency for which the entry number should be returned. + + Returns: + int: The entry number of the given frequency. + """ + # Round to closest integer + return int(round((frequency - self.start_frequency) / self.frequency_step)) + +class ElectricalLookupTable(LookupTable): + TYPE = "Electrical" + + def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None: + super().__init__(start_frequency, stop_frequency, frequency_step) + self.init_voltages() + + def init_voltages(self) -> None: + """Initialize the lookup table with default values.""" + for frequency in np.arange( + self.start_frequency, self.stop_frequency, self.frequency_step + ): + self.started_frequency = frequency + self.add_voltages(None, None) def add_voltages(self, tuning_voltage: float, matching_voltage: float) -> None: """Add a tuning and matching voltage for the last started frequency to the lookup table. @@ -223,7 +240,22 @@ class LookupTable: matching_voltage (float): The matching voltage for the given frequency.""" self.data[self.started_frequency] = (tuning_voltage, matching_voltage) + def get_voltages(self, frequency: float) -> tuple: + """Get the tuning and matching voltage for the given frequency. + Args: + frequency (float): The frequency for which the tuning and matching voltage should be returned. + + Returns: + tuple: The tuning and matching voltage for the given frequency. + """ + entry_number = self.get_entry_number(frequency) + key = list(self.data.keys())[entry_number] + return self.data[key] + +class MechanicalLookupTable(LookupTable): + TYPE = "Mechanical" + pass class AutoTMModel(ModuleModel): available_devices_changed = pyqtSignal(list) serial_changed = pyqtSignal(QSerialPort) From 67b21d27b768891bf02e9d0978a78162085a02cb Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 7 Dec 2023 20:13:34 +0100 Subject: [PATCH 09/31] Implemented stepper select and homing. --- src/nqrduck_autotm/controller.py | 30 +++++++++++- src/nqrduck_autotm/model.py | 20 ++++++++ src/nqrduck_autotm/resources/autotm_widget.ui | 48 ++++++++++++------- src/nqrduck_autotm/view.py | 30 ++++++++++++ src/nqrduck_autotm/widget.py | 36 ++++++++------ 5 files changed, 132 insertions(+), 32 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index a65fd2f..555f7a1 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -98,7 +98,7 @@ class AutoTMController(ModuleController): MAX_FREQUENCY = 200e6 # Hz try: - start_frequence = start_frequency.replace(",", ".") + start_frequency = start_frequency.replace(",", ".") stop_frequency = stop_frequency.replace(",", ".") start_frequency = float(start_frequency) * 1e6 stop_frequency = float(stop_frequency) * 1e6 @@ -264,6 +264,16 @@ class AutoTMController(ModuleController): self.module.view.add_info_text("ATM Error: " + text[1:]) elif text.startswith("v"): self.process_voltage_sweep_result(text) + elif text.startswith("p"): + # Format is pm + text = text[1:].split("m") + tuning_position, matching_position = map(int, text) + self.module.model.tuning_stepper.position = tuning_position + self.module.model.matching_stepper.position = matching_position + self.module.model.tuning_stepper.homed = True + self.module.model.matching_stepper.homed = True + logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) + self.module.view.on_active_stepper_changed() def on_short_calibration( @@ -609,3 +619,21 @@ class AutoTMController(ModuleController): """ logger.debug("Homing") self.send_command("h") + + @pyqtSlot(str) + def on_stepper_changed(self, stepper: str) -> None: + """This method is called when the stepper position is changed. + It sends the command to the atm system to change the stepper position. + + Args: + stepper (str): The stepper that is being changed. Either 'tuning' or 'matching'. + """ + logger.debug("Stepper %s changed", stepper) + stepper = stepper.lower() + if stepper == "tuning": + self.module.model.active_stepper = self.module.model.tuning_stepper + elif stepper == "matching": + self.module.model.active_stepper = self.module.model.matching_stepper + + + diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 1e407ca..fdd356a 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -217,6 +217,12 @@ class LookupTable: # Round to closest integer return int(round((frequency - self.start_frequency) / self.frequency_step)) +class Stepper: + + def __init__(self) -> None: + self.homed = False + self.position = 0 + class ElectricalLookupTable(LookupTable): TYPE = "Electrical" @@ -260,6 +266,7 @@ class AutoTMModel(ModuleModel): available_devices_changed = pyqtSignal(list) serial_changed = pyqtSignal(QSerialPort) data_points_changed = pyqtSignal(list) + active_stepper_changed = pyqtSignal(Stepper) short_calibration_finished = pyqtSignal(S11Data) open_calibration_finished = pyqtSignal(S11Data) @@ -273,6 +280,10 @@ class AutoTMModel(ModuleModel): self.calibration = None self.serial = None + self.tuning_stepper = Stepper() + self.matching_stepper = Stepper() + self.active_stepper = self.tuning_stepper + @property def available_devices(self): return self._available_devices @@ -318,6 +329,15 @@ class AutoTMModel(ModuleModel): self._measurement = value self.measurement_finished.emit(value) + @property + def active_stepper(self): + return self._active_stepper + + @active_stepper.setter + def active_stepper(self, value): + self._active_stepper = value + self.active_stepper_changed.emit(value) + # Calibration properties @property diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 1b46085..02852d3 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -152,7 +152,7 @@ - 1 + 0 @@ -160,8 +160,8 @@ - - + + Home @@ -181,6 +181,9 @@ + + + @@ -195,14 +198,14 @@ - + - - + + @@ -216,7 +219,14 @@ - + + + + Absolute: + + + + -1000 @@ -229,29 +239,33 @@ - + Step Size: - - - - Absolute: - - - - + Go - - + + + + Position: + + + + + + + 0 + + diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index fcd9124..dc5d9eb 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -39,6 +39,9 @@ class AutoTMView(ModuleView): # Disable the connectButton while no devices are selected self._ui_form.connectButton.setDisabled(True) + self._ui_form.decreaseButton.setEnabled(False) + self._ui_form.increaseButton.setEnabled(False) + self._ui_form.absoluteGoButton.setEnabled(False) # On clicking of the refresh button scan for available usb devices self._ui_form.refreshButton.clicked.connect(self.module.controller.find_devices) @@ -111,6 +114,12 @@ class AutoTMView(ModuleView): self._ui_form.startButton.setIcon(Logos.Play_16x16()) self._ui_form.startButton.setIconSize(self._ui_form.startButton.size()) + # Stepper selection + self._ui_form.stepperselectBox.currentIndexChanged.connect(lambda: self.module.controller.on_stepper_changed(self._ui_form.stepperselectBox.currentText())) + + # Active stepper changed + self.module.model.active_stepper_changed.connect(self.on_active_stepper_changed) + self.init_plot() self.init_labels() @@ -156,6 +165,10 @@ class AutoTMView(ModuleView): self._ui_form.connectButton.setEnabled(False) logger.debug("Updated available devices list") + def on_stepper_changed(): + """Update the stepper position label according to the current stepper position.""" + logger.debug("Updating stepper position label") + @pyqtSlot() def on_connect_button_clicked(self) -> None: """This method is called when the connect button is clicked. @@ -184,6 +197,23 @@ class AutoTMView(ModuleView): logger.debug("Updated serial connection label") + @pyqtSlot() + def on_active_stepper_changed(self) -> None: + """Update the stepper position label according to the current stepper position.""" + logger.debug("Updating stepper position label") + self._ui_form.stepperposLabel.setText(str(self.module.model.active_stepper.position)) + logger.debug("Updated stepper position label") + + # Only allow position change when stepper is homed + if self.module.model.active_stepper.homed: + self._ui_form.decreaseButton.setEnabled(True) + self._ui_form.increaseButton.setEnabled(True) + self._ui_form.absoluteGoButton.setEnabled(True) + else: + self._ui_form.decreaseButton.setEnabled(False) + self._ui_form.increaseButton.setEnabled(False) + self._ui_form.absoluteGoButton.setEnabled(False) + def plot_measurement(self, data: "S11Data") -> None: """Update the S11 plot with the current data points. diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 5f4e4ae..5f3cbe6 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -99,7 +99,7 @@ class Ui_Form(object): self.gridLayout_4.setObjectName("gridLayout_4") self.homeButton = QtWidgets.QPushButton(parent=self.mechTab) self.homeButton.setObjectName("homeButton") - self.gridLayout_4.addWidget(self.homeButton, 4, 1, 1, 1) + self.gridLayout_4.addWidget(self.homeButton, 5, 1, 1, 1) self.label_16 = QtWidgets.QLabel(parent=self.mechTab) font = QtGui.QFont() font.setBold(True) @@ -107,6 +107,9 @@ class Ui_Form(object): self.label_16.setFont(font) self.label_16.setObjectName("label_16") self.gridLayout_4.addWidget(self.label_16, 0, 0, 1, 3) + self.absoluteposBox = QtWidgets.QSpinBox(parent=self.mechTab) + self.absoluteposBox.setObjectName("absoluteposBox") + self.gridLayout_4.addWidget(self.absoluteposBox, 6, 1, 1, 1) self.stepperselectBox = QtWidgets.QComboBox(parent=self.mechTab) self.stepperselectBox.setObjectName("stepperselectBox") self.stepperselectBox.addItem("") @@ -114,31 +117,34 @@ class Ui_Form(object): self.gridLayout_4.addWidget(self.stepperselectBox, 1, 1, 1, 1) self.decreaseButton = QtWidgets.QPushButton(parent=self.mechTab) self.decreaseButton.setObjectName("decreaseButton") - self.gridLayout_4.addWidget(self.decreaseButton, 4, 0, 1, 1) + self.gridLayout_4.addWidget(self.decreaseButton, 5, 0, 1, 1) self.increaseButton = QtWidgets.QPushButton(parent=self.mechTab) self.increaseButton.setObjectName("increaseButton") - self.gridLayout_4.addWidget(self.increaseButton, 4, 2, 1, 1) + self.gridLayout_4.addWidget(self.increaseButton, 5, 2, 1, 1) self.label_18 = QtWidgets.QLabel(parent=self.mechTab) self.label_18.setObjectName("label_18") self.gridLayout_4.addWidget(self.label_18, 1, 0, 1, 1) + self.label_20 = QtWidgets.QLabel(parent=self.mechTab) + self.label_20.setObjectName("label_20") + self.gridLayout_4.addWidget(self.label_20, 6, 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, 2, 1, 1, 1) + self.gridLayout_4.addWidget(self.stepsizeBox, 3, 1, 1, 1) self.label_17 = QtWidgets.QLabel(parent=self.mechTab) self.label_17.setObjectName("label_17") - self.gridLayout_4.addWidget(self.label_17, 2, 0, 1, 1) - self.label_20 = QtWidgets.QLabel(parent=self.mechTab) - self.label_20.setObjectName("label_20") - self.gridLayout_4.addWidget(self.label_20, 5, 0, 1, 1) + self.gridLayout_4.addWidget(self.label_17, 3, 0, 1, 1) self.absoluteGoButton = QtWidgets.QPushButton(parent=self.mechTab) self.absoluteGoButton.setObjectName("absoluteGoButton") - self.gridLayout_4.addWidget(self.absoluteGoButton, 5, 2, 1, 1) - self.absoluteposBox = QtWidgets.QSpinBox(parent=self.mechTab) - self.absoluteposBox.setObjectName("absoluteposBox") - self.gridLayout_4.addWidget(self.absoluteposBox, 5, 1, 1, 1) + self.gridLayout_4.addWidget(self.absoluteGoButton, 6, 2, 1, 1) + self.label_4 = QtWidgets.QLabel(parent=self.mechTab) + self.label_4.setObjectName("label_4") + self.gridLayout_4.addWidget(self.label_4, 2, 0, 1, 1) + self.stepperposLabel = QtWidgets.QLabel(parent=self.mechTab) + self.stepperposLabel.setObjectName("stepperposLabel") + self.gridLayout_4.addWidget(self.stepperposLabel, 2, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout_4) self.positionButton = QtWidgets.QPushButton(parent=self.mechTab) self.positionButton.setObjectName("positionButton") @@ -277,7 +283,7 @@ class Ui_Form(object): self.horizontalLayout_2.setStretch(1, 1) self.retranslateUi(Form) - self.typeTab.setCurrentIndex(1) + self.typeTab.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): @@ -300,9 +306,11 @@ class Ui_Form(object): self.decreaseButton.setText(_translate("Form", "-")) self.increaseButton.setText(_translate("Form", "+")) self.label_18.setText(_translate("Form", "Stepper:")) - self.label_17.setText(_translate("Form", "Step Size:")) self.label_20.setText(_translate("Form", "Absolute:")) + self.label_17.setText(_translate("Form", "Step Size:")) self.absoluteGoButton.setText(_translate("Form", "Go")) + self.label_4.setText(_translate("Form", "Position:")) + self.stepperposLabel.setText(_translate("Form", "0")) self.positionButton.setText(_translate("Form", "Saved Positions")) self.mechLUTButton.setText(_translate("Form", "Generate LUT")) self.viewmechLUTButton.setText(_translate("Form", "View LUT")) From 487f7ce0c75fa0ff5d2c5245c44e562191cdb815 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 8 Dec 2023 08:17:44 +0100 Subject: [PATCH 10/31] Implemented relative stepper movement. --- src/nqrduck_autotm/controller.py | 28 ++++++++++++++++++++++++++++ src/nqrduck_autotm/model.py | 12 ++++++++++-- src/nqrduck_autotm/view.py | 4 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 555f7a1..d98fac7 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -634,6 +634,34 @@ class AutoTMController(ModuleController): self.module.model.active_stepper = self.module.model.tuning_stepper elif stepper == "matching": self.module.model.active_stepper = self.module.model.matching_stepper + + def on_relative_move(self, steps : str) -> None: + """This method is called when the relative move button is pressed. + Args: + steps (str): The number of steps to move the stepper. + """ + + # First char is the stepper identifier, m for matching and t for tuning + motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] + + # We check if the steps are valid + stepper_current_position = self.module.model.active_stepper.position + + future_position = stepper_current_position + int(steps) + + if future_position < 0: + error = "Could not move stepper. Stepper position cannot be negative" + self.module.view.add_info_text(error) + return + + if future_position > self.module.model.active_stepper.MAX_STEPS: + error = f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}" + self.module.view.add_info_text(error) + return + + # We send the command to the atm system, the first m identifies this is a move command + command = f"m{motor_identifier}{steps}" + self.send_command(command) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index fdd356a..6c89896 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -223,6 +223,14 @@ class Stepper: self.homed = False self.position = 0 +class TuningStepper(Stepper): + TYPE = "Tuning" + MAX_STEPS = 1e6 + +class MatchingStepper(Stepper): + TYPE = "Matching" + MAX_STEPS = 1e6 + class ElectricalLookupTable(LookupTable): TYPE = "Electrical" @@ -280,8 +288,8 @@ class AutoTMModel(ModuleModel): self.calibration = None self.serial = None - self.tuning_stepper = Stepper() - self.matching_stepper = Stepper() + self.tuning_stepper = TuningStepper() + self.matching_stepper = MatchingStepper() self.active_stepper = self.tuning_stepper @property diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index dc5d9eb..9330bc7 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -116,6 +116,10 @@ class AutoTMView(ModuleView): # Stepper selection self._ui_form.stepperselectBox.currentIndexChanged.connect(lambda: self.module.controller.on_stepper_changed(self._ui_form.stepperselectBox.currentText())) + self._ui_form.increaseButton.clicked.connect(lambda: self.module.controller.on_relative_move(self._ui_form.stepsizeBox.text())) + self._ui_form.decreaseButton.clicked.connect(lambda: self.module.controller.on_relative_move("-" + self._ui_form.stepsizeBox.text())) + + self._ui_form.absoluteGoButton.clicked.connect(lambda: self.module.controller.on_absolute_move(self._ui_form.absoluteposBox.text())) # Active stepper changed self.module.model.active_stepper_changed.connect(self.on_active_stepper_changed) From f49cb04139ff26a00dd491f98dd3943b92b56cce Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 8 Dec 2023 08:26:41 +0100 Subject: [PATCH 11/31] Implemented absolute stepper movement. --- src/nqrduck_autotm/controller.py | 31 +++++++++++++++++++ src/nqrduck_autotm/resources/autotm_widget.ui | 18 ++++++----- src/nqrduck_autotm/widget.py | 11 ++++--- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index d98fac7..e3a699e 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -664,4 +664,35 @@ class AutoTMController(ModuleController): command = f"m{motor_identifier}{steps}" self.send_command(command) + def on_absolute_move(self, steps : str) -> None: + """This method is called when the absolute move button is pressed. + + Args: + steps (str): The number of steps to move the stepper. + """ + + # First char is the stepper identifier, m for matching and t for tuning + motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] + + # We check if the steps are valid + future_position = int(steps) + + if future_position < 0: + error = "Could not move stepper. Stepper position cannot be negative" + self.module.view.add_info_text(error) + return + + if future_position > self.module.model.active_stepper.MAX_STEPS: + error = f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}" + self.module.view.add_info_text(error) + return + + # We calculate the number of steps to move + stepper_current_position = self.module.model.active_stepper.position + steps = future_position - stepper_current_position + + # We send the command to the atm system, the first m identifies this is a move command + command = f"m{motor_identifier}{steps}" + self.send_command(command) + diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 02852d3..753fb72 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -7,7 +7,7 @@ 0 0 1280 - 1011 + 1089 @@ -95,7 +95,7 @@ - 80.200000000000003 + 80.099999999999994 @@ -182,7 +182,11 @@ - + + + 1000000 + + @@ -229,10 +233,10 @@ - -1000 + 0 - 1000 + 50000 500 @@ -309,7 +313,7 @@ Electrical - + @@ -515,7 +519,7 @@ 0 0 - 285 + 291 83 diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 5f3cbe6..a32d211 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") - Form.resize(1280, 1011) + Form.resize(1280, 1089) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -61,7 +61,7 @@ class Ui_Form(object): self.gridLayout_8 = QtWidgets.QGridLayout() self.gridLayout_8.setObjectName("gridLayout_8") self.stopfrequencyBox = QtWidgets.QDoubleSpinBox(parent=Form) - self.stopfrequencyBox.setProperty("value", 80.2) + self.stopfrequencyBox.setProperty("value", 80.1) self.stopfrequencyBox.setObjectName("stopfrequencyBox") self.gridLayout_8.addWidget(self.stopfrequencyBox, 1, 1, 1, 1) self.label_13 = QtWidgets.QLabel(parent=Form) @@ -108,6 +108,7 @@ class Ui_Form(object): self.label_16.setObjectName("label_16") self.gridLayout_4.addWidget(self.label_16, 0, 0, 1, 3) self.absoluteposBox = QtWidgets.QSpinBox(parent=self.mechTab) + self.absoluteposBox.setMaximum(1000000) self.absoluteposBox.setObjectName("absoluteposBox") self.gridLayout_4.addWidget(self.absoluteposBox, 6, 1, 1, 1) self.stepperselectBox = QtWidgets.QComboBox(parent=self.mechTab) @@ -128,8 +129,8 @@ class Ui_Form(object): self.label_20.setObjectName("label_20") self.gridLayout_4.addWidget(self.label_20, 6, 0, 1, 1) self.stepsizeBox = QtWidgets.QSpinBox(parent=self.mechTab) - self.stepsizeBox.setMinimum(-1000) - self.stepsizeBox.setMaximum(1000) + self.stepsizeBox.setMinimum(0) + self.stepsizeBox.setMaximum(50000) self.stepsizeBox.setProperty("value", 500) self.stepsizeBox.setObjectName("stepsizeBox") self.gridLayout_4.addWidget(self.stepsizeBox, 3, 1, 1, 1) @@ -264,7 +265,7 @@ class Ui_Form(object): self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QtWidgets.QWidget() - self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 285, 83)) + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 291, 83)) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.verticalLayout_2.addWidget(self.scrollArea) From 857ff6eb082ea5e2f2d8aafde32857d4995da437 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 8 Dec 2023 08:29:57 +0100 Subject: [PATCH 12/31] Refactoring. --- src/nqrduck_autotm/controller.py | 81 ++++++++++++-------------------- 1 file changed, 29 insertions(+), 52 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index e3a699e..da92aac 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -635,64 +635,41 @@ class AutoTMController(ModuleController): elif stepper == "matching": self.module.model.active_stepper = self.module.model.matching_stepper - def on_relative_move(self, steps : str) -> None: - """This method is called when the relative move button is pressed. - - Args: - steps (str): The number of steps to move the stepper. - """ - - # First char is the stepper identifier, m for matching and t for tuning - motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] - - # We check if the steps are valid - stepper_current_position = self.module.model.active_stepper.position - - future_position = stepper_current_position + int(steps) - + def validate_position(self, future_position: int) -> bool: + """Validate the stepper's future position.""" if future_position < 0: - error = "Could not move stepper. Stepper position cannot be negative" - self.module.view.add_info_text(error) - return - + self.module.view.add_error_text("Could not move stepper. Stepper position cannot be negative") + return False + if future_position > self.module.model.active_stepper.MAX_STEPS: - error = f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}" - self.module.view.add_info_text(error) - return - - # We send the command to the atm system, the first m identifies this is a move command + self.module.view.add_error_text(f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}") + return False + + return True + + def calculate_steps_for_absolute_move(self, target_position: int) -> int: + """Calculate the number of steps for an absolute move.""" + current_position = self.module.model.active_stepper.position + return target_position - current_position + + def send_stepper_command(self, steps: int) -> None: + """Send a command to the stepper motor based on the number of steps.""" + motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] command = f"m{motor_identifier}{steps}" self.send_command(command) - def on_absolute_move(self, steps : str) -> None: - """This method is called when the absolute move button is pressed. - - Args: - steps (str): The number of steps to move the stepper. - """ + def on_relative_move(self, steps: str) -> None: + """This method is called when the relative move button is pressed.""" + future_position = self.module.model.active_stepper.position + int(steps) + if self.validate_position(future_position): + self.send_stepper_command(int(steps)) # Convert the steps string to an integer - # First char is the stepper identifier, m for matching and t for tuning - motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] - - # We check if the steps are valid + def on_absolute_move(self, steps: str) -> None: + """This method is called when the absolute move button is pressed.""" future_position = int(steps) - - if future_position < 0: - error = "Could not move stepper. Stepper position cannot be negative" - self.module.view.add_info_text(error) - return - - if future_position > self.module.model.active_stepper.MAX_STEPS: - error = f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}" - self.module.view.add_info_text(error) - return - - # We calculate the number of steps to move - stepper_current_position = self.module.model.active_stepper.position - steps = future_position - stepper_current_position - - # We send the command to the atm system, the first m identifies this is a move command - command = f"m{motor_identifier}{steps}" - self.send_command(command) + if self.validate_position(future_position): + actual_steps = self.calculate_steps_for_absolute_move(future_position) + self.send_stepper_command(actual_steps) + From 6920b7545a41e70137e28f2d637876db566ed441 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 8 Dec 2023 09:26:54 +0100 Subject: [PATCH 13/31] Added loading saving of positions. --- src/nqrduck_autotm/controller.py | 40 +++++++- src/nqrduck_autotm/model.py | 36 +++++++ src/nqrduck_autotm/view.py | 171 +++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 1 deletion(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index da92aac..657f0bf 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -8,7 +8,7 @@ from PyQt6 import QtSerialPort from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt from PyQt6.QtWidgets import QApplication from nqrduck.module.module_controller import ModuleController -from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable +from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition logger = logging.getLogger(__name__) @@ -671,5 +671,43 @@ class AutoTMController(ModuleController): actual_steps = self.calculate_steps_for_absolute_move(future_position) self.send_stepper_command(actual_steps) + def load_positions(self, path : str) -> None: + """Load the saved positions from a json file. + + Args: + path (str): The path to the json file. + """ + # First clear the old positions + self.module.model.saved_positions = [] + + with open(path, "r") as f: + positions = json.load(f) + for position in positions: + logger.debug("Loading position: %s", position) + self.add_position(position["frequency"], position["tuning_position"], position["matching_position"]) + + + def save_positions(self, path: str) -> None: + """Save the current positions to a json file. + + Args: + path (str): The path to the json file. + """ + positions = self.module.model.saved_positions + with open(path, "w") as f: + json_position = [position.to_json() for position in positions] + json.dump(json_position, f) + + def add_position(self, frequency: str, tuning_position: str, matching_position: str) -> None: + """Add a position to the lookup table. + + Args: + frequency (str): The frequency of the position. + tuning_position (str): The tuning position. + matching_position (str): The matching position. + """ + logger.debug("Adding new position at %s MHz", frequency) + self.module.model.add_saved_position(frequency, tuning_position, matching_position) + diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 6c89896..c1d15ae 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -223,6 +223,25 @@ class Stepper: self.homed = False self.position = 0 +class SavedPosition(): + """This class is used to store a saved position for tuning and matching of electrical probeheads.""" + def __init__(self, frequency: float, tuning_position : int, matching_position : int) -> None: + self.frequency = frequency + self.tuning_position = tuning_position + self.matching_position = matching_position + + def to_json(self): + return { + "frequency": self.frequency, + "tuning_position": self.tuning_position, + "matching_position": self.matching_position, + } + + @classmethod + def from_json(cls, json): + logger.debug(json) + return cls(json[0], json[1], json[2]) + class TuningStepper(Stepper): TYPE = "Tuning" MAX_STEPS = 1e6 @@ -275,6 +294,7 @@ class AutoTMModel(ModuleModel): serial_changed = pyqtSignal(QSerialPort) data_points_changed = pyqtSignal(list) active_stepper_changed = pyqtSignal(Stepper) + saved_positions_changed = pyqtSignal(list) short_calibration_finished = pyqtSignal(S11Data) open_calibration_finished = pyqtSignal(S11Data) @@ -292,6 +312,8 @@ class AutoTMModel(ModuleModel): self.matching_stepper = MatchingStepper() self.active_stepper = self.tuning_stepper + self.saved_positions = [] + @property def available_devices(self): return self._available_devices @@ -325,6 +347,20 @@ class AutoTMModel(ModuleModel): self.data_points.clear() self.data_points_changed.emit(self.data_points) + @property + def saved_positions(self): + return self._saved_positions + + @saved_positions.setter + def saved_positions(self, value): + self._saved_positions = value + self.saved_positions_changed.emit(value) + + def add_saved_position(self, frequency: float, tuning_position: int, matching_position: int) -> None: + """Add a saved position to the model.""" + self.saved_positions.append(SavedPosition(frequency, tuning_position, matching_position)) + self.saved_positions_changed.emit(self.saved_positions) + @property def measurement(self): """The measurement property is used to store the current measurement. diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 9330bc7..14c6fbe 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -124,6 +124,9 @@ class AutoTMView(ModuleView): # Active stepper changed self.module.model.active_stepper_changed.connect(self.on_active_stepper_changed) + # Position Button + self._ui_form.positionButton.clicked.connect(self.on_position_button_clicked) + self.init_plot() self.init_labels() @@ -218,6 +221,15 @@ class AutoTMView(ModuleView): self._ui_form.increaseButton.setEnabled(False) self._ui_form.absoluteGoButton.setEnabled(False) + @pyqtSlot() + def on_position_button_clicked(self) -> None: + """This method is called when the position button is clicked. + It opens the position window. + """ + logger.debug("Position button clicked") + self.position_window = self.StepperSavedPositionsWindow(self.module, self) + self.position_window.show() + def plot_measurement(self, data: "S11Data") -> None: """Update the S11 plot with the current data points. @@ -336,6 +348,165 @@ class AutoTMView(ModuleView): self.lut_window = self.LutWindow(self.module) self.lut_window.show() + class StepperSavedPositionsWindow(QDialog): + def __init__(self, module, parent=None): + super().__init__(parent) + self.setParent(parent) + self.module = module + self.setWindowTitle("Saved positions") + # make window larger + self.resize(800, 800) + + # Add vertical main layout + main_layout = QVBoxLayout() + + # Create table widget + self.table_widget = QTableWidget() + self.table_widget.setColumnCount(4) + self.table_widget.setHorizontalHeaderLabels( + ["Frequency", "Tuning Position", "Matching Position", "Button"] + ) + + self.table_widget.setColumnWidth(0, 100) + self.table_widget.setColumnWidth(1, 200) + self.table_widget.setColumnWidth(2, 200) + self.table_widget.setColumnWidth(3, 100) + self.on_saved_positions_changed() + + # Add a 'Load Position' button (File selector) + load_position_button = QPushButton("Load Positions File") + load_position_button.clicked.connect(self.on_load_position_button_clicked) + main_layout.addWidget(load_position_button) + + # Add a 'Save Position' button (File selector) + save_position_button = QPushButton("Save Positions File") + save_position_button.clicked.connect(self.on_save_position_button_clicked) + main_layout.addWidget(save_position_button) + + # Add a 'New Position' button + new_position_button = QPushButton("New Position") + new_position_button.clicked.connect(self.on_new_position_button_clicked) + main_layout.addWidget(new_position_button) + + # Add table widget to main layout + main_layout.addWidget(self.table_widget) + + # On saved positions changed + self.module.model.saved_positions_changed.connect(self.on_saved_positions_changed) + + + self.setLayout(main_layout) + + def file_selector(self, mode) -> str: + """Opens a file selector and returns the selected file.""" + filedialog = QFileDialog() + if mode == "load": + filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) + elif mode == "save": + filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) + filedialog.setNameFilter("position files (*.pos)") + filedialog.setDefaultSuffix("pos") + filedialog.exec() + filename = filedialog.selectedFiles()[0] + return filename + + def on_load_position_button_clicked(self) -> None: + """File picker for loading a position from a file.""" + filename = self.file_selector("load") + logger.debug("Loading position from %s" % filename) + self.module.controller.load_positions(filename) + + def on_save_position_button_clicked(self) -> None: + """File picker for saving a position to a file.""" + filename = self.file_selector("save") + logger.debug("Saving position to %s" % filename) + self.module.controller.save_positions(filename) + + def on_new_position_button_clicked(self) -> None: + """Opens a new position dialog.""" + logger.debug("New position button clicked") + self.new_position_window = self.NewPositionWindow(self.module, self) + self.new_position_window.show() + + + def on_saved_positions_changed(self) -> None: + """This method is called when the saved positions changed. + It updates the table widget. + """ + logger.debug("Updating saved positions table") + self.table_widget.clearContents() + self.table_widget.setRowCount(0) + + for row, position in enumerate(self.module.model.saved_positions): + self.table_widget.insertRow(row) + self.table_widget.setItem(row, 0, QTableWidgetItem(str(position.frequency))) + self.table_widget.setItem( + row, 1, QTableWidgetItem(position.tuning_position) + ) + self.table_widget.setItem( + row, 2, QTableWidgetItem(position.matching_position) + ) + button = QPushButton("Go") + self.table_widget.setCellWidget(row, 3, button) + logger.debug("Updated saved positions table") + + class NewPositionWindow(QDialog): + def __init__(self, module, parent=None): + super().__init__(parent) + self.setParent(parent) + self.module = module + self.setWindowTitle("New Position") + + # Add vertical main layout + main_layout = QVBoxLayout() + + # Add horizontal layout for the frequency range + frequency_layout = QHBoxLayout() + main_layout.addLayout(frequency_layout) + frequency_label = QLabel("Frequency") + frequency_layout.addWidget(frequency_label) + frequency_edit = QLineEdit() + frequency_layout.addWidget(frequency_edit) + unit_label = QLabel("MHz") + frequency_layout.addWidget(unit_label) + frequency_layout.addStretch() + + # Add horizontal layout for the calibration type + type_layout = QHBoxLayout() + main_layout.addLayout(type_layout) + + # Add vertical layout for short calibration + tuning_layout = QVBoxLayout() + tuning_label = QLabel("Tuning Position") + tuning_layout.addWidget(tuning_label) + tuning_edit = QLineEdit() + tuning_layout.addWidget(tuning_edit) + type_layout.addLayout(tuning_layout) + + # Add vertical layout for open calibration + matching_layout = QVBoxLayout() + matching_label = QLabel("Matching Position") + matching_layout.addWidget(matching_label) + matching_edit = QLineEdit() + matching_layout.addWidget(matching_edit) + type_layout.addLayout(matching_layout) + + # Add vertical layout for save calibration + data_layout = QVBoxLayout() + # Apply button + apply_button = QPushButton("Apply") + apply_button.clicked.connect(lambda: self.on_apply_button_clicked(frequency_edit.text(), tuning_edit.text(), matching_edit.text())) + data_layout.addWidget(apply_button) + + main_layout.addLayout(data_layout) + + self.setLayout(main_layout) + + def on_apply_button_clicked(self, frequency: str, tuning_position: str, matching_position: str) -> None: + """This method is called when the apply button is clicked.""" + self.module.controller.add_position(frequency, tuning_position, matching_position) + # Close the calibration window + self.close() class LoadingSpinner(QDialog): """This class implements a spinner dialog that is shown during a frequency sweep.""" From 5933046d8393804e26903c74b05d2871332472bd Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 8 Dec 2023 10:09:42 +0100 Subject: [PATCH 14/31] Implemented going to position. --- src/nqrduck_autotm/controller.py | 65 +++++++++++++++++++++++--------- src/nqrduck_autotm/model.py | 10 ++--- src/nqrduck_autotm/view.py | 31 ++++++++++++--- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 657f0bf..4b3c6c6 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -8,7 +8,7 @@ from PyQt6 import QtSerialPort from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt from PyQt6.QtWidgets import QApplication from nqrduck.module.module_controller import ModuleController -from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition +from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition, Stepper logger = logging.getLogger(__name__) @@ -635,41 +635,50 @@ class AutoTMController(ModuleController): elif stepper == "matching": self.module.model.active_stepper = self.module.model.matching_stepper - def validate_position(self, future_position: int) -> bool: + def validate_position(self, future_position: int, stepper : Stepper) -> bool: """Validate the stepper's future position.""" if future_position < 0: self.module.view.add_error_text("Could not move stepper. Stepper position cannot be negative") return False - if future_position > self.module.model.active_stepper.MAX_STEPS: - self.module.view.add_error_text(f"Could not move stepper. Stepper position cannot be larger than {self.module.model.active_stepper.MAX_STEPS}") + if future_position > stepper.MAX_STEPS: + self.module.view.add_error_text(f"Could not move stepper. Stepper position cannot be larger than {stepper.MAX_STEPS}") return False return True - def calculate_steps_for_absolute_move(self, target_position: int) -> int: + def calculate_steps_for_absolute_move(self, target_position: int, stepper : Stepper) -> int: """Calculate the number of steps for an absolute move.""" - current_position = self.module.model.active_stepper.position + current_position = stepper.position return target_position - current_position - def send_stepper_command(self, steps: int) -> None: + def send_stepper_command(self, steps: int, stepper : Stepper) -> None: """Send a command to the stepper motor based on the number of steps.""" - motor_identifier = self.module.model.active_stepper.TYPE.lower()[0] + motor_identifier = stepper.TYPE.lower()[0] command = f"m{motor_identifier}{steps}" - self.send_command(command) + confirmation = self.send_command(command) + return confirmation - def on_relative_move(self, steps: str) -> None: + def on_relative_move(self, steps: str, stepper : Stepper = None ) -> None: """This method is called when the relative move button is pressed.""" - future_position = self.module.model.active_stepper.position + int(steps) - if self.validate_position(future_position): - self.send_stepper_command(int(steps)) # Convert the steps string to an integer + if stepper is None: + stepper = self.module.model.active_stepper - def on_absolute_move(self, steps: str) -> None: + future_position = stepper.position + int(steps) + if self.validate_position(future_position, stepper): + confirmation = self.send_stepper_command(int(steps), stepper) # Convert the steps string to an integer + return confirmation + + def on_absolute_move(self, steps: str, stepper : Stepper = None ) -> None: """This method is called when the absolute move button is pressed.""" + if stepper is None: + stepper = self.module.model.active_stepper + future_position = int(steps) - if self.validate_position(future_position): - actual_steps = self.calculate_steps_for_absolute_move(future_position) - self.send_stepper_command(actual_steps) + if self.validate_position(future_position, stepper): + actual_steps = self.calculate_steps_for_absolute_move(future_position, stepper) + confirmation = self.send_stepper_command(actual_steps, stepper) + return confirmation def load_positions(self, path : str) -> None: """Load the saved positions from a json file. @@ -679,7 +688,7 @@ class AutoTMController(ModuleController): """ # First clear the old positions self.module.model.saved_positions = [] - + with open(path, "r") as f: positions = json.load(f) for position in positions: @@ -709,5 +718,25 @@ class AutoTMController(ModuleController): logger.debug("Adding new position at %s MHz", frequency) self.module.model.add_saved_position(frequency, tuning_position, matching_position) + def on_go_to_position(self, position: SavedPosition) -> None: + """Go to the specified position. + + Args: + position (SavedPosition): The position to go to. + """ + logger.debug("Going to position: %s", position) + confirmation = self.on_absolute_move(position.tuning_position, self.module.model.tuning_stepper) + if confirmation: + self.on_absolute_move(position.matching_position, self.module.model.matching_stepper) + + def on_delete_position(self, position: SavedPosition) -> None: + """Delete the specified position. + + Args: + position (SavedPosition): The position to delete. + """ + logger.debug("Deleting position: %s", position) + self.module.model.delete_saved_position(position) + diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index c1d15ae..fa590d5 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -236,11 +236,6 @@ class SavedPosition(): "tuning_position": self.tuning_position, "matching_position": self.matching_position, } - - @classmethod - def from_json(cls, json): - logger.debug(json) - return cls(json[0], json[1], json[2]) class TuningStepper(Stepper): TYPE = "Tuning" @@ -361,6 +356,11 @@ class AutoTMModel(ModuleModel): self.saved_positions.append(SavedPosition(frequency, tuning_position, matching_position)) self.saved_positions_changed.emit(self.saved_positions) + def delete_saved_position(self, position: SavedPosition) -> None: + """Delete a saved position from the model.""" + self.saved_positions.remove(position) + self.saved_positions_changed.emit(self.saved_positions) + @property def measurement(self): """The measurement property is used to store the current measurement. diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 14c6fbe..bcdcf3f 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -216,10 +216,16 @@ class AutoTMView(ModuleView): self._ui_form.decreaseButton.setEnabled(True) self._ui_form.increaseButton.setEnabled(True) self._ui_form.absoluteGoButton.setEnabled(True) + self._ui_form.positionButton.setEnabled(True) + self._ui_form.mechLUTButton.setEnabled(True) + self._ui_form.viewmechLUTButton.setEnabled(True) else: self._ui_form.decreaseButton.setEnabled(False) self._ui_form.increaseButton.setEnabled(False) self._ui_form.absoluteGoButton.setEnabled(False) + self._ui_form.positionButton.setEnabled(False) + self._ui_form.mechLUTButton.setEnabled(False) + self._ui_form.viewmechLUTButton.setEnabled(False) @pyqtSlot() def on_position_button_clicked(self) -> None: @@ -362,15 +368,16 @@ class AutoTMView(ModuleView): # Create table widget self.table_widget = QTableWidget() - self.table_widget.setColumnCount(4) + self.table_widget.setColumnCount(5) self.table_widget.setHorizontalHeaderLabels( - ["Frequency", "Tuning Position", "Matching Position", "Button"] + ["Frequency (MHz)", "Tuning Position", "Matching Position", "Button", "Delete"] ) - self.table_widget.setColumnWidth(0, 100) + self.table_widget.setColumnWidth(0, 150) self.table_widget.setColumnWidth(1, 200) self.table_widget.setColumnWidth(2, 200) self.table_widget.setColumnWidth(3, 100) + self.table_widget.setColumnWidth(4, 100) self.on_saved_positions_changed() # Add a 'Load Position' button (File selector) @@ -446,8 +453,22 @@ class AutoTMView(ModuleView): self.table_widget.setItem( row, 2, QTableWidgetItem(position.matching_position) ) - button = QPushButton("Go") - self.table_widget.setCellWidget(row, 3, button) + go_button = QPushButton("Go") + go_button.clicked.connect( + lambda _, position=position: self.module.controller.on_go_to_position( + position + ) + ) + self.table_widget.setCellWidget(row, 3, go_button) + + delete_button = QPushButton("Delete") + delete_button.clicked.connect( + lambda _, position=position: self.module.controller.on_delete_position( + position + ) + ) + self.table_widget.setCellWidget(row, 4, delete_button) + logger.debug("Updated saved positions table") class NewPositionWindow(QDialog): From 850cf9e034e8f740ffdb34287da9f7b6d7073aba Mon Sep 17 00:00:00 2001 From: jupfi Date: Sun, 10 Dec 2023 18:15:35 +0100 Subject: [PATCH 15/31] Backlash correction for tuner. --- src/nqrduck_autotm/controller.py | 30 ++++++++++++++++++++++++++++++ src/nqrduck_autotm/model.py | 10 ++++++++++ src/nqrduck_autotm/view.py | 13 +++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 4b3c6c6..14e80a0 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -619,6 +619,7 @@ class AutoTMController(ModuleController): """ logger.debug("Homing") self.send_command("h") + self.module.model.tuning_stepper.last_direction = 1 @pyqtSlot(str) def on_stepper_changed(self, stepper: str) -> None: @@ -654,6 +655,18 @@ class AutoTMController(ModuleController): def send_stepper_command(self, steps: int, stepper : Stepper) -> None: """Send a command to the stepper motor based on the number of steps.""" + # Here we handle backlash of the tuner + # Determine the direction of the current steps + current_direction = np.sign(steps) # This will be -1,or 1 + if stepper.TYPE == "Tuning": + logger.debug("Stepper last direction: %s", stepper.last_direction) + logger.debug("Current direction: %s", current_direction) + if stepper.last_direction != current_direction: + steps = (abs(steps) + stepper.BACKLASH_STEPS) * current_direction + + stepper.last_direction = current_direction + logger.debug("Stepper last direction: %s", stepper.last_direction) + motor_identifier = stepper.TYPE.lower()[0] command = f"m{motor_identifier}{steps}" confirmation = self.send_command(command) @@ -738,5 +751,22 @@ class AutoTMController(ModuleController): logger.debug("Deleting position: %s", position) self.module.model.delete_saved_position(position) + def generate_mech_lut(self, start_frequency: str, stop_frequency: str, frequency_step: str) -> None: + """Generate a lookup table for the specified frequency range and voltage resolution. + + Args: + start_frequency (str): The start frequency in Hz. + stop_frequency (str): The stop frequency in Hz. + frequency_step (str): The frequency step in Hz. + """ + logger.debug("Generating mech LUT") + + # We create the lookup table + LUT = MechanicalLookupTable( + start_frequency, stop_frequency, frequency_step + ) + + + diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index fa590d5..10bc75a 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -240,7 +240,13 @@ class SavedPosition(): class TuningStepper(Stepper): TYPE = "Tuning" MAX_STEPS = 1e6 + BACKLASH_STEPS = 45 + def __init__(self) -> None: + super().__init__() + # Backlash stepper + self.last_direction = None + class MatchingStepper(Stepper): TYPE = "Matching" MAX_STEPS = 1e6 @@ -285,6 +291,7 @@ class MechanicalLookupTable(LookupTable): TYPE = "Mechanical" pass class AutoTMModel(ModuleModel): + available_devices_changed = pyqtSignal(list) serial_changed = pyqtSignal(QSerialPort) data_points_changed = pyqtSignal(list) @@ -309,6 +316,9 @@ class AutoTMModel(ModuleModel): self.saved_positions = [] + self.el_lut = None + self.mech_lut = None + @property def available_devices(self): return self._available_devices diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index bcdcf3f..2d5ad33 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -73,6 +73,15 @@ class AutoTMView(ModuleView): ) ) + # On clicking of the generateLUTButton call the generate_lut method + self._ui_form.generateLUTButton.clicked.connect( + lambda: self.module.controller.generate_mech_lut( + self._ui_form.startfrequencyBox.text(), + self._ui_form.stopfrequencyBox.text(), + self._ui_form.frequencystepBox.text(), + ) + ) + # On clicking of the viewLUTButton call the view_lut method self._ui_form.viewelLUTButton.clicked.connect(self.view_el_lut) @@ -351,6 +360,10 @@ class AutoTMView(ModuleView): def view_el_lut(self) -> None: """Creates a new Dialog that shows the currently active electrical LUT.""" logger.debug("View LUT") + if self.module.model.el_lut is None: + logger.debug("No LUT available") + self.add_error_text("No LUT available") + return self.lut_window = self.LutWindow(self.module) self.lut_window.show() From 039e9fe2d976b61100153e68bd432a1274ed6368 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 11 Dec 2023 08:03:02 +0100 Subject: [PATCH 16/31] Changed communication for serial connections to signal and slots. --- src/nqrduck_autotm/controller.py | 256 +++++++++++++++++++++++++------ src/nqrduck_autotm/model.py | 119 ++++++++++---- src/nqrduck_autotm/view.py | 15 +- 3 files changed, 306 insertions(+), 84 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 14e80a0..16c98c5 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -16,6 +16,20 @@ logger = logging.getLogger(__name__) class AutoTMController(ModuleController): BAUDRATE = 115200 + def on_loading(self): + """This method is called when the module is loaded. + It sets up the serial connection and connects the signals and slots. + """ + logger.debug("Setting up serial connection") + self.find_devices() + + # Connect signals + self.module.model.serial_data_received.connect(self.process_frequency_sweep_data) + self.module.model.serial_data_received.connect(self.process_measurement_data) + self.module.model.serial_data_received.connect(self.process_calibration_data) + self.module.model.serial_data_received.connect(self.print_info) + self.module.model.serial_data_received.connect(self.read_position_data) + @pyqtSlot(str, object) def process_signals(self, key: str, value: object) -> None: logger.debug("Received signal: %s", key) @@ -149,51 +163,60 @@ class AutoTMController(ModuleController): self.module.model.clear_data_points() self.module.view.create_frequency_sweep_spinner_dialog() - def process_frequency_sweep_data(self, text): + @pyqtSlot(str) + def process_frequency_sweep_data(self, text : str) -> None: """This method is called when data is received from the serial connection during a frequency sweep. It processes the data and adds it to the model. """ - 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 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) - def process_measurement_data(self): + @pyqtSlot(str) + def process_measurement_data(self, text : str) -> None: """This method is called when data is received from the serial connection during a measurement. It processes the data and adds it to the model. """ - logger.debug("Measurement finished") - self.module.model.measurement = S11Data( - self.module.model.data_points.copy() - ) - self.finish_frequency_sweep() + if self.module.model.active_calibration is None and text.startswith("r"): + logger.debug("Measurement finished") + self.module.model.measurement = S11Data( + self.module.model.data_points.copy() + ) + self.finish_frequency_sweep() - def process_calibration_data(self, calibration_type): + @pyqtSlot(str) + def process_calibration_data(self, text : str) -> None: """This method is called when data is received from the serial connection during a calibration. It processes the data and adds it to the model. Args: calibration_type (str): The type of calibration that is being performed. """ - logger.debug(f"{calibration_type.capitalize()} calibration finished") - setattr(self.module.model, f"{calibration_type}_calibration", - S11Data(self.module.model.data_points.copy())) - self.module.model.active_calibration = None - self.module.view.frequency_sweep_spinner.hide() + if text.startswith("r") and self.module.model.active_calibration in ["short", "open", "load"]: + calibration_type = self.module.model.active_calibration + logger.debug(f"{calibration_type.capitalize()} calibration finished") + setattr(self.module.model, f"{calibration_type}_calibration", + S11Data(self.module.model.data_points.copy())) + self.module.model.active_calibration = None + self.module.view.frequency_sweep_spinner.hide() - def process_voltage_sweep_result(self, text): + @pyqtSlot(str) + def process_voltage_sweep_result(self, text : str) -> None: """This method is called when data is received from the serial connection during a voltage sweep. It processes the data and adds it to the model. Args: text (str): The data received from the serial connection. """ - text = text[1:].split("t") - matching_voltage, tuning_voltage = map(float, text) - LUT = self.module.model.LUT - logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) - LUT.add_voltages(matching_voltage, tuning_voltage) - self.continue_or_finish_voltage_sweep(LUT) + if text.startswith("v"): + text = text[1:].split("t") + matching_voltage, tuning_voltage = map(float, text) + LUT = self.module.model.LUT + logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) + LUT.add_voltages(matching_voltage, tuning_voltage) + self.continue_or_finish_voltage_sweep(LUT) def finish_frequency_sweep(self): """This method is called when a frequency sweep is finished. @@ -244,37 +267,60 @@ class AutoTMController(ModuleController): self.module.view.add_info_text(f"Voltage sweep finished in {duration:.2f} seconds") self.module.nqrduck_signal.emit("LUT_finished", LUT) + @pyqtSlot(str) + def print_info(self, text : str) -> None: + """This method is called when data is received from the serial connection. + It prints the data to the info text box. + + Args: + text (str): The data received from the serial connection. + """ + if text.startswith("i"): + text = text[1:] + self.module.view.add_info_text(text) + elif text.startswith("e"): + text = text[1:] + self.module.view.add_error_text(text) + + @pyqtSlot(str) + def read_position_data(self, text : str) -> None: + """This method is called when data is received from the serial connection.""" + if text.startswith("p"): + # Format is pm + text = text[1:].split("m") + tuning_position, matching_position = map(int, text) + self.module.model.tuning_stepper.position = tuning_position + self.module.model.matching_stepper.position = matching_position + self.module.model.tuning_stepper.homed = True + self.module.model.matching_stepper.homed = True + logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) + self.module.view.on_active_stepper_changed() + def on_ready_read(self) -> None: """This method is called when data is received from the serial connection.""" serial = self.module.model.serial + if self.module.model.waiting_for_reflection: + logger.debug("Waiting for reflection data") + return + while serial.canReadLine(): text = serial.readLine().data().decode().rstrip("\r\n") - # logger.debug("Received data: %s", text) + logger.debug("Received data: %s", text) - if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible(): - self.process_frequency_sweep_data(text) - elif text.startswith("r"): - if self.module.model.active_calibration is None: - self.process_measurement_data() - elif self.module.model.active_calibration in ["short", "open", "load"]: - self.process_calibration_data(self.module.model.active_calibration) - elif text.startswith("i"): - self.module.view.add_info_text("ATM Info: " + text[1:]) - elif text.startswith("e"): - self.module.view.add_info_text("ATM Error: " + text[1:]) - elif text.startswith("v"): - self.process_voltage_sweep_result(text) - elif text.startswith("p"): - # Format is pm - text = text[1:].split("m") - tuning_position, matching_position = map(int, text) - self.module.model.tuning_stepper.position = tuning_position - self.module.model.matching_stepper.position = matching_position - self.module.model.tuning_stepper.homed = True - self.module.model.matching_stepper.homed = True - logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) - self.module.view.on_active_stepper_changed() + self.module.model.serial_data_received.emit(text) + def process_reflection_data(self, text): + """This method is called when data is received from the serial connection. + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. + """ + text = text[1:] + return_loss, phase = map(float, text.split("p")) + self.module.model.last_reflection = (return_loss, phase) + + ### Calibration Stuff ### def on_short_calibration( self, start_frequency: float, stop_frequency: float @@ -423,6 +469,8 @@ class AutoTMController(ModuleController): self.module.model.open_calibration = S11Data.from_json(data["open"]) self.module.model.load_calibration = S11Data.from_json(data["load"]) + ### Voltage Control ### + def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None: """This method is called when the set voltages button is pressed. It writes the specified tuning and matching voltage to the serial connection. @@ -471,7 +519,9 @@ class AutoTMController(ModuleController): # Emit nqrduck signal that T&M was successful self.module.nqrduck_signal.emit("confirm_tune_and_match", None) - def generate_lut( + ### Electrical Lookup Table ### + + def generate_electrical_lut( self, start_frequency: str, stop_frequency: str, @@ -613,6 +663,8 @@ class AutoTMController(ModuleController): logger.error("Could not send command. %s", e) self.module.view.add_error_text("Could not send command. %s" % e) + ### Stepper Motor Control ### + def homing(self) -> None: """This method is used to send the command 'h' to the atm system. This command is used to home the stepper motors of the atm system. @@ -693,6 +745,8 @@ class AutoTMController(ModuleController): confirmation = self.send_stepper_command(actual_steps, stepper) return confirmation + ### Position Saving and Loading ### + def load_positions(self, path : str) -> None: """Load the saved positions from a json file. @@ -751,7 +805,10 @@ class AutoTMController(ModuleController): logger.debug("Deleting position: %s", position) self.module.model.delete_saved_position(position) - def generate_mech_lut(self, start_frequency: str, stop_frequency: str, frequency_step: str) -> None: + + #### Mechanical tuning and matching #### + + def generate_mechanical_lut(self, start_frequency: str, stop_frequency: str, frequency_step: str) -> None: """Generate a lookup table for the specified frequency range and voltage resolution. Args: @@ -759,14 +816,111 @@ class AutoTMController(ModuleController): stop_frequency (str): The stop frequency in Hz. frequency_step (str): The frequency step in Hz. """ - logger.debug("Generating mech LUT") + try: + start_frequency = start_frequency.replace(",", ".") + stop_frequency = stop_frequency.replace(",", ".") + frequency_step = frequency_step.replace(",", ".") + start_frequency = float(start_frequency) + stop_frequency = float(stop_frequency) + frequency_step = float(frequency_step) + except ValueError: + error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be floats" + logger.error(error) + self.module.view.add_info_text(error) + return + + if ( + start_frequency < 0 + or stop_frequency < 0 + or frequency_step < 0 + ): + error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be positive" + logger.error(error) + self.module.view.add_info_text(error) + return + + if start_frequency > stop_frequency: + error = "Could not generate LUT. Start frequency must be smaller than stop frequency" + logger.error(error) + self.module.view.add_info_text(error) + return + + # - 0.1 is to prevent float errors + if frequency_step - 0.1 > (stop_frequency - start_frequency): + error = "Could not generate LUT. Frequency step must be smaller than the frequency range" + logger.error(error) + self.module.view.add_info_text(error) + return + + logger.debug( + "Generating LUT from %s MHz to %s MHz with a frequency step of %s MHz", + start_frequency, + stop_frequency, + frequency_step, + ) # We create the lookup table LUT = MechanicalLookupTable( start_frequency, stop_frequency, frequency_step ) + # Lock GUI + # self.module.view.create_mech_LUT_spinner_dialog() + + self.start_next_mechTM(LUT) + + + def start_next_mechTM(self, LUT): + """Start the next mechanical tuning and matching sweep.""" + + next_frequency = LUT.get_next_frequency() + LUT.started_frequency = next_frequency + logger.debug("Starting next mechanical tuning and matching:") + + # Now we vary the tuning capacitor position and matching capacitor position + # Step size tuner: + TUNER_STEP_SIZE = 10 + + # Step size matcher: + MATCHER_STEP_SIZE = 50 + + # We read the first reflection + reflection = self.read_reflection(next_frequency) + logger.debug("Reflection: %s", reflection) + + def read_reflection(self, frequency): + """Read the reflection at the specified frequency.""" + # We send the command to the atm system + self.module.model.waiting_for_reflection = True + + command = f"r{frequency}" + try: + + confirmation = self.send_command(command) + if confirmation: + if self.module.model.serial.waitForReadyRead(1000): + #if self.module.model.serial.canReadLine(): + text = self.module.model.serial.readLine().data().decode("utf-8") + if text: + logger.debug("Received reflection: %s", text) + #self.module.model.waiting_for_reflection = False + #return text + #else: + time.sleep(0.1) + else: + logger.error("Could not read reflection. No confirmation received") + self.module.view.add_error_text("Could not read reflection. No confirmation received") + self.module.model.waiting_for_reflection = False + return None + except Exception as e: + logger.error("Could not read reflection. %s", e) + self.module.view.add_error_text("Could not read reflection. %s" % e) + self.module.model.waiting_for_reflection = False + return None + + + diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 10bc75a..37efb78 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -177,33 +177,6 @@ class LookupTable: # This is the frequency at which the tuning and matching process was started self.started_frequency = None - - def is_incomplete(self) -> bool: - """This method returns True if the lookup table is incomplete, - i.e. if there are frequencies for which no the tuning or matching voltage is none. - - Returns: - bool: True if the lookup table is incomplete, False otherwise. - """ - return any( - [ - tuning_voltage is None or matching_voltage is None - for tuning_voltage, matching_voltage in self.data.values() - ] - ) - - def get_next_frequency(self) -> float: - """This method returns the next frequency for which the tuning and matching voltage is not yet set. - - Returns: - float: The next frequency for which the tuning and matching voltage is not yet set. - """ - - for frequency, (tuning_voltage, matching_voltage) in self.data.items(): - if tuning_voltage is None or matching_voltage is None: - return frequency - - return None def get_entry_number(self, frequency: float) -> int: """This method returns the entry number of the given frequency. @@ -286,10 +259,98 @@ class ElectricalLookupTable(LookupTable): entry_number = self.get_entry_number(frequency) key = list(self.data.keys())[entry_number] return self.data[key] + + def is_incomplete(self) -> bool: + """This method returns True if the lookup table is incomplete, + i.e. if there are frequencies for which no the tuning or matching voltage is none. + + Returns: + bool: True if the lookup table is incomplete, False otherwise. + """ + return any( + [ + tuning_voltage is None or matching_voltage is None + for tuning_voltage, matching_voltage in self.data.values() + ] + ) + + def get_next_frequency(self) -> float: + """This method returns the next frequency for which the tuning and matching voltage is not yet set. + + Returns: + float: The next frequency for which the tuning and matching voltage is not yet set. + """ + + for frequency, (tuning_voltage, matching_voltage) in self.data.items(): + if tuning_voltage is None or matching_voltage is None: + return frequency + + return None class MechanicalLookupTable(LookupTable): + # Hmm duplicate code TYPE = "Mechanical" - pass + + + def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None: + super().__init__(start_frequency, stop_frequency, frequency_step) + self.init_positions() + + def init_positions(self) -> None: + """Initialize the lookup table with default values.""" + for frequency in np.arange( + self.start_frequency, self.stop_frequency, self.frequency_step + ): + self.started_frequency = frequency + self.add_positions(None, None) + + def add_positions(self, tuning_position: int, matching_position: int) -> None: + """Add a tuning and matching position for the last started frequency to the lookup table. + + Args: + tuning_position (int): The tuning position for the given frequency. + matching_position (int): The matching position for the given frequency.""" + self.data[self.started_frequency] = (tuning_position, matching_position) + + def get_positions(self, frequency: float) -> tuple: + """Get the tuning and matching position for the given frequency. + + Args: + frequency (float): The frequency for which the tuning and matching position should be returned. + + Returns: + tuple: The tuning and matching position for the given frequency. + """ + entry_number = self.get_entry_number(frequency) + key = list(self.data.keys())[entry_number] + return self.data[key] + + def is_incomplete(self) -> bool: + """This method returns True if the lookup table is incomplete, + i.e. if there are frequencies for which no the tuning or matching position is none. + + Returns: + bool: True if the lookup table is incomplete, False otherwise. + """ + return any( + [ + tuning_position is None or matching_position is None + for tuning_position, matching_position in self.data.values() + ] + ) + + def get_next_frequency(self) -> float: + """This method returns the next frequency for which the tuning and matching position is not yet set. + + Returns: + float: The next frequency for which the tuning and matching position is not yet set. + """ + + for frequency, (tuning_position, matching_position) in self.data.items(): + if tuning_position is None or matching_position is None: + return frequency + + return None class AutoTMModel(ModuleModel): available_devices_changed = pyqtSignal(list) @@ -297,6 +358,7 @@ class AutoTMModel(ModuleModel): data_points_changed = pyqtSignal(list) active_stepper_changed = pyqtSignal(Stepper) saved_positions_changed = pyqtSignal(list) + serial_data_received = pyqtSignal(str) short_calibration_finished = pyqtSignal(S11Data) open_calibration_finished = pyqtSignal(S11Data) @@ -318,6 +380,7 @@ class AutoTMModel(ModuleModel): self.el_lut = None self.mech_lut = None + self.waiting_for_reflection = False @property def available_devices(self): diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 2d5ad33..c2932c8 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -64,18 +64,18 @@ class AutoTMView(ModuleView): ) ) - # On clicking of the generateLUTButton call the generate_lut method + # On clicking of the generateLUTButton call the generate_mechanical_lut method self._ui_form.generateLUTButton.clicked.connect( - lambda: self.module.controller.generate_lut( + lambda: self.module.controller.generate_electrical_lut( self._ui_form.startfrequencyBox.text(), self._ui_form.stopfrequencyBox.text(), self._ui_form.frequencystepBox.text(), ) ) - # On clicking of the generateLUTButton call the generate_lut method - self._ui_form.generateLUTButton.clicked.connect( - lambda: self.module.controller.generate_mech_lut( + # On clicking of the generateLUTButton call the generate_electrical_lut method + self._ui_form.mechLUTButton.clicked.connect( + lambda: self.module.controller.generate_mechanical_lut( self._ui_form.startfrequencyBox.text(), self._ui_form.stopfrequencyBox.text(), self._ui_form.frequencystepBox.text(), @@ -357,6 +357,11 @@ class AutoTMView(ModuleView): self.el_LUT_spinner = self.LoadingSpinner("Generating electrical LUT ...", self) self.el_LUT_spinner.show() + def create_mech_LUT_spinner_dialog(self) -> None: + """Creates a mechanical LUT spinner dialog.""" + self.mech_LUT_spinner = self.LoadingSpinner("Generating mechanical LUT ...", self) + self.mech_LUT_spinner.show() + def view_el_lut(self) -> None: """Creates a new Dialog that shows the currently active electrical LUT.""" logger.debug("View LUT") From a6ce0a7588d9b74530f6096ce9dca17cb7fc41b0 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 11 Dec 2023 09:04:26 +0100 Subject: [PATCH 17/31] Fixed bug where movement of steppers in quick succession didn't yield confirmation by the atm. --- src/nqrduck_autotm/controller.py | 121 ++++++++++++++++++++++--------- src/nqrduck_autotm/model.py | 2 +- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 16c98c5..c8b2a8f 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -1,4 +1,5 @@ import logging +import time import numpy as np import json import time @@ -29,6 +30,7 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.connect(self.process_calibration_data) self.module.model.serial_data_received.connect(self.print_info) self.module.model.serial_data_received.connect(self.read_position_data) + self.module.model.serial_data_received.connect(self.process_reflection_data) @pyqtSlot(str, object) def process_signals(self, key: str, value: object) -> None: @@ -299,9 +301,6 @@ class AutoTMController(ModuleController): def on_ready_read(self) -> None: """This method is called when data is received from the serial connection.""" serial = self.module.model.serial - if self.module.model.waiting_for_reflection: - logger.debug("Waiting for reflection data") - return while serial.canReadLine(): text = serial.readLine().data().decode().rstrip("\r\n") @@ -309,6 +308,7 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.emit(text) + @pyqtSlot(str) def process_reflection_data(self, text): """This method is called when data is received from the serial connection. It processes the data and adds it to the model. @@ -316,9 +316,10 @@ class AutoTMController(ModuleController): Args: text (str): The data received from the serial connection. """ - text = text[1:] - return_loss, phase = map(float, text.split("p")) - self.module.model.last_reflection = (return_loss, phase) + if text.startswith("m"): + text = text[1:] + return_loss, phase = map(float, text.split("p")) + self.module.model.last_reflection = (return_loss, phase) ### Calibration Stuff ### @@ -724,25 +725,49 @@ class AutoTMController(ModuleController): confirmation = self.send_command(command) return confirmation - def on_relative_move(self, steps: str, stepper : Stepper = None ) -> None: + def on_relative_move(self, steps: str, stepper: Stepper = None) -> None: """This method is called when the relative move button is pressed.""" + timeout_duration = 15 # timeout in seconds + start_time = time.time() + if stepper is None: stepper = self.module.model.active_stepper + stepper_position = stepper.position future_position = stepper.position + int(steps) if self.validate_position(future_position, stepper): confirmation = self.send_stepper_command(int(steps), stepper) # Convert the steps string to an integer + + while stepper_position == stepper.position: + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > timeout_duration: + logger.error("Relative move timed out") + break # or handle timeout differently + return confirmation - def on_absolute_move(self, steps: str, stepper : Stepper = None ) -> None: + def on_absolute_move(self, steps: str, stepper: Stepper = None) -> None: """This method is called when the absolute move button is pressed.""" + timeout_duration = 15 # timeout in seconds + start_time = time.time() + if stepper is None: stepper = self.module.model.active_stepper + stepper_position = stepper.position future_position = int(steps) if self.validate_position(future_position, stepper): actual_steps = self.calculate_steps_for_absolute_move(future_position, stepper) confirmation = self.send_stepper_command(actual_steps, stepper) + + while stepper_position == stepper.position: + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > timeout_duration: + logger.error("Absolute move timed out") + break # or handle timeout differently + return confirmation ### Position Saving and Loading ### @@ -877,50 +902,78 @@ class AutoTMController(ModuleController): LUT.started_frequency = next_frequency logger.debug("Starting next mechanical tuning and matching:") + def get_magnitude(reflection): + CENTER_POINT_MAGNITUDE = 900 # mV + MAGNITUDE_SLOPE = 30 # dB/mV + return (reflection[0] - CENTER_POINT_MAGNITUDE) / MAGNITUDE_SLOPE + # Now we vary the tuning capacitor position and matching capacitor position # Step size tuner: - TUNER_STEP_SIZE = 10 + TUNER_STEP_SIZE = 20 # Step size matcher: - MATCHER_STEP_SIZE = 50 + MATCHER_STEP_SIZE = 5 # We read the first reflection - reflection = self.read_reflection(next_frequency) - logger.debug("Reflection: %s", reflection) + last_reflection = self.read_reflection(next_frequency) + last_magnitude = get_magnitude(last_reflection) - def read_reflection(self, frequency): - """Read the reflection at the specified frequency.""" + # Now we tune and match our probe coil for every frequency + stepper = self.module.model.tuning_stepper + new_magnitude = 1e6 # Big + + while new_magnitude > last_magnitude: + # We move the stepper + last_magnitude = new_magnitude + + self.on_relative_move(TUNER_STEP_SIZE, stepper) + + # We read the reflection + new_magnitude = get_magnitude(self.read_reflection(next_frequency)) + + # We move the stepper back + self.on_relative_move(-TUNER_STEP_SIZE, stepper) + logger.debug("Tuning capacitor position: %s", stepper.position) + + def read_reflection(self, frequency) -> tuple: + """Starts a reflection measurement and reads the reflection at the specified frequency.""" # We send the command to the atm system - self.module.model.waiting_for_reflection = True - command = f"r{frequency}" try: - confirmation = self.send_command(command) if confirmation: - if self.module.model.serial.waitForReadyRead(1000): - #if self.module.model.serial.canReadLine(): - text = self.module.model.serial.readLine().data().decode("utf-8") - if text: - logger.debug("Received reflection: %s", text) - #self.module.model.waiting_for_reflection = False - #return text - #else: - time.sleep(0.1) + reflection = self.module.model.last_reflection + + # Set the timeout duration (e.g., 5 seconds) + timeout_duration = 5 + # Record the start time + start_time = time.time() + + # Wait for reflection data until the timeout is reached + while reflection is None: + # Check if the timeout has been reached + if time.time() - start_time > timeout_duration: + logger.error("Reading reflection timed out after %d seconds", timeout_duration) + self.module.view.add_error_text(f"Could not read reflection. Timed out after {timeout_duration} seconds") + return None + + # Refresh the reflection data + reflection = self.module.model.last_reflection + QApplication.processEvents() + + # Reset the reflection cache + self.module.model.last_reflection = None + + return reflection + else: logger.error("Could not read reflection. No confirmation received") self.module.view.add_error_text("Could not read reflection. No confirmation received") - self.module.model.waiting_for_reflection = False return None - + except Exception as e: logger.error("Could not read reflection. %s", e) - self.module.view.add_error_text("Could not read reflection. %s" % e) - self.module.model.waiting_for_reflection = False + self.module.view.add_error_text(f"Could not read reflection. {e}") return None - - - - diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 37efb78..48502c1 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -380,7 +380,7 @@ class AutoTMModel(ModuleModel): self.el_lut = None self.mech_lut = None - self.waiting_for_reflection = False + self.last_reflection = None @property def available_devices(self): From 3046b6c7d5fc1ab81c3643e8a1318a9f864f9c01 Mon Sep 17 00:00:00 2001 From: jupfi Date: Mon, 11 Dec 2023 09:23:37 +0100 Subject: [PATCH 18/31] Added voltage sweep signal. --- src/nqrduck_autotm/controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index c8b2a8f..d9c6457 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -28,6 +28,7 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.connect(self.process_frequency_sweep_data) self.module.model.serial_data_received.connect(self.process_measurement_data) self.module.model.serial_data_received.connect(self.process_calibration_data) + self.module.model.serial_data_received.connect(self.process_voltage_sweep_result) self.module.model.serial_data_received.connect(self.print_info) self.module.model.serial_data_received.connect(self.read_position_data) self.module.model.serial_data_received.connect(self.process_reflection_data) @@ -922,6 +923,8 @@ class AutoTMController(ModuleController): stepper = self.module.model.tuning_stepper new_magnitude = 1e6 # Big + # Probably also start a frequency sweep (?) + while new_magnitude > last_magnitude: # We move the stepper last_magnitude = new_magnitude From bdfe4e3e9c540afa0ec059eb68d3872587e06d39 Mon Sep 17 00:00:00 2001 From: jupfi Date: Tue, 12 Dec 2023 16:05:30 +0100 Subject: [PATCH 19/31] Implemented LUT creation for mechanical probe coils. --- src/nqrduck_autotm/controller.py | 81 ++++++++++++------- src/nqrduck_autotm/model.py | 6 ++ src/nqrduck_autotm/resources/autotm_widget.ui | 2 +- src/nqrduck_autotm/view.py | 27 ++++++- src/nqrduck_autotm/widget.py | 4 +- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index d9c6457..f947f55 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -32,6 +32,7 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.connect(self.print_info) self.module.model.serial_data_received.connect(self.read_position_data) self.module.model.serial_data_received.connect(self.process_reflection_data) + self.module.model.serial_data_received.connect(self.process_position_sweep_result) @pyqtSlot(str, object) def process_signals(self, key: str, value: object) -> None: @@ -216,7 +217,7 @@ class AutoTMController(ModuleController): if text.startswith("v"): text = text[1:].split("t") matching_voltage, tuning_voltage = map(float, text) - LUT = self.module.model.LUT + LUT = self.module.model.el_lut logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) LUT.add_voltages(matching_voltage, tuning_voltage) self.continue_or_finish_voltage_sweep(LUT) @@ -265,6 +266,7 @@ class AutoTMController(ModuleController): LUT (LookupTable): The lookup table that is being generated.""" logger.debug("Voltage sweep finished") self.module.view.el_LUT_spinner.hide() + self.module.model.LUT = LUT self.module.model.voltage_sweep_stop = time.time() duration = self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start self.module.view.add_info_text(f"Voltage sweep finished in {duration:.2f} seconds") @@ -596,7 +598,7 @@ class AutoTMController(ModuleController): confirmation = self.send_command(command) # If the command was send successfully, we set the LUT if confirmation: - self.module.model.LUT = LUT + self.module.model.el_lut = LUT self.module.view.create_el_LUT_spinner_dialog() def switch_to_preamp(self) -> None: @@ -674,6 +676,7 @@ class AutoTMController(ModuleController): logger.debug("Homing") self.send_command("h") self.module.model.tuning_stepper.last_direction = 1 + self.module.model.matching_stepper.last_direction = 1 @pyqtSlot(str) def on_stepper_changed(self, stepper: str) -> None: @@ -711,18 +714,19 @@ class AutoTMController(ModuleController): """Send a command to the stepper motor based on the number of steps.""" # Here we handle backlash of the tuner # Determine the direction of the current steps + backlash = 0 current_direction = np.sign(steps) # This will be -1,or 1 if stepper.TYPE == "Tuning": logger.debug("Stepper last direction: %s", stepper.last_direction) logger.debug("Current direction: %s", current_direction) if stepper.last_direction != current_direction: - steps = (abs(steps) + stepper.BACKLASH_STEPS) * current_direction + backlash = stepper.BACKLASH_STEPS * current_direction stepper.last_direction = current_direction logger.debug("Stepper last direction: %s", stepper.last_direction) motor_identifier = stepper.TYPE.lower()[0] - command = f"m{motor_identifier}{steps}" + command = f"m{motor_identifier}{steps},{backlash}" confirmation = self.send_command(command) return confirmation @@ -893,6 +897,8 @@ class AutoTMController(ModuleController): # Lock GUI # self.module.view.create_mech_LUT_spinner_dialog() + self.module.model.mech_lut = LUT + self.start_next_mechTM(LUT) @@ -903,41 +909,62 @@ class AutoTMController(ModuleController): LUT.started_frequency = next_frequency logger.debug("Starting next mechanical tuning and matching:") - def get_magnitude(reflection): - CENTER_POINT_MAGNITUDE = 900 # mV - MAGNITUDE_SLOPE = 30 # dB/mV - return (reflection[0] - CENTER_POINT_MAGNITUDE) / MAGNITUDE_SLOPE - # Now we vary the tuning capacitor position and matching capacitor position # Step size tuner: TUNER_STEP_SIZE = 20 - # Step size matcher: - MATCHER_STEP_SIZE = 5 + MATCHER_STEP_SIZE = 50 - # We read the first reflection - last_reflection = self.read_reflection(next_frequency) - last_magnitude = get_magnitude(last_reflection) + TUNING_RANGE = 60 + MATCHING_RANGE = 500 - # Now we tune and match our probe coil for every frequency - stepper = self.module.model.tuning_stepper - new_magnitude = 1e6 # Big + tuning_backlash = 45 + # I'm not sure about this value ... + matching_backlash = 0 - # Probably also start a frequency sweep (?) + # Command for the position sweep: pt,,,m,,," + tuning_last_direction = self.module.model.tuning_stepper.last_direction + matching_last_direction = self.module.model.matching_stepper.last_direction + command = f"p{next_frequency}t{TUNING_RANGE},{TUNER_STEP_SIZE},{tuning_backlash},{tuning_last_direction}m{MATCHING_RANGE},{MATCHER_STEP_SIZE},{matching_backlash},{matching_last_direction}" - while new_magnitude > last_magnitude: - # We move the stepper - last_magnitude = new_magnitude + confirmation = self.send_command(command) - self.on_relative_move(TUNER_STEP_SIZE, stepper) + @pyqtSlot(str) + def process_position_sweep_result(self, text): + if text.startswith("z"): + text = text[1:] + # Format is z,m, + text = text.split("m") + tuning_position, tuning_last_direction = map(int, text[0].split(",")) + matching_position, matching_last_direction = map(int, text[1].split(",")) - # We read the reflection - new_magnitude = get_magnitude(self.read_reflection(next_frequency)) + # Keep backlash compensation consistent + self.module.model.tuning_stepper.last_direction = tuning_last_direction + self.module.model.matching_stepper.last_direction = matching_last_direction - # We move the stepper back - self.on_relative_move(-TUNER_STEP_SIZE, stepper) - logger.debug("Tuning capacitor position: %s", stepper.position) + logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) + LUT = self.module.model.mech_lut + logger.debug("Received position sweep result: %s %s", matching_position, tuning_position) + LUT.add_positions(tuning_position, matching_position) + self.continue_or_finish_position_sweep(LUT) + + def continue_or_finish_position_sweep(self, LUT): + """Continue or finish the position sweep.""" + if LUT.is_incomplete(): + self.start_next_mechTM(LUT) + else: + self.finish_position_sweep(LUT) + + def finish_position_sweep(self, LUT): + """Finish the position sweep.""" + logger.debug("Finished position sweep") + self.module.model.mech_lut = LUT + self.module.model.LUT = LUT + # self.module.view.create_mech_LUT_spinner_dialog() + + + # This method isn't used anymore but it might be useful in the future so I'll keep it here def read_reflection(self, frequency) -> tuple: """Starts a reflection measurement and reads the reflection at the specified frequency.""" # We send the command to the atm system diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 48502c1..49e3e2a 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -224,6 +224,10 @@ class MatchingStepper(Stepper): TYPE = "Matching" MAX_STEPS = 1e6 + def __init__(self) -> None: + super().__init__() + self.last_direction = None + class ElectricalLookupTable(LookupTable): TYPE = "Electrical" @@ -380,6 +384,8 @@ class AutoTMModel(ModuleModel): self.el_lut = None self.mech_lut = None + self.LUT = None + self.last_reflection = None @property diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 753fb72..786475c 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -236,7 +236,7 @@ 0 - 50000 + 1000000 500 diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index c2932c8..fafae94 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -85,6 +85,8 @@ class AutoTMView(ModuleView): # On clicking of the viewLUTButton call the view_lut method self._ui_form.viewelLUTButton.clicked.connect(self.view_el_lut) + self._ui_form.viewmechLUTButton.clicked.connect(self.view_mech_lut) + # On clicking of the setvoltagesButton call the set_voltages method self._ui_form.setvoltagesButton.clicked.connect( lambda: self.module.controller.set_voltages( @@ -372,6 +374,16 @@ class AutoTMView(ModuleView): self.lut_window = self.LutWindow(self.module) self.lut_window.show() + def view_mech_lut(self) -> None: + """Creates a new Dialog that shows the currently active mechanical LUT.""" + logger.debug("View mechanical LUT") + if self.module.model.mech_lut is None: + logger.debug("No LUT available") + self.add_error_text("No LUT available") + return + self.lut_window = self.LutWindow(self.module) + self.lut_window.show() + class StepperSavedPositionsWindow(QDialog): def __init__(self, module, parent=None): super().__init__(parent) @@ -576,13 +588,20 @@ class AutoTMView(ModuleView): # Add vertical main layout main_layout = QVBoxLayout() + LUT = self.module.model.LUT + # Create table widget self.table_widget = QTableWidget() self.table_widget.setColumnCount(3) - self.table_widget.setHorizontalHeaderLabels( - ["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"] - ) - LUT = self.module.model.LUT + if LUT.TYPE == "Mechanical": + self.table_widget.setHorizontalHeaderLabels( + ["Frequency (MHz)", "Tuning Position", "Matching Position"] + ) + elif LUT.TYPE == "Electrical": + self.table_widget.setHorizontalHeaderLabels( + ["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"] + ) + for row, frequency in enumerate(LUT.data.keys()): self.table_widget.insertRow(row) self.table_widget.setItem(row, 0, QTableWidgetItem(str(frequency))) diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index a32d211..3b0fb83 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -1,4 +1,4 @@ -# Form implementation generated from reading ui file '../Modules/nqrduck-autotm/src/nqrduck_autotm/resources/autotm_widget.ui' +# Form implementation generated from reading ui file 'Modules/nqrduck-autotm/src/nqrduck_autotm/resources/autotm_widget.ui' # # Created by: PyQt6 UI code generator 6.5.1 # @@ -130,7 +130,7 @@ class Ui_Form(object): self.gridLayout_4.addWidget(self.label_20, 6, 0, 1, 1) self.stepsizeBox = QtWidgets.QSpinBox(parent=self.mechTab) self.stepsizeBox.setMinimum(0) - self.stepsizeBox.setMaximum(50000) + self.stepsizeBox.setMaximum(1000000) self.stepsizeBox.setProperty("value", 500) self.stepsizeBox.setObjectName("stepsizeBox") self.gridLayout_4.addWidget(self.stepsizeBox, 3, 1, 1, 1) From 7dbf01017f3452ecf31bd5eb78a4357a05e4cc07 Mon Sep 17 00:00:00 2001 From: jupfi Date: Tue, 12 Dec 2023 16:24:45 +0100 Subject: [PATCH 20/31] Implemented testing of LUT. --- src/nqrduck_autotm/controller.py | 10 ++++++++ src/nqrduck_autotm/view.py | 39 ++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index f947f55..5c31154 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -963,6 +963,16 @@ class AutoTMController(ModuleController): self.module.model.LUT = LUT # self.module.view.create_mech_LUT_spinner_dialog() + def go_to_position(self, tuning_position : int, matching_position : int) -> None: + """Go to the specified position. + + Args: + position (SavedPosition): The position to go to. + """ + confirmation = self.on_absolute_move(tuning_position, self.module.model.tuning_stepper) + if confirmation: + self.on_absolute_move(matching_position, self.module.model.matching_stepper) + # This method isn't used anymore but it might be useful in the future so I'll keep it here def read_reflection(self, frequency) -> tuple: diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index fafae94..32a71d9 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -585,6 +585,9 @@ class AutoTMView(ModuleView): self.setParent(parent) self.setWindowTitle("LUT") + # Set size + self.resize(800, 800) + # Add vertical main layout main_layout = QVBoxLayout() @@ -592,7 +595,12 @@ class AutoTMView(ModuleView): # Create table widget self.table_widget = QTableWidget() - self.table_widget.setColumnCount(3) + self.table_widget.setColumnCount(4) + self.table_widget.setColumnWidth(0, 150) + self.table_widget.setColumnWidth(1, 200) + self.table_widget.setColumnWidth(2, 200) + self.table_widget.setColumnWidth(3, 100) + if LUT.TYPE == "Mechanical": self.table_widget.setHorizontalHeaderLabels( ["Frequency (MHz)", "Tuning Position", "Matching Position"] @@ -612,14 +620,31 @@ class AutoTMView(ModuleView): row, 2, QTableWidgetItem(str(LUT.data[frequency][1])) ) + # Button to test the specific entry in the LUT + test_button = QPushButton("Test") + # For electrical probe coils the matching voltage is the first entry in the LUT + if LUT.TYPE == "Electrical": + tuning_voltage = str(LUT.data[frequency][1]) + matching_voltage = str(LUT.data[frequency][0]) + test_button.clicked.connect( + lambda _, tuning_voltage=tuning_voltage, matching_voltage=matching_voltage: self.module.controller.set_voltages( + tuning_voltage, matching_voltage + ) + ) + # For mechanical probe coils the tuning voltage is the first entry in the LUT + elif LUT.TYPE == "Mechanical": + tuning_position = str(LUT.data[frequency][0]) + matching_position = str(LUT.data[frequency][1]) + test_button.clicked.connect( + lambda _, tuning_position=tuning_position, matching_position=matching_position: self.module.controller.go_to_position( + tuning_position, matching_position + ) + ) + + self.table_widget.setCellWidget(row, 3, test_button) + # Add table widget to main layout main_layout.addWidget(self.table_widget) - - # Add Test LUT button - test_lut_button = QPushButton("Test LUT") - test_lut_button.clicked.connect(self.test_lut) - main_layout.addWidget(test_lut_button) - self.setLayout(main_layout) def test_lut(self): From 4bd080e7ac8ffb9239b86e25b0717340d409bc4f Mon Sep 17 00:00:00 2001 From: jupfi Date: Tue, 12 Dec 2023 16:57:50 +0100 Subject: [PATCH 21/31] Added spinner to lock gui. --- src/nqrduck_autotm/controller.py | 11 ++++++++--- src/nqrduck_autotm/model.py | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 5c31154..642b400 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -895,7 +895,7 @@ class AutoTMController(ModuleController): ) # Lock GUI - # self.module.view.create_mech_LUT_spinner_dialog() + self.module.view.create_mech_LUT_spinner_dialog() self.module.model.mech_lut = LUT @@ -918,7 +918,7 @@ class AutoTMController(ModuleController): TUNING_RANGE = 60 MATCHING_RANGE = 500 - tuning_backlash = 45 + tuning_backlash = self.module.model.tuning_stepper.BACKLASH_STEPS # I'm not sure about this value ... matching_backlash = 0 @@ -942,6 +942,11 @@ class AutoTMController(ModuleController): self.module.model.tuning_stepper.last_direction = tuning_last_direction self.module.model.matching_stepper.last_direction = matching_last_direction + # Update the positions + self.module.model.tuning_stepper.position = tuning_position + self.module.model.matching_stepper.position = matching_position + self.module.view.on_active_stepper_changed() + logger.debug("Tuning position: %s, Matching position: %s", tuning_position, matching_position) LUT = self.module.model.mech_lut @@ -961,7 +966,7 @@ class AutoTMController(ModuleController): logger.debug("Finished position sweep") self.module.model.mech_lut = LUT self.module.model.LUT = LUT - # self.module.view.create_mech_LUT_spinner_dialog() + self.module.view.el_LUT_spinner.hide() def go_to_position(self, tuning_position : int, matching_position : int) -> None: """Go to the specified position. diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 49e3e2a..07250c3 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -213,7 +213,7 @@ class SavedPosition(): class TuningStepper(Stepper): TYPE = "Tuning" MAX_STEPS = 1e6 - BACKLASH_STEPS = 45 + BACKLASH_STEPS = 60 def __init__(self) -> None: super().__init__() @@ -224,6 +224,8 @@ class MatchingStepper(Stepper): TYPE = "Matching" MAX_STEPS = 1e6 + BACKLASH_STEPS = 0 + def __init__(self) -> None: super().__init__() self.last_direction = None @@ -385,7 +387,7 @@ class AutoTMModel(ModuleModel): self.el_lut = None self.mech_lut = None self.LUT = None - + self.last_reflection = None @property From d01af53751450918587d611f02d1bfa3ec3ab6a3 Mon Sep 17 00:00:00 2001 From: jupfi Date: Tue, 12 Dec 2023 17:12:03 +0100 Subject: [PATCH 22/31] Upsi fixed bug. --- src/nqrduck_autotm/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 642b400..d83c233 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -966,7 +966,7 @@ class AutoTMController(ModuleController): logger.debug("Finished position sweep") self.module.model.mech_lut = LUT self.module.model.LUT = LUT - self.module.view.el_LUT_spinner.hide() + self.module.view.mech_LUT_spinner.hide() def go_to_position(self, tuning_position : int, matching_position : int) -> None: """Go to the specified position. From 7b9f680b96071145d97d1321453a6f0af1a7b603 Mon Sep 17 00:00:00 2001 From: jupfi Date: Tue, 12 Dec 2023 18:32:08 +0100 Subject: [PATCH 23/31] Added reflection measurement to tune_and_match confirm signal. --- src/nqrduck_autotm/controller.py | 30 ++++++++++++++++++++++++------ src/nqrduck_autotm/model.py | 4 ++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index d83c233..4e007aa 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -48,10 +48,18 @@ class AutoTMController(ModuleController): return elif self.module.model.LUT.TYPE == "Electrical": tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) - self.set_voltages(str(tunning_voltage), str(matching_voltage)) + confirmation = self.set_voltages(str(tunning_voltage), str(matching_voltage)) + if confirmation: + reflection = self.read_reflection(frequency) + self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) elif self.module.model.LUT.TYPE == "Mechanical": - pass + tuning_position, matching_position = self.module.model.LUT.get_positions(frequency) + self.go_to_position(tuning_position, matching_position) + + reflection = self.read_reflection(frequency) + + self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) def find_devices(self) -> None: """Scan for available serial devices and add them to the model as available devices.""" @@ -520,8 +528,10 @@ class AutoTMController(ModuleController): confirmation = self.send_command(command) if confirmation: logger.debug("Voltages set successfully") + return True # Emit nqrduck signal that T&M was successful - self.module.nqrduck_signal.emit("confirm_tune_and_match", None) + # Maybe we measure the reflection first so we don't damage the PA lol + # The broadband module can then decide what to do with the signal ### Electrical Lookup Table ### @@ -967,6 +977,7 @@ class AutoTMController(ModuleController): self.module.model.mech_lut = LUT self.module.model.LUT = LUT self.module.view.mech_LUT_spinner.hide() + self.module.nqrduck_signal.emit("LUT_finished", LUT) def go_to_position(self, tuning_position : int, matching_position : int) -> None: """Go to the specified position. @@ -976,11 +987,13 @@ class AutoTMController(ModuleController): """ confirmation = self.on_absolute_move(tuning_position, self.module.model.tuning_stepper) if confirmation: - self.on_absolute_move(matching_position, self.module.model.matching_stepper) + confirmation = self.on_absolute_move(matching_position, self.module.model.matching_stepper) + if confirmation: + return True # This method isn't used anymore but it might be useful in the future so I'll keep it here - def read_reflection(self, frequency) -> tuple: + def read_reflection(self, frequency) -> float: """Starts a reflection measurement and reads the reflection at the specified frequency.""" # We send the command to the atm system command = f"r{frequency}" @@ -1009,7 +1022,12 @@ class AutoTMController(ModuleController): # Reset the reflection cache self.module.model.last_reflection = None - return reflection + magnitude = reflection[0] + CENTER_POINT_MAGNITUDE = 900 # mV + MAGNITUDE_SLOPE = 30 # dB/mV + magnitude = (magnitude - CENTER_POINT_MAGNITUDE) / MAGNITUDE_SLOPE + + return -magnitude else: logger.error("Could not read reflection. No confirmation received") diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 07250c3..f9571c4 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -240,7 +240,7 @@ class ElectricalLookupTable(LookupTable): def init_voltages(self) -> None: """Initialize the lookup table with default values.""" for frequency in np.arange( - self.start_frequency, self.stop_frequency, self.frequency_step + self.start_frequency, self.stop_frequency + self.frequency_step, self.frequency_step ): self.started_frequency = frequency self.add_voltages(None, None) @@ -305,7 +305,7 @@ class MechanicalLookupTable(LookupTable): def init_positions(self) -> None: """Initialize the lookup table with default values.""" for frequency in np.arange( - self.start_frequency, self.stop_frequency, self.frequency_step + self.start_frequency, self.stop_frequency + self.frequency_step, self.frequency_step ): self.started_frequency = frequency self.add_positions(None, None) From 6c70c6360ff9b6a82712234375e49c23cf381470 Mon Sep 17 00:00:00 2001 From: jupfi Date: Thu, 14 Dec 2023 19:24:29 +0100 Subject: [PATCH 24/31] Added automatic switching of singal path. --- src/nqrduck_autotm/controller.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 4e007aa..0887adb 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -50,14 +50,20 @@ class AutoTMController(ModuleController): tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) confirmation = self.set_voltages(str(tunning_voltage), str(matching_voltage)) if confirmation: + # We need to change the signal pathway to preamp to measure the reflection + self.switch_to_atm() reflection = self.read_reflection(frequency) + # We need to change the signal pathway back to atm to perform a measurement + self.switch_to_preamp() self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) elif self.module.model.LUT.TYPE == "Mechanical": tuning_position, matching_position = self.module.model.LUT.get_positions(frequency) self.go_to_position(tuning_position, matching_position) - + # Switch to atm to measure the reflection reflection = self.read_reflection(frequency) + # Switch back to preamp to perform a measurement + self.switch_to_preamp() self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) @@ -107,6 +113,10 @@ class AutoTMController(ModuleController): self.module.model.serial = serial logger.debug("Connected to device %s", device) + + # On opening of the command we set the switch position to atm + self.switch_to_atm() + except Exception as e: logger.error("Could not connect to device %s: %s", device, e) @@ -527,11 +537,11 @@ class AutoTMController(ModuleController): command = "v%sv%s" % (matching_voltage, tuning_voltage) confirmation = self.send_command(command) if confirmation: + TIMEOUT = 5 # seconds + start_time = time.time() + logger.debug("Voltages set successfully") return True - # Emit nqrduck signal that T&M was successful - # Maybe we measure the reflection first so we don't damage the PA lol - # The broadband module can then decide what to do with the signal ### Electrical Lookup Table ### @@ -921,11 +931,11 @@ class AutoTMController(ModuleController): # Now we vary the tuning capacitor position and matching capacitor position # Step size tuner: - TUNER_STEP_SIZE = 20 + TUNER_STEP_SIZE = 10 # Step size matcher: MATCHER_STEP_SIZE = 50 - TUNING_RANGE = 60 + TUNING_RANGE = 40 MATCHING_RANGE = 500 tuning_backlash = self.module.model.tuning_stepper.BACKLASH_STEPS From 5457b40a65951886e5f6e7746a721444cbf36be2 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 15 Dec 2023 15:26:49 +0100 Subject: [PATCH 25/31] Added tuning and matching voltages to model and updated the values accordingly. Added an option for voltage sweep with predifined voltages. --- src/nqrduck_autotm/controller.py | 49 ++++++++++++--- src/nqrduck_autotm/model.py | 3 + src/nqrduck_autotm/resources/autotm_widget.ui | 63 ++++++++++--------- src/nqrduck_autotm/widget.py | 48 +++++++------- 4 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 4e007aa..ea163a2 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -7,6 +7,7 @@ from serial.tools.list_ports import comports from PyQt6.QtTest import QTest from PyQt6 import QtSerialPort from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt +from PyQt6.QtCore import QTimer from PyQt6.QtWidgets import QApplication from nqrduck.module.module_controller import ModuleController from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition, Stepper @@ -49,6 +50,7 @@ class AutoTMController(ModuleController): elif self.module.model.LUT.TYPE == "Electrical": tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) confirmation = self.set_voltages(str(tunning_voltage), str(matching_voltage)) + # We need to waitfor the voltages to be set it would be nicer to have this confirmed by the ATM if confirmation: reflection = self.read_reflection(frequency) self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) @@ -226,9 +228,14 @@ class AutoTMController(ModuleController): text = text[1:].split("t") matching_voltage, tuning_voltage = map(float, text) LUT = self.module.model.el_lut - logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) - LUT.add_voltages(matching_voltage, tuning_voltage) - self.continue_or_finish_voltage_sweep(LUT) + if LUT is not None: + if LUT.is_incomplete(): + logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) + LUT.add_voltages(matching_voltage, tuning_voltage) + self.continue_or_finish_voltage_sweep(LUT) + + self.module.model.tuning_voltage = tuning_voltage + self.module.model.matching_voltage = matching_voltage def finish_frequency_sweep(self): """This method is called when a frequency sweep is finished. @@ -261,7 +268,14 @@ class AutoTMController(ModuleController): LUT (LookupTable): The lookup table that is being generated. """ next_frequency = LUT.get_next_frequency() - command = f"s{next_frequency}" + # We write the first command to the serial connection + if self.module.view._ui_form.prevVoltagecheckBox.isChecked(): + # Command format is soo + # We use the currently set voltages + command = "s%so%so%s" % (next_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage) + else: + command = "s%s" % (next_frequency) + LUT.started_frequency = next_frequency logger.debug("Starting next voltage sweep: %s", command) self.send_command(command) @@ -493,6 +507,8 @@ class AutoTMController(ModuleController): """ logger.debug("Setting voltages") MAX_VOLTAGE = 5 # V + timeout_duration = 15 # timeout in seconds + try: tuning_voltage = tuning_voltage.replace(",", ".") matching_voltage = matching_voltage.replace(",", ".") @@ -525,13 +541,20 @@ class AutoTMController(ModuleController): ) command = "v%sv%s" % (matching_voltage, tuning_voltage) + + start_time = time.time() + confirmation = self.send_command(command) if confirmation: - logger.debug("Voltages set successfully") - return True - # Emit nqrduck signal that T&M was successful - # Maybe we measure the reflection first so we don't damage the PA lol - # The broadband module can then decide what to do with the signal + while matching_voltage == self.module.model.matching_voltage: + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > timeout_duration: + logger.error("Absolute move timed out") + break # or handle timeout differently + + logger.debug("Voltages set successfully") + return confirmation ### Electrical Lookup Table ### @@ -601,7 +624,13 @@ class AutoTMController(ModuleController): LUT.started_frequency = start_frequency # We write the first command to the serial connection - command = "s%s" % (start_frequency) + if self.module.view._ui_form.prevVoltagecheckBox.isChecked(): + # Command format is soo + # We use the currently set voltages + logger.debug("Starting preset Voltage sweep with voltage Tuning: %s V and Matching: %s V", self.module.model.tuning_voltage, self.module.model.matching_voltage) + command = "s%so%so%s" % (start_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage) + else: + command = "s%s" % (start_frequency) # For timing of the voltage sweep self.module.model.voltage_sweep_start = time.time() diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index f9571c4..391e0ff 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -390,6 +390,9 @@ class AutoTMModel(ModuleModel): self.last_reflection = None + self.tuning_voltage = None + self.matching_voltage = None + @property def available_devices(self): return self._available_devices diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index 786475c..dcef9b2 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -152,7 +152,7 @@ - 0 + 1 @@ -313,11 +313,17 @@ Electrical - - - + + + + + + 75 + true + + - Generate LUT + Set Voltages: @@ -328,6 +334,16 @@ + + + + + + + Generate LUT + + + @@ -344,27 +360,7 @@ - - - - Voltage Matching - - - - - - - - 75 - true - - - - Set Voltages: - - - - + View LUT @@ -378,8 +374,19 @@ - - + + + + Voltage Matching + + + + + + + Start from previous Voltage + + diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 3b0fb83..18fe98a 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -164,12 +164,22 @@ class Ui_Form(object): self.elecTab.setObjectName("elecTab") self.gridLayout_3 = QtWidgets.QGridLayout(self.elecTab) self.gridLayout_3.setObjectName("gridLayout_3") - self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) - self.generateLUTButton.setObjectName("generateLUTButton") - self.gridLayout_3.addWidget(self.generateLUTButton, 8, 0, 1, 2) + self.label_9 = QtWidgets.QLabel(parent=self.elecTab) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_9.setFont(font) + self.label_9.setObjectName("label_9") + self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1) self.label_2 = QtWidgets.QLabel(parent=self.elecTab) self.label_2.setObjectName("label_2") self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1) + self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) + self.matchingBox.setObjectName("matchingBox") + self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1) + self.generateLUTButton = QtWidgets.QPushButton(parent=self.elecTab) + self.generateLUTButton.setObjectName("generateLUTButton") + self.gridLayout_3.addWidget(self.generateLUTButton, 8, 0, 1, 2) self.tuningBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) self.tuningBox.setObjectName("tuningBox") self.gridLayout_3.addWidget(self.tuningBox, 1, 1, 1, 1) @@ -180,25 +190,18 @@ class Ui_Form(object): self.label_11.setFont(font) self.label_11.setObjectName("label_11") self.gridLayout_3.addWidget(self.label_11, 4, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(parent=self.elecTab) - self.label_3.setObjectName("label_3") - self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) - self.label_9 = QtWidgets.QLabel(parent=self.elecTab) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_9.setFont(font) - self.label_9.setObjectName("label_9") - self.gridLayout_3.addWidget(self.label_9, 0, 0, 1, 1) self.viewelLUTButton = QtWidgets.QPushButton(parent=self.elecTab) self.viewelLUTButton.setObjectName("viewelLUTButton") - self.gridLayout_3.addWidget(self.viewelLUTButton, 9, 0, 1, 2) + self.gridLayout_3.addWidget(self.viewelLUTButton, 10, 0, 1, 2) self.setvoltagesButton = QtWidgets.QPushButton(parent=self.elecTab) self.setvoltagesButton.setObjectName("setvoltagesButton") self.gridLayout_3.addWidget(self.setvoltagesButton, 3, 0, 1, 2) - self.matchingBox = QtWidgets.QDoubleSpinBox(parent=self.elecTab) - self.matchingBox.setObjectName("matchingBox") - self.gridLayout_3.addWidget(self.matchingBox, 2, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=self.elecTab) + self.label_3.setObjectName("label_3") + self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1) + self.prevVoltagecheckBox = QtWidgets.QCheckBox(parent=self.elecTab) + self.prevVoltagecheckBox.setObjectName("prevVoltagecheckBox") + self.gridLayout_3.addWidget(self.prevVoltagecheckBox, 9, 0, 1, 1) self.typeTab.addTab(self.elecTab, "") self.verticalLayout_2.addWidget(self.typeTab) self.rfswitchLabel = QtWidgets.QLabel(parent=Form) @@ -284,7 +287,7 @@ class Ui_Form(object): self.horizontalLayout_2.setStretch(1, 1) self.retranslateUi(Form) - self.typeTab.setCurrentIndex(0) + self.typeTab.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): @@ -316,13 +319,14 @@ class Ui_Form(object): self.mechLUTButton.setText(_translate("Form", "Generate LUT")) self.viewmechLUTButton.setText(_translate("Form", "View LUT")) self.typeTab.setTabText(self.typeTab.indexOf(self.mechTab), _translate("Form", "Mechanical")) - self.generateLUTButton.setText(_translate("Form", "Generate LUT")) - self.label_2.setText(_translate("Form", "Voltage Tuning")) - self.label_11.setText(_translate("Form", "Generate LUT:")) - self.label_3.setText(_translate("Form", "Voltage Matching")) self.label_9.setText(_translate("Form", "Set Voltages:")) + self.label_2.setText(_translate("Form", "Voltage Tuning")) + self.generateLUTButton.setText(_translate("Form", "Generate LUT")) + self.label_11.setText(_translate("Form", "Generate LUT:")) self.viewelLUTButton.setText(_translate("Form", "View LUT")) self.setvoltagesButton.setText(_translate("Form", "Set Voltages")) + self.label_3.setText(_translate("Form", "Voltage Matching")) + self.prevVoltagecheckBox.setText(_translate("Form", "Start from previous Voltage")) self.typeTab.setTabText(self.typeTab.indexOf(self.elecTab), _translate("Form", "Electrical")) self.rfswitchLabel.setText(_translate("Form", "RF Switch:")) self.switchATMButton.setText(_translate("Form", "ATM")) From 6fef828edc46b34caaa19de7194b3bce3323db2d Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 15 Dec 2023 16:18:46 +0100 Subject: [PATCH 26/31] Added automatic signal path switching. --- src/nqrduck_autotm/controller.py | 57 ++++++++++++++++++++++++++++++-- src/nqrduck_autotm/model.py | 3 ++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 97f93a8..e7e402b 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -34,6 +34,7 @@ class AutoTMController(ModuleController): self.module.model.serial_data_received.connect(self.read_position_data) self.module.model.serial_data_received.connect(self.process_reflection_data) self.module.model.serial_data_received.connect(self.process_position_sweep_result) + self.module.model.serial_data_received.connect(self.process_signalpath_data) @pyqtSlot(str, object) def process_signals(self, key: str, value: object) -> None: @@ -50,7 +51,6 @@ class AutoTMController(ModuleController): elif self.module.model.LUT.TYPE == "Electrical": tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) confirmation = self.set_voltages(str(tunning_voltage), str(matching_voltage)) - # We need to waitfor the voltages to be set it would be nicer to have this confirmed by the ATM if confirmation: # We need to change the signal pathway to preamp to measure the reflection self.switch_to_atm() @@ -62,6 +62,7 @@ class AutoTMController(ModuleController): elif self.module.model.LUT.TYPE == "Mechanical": tuning_position, matching_position = self.module.model.LUT.get_positions(frequency) self.go_to_position(tuning_position, matching_position) + self.switch_to_atm() # Switch to atm to measure the reflection reflection = self.read_reflection(frequency) # Switch back to preamp to perform a measurement @@ -550,17 +551,21 @@ class AutoTMController(ModuleController): matching_voltage, ) + if tuning_voltage == self.module.model.tuning_voltage and matching_voltage == self.module.model.matching_voltage: + logger.debug("Voltages already set") + return + command = "v%sv%s" % (matching_voltage, tuning_voltage) start_time = time.time() confirmation = self.send_command(command) if confirmation: - while matching_voltage == self.module.model.matching_voltage: + while matching_voltage == self.module.model.matching_voltage and tuning_voltage == self.module.model.tuning_voltage: QApplication.processEvents() # Check for timeout if time.time() - start_time > timeout_duration: - logger.error("Absolute move timed out") + logger.error("Voltage setting timed out") break # or handle timeout differently logger.debug("Voltages set successfully") @@ -626,6 +631,9 @@ class AutoTMController(ModuleController): frequency_step, ) + self.switch_to_atm() + # self.set_voltages("0", "0") + # We create the lookup table LUT = ElectricalLookupTable( start_frequency, stop_frequency, frequency_step @@ -654,16 +662,56 @@ class AutoTMController(ModuleController): """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 is the mode for either NQR or NMR measurements or if on wants to check the tuning of the probe coil on a network analyzer. """ + if self.module.model.signal_path == "preamp": + logger.debug("Already in preamp") + return + + TIMEOUT = 1 # s logger.debug("Switching to preamp") self.send_command("cp") + start_time = time.time() + while self.module.model.signal_path != "preamp": + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > TIMEOUT: + logger.error("Switching to preamp timed out") + break + 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. In this state the atm system can be used to measure the reflection coefficient of the probecoils. """ + if self.module.model.signal_path == "atm": + logger.debug("Already in atm mode") + return + + TIMEOUT = 1 # s logger.debug("Switching to atm") self.send_command("ca") + start_time = time.time() + while self.module.model.signal_path != "atm": + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > TIMEOUT: + logger.error("Switching to atm timed out") + break + + def process_signalpath_data(self, text : str) -> None: + """This method is called when data is received from the serial connection. + It processes the data and adds it to the model. + + Args: + text (str): The data received from the serial connection. + """ + if text.startswith("c"): + text = text[1:] + if text == "p": + self.module.model.signal_path = "preamp" + elif text == "a": + self.module.model.signal_path = "atm" + def send_command(self, command: str) -> bool: """This method is used to send a command to the active serial connection. @@ -938,6 +986,8 @@ class AutoTMController(ModuleController): frequency_step, ) + self.switch_to_atm() + # We create the lookup table LUT = MechanicalLookupTable( start_frequency, stop_frequency, frequency_step @@ -1038,6 +1088,7 @@ class AutoTMController(ModuleController): command = f"r{frequency}" try: confirmation = self.send_command(command) + QApplication.processEvents() if confirmation: reflection = self.module.model.last_reflection diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 391e0ff..13672f3 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -393,6 +393,9 @@ class AutoTMModel(ModuleModel): self.tuning_voltage = None self.matching_voltage = None + # AutoTM system or preamp + self.signal_path = None + @property def available_devices(self): return self._available_devices From 1b798cf8a27ab2b3a71a487d970c14c575b4d6b0 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 15 Dec 2023 17:17:12 +0100 Subject: [PATCH 27/31] Fixed bug with voltage setting. --- src/nqrduck_autotm/controller.py | 37 ++++++++++++++++++++++---------- src/nqrduck_autotm/view.py | 6 +++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index e7e402b..175aa3c 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -49,8 +49,8 @@ class AutoTMController(ModuleController): logger.error("Could not tune and match. No LUT available.") return elif self.module.model.LUT.TYPE == "Electrical": - tunning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) - confirmation = self.set_voltages(str(tunning_voltage), str(matching_voltage)) + tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) + confirmation = self.set_voltages(str(tuning_voltage), str(matching_voltage)) if confirmation: # We need to change the signal pathway to preamp to measure the reflection self.switch_to_atm() @@ -120,6 +120,8 @@ class AutoTMController(ModuleController): # On opening of the command we set the switch position to atm self.switch_to_atm() + self.set_voltages("0", "0") + except Exception as e: logger.error("Could not connect to device %s: %s", device, e) @@ -237,16 +239,17 @@ class AutoTMController(ModuleController): """ if text.startswith("v"): text = text[1:].split("t") - matching_voltage, tuning_voltage = map(float, text) + tuning_voltage, matching_voltage = map(float, text) LUT = self.module.model.el_lut if LUT is not None: if LUT.is_incomplete(): - logger.debug("Received voltage sweep result: %s %s", matching_voltage, tuning_voltage) - LUT.add_voltages(matching_voltage, tuning_voltage) + logger.debug("Received voltage sweep result: Tuning %s Matching %s", tuning_voltage, matching_voltage) + LUT.add_voltages(tuning_voltage, matching_voltage) self.continue_or_finish_voltage_sweep(LUT) self.module.model.tuning_voltage = tuning_voltage self.module.model.matching_voltage = matching_voltage + logger.debug("Updated voltages: Tuning %s Matching %s", self.module.model.tuning_voltage, self.module.model.matching_voltage) def finish_frequency_sweep(self): """This method is called when a frequency sweep is finished. @@ -555,22 +558,25 @@ class AutoTMController(ModuleController): logger.debug("Voltages already set") return - command = "v%sv%s" % (matching_voltage, tuning_voltage) + command = "v%sv%s" % (tuning_voltage, matching_voltage) start_time = time.time() confirmation = self.send_command(command) if confirmation: - while matching_voltage == self.module.model.matching_voltage and tuning_voltage == self.module.model.tuning_voltage: + while matching_voltage != self.module.model.matching_voltage and tuning_voltage != self.module.model.tuning_voltage: QApplication.processEvents() # Check for timeout if time.time() - start_time > timeout_duration: logger.error("Voltage setting timed out") - break # or handle timeout differently - - logger.debug("Voltages set successfully") - return confirmation + break + logger.debug("Voltages set successfully") + return confirmation + else: + logger.error("Could not set voltages") + return confirmation + ### Electrical Lookup Table ### def generate_electrical_lut( @@ -837,6 +843,10 @@ class AutoTMController(ModuleController): stepper_position = stepper.position future_position = stepper.position + int(steps) + if future_position == stepper_position: + logger.debug("Stepper already at position") + return + if self.validate_position(future_position, stepper): confirmation = self.send_stepper_command(int(steps), stepper) # Convert the steps string to an integer @@ -859,6 +869,11 @@ class AutoTMController(ModuleController): stepper_position = stepper.position future_position = int(steps) + + if future_position == stepper_position: + logger.debug("Stepper already at position") + return + if self.validate_position(future_position, stepper): actual_steps = self.calculate_steps_for_absolute_move(future_position, stepper) confirmation = self.send_stepper_command(actual_steps, stepper) diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 32a71d9..8929ce2 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -607,7 +607,7 @@ class AutoTMView(ModuleView): ) elif LUT.TYPE == "Electrical": self.table_widget.setHorizontalHeaderLabels( - ["Frequency (MHz)", "Matching Voltage", "Tuning Voltage"] + ["Frequency (MHz)", "Tuning Voltage", "Matching Voltage"] ) for row, frequency in enumerate(LUT.data.keys()): @@ -624,8 +624,8 @@ class AutoTMView(ModuleView): test_button = QPushButton("Test") # For electrical probe coils the matching voltage is the first entry in the LUT if LUT.TYPE == "Electrical": - tuning_voltage = str(LUT.data[frequency][1]) - matching_voltage = str(LUT.data[frequency][0]) + tuning_voltage = str(LUT.data[frequency][0]) + matching_voltage = str(LUT.data[frequency][1]) test_button.clicked.connect( lambda _, tuning_voltage=tuning_voltage, matching_voltage=matching_voltage: self.module.controller.set_voltages( tuning_voltage, matching_voltage From bbe1798f06a4f9c6c80adecded0ad46a076be5b7 Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 15 Dec 2023 17:24:59 +0100 Subject: [PATCH 28/31] Added lime safety. --- src/nqrduck_autotm/controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 175aa3c..a2f39c9 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -68,7 +68,10 @@ class AutoTMController(ModuleController): # Switch back to preamp to perform a measurement self.switch_to_preamp() - self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) + # The Lime doesn"t like it if we send the command to switch to atm and then immediately send the command to measure the reflection. + # So we wait a bit before starting the measurement + + QTimer.singleShot(100, lambda: self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection)) def find_devices(self) -> None: """Scan for available serial devices and add them to the model as available devices.""" From 2d7fbf34792c53629a66dce11e6249d06065aa2a Mon Sep 17 00:00:00 2001 From: jupfi Date: Sat, 16 Dec 2023 13:20:33 +0100 Subject: [PATCH 29/31] Added saving and loading of S11 measurements. --- src/nqrduck_autotm/controller.py | 29 +++++++++++++++ src/nqrduck_autotm/model.py | 1 + src/nqrduck_autotm/resources/autotm_widget.ui | 35 +++++++++++++++++++ src/nqrduck_autotm/view.py | 29 +++++++++++++++ src/nqrduck_autotm/widget.py | 16 +++++++++ 5 files changed, 110 insertions(+) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index a2f39c9..bcc9515 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -512,6 +512,35 @@ class AutoTMController(ModuleController): self.module.model.open_calibration = S11Data.from_json(data["open"]) self.module.model.load_calibration = S11Data.from_json(data["load"]) + def save_measurement(self, filename: str) -> None: + """Save measurement to file. + + Args: + filename (str): Path to file. + """ + logger.debug("Saving measurement.") + if not self.module.model.measurement: + logger.debug("No measurement to save.") + return + + measurement = self.module.model.measurement.to_json() + + with open(filename, "w") as f: + json.dump(measurement, f) + + def load_measurement(self, filename: str) -> None: + """Load measurement from file. + + Args: + filename (str): Path to file. + """ + + logger.debug("Loading measurement.") + + with open(filename, "r") as f: + measurement = json.load(f) + self.module.model.measurement = S11Data.from_json(measurement) + ### Voltage Control ### def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None: diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 13672f3..26fd7c3 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) class S11Data: + FILE_EXTENSION = "s11" # 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_MAGNITUDE = 900 # mV diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index dcef9b2..ec69b53 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -547,6 +547,41 @@ + + + + + + + + Import Measurement + + + + + + + Export Measurement + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 8929ce2..d7030c6 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -21,6 +21,7 @@ from nqrduck.contrib.mplwidget import MplWidget from nqrduck.assets.icons import Logos from nqrduck.assets.animations import DuckAnimations from .widget import Ui_Form +from .model import S11Data logger = logging.getLogger(__name__) @@ -138,6 +139,16 @@ class AutoTMView(ModuleView): # Position Button self._ui_form.positionButton.clicked.connect(self.on_position_button_clicked) + # Import and export buttons + + self._ui_form.exportButton.setIcon(Logos.Save16x16()) + self._ui_form.exportButton.setIconSize(self._ui_form.exportButton.size()) + self._ui_form.exportButton.clicked.connect(self.on_export_button_clicked) + + self._ui_form.importButton.setIcon(Logos.Load16x16()) + self._ui_form.importButton.setIconSize(self._ui_form.importButton.size()) + self._ui_form.importButton.clicked.connect(self.on_import_button_clicked) + self.init_plot() self.init_labels() @@ -384,6 +395,24 @@ class AutoTMView(ModuleView): self.lut_window = self.LutWindow(self.module) self.lut_window.show() + @pyqtSlot() + def on_export_button_clicked(self) -> None: + """Slot for when the export button is clicked.""" + logger.debug("Export button clicked") + file_manager = self.QFileManager(S11Data.FILE_EXTENSION, parent=self.widget) + file_name = file_manager.saveFileDialog() + if file_name: + self.module.controller.save_measurement(file_name) + + @pyqtSlot() + def on_import_button_clicked(self) -> None: + """Slot for when the import button is clicked.""" + logger.debug("Import button clicked") + file_manager = self.QFileManager(S11Data.FILE_EXTENSION, parent=self.widget) + file_name = file_manager.loadFileDialog() + if file_name: + self.module.controller.load_measurement(file_name) + class StepperSavedPositionsWindow(QDialog): def __init__(self, module, parent=None): super().__init__(parent) diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index 18fe98a..9758e6b 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -283,6 +283,20 @@ class Ui_Form(object): self.S11Plot.setSizePolicy(sizePolicy) self.S11Plot.setObjectName("S11Plot") self.verticalLayout_5.addWidget(self.S11Plot) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout_4 = QtWidgets.QVBoxLayout() + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.importButton = QtWidgets.QPushButton(parent=Form) + self.importButton.setObjectName("importButton") + self.verticalLayout_4.addWidget(self.importButton) + self.exportButton = QtWidgets.QPushButton(parent=Form) + self.exportButton.setObjectName("exportButton") + self.verticalLayout_4.addWidget(self.exportButton) + self.horizontalLayout.addLayout(self.verticalLayout_4) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.verticalLayout_5.addLayout(self.horizontalLayout) self.horizontalLayout_2.addLayout(self.verticalLayout_5) self.horizontalLayout_2.setStretch(1, 1) @@ -342,4 +356,6 @@ class Ui_Form(object): self.calibrationButton.setText(_translate("Form", "Calibrate")) self.pushButton_3.setText(_translate("Form", "T&M Settings")) self.titleinfoLabel.setText(_translate("Form", "Info Box:")) + self.importButton.setText(_translate("Form", "Import Measurement")) + self.exportButton.setText(_translate("Form", "Export Measurement")) from nqrduck.contrib.mplwidget import MplWidget From c1bb983d0beab5dc0c75d78a938c0510a502a615 Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 20 Dec 2023 09:22:19 +0100 Subject: [PATCH 30/31] Removed confirmation of commands for broadband measurements. This causes bugs atm. --- src/nqrduck_autotm/controller.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index bcc9515..e21525d 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -51,13 +51,12 @@ class AutoTMController(ModuleController): elif self.module.model.LUT.TYPE == "Electrical": tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency) confirmation = self.set_voltages(str(tuning_voltage), str(matching_voltage)) - if confirmation: - # We need to change the signal pathway to preamp to measure the reflection - self.switch_to_atm() - reflection = self.read_reflection(frequency) - # We need to change the signal pathway back to atm to perform a measurement - self.switch_to_preamp() - self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) + # We need to change the signal pathway to preamp to measure the reflection + self.switch_to_atm() + reflection = self.read_reflection(frequency) + # We need to change the signal pathway back to atm to perform a measurement + self.switch_to_preamp() + self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection) elif self.module.model.LUT.TYPE == "Mechanical": tuning_position, matching_position = self.module.model.LUT.get_positions(frequency) @@ -595,13 +594,12 @@ class AutoTMController(ModuleController): start_time = time.time() confirmation = self.send_command(command) - if confirmation: - while matching_voltage != self.module.model.matching_voltage and tuning_voltage != self.module.model.tuning_voltage: - QApplication.processEvents() - # Check for timeout - if time.time() - start_time > timeout_duration: - logger.error("Voltage setting timed out") - break + while matching_voltage != self.module.model.matching_voltage and tuning_voltage != self.module.model.tuning_voltage: + QApplication.processEvents() + # Check for timeout + if time.time() - start_time > timeout_duration: + logger.error("Voltage setting timed out") + break logger.debug("Voltages set successfully") return confirmation From e0dab7a89169253004470ded64a8fbfe9c4dd945 Mon Sep 17 00:00:00 2001 From: jupfi Date: Wed, 20 Dec 2023 09:25:47 +0100 Subject: [PATCH 31/31] Fixed label position of phase. --- src/nqrduck_autotm/view.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index d7030c6..89bd753 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -164,7 +164,7 @@ class AutoTMView(ModuleView): """Initialize the S11 plot.""" ax = self._ui_form.S11Plot.canvas.ax ax.set_xlabel("Frequency (MHz)") - ax.set_ylabel("S11 (dB)") + ax.set_ylabel("S11 (dB)", loc="center") ax.set_title("S11") ax.grid(True) ax.set_xlim(0, 100) @@ -300,7 +300,9 @@ class AutoTMView(ModuleView): else: magnitude_ax.plot(frequency, return_loss_db, color="blue") - self.phase_ax.set_ylabel("|Phase (deg)|") + self.phase_ax.yaxis.tick_right() + self.phase_ax.yaxis.set_label_position("right") + self.phase_ax.set_ylabel("Phase (deg)") self.phase_ax.plot(frequency, phase, color="orange", linestyle="--") # self.phase_ax.invert_yaxis()