mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-09-18 18:30:35 +00:00
Linting.
This commit is contained in:
parent
3a23b126fe
commit
bfbc20c2e9
5 changed files with 556 additions and 212 deletions
|
@ -1 +1,3 @@
|
|||
"""The NQRduck AutoTM module. It is used to automatically tune and match magnetic resonance probe coils."""
|
||||
|
||||
from .autotm import AutoTM as Module
|
|
@ -1,3 +1,5 @@
|
|||
"""The Module creation snippet for the NQRduck AutoTM module."""
|
||||
|
||||
from nqrduck.module.module import Module
|
||||
from .model import AutoTMModel
|
||||
from .view import AutoTMView
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""The controller for the NQRduck AutoTM module."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import numpy as np
|
||||
|
@ -8,47 +10,75 @@ from PyQt6.QtCore import pyqtSlot
|
|||
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
|
||||
from .model import (
|
||||
S11Data,
|
||||
ElectricalLookupTable,
|
||||
MechanicalLookupTable,
|
||||
SavedPosition,
|
||||
Stepper,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutoTMController(ModuleController):
|
||||
"""The controller for the NQRduck AutoTM module. It handles the serial connection and the signals and slots."""
|
||||
|
||||
BAUDRATE = 115200
|
||||
|
||||
def on_loading(self):
|
||||
def on_loading(self) -> None:
|
||||
"""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_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.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)
|
||||
self.module.model.serial_data_received.connect(self.process_position_sweep_result)
|
||||
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:
|
||||
"""Slot for setting the tune and match frequency.
|
||||
|
||||
Args:
|
||||
key (str): The key of the signal. If the key is "set_tune_and_match", the tune and match method is called.
|
||||
value (object): The value of the signal. The value is the frequency to tune and match to.
|
||||
"""
|
||||
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.
|
||||
"""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.
|
||||
|
||||
Args:
|
||||
frequency (float): The frequency to tune and match to.
|
||||
"""
|
||||
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":
|
||||
tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency)
|
||||
confirmation = self.set_voltages(str(tuning_voltage), str(matching_voltage))
|
||||
tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(
|
||||
frequency
|
||||
)
|
||||
_ = self.set_voltages(str(tuning_voltage), str(matching_voltage))
|
||||
# We need to change the signal pathway to preamp to measure the reflection
|
||||
self.switch_to_atm()
|
||||
reflection = self.read_reflection(frequency)
|
||||
|
@ -57,7 +87,9 @@ class AutoTMController(ModuleController):
|
|||
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)
|
||||
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
|
||||
|
@ -68,7 +100,12 @@ class AutoTMController(ModuleController):
|
|||
# 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))
|
||||
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."""
|
||||
|
@ -127,6 +164,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
def start_frequency_sweep(self, start_frequency: str, stop_frequency: str) -> None:
|
||||
"""This starts a frequency sweep on the device in the specified range.
|
||||
|
||||
The minimum start and stop frequency are specific to the AD4351 based frequency generator.
|
||||
|
||||
Args:
|
||||
|
@ -162,13 +200,8 @@ class AutoTMController(ModuleController):
|
|||
return
|
||||
|
||||
if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY:
|
||||
error = (
|
||||
"Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz"
|
||||
% (
|
||||
MIN_FREQUENCY / 1e6,
|
||||
MAX_FREQUENCY / 1e6,
|
||||
)
|
||||
)
|
||||
error = f"Could not start frequency sweep. Start and stop frequency must be between {MIN_FREQUENCY / 1e6} and {MAX_FREQUENCY / 1e6} MHz"
|
||||
|
||||
logger.error(error)
|
||||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
@ -182,7 +215,7 @@ class AutoTMController(ModuleController):
|
|||
)
|
||||
|
||||
# Print the command 'f<start>f<stop>f<step>' to the serial connection
|
||||
command = "f%sf%sf%s" % (start_frequency, stop_frequency, frequency_step)
|
||||
command = f"f{start_frequency}f{stop_frequency}f{frequency_step}"
|
||||
self.module.model.frequency_sweep_start = time.time()
|
||||
confirmation = self.send_command(command)
|
||||
if confirmation:
|
||||
|
@ -193,9 +226,16 @@ class AutoTMController(ModuleController):
|
|||
@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.
|
||||
|
||||
Args:
|
||||
text (str): The data received from the serial connection.
|
||||
"""
|
||||
if text.startswith("f") and self.module.view.frequency_sweep_spinner.isVisible():
|
||||
if (
|
||||
text.startswith("f")
|
||||
and self.module.view.frequency_sweep_spinner.isVisible()
|
||||
):
|
||||
text = text[1:].split("r")
|
||||
frequency = float(text[0])
|
||||
return_loss, phase = map(float, text[1].split("p"))
|
||||
|
@ -204,7 +244,11 @@ class AutoTMController(ModuleController):
|
|||
@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.
|
||||
|
||||
Args:
|
||||
text (str): The data received from the serial connection.
|
||||
"""
|
||||
if self.module.model.active_calibration is None and text.startswith("r"):
|
||||
logger.debug("Measurement finished")
|
||||
|
@ -216,22 +260,31 @@ class AutoTMController(ModuleController):
|
|||
@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.
|
||||
text (str): The data received from the serial connection.
|
||||
"""
|
||||
if text.startswith("r") and self.module.model.active_calibration in ["short", "open", "load"]:
|
||||
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()))
|
||||
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()
|
||||
|
||||
@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:
|
||||
|
@ -243,25 +296,40 @@ class AutoTMController(ModuleController):
|
|||
LUT = self.module.model.el_lut
|
||||
if LUT is not None:
|
||||
if LUT.is_incomplete():
|
||||
logger.debug("Received voltage sweep result: Tuning %s Matching %s", tuning_voltage, matching_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)
|
||||
logger.debug(
|
||||
"Updated voltages: Tuning %s Matching %s",
|
||||
self.module.model.tuning_voltage,
|
||||
self.module.model.matching_voltage,
|
||||
)
|
||||
|
||||
def finish_frequency_sweep(self):
|
||||
def finish_frequency_sweep(self) -> None:
|
||||
"""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")
|
||||
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):
|
||||
def continue_or_finish_voltage_sweep(self, LUT) -> None:
|
||||
"""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:
|
||||
|
@ -274,8 +342,9 @@ class AutoTMController(ModuleController):
|
|||
# Finish voltage sweep
|
||||
self.finish_voltage_sweep(LUT)
|
||||
|
||||
def start_next_voltage_sweep(self, LUT):
|
||||
def start_next_voltage_sweep(self, LUT) -> None:
|
||||
"""This method is called when a voltage sweep is finished.
|
||||
|
||||
It starts the next voltage sweep.
|
||||
|
||||
Args:
|
||||
|
@ -286,16 +355,17 @@ class AutoTMController(ModuleController):
|
|||
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
||||
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
||||
# We use the currently set voltages
|
||||
command = "s%so%so%s" % (next_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage)
|
||||
command = f"s{next_frequency}o{self.module.model.tuning_voltage}o{self.module.model.matching_voltage}"
|
||||
else:
|
||||
command = "s%s" % (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):
|
||||
def finish_voltage_sweep(self, LUT) -> None:
|
||||
"""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:
|
||||
|
@ -305,13 +375,18 @@ class AutoTMController(ModuleController):
|
|||
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")
|
||||
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)
|
||||
|
||||
@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:
|
||||
|
@ -326,7 +401,13 @@ class AutoTMController(ModuleController):
|
|||
|
||||
@pyqtSlot(str)
|
||||
def read_position_data(self, text: str) -> None:
|
||||
"""This method is called when data is received from the serial connection."""
|
||||
"""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("p"):
|
||||
# Format is p<tuning_position>m<matching_position>
|
||||
text = text[1:].split("m")
|
||||
|
@ -335,7 +416,11 @@ class AutoTMController(ModuleController):
|
|||
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)
|
||||
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:
|
||||
|
@ -349,8 +434,9 @@ class AutoTMController(ModuleController):
|
|||
self.module.model.serial_data_received.emit(text)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def process_reflection_data(self, text):
|
||||
def process_reflection_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:
|
||||
|
@ -367,7 +453,12 @@ class AutoTMController(ModuleController):
|
|||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the short calibration button is pressed.
|
||||
|
||||
It starts a frequency sweep in the specified range and then starts a short calibration.
|
||||
|
||||
Args:
|
||||
start_frequency (float): The start frequency in MHz.
|
||||
stop_frequency (float): The stop frequency in MHz.
|
||||
"""
|
||||
logger.debug("Starting short calibration")
|
||||
self.module.model.init_short_calibration()
|
||||
|
@ -377,7 +468,12 @@ class AutoTMController(ModuleController):
|
|||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the open calibration button is pressed.
|
||||
|
||||
It starts a frequency sweep in the specified range and then starts an open calibration.
|
||||
|
||||
Args:
|
||||
start_frequency (float) : The start frequency in MHz.
|
||||
stop_frequency (float) : The stop frequency in MHz.
|
||||
"""
|
||||
logger.debug("Starting open calibration")
|
||||
self.module.model.init_open_calibration()
|
||||
|
@ -387,7 +483,12 @@ class AutoTMController(ModuleController):
|
|||
self, start_frequency: float, stop_frequency: float
|
||||
) -> None:
|
||||
"""This method is called when the load calibration button is pressed.
|
||||
|
||||
It starts a frequency sweep in the specified range and then loads a calibration.
|
||||
|
||||
Args:
|
||||
start_frequency (float) : The start frequency in MHz.
|
||||
stop_frequency (float) : The stop frequency in MHz.
|
||||
"""
|
||||
logger.debug("Starting load calibration")
|
||||
self.module.model.init_load_calibration()
|
||||
|
@ -395,6 +496,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
def calculate_calibration(self) -> None:
|
||||
"""This method is called when the calculate calibration button is pressed.
|
||||
|
||||
It calculates the calibration from the short, open and calibration data points.
|
||||
|
||||
@TODO: Improvements to the calibrations can be made the following ways:
|
||||
|
@ -406,17 +508,17 @@ class AutoTMController(ModuleController):
|
|||
"""
|
||||
logger.debug("Calculating calibration")
|
||||
# First we check if the short and open calibration data points are available
|
||||
if self.module.model.short_calibration == None:
|
||||
if self.module.model.short_calibration is None:
|
||||
logger.error(
|
||||
"Could not calculate calibration. No short calibration data points available."
|
||||
)
|
||||
return
|
||||
if self.module.model.open_calibration == None:
|
||||
if self.module.model.open_calibration is None:
|
||||
logger.error(
|
||||
"Could not calculate calibration. No open calibration data points available."
|
||||
)
|
||||
return
|
||||
if self.module.model.load_calibration == None:
|
||||
if self.module.model.load_calibration is None:
|
||||
logger.error(
|
||||
"Could not calculate calibration. No load calibration data points available."
|
||||
)
|
||||
|
@ -459,6 +561,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
def export_calibration(self, filename: str) -> None:
|
||||
"""This method is called when the export calibration button is pressed.
|
||||
|
||||
It exports the data of the short, open and load calibration to a file.
|
||||
|
||||
Args:
|
||||
|
@ -466,19 +569,19 @@ class AutoTMController(ModuleController):
|
|||
"""
|
||||
logger.debug("Exporting calibration")
|
||||
# First we check if the short and open calibration data points are available
|
||||
if self.module.model.short_calibration == None:
|
||||
if self.module.model.short_calibration is None:
|
||||
logger.error(
|
||||
"Could not export calibration. No short calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
if self.module.model.open_calibration == None:
|
||||
if self.module.model.open_calibration is None:
|
||||
logger.error(
|
||||
"Could not export calibration. No open calibration data points available."
|
||||
)
|
||||
return
|
||||
|
||||
if self.module.model.load_calibration == None:
|
||||
if self.module.model.load_calibration is None:
|
||||
logger.error(
|
||||
"Could not export calibration. No load calibration data points available."
|
||||
)
|
||||
|
@ -496,6 +599,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
def import_calibration(self, filename: str) -> None:
|
||||
"""This method is called when the import calibration button is pressed.
|
||||
|
||||
It imports the data of the short, open and load calibration from a file.
|
||||
|
||||
Args:
|
||||
|
@ -542,6 +646,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
@ -583,16 +688,22 @@ class AutoTMController(ModuleController):
|
|||
matching_voltage,
|
||||
)
|
||||
|
||||
if tuning_voltage == self.module.model.tuning_voltage and matching_voltage == self.module.model.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" % (tuning_voltage, matching_voltage)
|
||||
command = f"v{tuning_voltage}v{matching_voltage}"
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
confirmation = self.send_command(command)
|
||||
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:
|
||||
|
@ -614,6 +725,7 @@ class AutoTMController(ModuleController):
|
|||
frequency_step: 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.
|
||||
|
||||
Args:
|
||||
|
@ -635,11 +747,7 @@ class AutoTMController(ModuleController):
|
|||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
if (
|
||||
start_frequency < 0
|
||||
or stop_frequency < 0
|
||||
or frequency_step < 0
|
||||
):
|
||||
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)
|
||||
|
@ -669,9 +777,7 @@ class AutoTMController(ModuleController):
|
|||
# self.set_voltages("0", "0")
|
||||
|
||||
# We create the lookup table
|
||||
LUT = ElectricalLookupTable(
|
||||
start_frequency, stop_frequency, frequency_step
|
||||
)
|
||||
LUT = ElectricalLookupTable(start_frequency, stop_frequency, frequency_step)
|
||||
|
||||
LUT.started_frequency = start_frequency
|
||||
|
||||
|
@ -679,10 +785,14 @@ class AutoTMController(ModuleController):
|
|||
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
||||
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
||||
# 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)
|
||||
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 = f"s{start_frequency}o{self.module.model.tuning_voltage}o{self.module.model.matching_voltage}"
|
||||
else:
|
||||
command = "s%s" % (start_frequency)
|
||||
command = f"s{start_frequency}"
|
||||
|
||||
# For timing of the voltage sweep
|
||||
self.module.model.voltage_sweep_start = time.time()
|
||||
|
@ -694,6 +804,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
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'.
|
||||
|
||||
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":
|
||||
|
@ -714,6 +825,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
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":
|
||||
|
@ -734,6 +846,7 @@ class AutoTMController(ModuleController):
|
|||
|
||||
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:
|
||||
|
@ -765,7 +878,7 @@ class AutoTMController(ModuleController):
|
|||
)
|
||||
return False
|
||||
|
||||
if self.module.model.serial.isOpen() == False:
|
||||
if self.module.model.serial.isOpen() is False:
|
||||
logger.error("Could not send command. Serial connection is not open")
|
||||
self.module.view.add_error_text(
|
||||
"Could not send command. Serial connection is not open"
|
||||
|
@ -796,12 +909,13 @@ class AutoTMController(ModuleController):
|
|||
|
||||
except Exception as e:
|
||||
logger.error("Could not send command. %s", e)
|
||||
self.module.view.add_error_text("Could not send command. %s" % e)
|
||||
self.module.view.add_error_text(f"Could not send command. {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.
|
||||
"""
|
||||
logger.debug("Homing")
|
||||
|
@ -812,6 +926,7 @@ class AutoTMController(ModuleController):
|
|||
@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:
|
||||
|
@ -825,24 +940,51 @@ class AutoTMController(ModuleController):
|
|||
self.module.model.active_stepper = self.module.model.matching_stepper
|
||||
|
||||
def validate_position(self, future_position: int, stepper: Stepper) -> bool:
|
||||
"""Validate the stepper's future position."""
|
||||
"""Validate the stepper's future position.
|
||||
|
||||
Args:
|
||||
future_position (int): The future position of the stepper.
|
||||
stepper (Stepper): The stepper that is being moved.
|
||||
|
||||
Returns:
|
||||
bool: True if the position is valid, False otherwise.
|
||||
"""
|
||||
if future_position < 0:
|
||||
self.module.view.add_error_text("Could not move stepper. Stepper position cannot be negative")
|
||||
self.module.view.add_error_text(
|
||||
"Could not move stepper. Stepper position cannot be negative"
|
||||
)
|
||||
return False
|
||||
|
||||
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}")
|
||||
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, stepper : Stepper) -> int:
|
||||
"""Calculate the number of steps for an absolute move."""
|
||||
def calculate_steps_for_absolute_move(
|
||||
self, target_position: int, stepper: Stepper
|
||||
) -> int:
|
||||
"""Calculate the number of steps for an absolute move.
|
||||
|
||||
Args:
|
||||
target_position (int): The target position of the stepper.
|
||||
stepper (Stepper): The stepper that is being moved.
|
||||
|
||||
Returns:
|
||||
int: The number of steps to move.
|
||||
"""
|
||||
current_position = stepper.position
|
||||
return target_position - current_position
|
||||
|
||||
def send_stepper_command(self, steps: int, stepper: Stepper) -> None:
|
||||
"""Send a command to the stepper motor based on the number of steps."""
|
||||
"""Send a command to the stepper motor based on the number of steps.
|
||||
|
||||
Args:
|
||||
steps (int): The number of steps to move.
|
||||
stepper (Stepper): The stepper that is being moved.
|
||||
"""
|
||||
# Here we handle backlash of the tuner
|
||||
# Determine the direction of the current steps
|
||||
backlash = 0
|
||||
|
@ -862,7 +1004,12 @@ class AutoTMController(ModuleController):
|
|||
return confirmation
|
||||
|
||||
def on_relative_move(self, steps: str, stepper: Stepper = None) -> None:
|
||||
"""This method is called when the relative move button is pressed."""
|
||||
"""This method is called when the relative move button is pressed.
|
||||
|
||||
Args:
|
||||
steps (str): The number of steps to move.
|
||||
stepper (Stepper): The stepper that is being moved. Default is None.
|
||||
"""
|
||||
timeout_duration = 15 # timeout in seconds
|
||||
start_time = time.time()
|
||||
|
||||
|
@ -876,7 +1023,9 @@ class AutoTMController(ModuleController):
|
|||
return
|
||||
|
||||
if self.validate_position(future_position, stepper):
|
||||
confirmation = self.send_stepper_command(int(steps), stepper) # Convert the steps string to an integer
|
||||
confirmation = self.send_stepper_command(
|
||||
int(steps), stepper
|
||||
) # Convert the steps string to an integer
|
||||
|
||||
while stepper_position == stepper.position:
|
||||
QApplication.processEvents()
|
||||
|
@ -888,7 +1037,12 @@ class AutoTMController(ModuleController):
|
|||
return confirmation
|
||||
|
||||
def on_absolute_move(self, steps: str, stepper: Stepper = None) -> None:
|
||||
"""This method is called when the absolute move button is pressed."""
|
||||
"""This method is called when the absolute move button is pressed.
|
||||
|
||||
Args:
|
||||
steps (str): The number of steps to move.
|
||||
stepper (Stepper): The stepper that is being moved. Default is None.
|
||||
"""
|
||||
timeout_duration = 15 # timeout in seconds
|
||||
start_time = time.time()
|
||||
|
||||
|
@ -903,7 +1057,9 @@ class AutoTMController(ModuleController):
|
|||
return
|
||||
|
||||
if self.validate_position(future_position, stepper):
|
||||
actual_steps = self.calculate_steps_for_absolute_move(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:
|
||||
|
@ -930,8 +1086,11 @@ class AutoTMController(ModuleController):
|
|||
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"])
|
||||
|
||||
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.
|
||||
|
@ -944,7 +1103,9 @@ class AutoTMController(ModuleController):
|
|||
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:
|
||||
def add_position(
|
||||
self, frequency: str, tuning_position: str, matching_position: str
|
||||
) -> None:
|
||||
"""Add a position to the lookup table.
|
||||
|
||||
Args:
|
||||
|
@ -953,7 +1114,9 @@ class AutoTMController(ModuleController):
|
|||
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)
|
||||
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.
|
||||
|
@ -962,9 +1125,13 @@ class AutoTMController(ModuleController):
|
|||
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)
|
||||
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)
|
||||
self.on_absolute_move(
|
||||
position.matching_position, self.module.model.matching_stepper
|
||||
)
|
||||
|
||||
def on_delete_position(self, position: SavedPosition) -> None:
|
||||
"""Delete the specified position.
|
||||
|
@ -975,10 +1142,11 @@ class AutoTMController(ModuleController):
|
|||
logger.debug("Deleting position: %s", position)
|
||||
self.module.model.delete_saved_position(position)
|
||||
|
||||
|
||||
#### Mechanical tuning and matching ####
|
||||
|
||||
def generate_mechanical_lut(self, start_frequency: str, stop_frequency: str, frequency_step: str) -> None:
|
||||
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:
|
||||
|
@ -999,11 +1167,7 @@ class AutoTMController(ModuleController):
|
|||
self.module.view.add_info_text(error)
|
||||
return
|
||||
|
||||
if (
|
||||
start_frequency < 0
|
||||
or stop_frequency < 0
|
||||
or frequency_step < 0
|
||||
):
|
||||
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)
|
||||
|
@ -1032,9 +1196,7 @@ class AutoTMController(ModuleController):
|
|||
self.switch_to_atm()
|
||||
|
||||
# We create the lookup table
|
||||
LUT = MechanicalLookupTable(
|
||||
start_frequency, stop_frequency, frequency_step
|
||||
)
|
||||
LUT = MechanicalLookupTable(start_frequency, stop_frequency, frequency_step)
|
||||
|
||||
# Lock GUI
|
||||
self.module.view.create_mech_LUT_spinner_dialog()
|
||||
|
@ -1043,9 +1205,12 @@ class AutoTMController(ModuleController):
|
|||
|
||||
self.start_next_mechTM(LUT)
|
||||
|
||||
def start_next_mechTM(self, LUT) -> None:
|
||||
"""Start the next mechanical tuning and matching sweep.
|
||||
|
||||
def start_next_mechTM(self, LUT):
|
||||
"""Start the next mechanical tuning and matching sweep."""
|
||||
Args:
|
||||
LUT (MechanicalLookupTable): The lookup table.
|
||||
"""
|
||||
next_frequency = LUT.get_next_frequency()
|
||||
LUT.started_frequency = next_frequency
|
||||
logger.debug("Starting next mechanical tuning and matching:")
|
||||
|
@ -1068,10 +1233,15 @@ class AutoTMController(ModuleController):
|
|||
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}"
|
||||
|
||||
confirmation = self.send_command(command)
|
||||
_ = self.send_command(command)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def process_position_sweep_result(self, text):
|
||||
def process_position_sweep_result(self, text) -> None:
|
||||
"""Process the result of the position sweep.
|
||||
|
||||
Args:
|
||||
text (str): The text received from the serial connection.
|
||||
"""
|
||||
if text.startswith("z"):
|
||||
text = text[1:]
|
||||
# Format is z<tuning_position>,<tuning_last_direction>m<matching_position>,<matching_last_direction>
|
||||
|
@ -1088,22 +1258,38 @@ class AutoTMController(ModuleController):
|
|||
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)
|
||||
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)
|
||||
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."""
|
||||
def continue_or_finish_position_sweep(self, LUT) -> None:
|
||||
"""Continue or finish the position sweep.
|
||||
|
||||
Args:
|
||||
LUT (MechanicalLookupTable): The lookup table.
|
||||
"""
|
||||
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."""
|
||||
def finish_position_sweep(self, LUT) -> None:
|
||||
"""Finish the position sweep.
|
||||
|
||||
Args:
|
||||
LUT (MechanicalLookupTable): The lookup table.
|
||||
"""
|
||||
logger.debug("Finished position sweep")
|
||||
self.module.model.mech_lut = LUT
|
||||
self.module.model.LUT = LUT
|
||||
|
@ -1114,18 +1300,29 @@ class AutoTMController(ModuleController):
|
|||
"""Go to the specified position.
|
||||
|
||||
Args:
|
||||
position (SavedPosition): The position to go to.
|
||||
tuning_position (int): The tuning position.
|
||||
matching_position (int): The matching position.
|
||||
"""
|
||||
confirmation = self.on_absolute_move(tuning_position, self.module.model.tuning_stepper)
|
||||
confirmation = self.on_absolute_move(
|
||||
tuning_position, self.module.model.tuning_stepper
|
||||
)
|
||||
if confirmation:
|
||||
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) -> float:
|
||||
"""Starts a reflection measurement and reads the reflection at the specified frequency."""
|
||||
def read_reflection(self, frequency : float) -> float:
|
||||
"""Starts a reflection measurement and reads the reflection at the specified frequency.
|
||||
|
||||
Args:
|
||||
frequency (float): The frequency at which to read the reflection.
|
||||
|
||||
Returns:
|
||||
float: The reflection at the specified frequency.
|
||||
"""
|
||||
# We send the command to the atm system
|
||||
command = f"r{frequency}"
|
||||
try:
|
||||
|
@ -1143,8 +1340,13 @@ class AutoTMController(ModuleController):
|
|||
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")
|
||||
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
|
||||
|
@ -1163,12 +1365,12 @@ class AutoTMController(ModuleController):
|
|||
|
||||
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.view.add_error_text(
|
||||
"Could not read reflection. No confirmation received"
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Could not read reflection. %s", e)
|
||||
self.module.view.add_error_text(f"Could not read reflection. {e}")
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
"""The module model for the NQRduck AutoTM module. It is used to store the data and state of the AutoTM module.
|
||||
|
||||
Additionally it includes the LookupTable class which is used to store tuning and matching voltages for different frequencies.
|
||||
|
||||
The S11Data class is used to store the S11 data that is read in via the serial connection.
|
||||
"""
|
||||
|
||||
import cmath
|
||||
import numpy as np
|
||||
import logging
|
||||
|
@ -10,6 +17,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class S11Data:
|
||||
"""This class is used to store the S11 data that is read in via the serial connection."""
|
||||
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
|
||||
|
@ -19,26 +27,32 @@ class S11Data:
|
|||
PHASE_SLOPE = 10 # deg/mV
|
||||
|
||||
def __init__(self, data_points: list) -> None:
|
||||
"""Initialize the S11 data."""
|
||||
self.frequency = np.array([data_point[0] for data_point in data_points])
|
||||
self.return_loss_mv = np.array([data_point[1] for data_point in data_points])
|
||||
self.phase_mv = np.array([data_point[2] for data_point in data_points])
|
||||
|
||||
@property
|
||||
def millivolts(self):
|
||||
"""The reflection data in millivolts. This is the raw data that is read in via the serial connection."""
|
||||
return self.frequency, self.return_loss_mv, self.phase_mv
|
||||
|
||||
@property
|
||||
def return_loss_db(self):
|
||||
"""Returns the return loss in dB calculated from the return loss in mV."""
|
||||
return (
|
||||
self.return_loss_mv - self.CENTER_POINT_MAGNITUDE
|
||||
) / self.MAGNITUDE_SLOPE
|
||||
|
||||
@property
|
||||
def phase_deg(self, phase_correction=True):
|
||||
"""Returns the absolute value of the phase in degrees
|
||||
def phase_deg(self, phase_correction=True) -> np.array:
|
||||
"""Returns the absolute value of the phase in degrees.
|
||||
|
||||
Keyword Arguments:
|
||||
phase_correction {bool} -- If True, the phase correction is applied. (default: {False})
|
||||
Args:
|
||||
phase_correction (bool, optional): If True, the phase correction is applied. Defaults to True.
|
||||
|
||||
Returns:
|
||||
np.array: The absolute value of the phase in degrees.
|
||||
"""
|
||||
phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
|
||||
if phase_correction:
|
||||
|
@ -48,11 +62,12 @@ class S11Data:
|
|||
|
||||
@property
|
||||
def phase_rad(self):
|
||||
"""Returns the phase in radians."""
|
||||
return self.phase_deg * cmath.pi / 180
|
||||
|
||||
@property
|
||||
def gamma(self):
|
||||
"""Complex reflection coefficient"""
|
||||
"""Complex reflection coefficient."""
|
||||
if len(self.return_loss_db) != len(self.phase_rad):
|
||||
raise ValueError("return_loss_db and phase_rad must be the same length")
|
||||
|
||||
|
@ -65,6 +80,7 @@ class S11Data:
|
|||
self, frequency_data: np.array, phase_data: np.array
|
||||
) -> np.array:
|
||||
"""This method fixes the phase sign of the phase data.
|
||||
|
||||
The AD8302 can only measure the absolute value of the phase.
|
||||
Therefore we need to correct the phase sign. This can be done via the slope of the phase.
|
||||
If the slope is negative, the phase is positive and vice versa.
|
||||
|
@ -145,6 +161,7 @@ class S11Data:
|
|||
return phase_data_corrected
|
||||
|
||||
def to_json(self):
|
||||
"""Convert the S11 data to a JSON serializable format."""
|
||||
return {
|
||||
"frequency": self.frequency.tolist(),
|
||||
"return_loss_mv": self.return_loss_mv.tolist(),
|
||||
|
@ -153,6 +170,7 @@ class S11Data:
|
|||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
"""Create an S11Data object from a JSON serializable format."""
|
||||
f = json["frequency"]
|
||||
rl = json["return_loss_mv"]
|
||||
p = json["phase_mv"]
|
||||
|
@ -171,6 +189,7 @@ class LookupTable:
|
|||
stop_frequency: float,
|
||||
frequency_step: float,
|
||||
) -> None:
|
||||
"""Initialize the lookup table."""
|
||||
self.start_frequency = start_frequency
|
||||
self.stop_frequency = stop_frequency
|
||||
self.frequency_step = frequency_step
|
||||
|
@ -190,57 +209,78 @@ class LookupTable:
|
|||
# Round to closest integer
|
||||
return int(round((frequency - self.start_frequency) / self.frequency_step))
|
||||
|
||||
class Stepper:
|
||||
|
||||
class Stepper:
|
||||
"""This class is used to store the state of a stepper motor."""
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the stepper motor."""
|
||||
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:
|
||||
|
||||
def __init__(
|
||||
self, frequency: float, tuning_position: int, matching_position: int
|
||||
) -> None:
|
||||
"""Initialize the saved position."""
|
||||
self.frequency = frequency
|
||||
self.tuning_position = tuning_position
|
||||
self.matching_position = matching_position
|
||||
|
||||
def to_json(self):
|
||||
"""Convert the saved position to a JSON serializable format."""
|
||||
return {
|
||||
"frequency": self.frequency,
|
||||
"tuning_position": self.tuning_position,
|
||||
"matching_position": self.matching_position,
|
||||
}
|
||||
|
||||
|
||||
class TuningStepper(Stepper):
|
||||
"""This class is used to store the state of the tuning stepper motor."""
|
||||
TYPE = "Tuning"
|
||||
MAX_STEPS = 1e6
|
||||
BACKLASH_STEPS = 60
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the tuning stepper motor."""
|
||||
super().__init__()
|
||||
# Backlash stepper
|
||||
self.last_direction = None
|
||||
|
||||
|
||||
class MatchingStepper(Stepper):
|
||||
"""This class is used to store the state of the matching stepper motor."""
|
||||
TYPE = "Matching"
|
||||
MAX_STEPS = 1e6
|
||||
|
||||
BACKLASH_STEPS = 0
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the matching stepper motor."""
|
||||
super().__init__()
|
||||
self.last_direction = None
|
||||
|
||||
|
||||
class ElectricalLookupTable(LookupTable):
|
||||
"""This class is used to store a lookup table for tuning and matching of electrical probeheads."""
|
||||
TYPE = "Electrical"
|
||||
|
||||
def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None:
|
||||
def __init__(
|
||||
self, start_frequency: float, stop_frequency: float, frequency_step: float
|
||||
) -> None:
|
||||
"""Initialize the lookup table."""
|
||||
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.frequency_step
|
||||
self.start_frequency,
|
||||
self.stop_frequency + self.frequency_step,
|
||||
self.frequency_step,
|
||||
):
|
||||
self.started_frequency = frequency
|
||||
self.add_voltages(None, None)
|
||||
|
@ -268,8 +308,9 @@ class ElectricalLookupTable(LookupTable):
|
|||
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.
|
||||
"""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.
|
||||
|
@ -293,19 +334,25 @@ class ElectricalLookupTable(LookupTable):
|
|||
|
||||
return None
|
||||
|
||||
|
||||
class MechanicalLookupTable(LookupTable):
|
||||
"""This class is used to store a lookup table for tuning and matching of mechanical probeheads."""
|
||||
# Hmm duplicate code
|
||||
TYPE = "Mechanical"
|
||||
|
||||
|
||||
def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None:
|
||||
def __init__(
|
||||
self, start_frequency: float, stop_frequency: float, frequency_step: float
|
||||
) -> None:
|
||||
"""Initialize the lookup table."""
|
||||
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.frequency_step
|
||||
self.start_frequency,
|
||||
self.stop_frequency + self.frequency_step,
|
||||
self.frequency_step,
|
||||
):
|
||||
self.started_frequency = frequency
|
||||
self.add_positions(None, None)
|
||||
|
@ -333,8 +380,9 @@ class MechanicalLookupTable(LookupTable):
|
|||
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.
|
||||
"""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.
|
||||
|
@ -357,8 +405,10 @@ class MechanicalLookupTable(LookupTable):
|
|||
return frequency
|
||||
|
||||
return None
|
||||
class AutoTMModel(ModuleModel):
|
||||
|
||||
|
||||
class AutoTMModel(ModuleModel):
|
||||
"""The module model for the NQRduck AutoTM module. It is used to store the data and state of the AutoTM module."""
|
||||
available_devices_changed = pyqtSignal(list)
|
||||
serial_changed = pyqtSignal(QSerialPort)
|
||||
data_points_changed = pyqtSignal(list)
|
||||
|
@ -372,6 +422,7 @@ class AutoTMModel(ModuleModel):
|
|||
measurement_finished = pyqtSignal(S11Data)
|
||||
|
||||
def __init__(self, module) -> None:
|
||||
"""Initialize the AutoTM model."""
|
||||
super().__init__(module)
|
||||
self.data_points = []
|
||||
self.active_calibration = None
|
||||
|
@ -398,6 +449,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def available_devices(self):
|
||||
"""The available_devices property is used to store the available serial devices."""
|
||||
return self._available_devices
|
||||
|
||||
@available_devices.setter
|
||||
|
@ -419,6 +471,7 @@ class AutoTMModel(ModuleModel):
|
|||
self, frequency: float, return_loss: float, phase: float
|
||||
) -> None:
|
||||
"""Add a data point to the model. These data points are our intermediate data points read in via the serial connection.
|
||||
|
||||
They will be saved in the according properties later on.
|
||||
"""
|
||||
self.data_points.append((frequency, return_loss, phase))
|
||||
|
@ -431,6 +484,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def saved_positions(self):
|
||||
"""The saved_positions property is used to store the saved positions for tuning and matching of the probeheads."""
|
||||
return self._saved_positions
|
||||
|
||||
@saved_positions.setter
|
||||
|
@ -438,9 +492,13 @@ class AutoTMModel(ModuleModel):
|
|||
self._saved_positions = value
|
||||
self.saved_positions_changed.emit(value)
|
||||
|
||||
def add_saved_position(self, frequency: float, tuning_position: int, matching_position: int) -> None:
|
||||
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.append(
|
||||
SavedPosition(frequency, tuning_position, matching_position)
|
||||
)
|
||||
self.saved_positions_changed.emit(self.saved_positions)
|
||||
|
||||
def delete_saved_position(self, position: SavedPosition) -> None:
|
||||
|
@ -451,6 +509,7 @@ class AutoTMModel(ModuleModel):
|
|||
@property
|
||||
def measurement(self):
|
||||
"""The measurement property is used to store the current measurement.
|
||||
|
||||
This is the measurement that is shown in the main S11 plot
|
||||
"""
|
||||
return self._measurement
|
||||
|
@ -463,6 +522,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def active_stepper(self):
|
||||
"""The active_stepper property is used to store the active stepper motor."""
|
||||
return self._active_stepper
|
||||
|
||||
@active_stepper.setter
|
||||
|
@ -474,6 +534,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def active_calibration(self):
|
||||
"""The active_calibration property is used to store the active calibration type."""
|
||||
return self._active_calibration
|
||||
|
||||
@active_calibration.setter
|
||||
|
@ -482,6 +543,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def short_calibration(self):
|
||||
"""The short_calibration property is used to store the short calibration data."""
|
||||
return self._short_calibration
|
||||
|
||||
@short_calibration.setter
|
||||
|
@ -497,6 +559,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def open_calibration(self):
|
||||
"""The open calibration data."""
|
||||
return self._open_calibration
|
||||
|
||||
@open_calibration.setter
|
||||
|
@ -512,6 +575,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def load_calibration(self):
|
||||
"""The load calibration data."""
|
||||
return self._load_calibration
|
||||
|
||||
@load_calibration.setter
|
||||
|
@ -527,6 +591,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def calibration(self):
|
||||
"""The calibration data."""
|
||||
return self._calibration
|
||||
|
||||
@calibration.setter
|
||||
|
@ -536,6 +601,7 @@ class AutoTMModel(ModuleModel):
|
|||
|
||||
@property
|
||||
def LUT(self):
|
||||
"""The lookup table for tuning and matching of the probeheads."""
|
||||
return self._LUT
|
||||
|
||||
@LUT.setter
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"""This module contains the view class for the AutoTM module."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import cmath
|
||||
|
@ -27,7 +29,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class AutoTMView(ModuleView):
|
||||
"""The view class for the AutoTM module."""
|
||||
def __init__(self, module):
|
||||
"""Initializes the AutoTM view."""
|
||||
super().__init__(module)
|
||||
|
||||
widget = QWidget()
|
||||
|
@ -127,11 +131,27 @@ class AutoTMView(ModuleView):
|
|||
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()))
|
||||
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.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()))
|
||||
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)
|
||||
|
@ -175,6 +195,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
def on_calibration_button_clicked(self) -> None:
|
||||
"""This method is called when the calibration button is clicked.
|
||||
|
||||
It opens the calibration window.
|
||||
"""
|
||||
logger.debug("Calibration button clicked")
|
||||
|
@ -183,7 +204,11 @@ class AutoTMView(ModuleView):
|
|||
|
||||
@pyqtSlot(list)
|
||||
def on_available_devices_changed(self, available_devices: list) -> None:
|
||||
"""Update the available devices list in the view."""
|
||||
"""Update the available devices list in the view.
|
||||
|
||||
Args:
|
||||
available_devices (list): List of available devices.
|
||||
"""
|
||||
logger.debug("Updating available devices list")
|
||||
self._ui_form.portBox.clear()
|
||||
self._ui_form.portBox.addItems(available_devices)
|
||||
|
@ -201,6 +226,7 @@ class AutoTMView(ModuleView):
|
|||
@pyqtSlot()
|
||||
def on_connect_button_clicked(self) -> None:
|
||||
"""This method is called when the connect button is clicked.
|
||||
|
||||
It calls the connect method of the controller with the currently selected device.
|
||||
"""
|
||||
logger.debug("Connect button clicked")
|
||||
|
@ -217,7 +243,7 @@ class AutoTMView(ModuleView):
|
|||
logger.debug("Updating serial connection label")
|
||||
if serial.isOpen():
|
||||
self._ui_form.connectionLabel.setText(serial.portName())
|
||||
self.add_info_text("Connected to device %s" % serial.portName())
|
||||
self.add_info_text(f"Connected to device {serial.portName()}")
|
||||
# Change the connectButton to a disconnectButton
|
||||
self._ui_form.connectButton.setText("Disconnect")
|
||||
else:
|
||||
|
@ -231,7 +257,9 @@ class AutoTMView(ModuleView):
|
|||
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))
|
||||
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
|
||||
|
@ -253,6 +281,7 @@ class AutoTMView(ModuleView):
|
|||
@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")
|
||||
|
@ -263,7 +292,7 @@ class AutoTMView(ModuleView):
|
|||
"""Update the S11 plot with the current data points.
|
||||
|
||||
Args:
|
||||
data_points (list): List of data points to plot.
|
||||
data (S11Data): The current S11 data points.
|
||||
|
||||
@TODO: implement proper calibration. See the controller class for more information.
|
||||
"""
|
||||
|
@ -328,7 +357,7 @@ class AutoTMView(ModuleView):
|
|||
"""
|
||||
# Add a timestamp to the text
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
text = "[%s] %s" % (timestamp, text)
|
||||
text = f"[{timestamp}] {text}"
|
||||
text_label = QLabel(text)
|
||||
text_label.setStyleSheet("font-size: 25px;")
|
||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
||||
|
@ -351,7 +380,7 @@ class AutoTMView(ModuleView):
|
|||
)
|
||||
# Add a timestamp to the text
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
text = "[%s] %s" % (timestamp, text)
|
||||
text = f"[{timestamp}] {text}"
|
||||
text_label = QLabel(text)
|
||||
text_label.setStyleSheet("font-size: 25px; color: red;")
|
||||
|
||||
|
@ -365,7 +394,9 @@ class AutoTMView(ModuleView):
|
|||
|
||||
def create_frequency_sweep_spinner_dialog(self) -> None:
|
||||
"""Creates a frequency sweep spinner dialog."""
|
||||
self.frequency_sweep_spinner = self.LoadingSpinner("Performing frequency sweep ...", self)
|
||||
self.frequency_sweep_spinner = self.LoadingSpinner(
|
||||
"Performing frequency sweep ...", self
|
||||
)
|
||||
self.frequency_sweep_spinner.show()
|
||||
|
||||
def create_el_LUT_spinner_dialog(self) -> None:
|
||||
|
@ -375,7 +406,9 @@ class AutoTMView(ModuleView):
|
|||
|
||||
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 = self.LoadingSpinner(
|
||||
"Generating mechanical LUT ...", self
|
||||
)
|
||||
self.mech_LUT_spinner.show()
|
||||
|
||||
def view_el_lut(self) -> None:
|
||||
|
@ -417,7 +450,9 @@ class AutoTMView(ModuleView):
|
|||
self.module.controller.load_measurement(file_name)
|
||||
|
||||
class StepperSavedPositionsWindow(QDialog):
|
||||
"""This class implements a window that shows the saved positions of the stepper."""
|
||||
def __init__(self, module, parent=None):
|
||||
"""Initializes the StepperSavedPositionsWindow."""
|
||||
super().__init__(parent)
|
||||
self.setParent(parent)
|
||||
self.module = module
|
||||
|
@ -432,7 +467,13 @@ class AutoTMView(ModuleView):
|
|||
self.table_widget = QTableWidget()
|
||||
self.table_widget.setColumnCount(5)
|
||||
self.table_widget.setHorizontalHeaderLabels(
|
||||
["Frequency (MHz)", "Tuning Position", "Matching Position", "Button", "Delete"]
|
||||
[
|
||||
"Frequency (MHz)",
|
||||
"Tuning Position",
|
||||
"Matching Position",
|
||||
"Button",
|
||||
"Delete",
|
||||
]
|
||||
)
|
||||
|
||||
self.table_widget.setColumnWidth(0, 150)
|
||||
|
@ -461,8 +502,9 @@ class AutoTMView(ModuleView):
|
|||
main_layout.addWidget(self.table_widget)
|
||||
|
||||
# On saved positions changed
|
||||
self.module.model.saved_positions_changed.connect(self.on_saved_positions_changed)
|
||||
|
||||
self.module.model.saved_positions_changed.connect(
|
||||
self.on_saved_positions_changed
|
||||
)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
|
@ -482,13 +524,13 @@ class AutoTMView(ModuleView):
|
|||
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)
|
||||
logger.debug(f"Loading position from {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)
|
||||
logger.debug(f"Saving position to {filename}")
|
||||
self.module.controller.save_positions(filename)
|
||||
|
||||
def on_new_position_button_clicked(self) -> None:
|
||||
|
@ -497,9 +539,9 @@ class AutoTMView(ModuleView):
|
|||
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")
|
||||
|
@ -508,7 +550,9 @@ class AutoTMView(ModuleView):
|
|||
|
||||
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, 0, QTableWidgetItem(str(position.frequency))
|
||||
)
|
||||
self.table_widget.setItem(
|
||||
row, 1, QTableWidgetItem(position.tuning_position)
|
||||
)
|
||||
|
@ -517,7 +561,8 @@ class AutoTMView(ModuleView):
|
|||
)
|
||||
go_button = QPushButton("Go")
|
||||
go_button.clicked.connect(
|
||||
lambda _, position=position: self.module.controller.on_go_to_position(
|
||||
lambda _,
|
||||
position=position: self.module.controller.on_go_to_position(
|
||||
position
|
||||
)
|
||||
)
|
||||
|
@ -525,7 +570,8 @@ class AutoTMView(ModuleView):
|
|||
|
||||
delete_button = QPushButton("Delete")
|
||||
delete_button.clicked.connect(
|
||||
lambda _, position=position: self.module.controller.on_delete_position(
|
||||
lambda _,
|
||||
position=position: self.module.controller.on_delete_position(
|
||||
position
|
||||
)
|
||||
)
|
||||
|
@ -534,7 +580,9 @@ class AutoTMView(ModuleView):
|
|||
logger.debug("Updated saved positions table")
|
||||
|
||||
class NewPositionWindow(QDialog):
|
||||
"""This class implements a window for adding a new position."""
|
||||
def __init__(self, module, parent=None):
|
||||
"""Initializes the NewPositionWindow."""
|
||||
super().__init__(parent)
|
||||
self.setParent(parent)
|
||||
self.module = module
|
||||
|
@ -578,22 +626,32 @@ class AutoTMView(ModuleView):
|
|||
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()))
|
||||
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:
|
||||
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)
|
||||
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."""
|
||||
|
||||
def __init__(self, text: str, parent=None):
|
||||
"""Initializes the LoadingSpinner."""
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Loading")
|
||||
self.setModal(True)
|
||||
|
@ -611,7 +669,9 @@ class AutoTMView(ModuleView):
|
|||
self.spinner_movie.start()
|
||||
|
||||
class LutWindow(QDialog):
|
||||
"""This class implements a window that shows the LUT."""
|
||||
def __init__(self, module, parent=None):
|
||||
"""Initializes the LutWindow."""
|
||||
super().__init__()
|
||||
self.module = module
|
||||
self.setParent(parent)
|
||||
|
@ -659,7 +719,9 @@ class AutoTMView(ModuleView):
|
|||
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(
|
||||
lambda _,
|
||||
tuning_voltage=tuning_voltage,
|
||||
matching_voltage=matching_voltage: self.module.controller.set_voltages(
|
||||
tuning_voltage, matching_voltage
|
||||
)
|
||||
)
|
||||
|
@ -668,7 +730,9 @@ class AutoTMView(ModuleView):
|
|||
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(
|
||||
lambda _,
|
||||
tuning_position=tuning_position,
|
||||
matching_position=matching_position: self.module.controller.go_to_position(
|
||||
tuning_position, matching_position
|
||||
)
|
||||
)
|
||||
|
@ -681,6 +745,7 @@ class AutoTMView(ModuleView):
|
|||
|
||||
def test_lut(self):
|
||||
"""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
|
||||
|
@ -690,7 +755,9 @@ class AutoTMView(ModuleView):
|
|||
self.module.controller.set_voltages(tuning_voltage, matching_voltage)
|
||||
|
||||
class CalibrationWindow(QDialog):
|
||||
"""The calibration Dialog."""
|
||||
def __init__(self, module, parent=None):
|
||||
"""Initializes the CalibrationWindow."""
|
||||
super().__init__(parent)
|
||||
self.setParent(parent)
|
||||
self.module = module
|
||||
|
@ -791,18 +858,22 @@ class AutoTMView(ModuleView):
|
|||
)
|
||||
|
||||
def on_short_calibration_finished(self, short_calibration: "S11Data") -> None:
|
||||
"""This method is called when the short calibration has finished. It plots the calibration data on the short_plot widget."""
|
||||
self.on_calibration_finished("short", self.short_plot, short_calibration)
|
||||
|
||||
def on_open_calibration_finished(self, open_calibration: "S11Data") -> None:
|
||||
"""This method is called when the open calibration has finished. It plots the calibration data on the open_plot widget."""
|
||||
self.on_calibration_finished("open", self.open_plot, open_calibration)
|
||||
|
||||
def on_load_calibration_finished(self, load_calibration: "S11Data") -> None:
|
||||
"""This method is called when the load calibration has finished. It plots the calibration data on the load_plot widget."""
|
||||
self.on_calibration_finished("load", self.load_plot, load_calibration)
|
||||
|
||||
def on_calibration_finished(
|
||||
self, type: str, widget: MplWidget, data: "S11Data"
|
||||
) -> None:
|
||||
"""This method is called when a calibration has finished.
|
||||
|
||||
It plots the calibration data on the given widget.
|
||||
"""
|
||||
frequency = data.frequency
|
||||
|
@ -829,13 +900,14 @@ class AutoTMView(ModuleView):
|
|||
widget.canvas.flush_events()
|
||||
|
||||
def on_export_button_clicked(self) -> None:
|
||||
"""Called when the export button was clicked."""
|
||||
filedialog = QFileDialog()
|
||||
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
|
||||
filedialog.setNameFilter("calibration files (*.cal)")
|
||||
filedialog.setDefaultSuffix("cal")
|
||||
filedialog.exec()
|
||||
filename = filedialog.selectedFiles()[0]
|
||||
logger.debug("Exporting calibration to %s" % filename)
|
||||
logger.debug(f"Exporting calibration to {filename}")
|
||||
self.module.controller.export_calibration(filename)
|
||||
|
||||
def on_import_button_clicked(self) -> None:
|
||||
|
@ -846,7 +918,7 @@ class AutoTMView(ModuleView):
|
|||
filedialog.setDefaultSuffix("cal")
|
||||
filedialog.exec()
|
||||
filename = filedialog.selectedFiles()[0]
|
||||
logger.debug("Importing calibration from %s" % filename)
|
||||
logger.debug(f"Importing calibration from {filename}")
|
||||
self.module.controller.import_calibration(filename)
|
||||
|
||||
def on_apply_button_clicked(self) -> None:
|
||||
|
|
Loading…
Reference in a new issue