mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-11-09 11:40:02 +00:00
Added loading saving of positions.
This commit is contained in:
parent
857ff6eb08
commit
6920b7545a
3 changed files with 246 additions and 1 deletions
|
@ -8,7 +8,7 @@ from PyQt6 import QtSerialPort
|
||||||
from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt
|
from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot, Qt
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
from nqrduck.module.module_controller import ModuleController
|
from nqrduck.module.module_controller import ModuleController
|
||||||
from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable
|
from .model import S11Data, ElectricalLookupTable, MechanicalLookupTable, SavedPosition
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -671,5 +671,43 @@ class AutoTMController(ModuleController):
|
||||||
actual_steps = self.calculate_steps_for_absolute_move(future_position)
|
actual_steps = self.calculate_steps_for_absolute_move(future_position)
|
||||||
self.send_stepper_command(actual_steps)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -223,6 +223,25 @@ class Stepper:
|
||||||
self.homed = False
|
self.homed = False
|
||||||
self.position = 0
|
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):
|
class TuningStepper(Stepper):
|
||||||
TYPE = "Tuning"
|
TYPE = "Tuning"
|
||||||
MAX_STEPS = 1e6
|
MAX_STEPS = 1e6
|
||||||
|
@ -275,6 +294,7 @@ class AutoTMModel(ModuleModel):
|
||||||
serial_changed = pyqtSignal(QSerialPort)
|
serial_changed = pyqtSignal(QSerialPort)
|
||||||
data_points_changed = pyqtSignal(list)
|
data_points_changed = pyqtSignal(list)
|
||||||
active_stepper_changed = pyqtSignal(Stepper)
|
active_stepper_changed = pyqtSignal(Stepper)
|
||||||
|
saved_positions_changed = pyqtSignal(list)
|
||||||
|
|
||||||
short_calibration_finished = pyqtSignal(S11Data)
|
short_calibration_finished = pyqtSignal(S11Data)
|
||||||
open_calibration_finished = pyqtSignal(S11Data)
|
open_calibration_finished = pyqtSignal(S11Data)
|
||||||
|
@ -292,6 +312,8 @@ class AutoTMModel(ModuleModel):
|
||||||
self.matching_stepper = MatchingStepper()
|
self.matching_stepper = MatchingStepper()
|
||||||
self.active_stepper = self.tuning_stepper
|
self.active_stepper = self.tuning_stepper
|
||||||
|
|
||||||
|
self.saved_positions = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_devices(self):
|
def available_devices(self):
|
||||||
return self._available_devices
|
return self._available_devices
|
||||||
|
@ -325,6 +347,20 @@ class AutoTMModel(ModuleModel):
|
||||||
self.data_points.clear()
|
self.data_points.clear()
|
||||||
self.data_points_changed.emit(self.data_points)
|
self.data_points_changed.emit(self.data_points)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def 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
|
@property
|
||||||
def measurement(self):
|
def measurement(self):
|
||||||
"""The measurement property is used to store the current measurement.
|
"""The measurement property is used to store the current measurement.
|
||||||
|
|
|
@ -124,6 +124,9 @@ class AutoTMView(ModuleView):
|
||||||
# Active stepper changed
|
# Active stepper changed
|
||||||
self.module.model.active_stepper_changed.connect(self.on_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_plot()
|
||||||
self.init_labels()
|
self.init_labels()
|
||||||
|
|
||||||
|
@ -218,6 +221,15 @@ class AutoTMView(ModuleView):
|
||||||
self._ui_form.increaseButton.setEnabled(False)
|
self._ui_form.increaseButton.setEnabled(False)
|
||||||
self._ui_form.absoluteGoButton.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:
|
def plot_measurement(self, data: "S11Data") -> None:
|
||||||
"""Update the S11 plot with the current data points.
|
"""Update the S11 plot with the current data points.
|
||||||
|
|
||||||
|
@ -336,6 +348,165 @@ class AutoTMView(ModuleView):
|
||||||
self.lut_window = self.LutWindow(self.module)
|
self.lut_window = self.LutWindow(self.module)
|
||||||
self.lut_window.show()
|
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):
|
class LoadingSpinner(QDialog):
|
||||||
"""This class implements a spinner dialog that is shown during a frequency sweep."""
|
"""This class implements a spinner dialog that is shown during a frequency sweep."""
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue