mirror of
https://github.com/nqrduck/nqrduck-autotm.git
synced 2024-09-19 10:50: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
|
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 nqrduck.module.module import Module
|
||||||
from .model import AutoTMModel
|
from .model import AutoTMModel
|
||||||
from .view import AutoTMView
|
from .view import AutoTMView
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"""The controller for the NQRduck AutoTM module."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -8,47 +10,75 @@ from PyQt6.QtCore import pyqtSlot
|
||||||
from PyQt6.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
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, SavedPosition, Stepper
|
from .model import (
|
||||||
|
S11Data,
|
||||||
|
ElectricalLookupTable,
|
||||||
|
MechanicalLookupTable,
|
||||||
|
SavedPosition,
|
||||||
|
Stepper,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AutoTMController(ModuleController):
|
class AutoTMController(ModuleController):
|
||||||
|
"""The controller for the NQRduck AutoTM module. It handles the serial connection and the signals and slots."""
|
||||||
|
|
||||||
BAUDRATE = 115200
|
BAUDRATE = 115200
|
||||||
|
|
||||||
def on_loading(self):
|
def on_loading(self) -> None:
|
||||||
"""This method is called when the module is loaded.
|
"""This method is called when the module is loaded.
|
||||||
|
|
||||||
It sets up the serial connection and connects the signals and slots.
|
It sets up the serial connection and connects the signals and slots.
|
||||||
"""
|
"""
|
||||||
logger.debug("Setting up serial connection")
|
logger.debug("Setting up serial connection")
|
||||||
self.find_devices()
|
self.find_devices()
|
||||||
|
|
||||||
# Connect signals
|
# 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_measurement_data)
|
||||||
self.module.model.serial_data_received.connect(self.process_calibration_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.print_info)
|
||||||
self.module.model.serial_data_received.connect(self.read_position_data)
|
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_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)
|
self.module.model.serial_data_received.connect(self.process_signalpath_data)
|
||||||
|
|
||||||
@pyqtSlot(str, object)
|
@pyqtSlot(str, object)
|
||||||
def process_signals(self, key: str, value: object) -> None:
|
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)
|
logger.debug("Received signal: %s", key)
|
||||||
if key == "set_tune_and_match":
|
if key == "set_tune_and_match":
|
||||||
self.tune_and_match(value)
|
self.tune_and_match(value)
|
||||||
|
|
||||||
def tune_and_match(self, frequency: float) -> None:
|
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:
|
if self.module.model.LUT is None:
|
||||||
logger.error("Could not tune and match. No LUT available.")
|
logger.error("Could not tune and match. No LUT available.")
|
||||||
return
|
return
|
||||||
elif self.module.model.LUT.TYPE == "Electrical":
|
elif self.module.model.LUT.TYPE == "Electrical":
|
||||||
tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(frequency)
|
tuning_voltage, matching_voltage = self.module.model.LUT.get_voltages(
|
||||||
confirmation = self.set_voltages(str(tuning_voltage), str(matching_voltage))
|
frequency
|
||||||
|
)
|
||||||
|
_ = self.set_voltages(str(tuning_voltage), str(matching_voltage))
|
||||||
# We need to change the signal pathway to preamp to measure the reflection
|
# We need to change the signal pathway to preamp to measure the reflection
|
||||||
self.switch_to_atm()
|
self.switch_to_atm()
|
||||||
reflection = self.read_reflection(frequency)
|
reflection = self.read_reflection(frequency)
|
||||||
|
@ -57,7 +87,9 @@ class AutoTMController(ModuleController):
|
||||||
self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection)
|
self.module.nqrduck_signal.emit("confirm_tune_and_match", reflection)
|
||||||
|
|
||||||
elif self.module.model.LUT.TYPE == "Mechanical":
|
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.go_to_position(tuning_position, matching_position)
|
||||||
self.switch_to_atm()
|
self.switch_to_atm()
|
||||||
# Switch to atm to measure the reflection
|
# 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.
|
# 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
|
# 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:
|
def find_devices(self) -> None:
|
||||||
"""Scan for available serial devices and add them to the model as available devices."""
|
"""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:
|
def start_frequency_sweep(self, start_frequency: str, stop_frequency: str) -> None:
|
||||||
"""This starts a frequency sweep on the device in the specified range.
|
"""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.
|
The minimum start and stop frequency are specific to the AD4351 based frequency generator.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -162,13 +200,8 @@ class AutoTMController(ModuleController):
|
||||||
return
|
return
|
||||||
|
|
||||||
if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY:
|
if start_frequency < MIN_FREQUENCY or stop_frequency > MAX_FREQUENCY:
|
||||||
error = (
|
error = f"Could not start frequency sweep. Start and stop frequency must be between {MIN_FREQUENCY / 1e6} and {MAX_FREQUENCY / 1e6} MHz"
|
||||||
"Could not start frequency sweep. Start and stop frequency must be between %s and %s MHz"
|
|
||||||
% (
|
|
||||||
MIN_FREQUENCY / 1e6,
|
|
||||||
MAX_FREQUENCY / 1e6,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
@ -182,7 +215,7 @@ class AutoTMController(ModuleController):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Print the command 'f<start>f<stop>f<step>' to the serial connection
|
# 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()
|
self.module.model.frequency_sweep_start = time.time()
|
||||||
confirmation = self.send_command(command)
|
confirmation = self.send_command(command)
|
||||||
if confirmation:
|
if confirmation:
|
||||||
|
@ -191,20 +224,31 @@ class AutoTMController(ModuleController):
|
||||||
self.module.view.create_frequency_sweep_spinner_dialog()
|
self.module.view.create_frequency_sweep_spinner_dialog()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def process_frequency_sweep_data(self, text : str) -> None:
|
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.
|
"""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.
|
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")
|
text = text[1:].split("r")
|
||||||
frequency = float(text[0])
|
frequency = float(text[0])
|
||||||
return_loss, phase = map(float, text[1].split("p"))
|
return_loss, phase = map(float, text[1].split("p"))
|
||||||
self.module.model.add_data_point(frequency, return_loss, phase)
|
self.module.model.add_data_point(frequency, return_loss, phase)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def process_measurement_data(self, text : str) -> None:
|
def process_measurement_data(self, text: str) -> None:
|
||||||
"""This method is called when data is received from the serial connection during a measurement.
|
"""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.
|
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"):
|
if self.module.model.active_calibration is None and text.startswith("r"):
|
||||||
logger.debug("Measurement finished")
|
logger.debug("Measurement finished")
|
||||||
|
@ -214,24 +258,33 @@ class AutoTMController(ModuleController):
|
||||||
self.finish_frequency_sweep()
|
self.finish_frequency_sweep()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def process_calibration_data(self, text : str) -> None:
|
def process_calibration_data(self, text: str) -> None:
|
||||||
"""This method is called when data is received from the serial connection during a calibration.
|
"""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.
|
It processes the data and adds it to the model.
|
||||||
|
|
||||||
Args:
|
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 [
|
||||||
calibration_type = self.module.model.active_calibration
|
"short",
|
||||||
|
"open",
|
||||||
|
"load",
|
||||||
|
]:
|
||||||
|
calibration_type = self.module.model.active_calibration
|
||||||
logger.debug(f"{calibration_type.capitalize()} calibration finished")
|
logger.debug(f"{calibration_type.capitalize()} calibration finished")
|
||||||
setattr(self.module.model, f"{calibration_type}_calibration",
|
setattr(
|
||||||
S11Data(self.module.model.data_points.copy()))
|
self.module.model,
|
||||||
|
f"{calibration_type}_calibration",
|
||||||
|
S11Data(self.module.model.data_points.copy()),
|
||||||
|
)
|
||||||
self.module.model.active_calibration = None
|
self.module.model.active_calibration = None
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def process_voltage_sweep_result(self, text : str) -> None:
|
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.
|
"""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.
|
It processes the data and adds it to the model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -243,25 +296,40 @@ class AutoTMController(ModuleController):
|
||||||
LUT = self.module.model.el_lut
|
LUT = self.module.model.el_lut
|
||||||
if LUT is not None:
|
if LUT is not None:
|
||||||
if LUT.is_incomplete():
|
if LUT.is_incomplete():
|
||||||
logger.debug("Received voltage sweep result: Tuning %s Matching %s", tuning_voltage, matching_voltage)
|
logger.debug(
|
||||||
LUT.add_voltages(tuning_voltage, matching_voltage)
|
"Received voltage sweep result: Tuning %s Matching %s",
|
||||||
self.continue_or_finish_voltage_sweep(LUT)
|
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.tuning_voltage = tuning_voltage
|
||||||
self.module.model.matching_voltage = matching_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.
|
"""This method is called when a frequency sweep is finished.
|
||||||
|
|
||||||
It hides the frequency sweep spinner dialog and adds the data to the model.
|
It hides the frequency sweep spinner dialog and adds the data to the model.
|
||||||
"""
|
"""
|
||||||
self.module.view.frequency_sweep_spinner.hide()
|
self.module.view.frequency_sweep_spinner.hide()
|
||||||
self.module.model.frequency_sweep_stop = time.time()
|
self.module.model.frequency_sweep_stop = time.time()
|
||||||
duration = self.module.model.frequency_sweep_stop - self.module.model.frequency_sweep_start
|
duration = (
|
||||||
self.module.view.add_info_text(f"Frequency sweep finished in {duration:.2f} seconds")
|
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.
|
"""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.
|
It checks if the voltage sweep is finished or if the next voltage sweep should be started.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -274,8 +342,9 @@ class AutoTMController(ModuleController):
|
||||||
# Finish voltage sweep
|
# Finish voltage sweep
|
||||||
self.finish_voltage_sweep(LUT)
|
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.
|
"""This method is called when a voltage sweep is finished.
|
||||||
|
|
||||||
It starts the next voltage sweep.
|
It starts the next voltage sweep.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -286,32 +355,38 @@ class AutoTMController(ModuleController):
|
||||||
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
||||||
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
||||||
# We use the currently set voltages
|
# 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:
|
else:
|
||||||
command = "s%s" % (next_frequency)
|
command = f"s{next_frequency}"
|
||||||
|
|
||||||
LUT.started_frequency = next_frequency
|
LUT.started_frequency = next_frequency
|
||||||
logger.debug("Starting next voltage sweep: %s", command)
|
logger.debug("Starting next voltage sweep: %s", command)
|
||||||
self.send_command(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.
|
"""This method is called when a voltage sweep is finished.
|
||||||
|
|
||||||
It hides the voltage sweep spinner dialog and adds the data to the model.
|
It hides the voltage sweep spinner dialog and adds the data to the model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
LUT (LookupTable): The lookup table that is being generated.
|
LUT (LookupTable): The lookup table that is being generated.
|
||||||
"""
|
"""
|
||||||
logger.debug("Voltage sweep finished")
|
logger.debug("Voltage sweep finished")
|
||||||
self.module.view.el_LUT_spinner.hide()
|
self.module.view.el_LUT_spinner.hide()
|
||||||
self.module.model.LUT = LUT
|
self.module.model.LUT = LUT
|
||||||
self.module.model.voltage_sweep_stop = time.time()
|
self.module.model.voltage_sweep_stop = time.time()
|
||||||
duration = self.module.model.voltage_sweep_stop - self.module.model.voltage_sweep_start
|
duration = (
|
||||||
self.module.view.add_info_text(f"Voltage sweep finished in {duration:.2f} seconds")
|
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)
|
self.module.nqrduck_signal.emit("LUT_finished", LUT)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def print_info(self, text : str) -> None:
|
def print_info(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 prints the data to the info text box.
|
It prints the data to the info text box.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -325,8 +400,14 @@ class AutoTMController(ModuleController):
|
||||||
self.module.view.add_error_text(text)
|
self.module.view.add_error_text(text)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def read_position_data(self, text : str) -> None:
|
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"):
|
if text.startswith("p"):
|
||||||
# Format is p<tuning_position>m<matching_position>
|
# Format is p<tuning_position>m<matching_position>
|
||||||
text = text[1:].split("m")
|
text = text[1:].split("m")
|
||||||
|
@ -335,7 +416,11 @@ class AutoTMController(ModuleController):
|
||||||
self.module.model.matching_stepper.position = matching_position
|
self.module.model.matching_stepper.position = matching_position
|
||||||
self.module.model.tuning_stepper.homed = True
|
self.module.model.tuning_stepper.homed = True
|
||||||
self.module.model.matching_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()
|
self.module.view.on_active_stepper_changed()
|
||||||
|
|
||||||
def on_ready_read(self) -> None:
|
def on_ready_read(self) -> None:
|
||||||
|
@ -349,8 +434,9 @@ class AutoTMController(ModuleController):
|
||||||
self.module.model.serial_data_received.emit(text)
|
self.module.model.serial_data_received.emit(text)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@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.
|
"""This method is called when data is received from the serial connection.
|
||||||
|
|
||||||
It processes the data and adds it to the model.
|
It processes the data and adds it to the model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -367,7 +453,12 @@ class AutoTMController(ModuleController):
|
||||||
self, start_frequency: float, stop_frequency: float
|
self, start_frequency: float, stop_frequency: float
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This method is called when the short calibration button is pressed.
|
"""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.
|
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")
|
logger.debug("Starting short calibration")
|
||||||
self.module.model.init_short_calibration()
|
self.module.model.init_short_calibration()
|
||||||
|
@ -377,7 +468,12 @@ class AutoTMController(ModuleController):
|
||||||
self, start_frequency: float, stop_frequency: float
|
self, start_frequency: float, stop_frequency: float
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This method is called when the open calibration button is pressed.
|
"""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.
|
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")
|
logger.debug("Starting open calibration")
|
||||||
self.module.model.init_open_calibration()
|
self.module.model.init_open_calibration()
|
||||||
|
@ -387,7 +483,12 @@ class AutoTMController(ModuleController):
|
||||||
self, start_frequency: float, stop_frequency: float
|
self, start_frequency: float, stop_frequency: float
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This method is called when the load calibration button is pressed.
|
"""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.
|
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")
|
logger.debug("Starting load calibration")
|
||||||
self.module.model.init_load_calibration()
|
self.module.model.init_load_calibration()
|
||||||
|
@ -395,6 +496,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def calculate_calibration(self) -> None:
|
def calculate_calibration(self) -> None:
|
||||||
"""This method is called when the calculate calibration button is pressed.
|
"""This method is called when the calculate calibration button is pressed.
|
||||||
|
|
||||||
It calculates the calibration from the short, open and calibration data points.
|
It calculates the calibration from the short, open and calibration data points.
|
||||||
|
|
||||||
@TODO: Improvements to the calibrations can be made the following ways:
|
@TODO: Improvements to the calibrations can be made the following ways:
|
||||||
|
@ -406,17 +508,17 @@ class AutoTMController(ModuleController):
|
||||||
"""
|
"""
|
||||||
logger.debug("Calculating calibration")
|
logger.debug("Calculating calibration")
|
||||||
# First we check if the short and open calibration data points are available
|
# 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(
|
logger.error(
|
||||||
"Could not calculate calibration. No short calibration data points available."
|
"Could not calculate calibration. No short calibration data points available."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if self.module.model.open_calibration == None:
|
if self.module.model.open_calibration is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not calculate calibration. No open calibration data points available."
|
"Could not calculate calibration. No open calibration data points available."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if self.module.model.load_calibration == None:
|
if self.module.model.load_calibration is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not calculate calibration. No load calibration data points available."
|
"Could not calculate calibration. No load calibration data points available."
|
||||||
)
|
)
|
||||||
|
@ -459,6 +561,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def export_calibration(self, filename: str) -> None:
|
def export_calibration(self, filename: str) -> None:
|
||||||
"""This method is called when the export calibration button is pressed.
|
"""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.
|
It exports the data of the short, open and load calibration to a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -466,19 +569,19 @@ class AutoTMController(ModuleController):
|
||||||
"""
|
"""
|
||||||
logger.debug("Exporting calibration")
|
logger.debug("Exporting calibration")
|
||||||
# First we check if the short and open calibration data points are available
|
# 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(
|
logger.error(
|
||||||
"Could not export calibration. No short calibration data points available."
|
"Could not export calibration. No short calibration data points available."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.module.model.open_calibration == None:
|
if self.module.model.open_calibration is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not export calibration. No open calibration data points available."
|
"Could not export calibration. No open calibration data points available."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.module.model.load_calibration == None:
|
if self.module.model.load_calibration is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Could not export calibration. No load calibration data points available."
|
"Could not export calibration. No load calibration data points available."
|
||||||
)
|
)
|
||||||
|
@ -496,6 +599,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def import_calibration(self, filename: str) -> None:
|
def import_calibration(self, filename: str) -> None:
|
||||||
"""This method is called when the import calibration button is pressed.
|
"""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.
|
It imports the data of the short, open and load calibration from a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -542,6 +646,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None:
|
def set_voltages(self, tuning_voltage: str, matching_voltage: str) -> None:
|
||||||
"""This method is called when the set voltages button is pressed.
|
"""This method is called when the set voltages button is pressed.
|
||||||
|
|
||||||
It writes the specified tuning and matching voltage to the serial connection.
|
It writes the specified tuning and matching voltage to the serial connection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -583,16 +688,22 @@ class AutoTMController(ModuleController):
|
||||||
matching_voltage,
|
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")
|
logger.debug("Voltages already set")
|
||||||
return
|
return
|
||||||
|
|
||||||
command = "v%sv%s" % (tuning_voltage, matching_voltage)
|
command = f"v{tuning_voltage}v{matching_voltage}"
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
confirmation = self.send_command(command)
|
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()
|
QApplication.processEvents()
|
||||||
# Check for timeout
|
# Check for timeout
|
||||||
if time.time() - start_time > timeout_duration:
|
if time.time() - start_time > timeout_duration:
|
||||||
|
@ -614,6 +725,7 @@ class AutoTMController(ModuleController):
|
||||||
frequency_step: str,
|
frequency_step: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This method is called when the generate LUT button is pressed.
|
"""This method is called when the generate LUT button is pressed.
|
||||||
|
|
||||||
It generates a lookup table for the specified frequency range and voltage resolution.
|
It generates a lookup table for the specified frequency range and voltage resolution.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -635,11 +747,7 @@ class AutoTMController(ModuleController):
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0:
|
||||||
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"
|
error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be positive"
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
|
@ -669,9 +777,7 @@ class AutoTMController(ModuleController):
|
||||||
# self.set_voltages("0", "0")
|
# self.set_voltages("0", "0")
|
||||||
|
|
||||||
# We create the lookup table
|
# We create the lookup table
|
||||||
LUT = ElectricalLookupTable(
|
LUT = ElectricalLookupTable(start_frequency, stop_frequency, frequency_step)
|
||||||
start_frequency, stop_frequency, frequency_step
|
|
||||||
)
|
|
||||||
|
|
||||||
LUT.started_frequency = start_frequency
|
LUT.started_frequency = start_frequency
|
||||||
|
|
||||||
|
@ -679,10 +785,14 @@ class AutoTMController(ModuleController):
|
||||||
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
if self.module.view._ui_form.prevVoltagecheckBox.isChecked():
|
||||||
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
# Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
|
||||||
# We use the currently set voltages
|
# 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)
|
logger.debug(
|
||||||
command = "s%so%so%s" % (start_frequency, self.module.model.tuning_voltage, self.module.model.matching_voltage)
|
"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:
|
else:
|
||||||
command = "s%s" % (start_frequency)
|
command = f"s{start_frequency}"
|
||||||
|
|
||||||
# For timing of the voltage sweep
|
# For timing of the voltage sweep
|
||||||
self.module.model.voltage_sweep_start = time.time()
|
self.module.model.voltage_sweep_start = time.time()
|
||||||
|
@ -694,6 +804,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def switch_to_preamp(self) -> None:
|
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 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.
|
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":
|
if self.module.model.signal_path == "preamp":
|
||||||
|
@ -714,6 +825,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
def switch_to_atm(self) -> None:
|
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.
|
"""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.
|
In this state the atm system can be used to measure the reflection coefficient of the probecoils.
|
||||||
"""
|
"""
|
||||||
if self.module.model.signal_path == "atm":
|
if self.module.model.signal_path == "atm":
|
||||||
|
@ -732,8 +844,9 @@ class AutoTMController(ModuleController):
|
||||||
logger.error("Switching to atm timed out")
|
logger.error("Switching to atm timed out")
|
||||||
break
|
break
|
||||||
|
|
||||||
def process_signalpath_data(self, text : str) -> None:
|
def process_signalpath_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.
|
It processes the data and adds it to the model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -765,7 +878,7 @@ class AutoTMController(ModuleController):
|
||||||
)
|
)
|
||||||
return False
|
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")
|
logger.error("Could not send command. Serial connection is not open")
|
||||||
self.module.view.add_error_text(
|
self.module.view.add_error_text(
|
||||||
"Could not send command. Serial connection is not open"
|
"Could not send command. Serial connection is not open"
|
||||||
|
@ -796,12 +909,13 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Could not send command. %s", 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 ###
|
### Stepper Motor Control ###
|
||||||
|
|
||||||
def homing(self) -> None:
|
def homing(self) -> None:
|
||||||
"""This method is used to send the command 'h' to the atm system.
|
"""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.
|
This command is used to home the stepper motors of the atm system.
|
||||||
"""
|
"""
|
||||||
logger.debug("Homing")
|
logger.debug("Homing")
|
||||||
|
@ -812,6 +926,7 @@ class AutoTMController(ModuleController):
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def on_stepper_changed(self, stepper: str) -> None:
|
def on_stepper_changed(self, stepper: str) -> None:
|
||||||
"""This method is called when the stepper position is changed.
|
"""This method is called when the stepper position is changed.
|
||||||
|
|
||||||
It sends the command to the atm system to change the stepper position.
|
It sends the command to the atm system to change the stepper position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -824,25 +939,52 @@ class AutoTMController(ModuleController):
|
||||||
elif stepper == "matching":
|
elif stepper == "matching":
|
||||||
self.module.model.active_stepper = self.module.model.matching_stepper
|
self.module.model.active_stepper = self.module.model.matching_stepper
|
||||||
|
|
||||||
def validate_position(self, future_position: int, stepper : Stepper) -> bool:
|
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:
|
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
|
return False
|
||||||
|
|
||||||
if future_position > 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}")
|
self.module.view.add_error_text(
|
||||||
|
f"Could not move stepper. Stepper position cannot be larger than {stepper.MAX_STEPS}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def calculate_steps_for_absolute_move(self, target_position: int, stepper : Stepper) -> int:
|
def calculate_steps_for_absolute_move(
|
||||||
"""Calculate the number of steps for an 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
|
current_position = stepper.position
|
||||||
return target_position - current_position
|
return target_position - current_position
|
||||||
|
|
||||||
def send_stepper_command(self, steps: int, stepper : Stepper) -> None:
|
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
|
# Here we handle backlash of the tuner
|
||||||
# Determine the direction of the current steps
|
# Determine the direction of the current steps
|
||||||
backlash = 0
|
backlash = 0
|
||||||
|
@ -862,7 +1004,12 @@ class AutoTMController(ModuleController):
|
||||||
return confirmation
|
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."""
|
"""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
|
timeout_duration = 15 # timeout in seconds
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
|
@ -876,7 +1023,9 @@ class AutoTMController(ModuleController):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.validate_position(future_position, stepper):
|
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:
|
while stepper_position == stepper.position:
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
|
@ -888,7 +1037,12 @@ class AutoTMController(ModuleController):
|
||||||
return confirmation
|
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."""
|
"""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
|
timeout_duration = 15 # timeout in seconds
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
|
@ -903,7 +1057,9 @@ class AutoTMController(ModuleController):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.validate_position(future_position, stepper):
|
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)
|
confirmation = self.send_stepper_command(actual_steps, stepper)
|
||||||
|
|
||||||
while stepper_position == stepper.position:
|
while stepper_position == stepper.position:
|
||||||
|
@ -917,7 +1073,7 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
### Position Saving and Loading ###
|
### Position Saving and Loading ###
|
||||||
|
|
||||||
def load_positions(self, path : str) -> None:
|
def load_positions(self, path: str) -> None:
|
||||||
"""Load the saved positions from a json file.
|
"""Load the saved positions from a json file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -930,8 +1086,11 @@ class AutoTMController(ModuleController):
|
||||||
positions = json.load(f)
|
positions = json.load(f)
|
||||||
for position in positions:
|
for position in positions:
|
||||||
logger.debug("Loading position: %s", position)
|
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:
|
def save_positions(self, path: str) -> None:
|
||||||
"""Save the current positions to a json file.
|
"""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_position = [position.to_json() for position in positions]
|
||||||
json.dump(json_position, f)
|
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.
|
"""Add a position to the lookup table.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -953,7 +1114,9 @@ class AutoTMController(ModuleController):
|
||||||
matching_position (str): The matching position.
|
matching_position (str): The matching position.
|
||||||
"""
|
"""
|
||||||
logger.debug("Adding new position at %s MHz", frequency)
|
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:
|
def on_go_to_position(self, position: SavedPosition) -> None:
|
||||||
"""Go to the specified position.
|
"""Go to the specified position.
|
||||||
|
@ -962,9 +1125,13 @@ class AutoTMController(ModuleController):
|
||||||
position (SavedPosition): The position to go to.
|
position (SavedPosition): The position to go to.
|
||||||
"""
|
"""
|
||||||
logger.debug("Going to position: %s", position)
|
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:
|
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:
|
def on_delete_position(self, position: SavedPosition) -> None:
|
||||||
"""Delete the specified position.
|
"""Delete the specified position.
|
||||||
|
@ -975,10 +1142,11 @@ class AutoTMController(ModuleController):
|
||||||
logger.debug("Deleting position: %s", position)
|
logger.debug("Deleting position: %s", position)
|
||||||
self.module.model.delete_saved_position(position)
|
self.module.model.delete_saved_position(position)
|
||||||
|
|
||||||
|
|
||||||
#### Mechanical tuning and matching ####
|
#### 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.
|
"""Generate a lookup table for the specified frequency range and voltage resolution.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -999,11 +1167,7 @@ class AutoTMController(ModuleController):
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if start_frequency < 0 or stop_frequency < 0 or frequency_step < 0:
|
||||||
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"
|
error = "Could not generate LUT. Start frequency, stop frequency, frequency step must be positive"
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
self.module.view.add_info_text(error)
|
self.module.view.add_info_text(error)
|
||||||
|
@ -1032,9 +1196,7 @@ class AutoTMController(ModuleController):
|
||||||
self.switch_to_atm()
|
self.switch_to_atm()
|
||||||
|
|
||||||
# We create the lookup table
|
# We create the lookup table
|
||||||
LUT = MechanicalLookupTable(
|
LUT = MechanicalLookupTable(start_frequency, stop_frequency, frequency_step)
|
||||||
start_frequency, stop_frequency, frequency_step
|
|
||||||
)
|
|
||||||
|
|
||||||
# Lock GUI
|
# Lock GUI
|
||||||
self.module.view.create_mech_LUT_spinner_dialog()
|
self.module.view.create_mech_LUT_spinner_dialog()
|
||||||
|
@ -1043,9 +1205,12 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
self.start_next_mechTM(LUT)
|
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):
|
Args:
|
||||||
"""Start the next mechanical tuning and matching sweep."""
|
LUT (MechanicalLookupTable): The lookup table.
|
||||||
|
"""
|
||||||
next_frequency = LUT.get_next_frequency()
|
next_frequency = LUT.get_next_frequency()
|
||||||
LUT.started_frequency = next_frequency
|
LUT.started_frequency = next_frequency
|
||||||
logger.debug("Starting next mechanical tuning and matching:")
|
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
|
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}"
|
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)
|
@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"):
|
if text.startswith("z"):
|
||||||
text = text[1:]
|
text = text[1:]
|
||||||
# Format is z<tuning_position>,<tuning_last_direction>m<matching_position>,<matching_last_direction>
|
# Format is z<tuning_position>,<tuning_last_direction>m<matching_position>,<matching_last_direction>
|
||||||
|
@ -1088,44 +1258,71 @@ class AutoTMController(ModuleController):
|
||||||
self.module.model.matching_stepper.position = matching_position
|
self.module.model.matching_stepper.position = matching_position
|
||||||
self.module.view.on_active_stepper_changed()
|
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
|
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)
|
LUT.add_positions(tuning_position, matching_position)
|
||||||
self.continue_or_finish_position_sweep(LUT)
|
self.continue_or_finish_position_sweep(LUT)
|
||||||
|
|
||||||
def continue_or_finish_position_sweep(self, LUT):
|
def continue_or_finish_position_sweep(self, LUT) -> None:
|
||||||
"""Continue or finish the position sweep."""
|
"""Continue or finish the position sweep.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
LUT (MechanicalLookupTable): The lookup table.
|
||||||
|
"""
|
||||||
if LUT.is_incomplete():
|
if LUT.is_incomplete():
|
||||||
self.start_next_mechTM(LUT)
|
self.start_next_mechTM(LUT)
|
||||||
else:
|
else:
|
||||||
self.finish_position_sweep(LUT)
|
self.finish_position_sweep(LUT)
|
||||||
|
|
||||||
def finish_position_sweep(self, LUT):
|
def finish_position_sweep(self, LUT) -> None:
|
||||||
"""Finish the position sweep."""
|
"""Finish the position sweep.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
LUT (MechanicalLookupTable): The lookup table.
|
||||||
|
"""
|
||||||
logger.debug("Finished position sweep")
|
logger.debug("Finished position sweep")
|
||||||
self.module.model.mech_lut = LUT
|
self.module.model.mech_lut = LUT
|
||||||
self.module.model.LUT = LUT
|
self.module.model.LUT = LUT
|
||||||
self.module.view.mech_LUT_spinner.hide()
|
self.module.view.mech_LUT_spinner.hide()
|
||||||
self.module.nqrduck_signal.emit("LUT_finished", LUT)
|
self.module.nqrduck_signal.emit("LUT_finished", LUT)
|
||||||
|
|
||||||
def go_to_position(self, tuning_position : int, matching_position : int) -> None:
|
def go_to_position(self, tuning_position: int, matching_position: int) -> None:
|
||||||
"""Go to the specified position.
|
"""Go to the specified position.
|
||||||
|
|
||||||
Args:
|
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:
|
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:
|
if confirmation:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# This method isn't used anymore but it might be useful in the future so I'll keep it here
|
# 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:
|
def read_reflection(self, frequency : float) -> float:
|
||||||
"""Starts a reflection measurement and reads the reflection at the specified frequency."""
|
"""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
|
# We send the command to the atm system
|
||||||
command = f"r{frequency}"
|
command = f"r{frequency}"
|
||||||
try:
|
try:
|
||||||
|
@ -1143,8 +1340,13 @@ class AutoTMController(ModuleController):
|
||||||
while reflection is None:
|
while reflection is None:
|
||||||
# Check if the timeout has been reached
|
# Check if the timeout has been reached
|
||||||
if time.time() - start_time > timeout_duration:
|
if time.time() - start_time > timeout_duration:
|
||||||
logger.error("Reading reflection timed out after %d seconds", timeout_duration)
|
logger.error(
|
||||||
self.module.view.add_error_text(f"Could not read reflection. Timed out after {timeout_duration} seconds")
|
"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
|
return None
|
||||||
|
|
||||||
# Refresh the reflection data
|
# Refresh the reflection data
|
||||||
|
@ -1163,12 +1365,12 @@ class AutoTMController(ModuleController):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error("Could not read reflection. No confirmation received")
|
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
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Could not read reflection. %s", e)
|
logger.error("Could not read reflection. %s", e)
|
||||||
self.module.view.add_error_text(f"Could not read reflection. {e}")
|
self.module.view.add_error_text(f"Could not read reflection. {e}")
|
||||||
return None
|
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 cmath
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import logging
|
import logging
|
||||||
|
@ -10,6 +17,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class S11Data:
|
class S11Data:
|
||||||
|
"""This class is used to store the S11 data that is read in via the serial connection."""
|
||||||
FILE_EXTENSION = "s11"
|
FILE_EXTENSION = "s11"
|
||||||
# Conversion factors - the data is generally sent and received in mV
|
# Conversion factors - the data is generally sent and received in mV
|
||||||
# These values are used to convert the data to dB and degrees
|
# These values are used to convert the data to dB and degrees
|
||||||
|
@ -19,26 +27,32 @@ class S11Data:
|
||||||
PHASE_SLOPE = 10 # deg/mV
|
PHASE_SLOPE = 10 # deg/mV
|
||||||
|
|
||||||
def __init__(self, data_points: list) -> None:
|
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.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.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])
|
self.phase_mv = np.array([data_point[2] for data_point in data_points])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def millivolts(self):
|
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
|
return self.frequency, self.return_loss_mv, self.phase_mv
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def return_loss_db(self):
|
def return_loss_db(self):
|
||||||
|
"""Returns the return loss in dB calculated from the return loss in mV."""
|
||||||
return (
|
return (
|
||||||
self.return_loss_mv - self.CENTER_POINT_MAGNITUDE
|
self.return_loss_mv - self.CENTER_POINT_MAGNITUDE
|
||||||
) / self.MAGNITUDE_SLOPE
|
) / self.MAGNITUDE_SLOPE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def phase_deg(self, phase_correction=True):
|
def phase_deg(self, phase_correction=True) -> np.array:
|
||||||
"""Returns the absolute value of the phase in degrees
|
"""Returns the absolute value of the phase in degrees.
|
||||||
|
|
||||||
Keyword Arguments:
|
Args:
|
||||||
phase_correction {bool} -- If True, the phase correction is applied. (default: {False})
|
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
|
phase_deg = (self.phase_mv - self.CENTER_POINT_PHASE) / self.PHASE_SLOPE
|
||||||
if phase_correction:
|
if phase_correction:
|
||||||
|
@ -48,11 +62,12 @@ class S11Data:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def phase_rad(self):
|
def phase_rad(self):
|
||||||
|
"""Returns the phase in radians."""
|
||||||
return self.phase_deg * cmath.pi / 180
|
return self.phase_deg * cmath.pi / 180
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gamma(self):
|
def gamma(self):
|
||||||
"""Complex reflection coefficient"""
|
"""Complex reflection coefficient."""
|
||||||
if len(self.return_loss_db) != len(self.phase_rad):
|
if len(self.return_loss_db) != len(self.phase_rad):
|
||||||
raise ValueError("return_loss_db and phase_rad must be the same length")
|
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
|
self, frequency_data: np.array, phase_data: np.array
|
||||||
) -> np.array:
|
) -> np.array:
|
||||||
"""This method fixes the phase sign of the phase data.
|
"""This method fixes the phase sign of the phase data.
|
||||||
|
|
||||||
The AD8302 can only measure the absolute value of the phase.
|
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.
|
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.
|
If the slope is negative, the phase is positive and vice versa.
|
||||||
|
@ -145,6 +161,7 @@ class S11Data:
|
||||||
return phase_data_corrected
|
return phase_data_corrected
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
|
"""Convert the S11 data to a JSON serializable format."""
|
||||||
return {
|
return {
|
||||||
"frequency": self.frequency.tolist(),
|
"frequency": self.frequency.tolist(),
|
||||||
"return_loss_mv": self.return_loss_mv.tolist(),
|
"return_loss_mv": self.return_loss_mv.tolist(),
|
||||||
|
@ -153,6 +170,7 @@ class S11Data:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, json):
|
def from_json(cls, json):
|
||||||
|
"""Create an S11Data object from a JSON serializable format."""
|
||||||
f = json["frequency"]
|
f = json["frequency"]
|
||||||
rl = json["return_loss_mv"]
|
rl = json["return_loss_mv"]
|
||||||
p = json["phase_mv"]
|
p = json["phase_mv"]
|
||||||
|
@ -171,6 +189,7 @@ class LookupTable:
|
||||||
stop_frequency: float,
|
stop_frequency: float,
|
||||||
frequency_step: float,
|
frequency_step: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Initialize the lookup table."""
|
||||||
self.start_frequency = start_frequency
|
self.start_frequency = start_frequency
|
||||||
self.stop_frequency = stop_frequency
|
self.stop_frequency = stop_frequency
|
||||||
self.frequency_step = frequency_step
|
self.frequency_step = frequency_step
|
||||||
|
@ -190,57 +209,78 @@ class LookupTable:
|
||||||
# Round to closest integer
|
# Round to closest integer
|
||||||
return int(round((frequency - self.start_frequency) / self.frequency_step))
|
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:
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the stepper motor."""
|
||||||
self.homed = False
|
self.homed = False
|
||||||
self.position = 0
|
self.position = 0
|
||||||
|
|
||||||
|
|
||||||
class SavedPosition:
|
class SavedPosition:
|
||||||
"""This class is used to store a saved position for tuning and matching of electrical probeheads."""
|
"""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.frequency = frequency
|
||||||
self.tuning_position = tuning_position
|
self.tuning_position = tuning_position
|
||||||
self.matching_position = matching_position
|
self.matching_position = matching_position
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
|
"""Convert the saved position to a JSON serializable format."""
|
||||||
return {
|
return {
|
||||||
"frequency": self.frequency,
|
"frequency": self.frequency,
|
||||||
"tuning_position": self.tuning_position,
|
"tuning_position": self.tuning_position,
|
||||||
"matching_position": self.matching_position,
|
"matching_position": self.matching_position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TuningStepper(Stepper):
|
class TuningStepper(Stepper):
|
||||||
|
"""This class is used to store the state of the tuning stepper motor."""
|
||||||
TYPE = "Tuning"
|
TYPE = "Tuning"
|
||||||
MAX_STEPS = 1e6
|
MAX_STEPS = 1e6
|
||||||
BACKLASH_STEPS = 60
|
BACKLASH_STEPS = 60
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the tuning stepper motor."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
# Backlash stepper
|
# Backlash stepper
|
||||||
self.last_direction = None
|
self.last_direction = None
|
||||||
|
|
||||||
|
|
||||||
class MatchingStepper(Stepper):
|
class MatchingStepper(Stepper):
|
||||||
|
"""This class is used to store the state of the matching stepper motor."""
|
||||||
TYPE = "Matching"
|
TYPE = "Matching"
|
||||||
MAX_STEPS = 1e6
|
MAX_STEPS = 1e6
|
||||||
|
|
||||||
BACKLASH_STEPS = 0
|
BACKLASH_STEPS = 0
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the matching stepper motor."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.last_direction = None
|
self.last_direction = None
|
||||||
|
|
||||||
|
|
||||||
class ElectricalLookupTable(LookupTable):
|
class ElectricalLookupTable(LookupTable):
|
||||||
|
"""This class is used to store a lookup table for tuning and matching of electrical probeheads."""
|
||||||
TYPE = "Electrical"
|
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)
|
super().__init__(start_frequency, stop_frequency, frequency_step)
|
||||||
self.init_voltages()
|
self.init_voltages()
|
||||||
|
|
||||||
def init_voltages(self) -> None:
|
def init_voltages(self) -> None:
|
||||||
"""Initialize the lookup table with default values."""
|
"""Initialize the lookup table with default values."""
|
||||||
for frequency in np.arange(
|
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.started_frequency = frequency
|
||||||
self.add_voltages(None, None)
|
self.add_voltages(None, None)
|
||||||
|
@ -250,7 +290,7 @@ class ElectricalLookupTable(LookupTable):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tuning_voltage (float): The tuning voltage for the given frequency.
|
tuning_voltage (float): The tuning voltage for the given frequency.
|
||||||
matching_voltage (float): The matching voltage for the given frequency.
|
matching_voltage (float): The matching voltage for the given frequency.
|
||||||
"""
|
"""
|
||||||
self.data[self.started_frequency] = (tuning_voltage, matching_voltage)
|
self.data[self.started_frequency] = (tuning_voltage, matching_voltage)
|
||||||
|
|
||||||
|
@ -268,8 +308,9 @@ class ElectricalLookupTable(LookupTable):
|
||||||
return self.data[key]
|
return self.data[key]
|
||||||
|
|
||||||
def is_incomplete(self) -> bool:
|
def is_incomplete(self) -> bool:
|
||||||
"""This method returns True if the lookup table is incomplete,
|
"""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.
|
|
||||||
|
I.e. if there are frequencies for which no the tuning or matching voltage is none.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the lookup table is incomplete, False otherwise.
|
bool: True if the lookup table is incomplete, False otherwise.
|
||||||
|
@ -293,19 +334,25 @@ class ElectricalLookupTable(LookupTable):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class MechanicalLookupTable(LookupTable):
|
class MechanicalLookupTable(LookupTable):
|
||||||
|
"""This class is used to store a lookup table for tuning and matching of mechanical probeheads."""
|
||||||
# Hmm duplicate code
|
# Hmm duplicate code
|
||||||
TYPE = "Mechanical"
|
TYPE = "Mechanical"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
def __init__(self, start_frequency: float, stop_frequency: float, frequency_step: float) -> None:
|
self, start_frequency: float, stop_frequency: float, frequency_step: float
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the lookup table."""
|
||||||
super().__init__(start_frequency, stop_frequency, frequency_step)
|
super().__init__(start_frequency, stop_frequency, frequency_step)
|
||||||
self.init_positions()
|
self.init_positions()
|
||||||
|
|
||||||
def init_positions(self) -> None:
|
def init_positions(self) -> None:
|
||||||
"""Initialize the lookup table with default values."""
|
"""Initialize the lookup table with default values."""
|
||||||
for frequency in np.arange(
|
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.started_frequency = frequency
|
||||||
self.add_positions(None, None)
|
self.add_positions(None, None)
|
||||||
|
@ -315,7 +362,7 @@ class MechanicalLookupTable(LookupTable):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tuning_position (int): The tuning position for the given frequency.
|
tuning_position (int): The tuning position for the given frequency.
|
||||||
matching_position (int): The matching position for the given frequency.
|
matching_position (int): The matching position for the given frequency.
|
||||||
"""
|
"""
|
||||||
self.data[self.started_frequency] = (tuning_position, matching_position)
|
self.data[self.started_frequency] = (tuning_position, matching_position)
|
||||||
|
|
||||||
|
@ -333,8 +380,9 @@ class MechanicalLookupTable(LookupTable):
|
||||||
return self.data[key]
|
return self.data[key]
|
||||||
|
|
||||||
def is_incomplete(self) -> bool:
|
def is_incomplete(self) -> bool:
|
||||||
"""This method returns True if the lookup table is incomplete,
|
"""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.
|
|
||||||
|
I.e. if there are frequencies for which no the tuning or matching position is none.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the lookup table is incomplete, False otherwise.
|
bool: True if the lookup table is incomplete, False otherwise.
|
||||||
|
@ -357,8 +405,10 @@ class MechanicalLookupTable(LookupTable):
|
||||||
return frequency
|
return frequency
|
||||||
|
|
||||||
return None
|
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)
|
available_devices_changed = pyqtSignal(list)
|
||||||
serial_changed = pyqtSignal(QSerialPort)
|
serial_changed = pyqtSignal(QSerialPort)
|
||||||
data_points_changed = pyqtSignal(list)
|
data_points_changed = pyqtSignal(list)
|
||||||
|
@ -372,6 +422,7 @@ class AutoTMModel(ModuleModel):
|
||||||
measurement_finished = pyqtSignal(S11Data)
|
measurement_finished = pyqtSignal(S11Data)
|
||||||
|
|
||||||
def __init__(self, module) -> None:
|
def __init__(self, module) -> None:
|
||||||
|
"""Initialize the AutoTM model."""
|
||||||
super().__init__(module)
|
super().__init__(module)
|
||||||
self.data_points = []
|
self.data_points = []
|
||||||
self.active_calibration = None
|
self.active_calibration = None
|
||||||
|
@ -398,6 +449,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_devices(self):
|
def available_devices(self):
|
||||||
|
"""The available_devices property is used to store the available serial devices."""
|
||||||
return self._available_devices
|
return self._available_devices
|
||||||
|
|
||||||
@available_devices.setter
|
@available_devices.setter
|
||||||
|
@ -419,6 +471,7 @@ class AutoTMModel(ModuleModel):
|
||||||
self, frequency: float, return_loss: float, phase: float
|
self, frequency: float, return_loss: float, phase: float
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a data point to the model. These data points are our intermediate data points read in via the serial connection.
|
"""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.
|
They will be saved in the according properties later on.
|
||||||
"""
|
"""
|
||||||
self.data_points.append((frequency, return_loss, phase))
|
self.data_points.append((frequency, return_loss, phase))
|
||||||
|
@ -431,6 +484,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def saved_positions(self):
|
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
|
return self._saved_positions
|
||||||
|
|
||||||
@saved_positions.setter
|
@saved_positions.setter
|
||||||
|
@ -438,9 +492,13 @@ class AutoTMModel(ModuleModel):
|
||||||
self._saved_positions = value
|
self._saved_positions = value
|
||||||
self.saved_positions_changed.emit(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."""
|
"""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)
|
self.saved_positions_changed.emit(self.saved_positions)
|
||||||
|
|
||||||
def delete_saved_position(self, position: SavedPosition) -> None:
|
def delete_saved_position(self, position: SavedPosition) -> None:
|
||||||
|
@ -451,6 +509,7 @@ class AutoTMModel(ModuleModel):
|
||||||
@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.
|
||||||
|
|
||||||
This is the measurement that is shown in the main S11 plot
|
This is the measurement that is shown in the main S11 plot
|
||||||
"""
|
"""
|
||||||
return self._measurement
|
return self._measurement
|
||||||
|
@ -463,6 +522,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_stepper(self):
|
def active_stepper(self):
|
||||||
|
"""The active_stepper property is used to store the active stepper motor."""
|
||||||
return self._active_stepper
|
return self._active_stepper
|
||||||
|
|
||||||
@active_stepper.setter
|
@active_stepper.setter
|
||||||
|
@ -474,6 +534,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active_calibration(self):
|
def active_calibration(self):
|
||||||
|
"""The active_calibration property is used to store the active calibration type."""
|
||||||
return self._active_calibration
|
return self._active_calibration
|
||||||
|
|
||||||
@active_calibration.setter
|
@active_calibration.setter
|
||||||
|
@ -482,6 +543,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def short_calibration(self):
|
def short_calibration(self):
|
||||||
|
"""The short_calibration property is used to store the short calibration data."""
|
||||||
return self._short_calibration
|
return self._short_calibration
|
||||||
|
|
||||||
@short_calibration.setter
|
@short_calibration.setter
|
||||||
|
@ -497,6 +559,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def open_calibration(self):
|
def open_calibration(self):
|
||||||
|
"""The open calibration data."""
|
||||||
return self._open_calibration
|
return self._open_calibration
|
||||||
|
|
||||||
@open_calibration.setter
|
@open_calibration.setter
|
||||||
|
@ -512,6 +575,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def load_calibration(self):
|
def load_calibration(self):
|
||||||
|
"""The load calibration data."""
|
||||||
return self._load_calibration
|
return self._load_calibration
|
||||||
|
|
||||||
@load_calibration.setter
|
@load_calibration.setter
|
||||||
|
@ -527,6 +591,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def calibration(self):
|
def calibration(self):
|
||||||
|
"""The calibration data."""
|
||||||
return self._calibration
|
return self._calibration
|
||||||
|
|
||||||
@calibration.setter
|
@calibration.setter
|
||||||
|
@ -536,6 +601,7 @@ class AutoTMModel(ModuleModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def LUT(self):
|
def LUT(self):
|
||||||
|
"""The lookup table for tuning and matching of the probeheads."""
|
||||||
return self._LUT
|
return self._LUT
|
||||||
|
|
||||||
@LUT.setter
|
@LUT.setter
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"""This module contains the view class for the AutoTM module."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import cmath
|
import cmath
|
||||||
|
@ -27,7 +29,9 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AutoTMView(ModuleView):
|
class AutoTMView(ModuleView):
|
||||||
|
"""The view class for the AutoTM module."""
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
|
"""Initializes the AutoTM view."""
|
||||||
super().__init__(module)
|
super().__init__(module)
|
||||||
|
|
||||||
widget = QWidget()
|
widget = QWidget()
|
||||||
|
@ -127,11 +131,27 @@ class AutoTMView(ModuleView):
|
||||||
self._ui_form.startButton.setIconSize(self._ui_form.startButton.size())
|
self._ui_form.startButton.setIconSize(self._ui_form.startButton.size())
|
||||||
|
|
||||||
# Stepper selection
|
# Stepper selection
|
||||||
self._ui_form.stepperselectBox.currentIndexChanged.connect(lambda: self.module.controller.on_stepper_changed(self._ui_form.stepperselectBox.currentText()))
|
self._ui_form.stepperselectBox.currentIndexChanged.connect(
|
||||||
self._ui_form.increaseButton.clicked.connect(lambda: self.module.controller.on_relative_move(self._ui_form.stepsizeBox.text()))
|
lambda: self.module.controller.on_stepper_changed(
|
||||||
self._ui_form.decreaseButton.clicked.connect(lambda: self.module.controller.on_relative_move("-" + self._ui_form.stepsizeBox.text()))
|
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
|
# 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)
|
||||||
|
@ -175,6 +195,7 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
def on_calibration_button_clicked(self) -> None:
|
def on_calibration_button_clicked(self) -> None:
|
||||||
"""This method is called when the calibration button is clicked.
|
"""This method is called when the calibration button is clicked.
|
||||||
|
|
||||||
It opens the calibration window.
|
It opens the calibration window.
|
||||||
"""
|
"""
|
||||||
logger.debug("Calibration button clicked")
|
logger.debug("Calibration button clicked")
|
||||||
|
@ -183,7 +204,11 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
def on_available_devices_changed(self, available_devices: list) -> None:
|
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")
|
logger.debug("Updating available devices list")
|
||||||
self._ui_form.portBox.clear()
|
self._ui_form.portBox.clear()
|
||||||
self._ui_form.portBox.addItems(available_devices)
|
self._ui_form.portBox.addItems(available_devices)
|
||||||
|
@ -201,6 +226,7 @@ class AutoTMView(ModuleView):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_connect_button_clicked(self) -> None:
|
def on_connect_button_clicked(self) -> None:
|
||||||
"""This method is called when the connect button is clicked.
|
"""This method is called when the connect button is clicked.
|
||||||
|
|
||||||
It calls the connect method of the controller with the currently selected device.
|
It calls the connect method of the controller with the currently selected device.
|
||||||
"""
|
"""
|
||||||
logger.debug("Connect button clicked")
|
logger.debug("Connect button clicked")
|
||||||
|
@ -217,7 +243,7 @@ class AutoTMView(ModuleView):
|
||||||
logger.debug("Updating serial connection label")
|
logger.debug("Updating serial connection label")
|
||||||
if serial.isOpen():
|
if serial.isOpen():
|
||||||
self._ui_form.connectionLabel.setText(serial.portName())
|
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
|
# Change the connectButton to a disconnectButton
|
||||||
self._ui_form.connectButton.setText("Disconnect")
|
self._ui_form.connectButton.setText("Disconnect")
|
||||||
else:
|
else:
|
||||||
|
@ -231,7 +257,9 @@ class AutoTMView(ModuleView):
|
||||||
def on_active_stepper_changed(self) -> None:
|
def on_active_stepper_changed(self) -> None:
|
||||||
"""Update the stepper position label according to the current stepper position."""
|
"""Update the stepper position label according to the current stepper position."""
|
||||||
logger.debug("Updating stepper position label")
|
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")
|
logger.debug("Updated stepper position label")
|
||||||
|
|
||||||
# Only allow position change when stepper is homed
|
# Only allow position change when stepper is homed
|
||||||
|
@ -253,6 +281,7 @@ class AutoTMView(ModuleView):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_position_button_clicked(self) -> None:
|
def on_position_button_clicked(self) -> None:
|
||||||
"""This method is called when the position button is clicked.
|
"""This method is called when the position button is clicked.
|
||||||
|
|
||||||
It opens the position window.
|
It opens the position window.
|
||||||
"""
|
"""
|
||||||
logger.debug("Position button clicked")
|
logger.debug("Position button clicked")
|
||||||
|
@ -263,7 +292,7 @@ class AutoTMView(ModuleView):
|
||||||
"""Update the S11 plot with the current data points.
|
"""Update the S11 plot with the current data points.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data_points (list): List of data points to plot.
|
data (S11Data): The current S11 data points.
|
||||||
|
|
||||||
@TODO: implement proper calibration. See the controller class for more information.
|
@TODO: implement proper calibration. See the controller class for more information.
|
||||||
"""
|
"""
|
||||||
|
@ -328,7 +357,7 @@ class AutoTMView(ModuleView):
|
||||||
"""
|
"""
|
||||||
# Add a timestamp to the text
|
# Add a timestamp to the text
|
||||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||||
text = "[%s] %s" % (timestamp, text)
|
text = f"[{timestamp}] {text}"
|
||||||
text_label = QLabel(text)
|
text_label = QLabel(text)
|
||||||
text_label.setStyleSheet("font-size: 25px;")
|
text_label.setStyleSheet("font-size: 25px;")
|
||||||
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
self._ui_form.scrollAreaWidgetContents.layout().addWidget(text_label)
|
||||||
|
@ -351,7 +380,7 @@ class AutoTMView(ModuleView):
|
||||||
)
|
)
|
||||||
# Add a timestamp to the text
|
# Add a timestamp to the text
|
||||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||||
text = "[%s] %s" % (timestamp, text)
|
text = f"[{timestamp}] {text}"
|
||||||
text_label = QLabel(text)
|
text_label = QLabel(text)
|
||||||
text_label.setStyleSheet("font-size: 25px; color: red;")
|
text_label.setStyleSheet("font-size: 25px; color: red;")
|
||||||
|
|
||||||
|
@ -365,7 +394,9 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
def create_frequency_sweep_spinner_dialog(self) -> None:
|
def create_frequency_sweep_spinner_dialog(self) -> None:
|
||||||
"""Creates a frequency sweep spinner dialog."""
|
"""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()
|
self.frequency_sweep_spinner.show()
|
||||||
|
|
||||||
def create_el_LUT_spinner_dialog(self) -> None:
|
def create_el_LUT_spinner_dialog(self) -> None:
|
||||||
|
@ -375,7 +406,9 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
def create_mech_LUT_spinner_dialog(self) -> None:
|
def create_mech_LUT_spinner_dialog(self) -> None:
|
||||||
"""Creates a mechanical LUT spinner dialog."""
|
"""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()
|
self.mech_LUT_spinner.show()
|
||||||
|
|
||||||
def view_el_lut(self) -> None:
|
def view_el_lut(self) -> None:
|
||||||
|
@ -417,7 +450,9 @@ class AutoTMView(ModuleView):
|
||||||
self.module.controller.load_measurement(file_name)
|
self.module.controller.load_measurement(file_name)
|
||||||
|
|
||||||
class StepperSavedPositionsWindow(QDialog):
|
class StepperSavedPositionsWindow(QDialog):
|
||||||
|
"""This class implements a window that shows the saved positions of the stepper."""
|
||||||
def __init__(self, module, parent=None):
|
def __init__(self, module, parent=None):
|
||||||
|
"""Initializes the StepperSavedPositionsWindow."""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
self.module = module
|
self.module = module
|
||||||
|
@ -432,7 +467,13 @@ class AutoTMView(ModuleView):
|
||||||
self.table_widget = QTableWidget()
|
self.table_widget = QTableWidget()
|
||||||
self.table_widget.setColumnCount(5)
|
self.table_widget.setColumnCount(5)
|
||||||
self.table_widget.setHorizontalHeaderLabels(
|
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)
|
self.table_widget.setColumnWidth(0, 150)
|
||||||
|
@ -461,8 +502,9 @@ class AutoTMView(ModuleView):
|
||||||
main_layout.addWidget(self.table_widget)
|
main_layout.addWidget(self.table_widget)
|
||||||
|
|
||||||
# On saved positions changed
|
# 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)
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
@ -482,13 +524,13 @@ class AutoTMView(ModuleView):
|
||||||
def on_load_position_button_clicked(self) -> None:
|
def on_load_position_button_clicked(self) -> None:
|
||||||
"""File picker for loading a position from a file."""
|
"""File picker for loading a position from a file."""
|
||||||
filename = self.file_selector("load")
|
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)
|
self.module.controller.load_positions(filename)
|
||||||
|
|
||||||
def on_save_position_button_clicked(self) -> None:
|
def on_save_position_button_clicked(self) -> None:
|
||||||
"""File picker for saving a position to a file."""
|
"""File picker for saving a position to a file."""
|
||||||
filename = self.file_selector("save")
|
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)
|
self.module.controller.save_positions(filename)
|
||||||
|
|
||||||
def on_new_position_button_clicked(self) -> None:
|
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 = self.NewPositionWindow(self.module, self)
|
||||||
self.new_position_window.show()
|
self.new_position_window.show()
|
||||||
|
|
||||||
|
|
||||||
def on_saved_positions_changed(self) -> None:
|
def on_saved_positions_changed(self) -> None:
|
||||||
"""This method is called when the saved positions changed.
|
"""This method is called when the saved positions changed.
|
||||||
|
|
||||||
It updates the table widget.
|
It updates the table widget.
|
||||||
"""
|
"""
|
||||||
logger.debug("Updating saved positions table")
|
logger.debug("Updating saved positions table")
|
||||||
|
@ -508,7 +550,9 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
for row, position in enumerate(self.module.model.saved_positions):
|
for row, position in enumerate(self.module.model.saved_positions):
|
||||||
self.table_widget.insertRow(row)
|
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(
|
self.table_widget.setItem(
|
||||||
row, 1, QTableWidgetItem(position.tuning_position)
|
row, 1, QTableWidgetItem(position.tuning_position)
|
||||||
)
|
)
|
||||||
|
@ -517,7 +561,8 @@ class AutoTMView(ModuleView):
|
||||||
)
|
)
|
||||||
go_button = QPushButton("Go")
|
go_button = QPushButton("Go")
|
||||||
go_button.clicked.connect(
|
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
|
position
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -525,7 +570,8 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
delete_button = QPushButton("Delete")
|
delete_button = QPushButton("Delete")
|
||||||
delete_button.clicked.connect(
|
delete_button.clicked.connect(
|
||||||
lambda _, position=position: self.module.controller.on_delete_position(
|
lambda _,
|
||||||
|
position=position: self.module.controller.on_delete_position(
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -534,7 +580,9 @@ class AutoTMView(ModuleView):
|
||||||
logger.debug("Updated saved positions table")
|
logger.debug("Updated saved positions table")
|
||||||
|
|
||||||
class NewPositionWindow(QDialog):
|
class NewPositionWindow(QDialog):
|
||||||
|
"""This class implements a window for adding a new position."""
|
||||||
def __init__(self, module, parent=None):
|
def __init__(self, module, parent=None):
|
||||||
|
"""Initializes the NewPositionWindow."""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
self.module = module
|
self.module = module
|
||||||
|
@ -578,22 +626,32 @@ class AutoTMView(ModuleView):
|
||||||
data_layout = QVBoxLayout()
|
data_layout = QVBoxLayout()
|
||||||
# Apply button
|
# Apply button
|
||||||
apply_button = QPushButton("Apply")
|
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)
|
data_layout.addWidget(apply_button)
|
||||||
|
|
||||||
main_layout.addLayout(data_layout)
|
main_layout.addLayout(data_layout)
|
||||||
|
|
||||||
self.setLayout(main_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."""
|
"""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
|
# Close the calibration window
|
||||||
self.close()
|
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."""
|
||||||
|
|
||||||
def __init__(self, text : str, parent=None):
|
def __init__(self, text: str, parent=None):
|
||||||
|
"""Initializes the LoadingSpinner."""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("Loading")
|
self.setWindowTitle("Loading")
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
|
@ -611,7 +669,9 @@ class AutoTMView(ModuleView):
|
||||||
self.spinner_movie.start()
|
self.spinner_movie.start()
|
||||||
|
|
||||||
class LutWindow(QDialog):
|
class LutWindow(QDialog):
|
||||||
|
"""This class implements a window that shows the LUT."""
|
||||||
def __init__(self, module, parent=None):
|
def __init__(self, module, parent=None):
|
||||||
|
"""Initializes the LutWindow."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.module = module
|
self.module = module
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
|
@ -659,7 +719,9 @@ class AutoTMView(ModuleView):
|
||||||
tuning_voltage = str(LUT.data[frequency][0])
|
tuning_voltage = str(LUT.data[frequency][0])
|
||||||
matching_voltage = str(LUT.data[frequency][1])
|
matching_voltage = str(LUT.data[frequency][1])
|
||||||
test_button.clicked.connect(
|
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
|
tuning_voltage, matching_voltage
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -668,7 +730,9 @@ class AutoTMView(ModuleView):
|
||||||
tuning_position = str(LUT.data[frequency][0])
|
tuning_position = str(LUT.data[frequency][0])
|
||||||
matching_position = str(LUT.data[frequency][1])
|
matching_position = str(LUT.data[frequency][1])
|
||||||
test_button.clicked.connect(
|
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
|
tuning_position, matching_position
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -681,6 +745,7 @@ class AutoTMView(ModuleView):
|
||||||
|
|
||||||
def test_lut(self):
|
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.
|
"""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.
|
One can then view the matching on a seperate VNA.
|
||||||
"""
|
"""
|
||||||
# This should be in the controller
|
# This should be in the controller
|
||||||
|
@ -690,7 +755,9 @@ class AutoTMView(ModuleView):
|
||||||
self.module.controller.set_voltages(tuning_voltage, matching_voltage)
|
self.module.controller.set_voltages(tuning_voltage, matching_voltage)
|
||||||
|
|
||||||
class CalibrationWindow(QDialog):
|
class CalibrationWindow(QDialog):
|
||||||
|
"""The calibration Dialog."""
|
||||||
def __init__(self, module, parent=None):
|
def __init__(self, module, parent=None):
|
||||||
|
"""Initializes the CalibrationWindow."""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setParent(parent)
|
self.setParent(parent)
|
||||||
self.module = module
|
self.module = module
|
||||||
|
@ -791,18 +858,22 @@ class AutoTMView(ModuleView):
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_short_calibration_finished(self, short_calibration: "S11Data") -> None:
|
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)
|
self.on_calibration_finished("short", self.short_plot, short_calibration)
|
||||||
|
|
||||||
def on_open_calibration_finished(self, open_calibration: "S11Data") -> None:
|
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)
|
self.on_calibration_finished("open", self.open_plot, open_calibration)
|
||||||
|
|
||||||
def on_load_calibration_finished(self, load_calibration: "S11Data") -> None:
|
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)
|
self.on_calibration_finished("load", self.load_plot, load_calibration)
|
||||||
|
|
||||||
def on_calibration_finished(
|
def on_calibration_finished(
|
||||||
self, type: str, widget: MplWidget, data: "S11Data"
|
self, type: str, widget: MplWidget, data: "S11Data"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This method is called when a calibration has finished.
|
"""This method is called when a calibration has finished.
|
||||||
|
|
||||||
It plots the calibration data on the given widget.
|
It plots the calibration data on the given widget.
|
||||||
"""
|
"""
|
||||||
frequency = data.frequency
|
frequency = data.frequency
|
||||||
|
@ -829,13 +900,14 @@ class AutoTMView(ModuleView):
|
||||||
widget.canvas.flush_events()
|
widget.canvas.flush_events()
|
||||||
|
|
||||||
def on_export_button_clicked(self) -> None:
|
def on_export_button_clicked(self) -> None:
|
||||||
|
"""Called when the export button was clicked."""
|
||||||
filedialog = QFileDialog()
|
filedialog = QFileDialog()
|
||||||
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
|
filedialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
|
||||||
filedialog.setNameFilter("calibration files (*.cal)")
|
filedialog.setNameFilter("calibration files (*.cal)")
|
||||||
filedialog.setDefaultSuffix("cal")
|
filedialog.setDefaultSuffix("cal")
|
||||||
filedialog.exec()
|
filedialog.exec()
|
||||||
filename = filedialog.selectedFiles()[0]
|
filename = filedialog.selectedFiles()[0]
|
||||||
logger.debug("Exporting calibration to %s" % filename)
|
logger.debug(f"Exporting calibration to {filename}")
|
||||||
self.module.controller.export_calibration(filename)
|
self.module.controller.export_calibration(filename)
|
||||||
|
|
||||||
def on_import_button_clicked(self) -> None:
|
def on_import_button_clicked(self) -> None:
|
||||||
|
@ -846,7 +918,7 @@ class AutoTMView(ModuleView):
|
||||||
filedialog.setDefaultSuffix("cal")
|
filedialog.setDefaultSuffix("cal")
|
||||||
filedialog.exec()
|
filedialog.exec()
|
||||||
filename = filedialog.selectedFiles()[0]
|
filename = filedialog.selectedFiles()[0]
|
||||||
logger.debug("Importing calibration from %s" % filename)
|
logger.debug(f"Importing calibration from {filename}")
|
||||||
self.module.controller.import_calibration(filename)
|
self.module.controller.import_calibration(filename)
|
||||||
|
|
||||||
def on_apply_button_clicked(self) -> None:
|
def on_apply_button_clicked(self) -> None:
|
||||||
|
|
Loading…
Reference in a new issue