diff --git a/README.md b/README.md index e69de29..14e0bf9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,5 @@ +# nqrduck-autotm +A module for the [nqrduck](https://github.com/nqrduck/nqrduck) project. This module is used to automatically tune and match mechanical and electrical probes. + +### Notes +- The active user needs to be in the correct group to use serial ports. For example 'uucp' in Arch Linux and 'dialout' in Ubuntu. \ No newline at end of file diff --git a/src/nqrduck_autotm/controller.py b/src/nqrduck_autotm/controller.py index 7e846ac..396dac0 100644 --- a/src/nqrduck_autotm/controller.py +++ b/src/nqrduck_autotm/controller.py @@ -1,6 +1,7 @@ import logging -import serial from serial.tools.list_ports import comports +from PyQt6 import QtSerialPort +from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot from nqrduck.module.module_controller import ModuleController logger = logging.getLogger(__name__) @@ -21,12 +22,33 @@ class AutoTMController(ModuleController): """Connect to the specified device. """ logger.debug("Connecting to device %s", device) try: - self.module.model.serial = serial.Serial(device, self.BAUDRATE, timeout=0.1) + self.module.model.serial = QtSerialPort.QSerialPort(device, baudRate=self.BAUDRATE, readyRead=self.on_ready_read) + self.module.model.serial.open(QtSerialPort.QSerialPort.OpenModeFlag.ReadWrite) + + + logger.debug("Connected to device %s", device) - except serial.SerialException as e: - logger.error("Failed to connect to device %s", device) - logger.error(e) + except Exception as e: + logger.error("Could not connect to device %s: %s", device, e) def start_frequency_sweep(self, start_frequency : float, stop_frequency : float) -> None: """ This starts a frequency sweep on the device in the specified range.""" - pass \ No newline at end of file + logger.debug("Starting frequency sweep from %s to %s", start_frequency, stop_frequency) + # Print the command 'f ' to the serial connection + command = "f %s %s" % (start_frequency, stop_frequency) + self.module.model.serial.write(command.encode('utf-8')) + + def on_ready_read(self) -> None: + """This method is called when data is received from the serial connection. """ + serial = self.module.model.serial + while serial.canReadLine(): + text = serial.readLine().data().decode() + text = text.rstrip('\r\n') + logger.debug("Received data: %s", text) + if text.startswith("f"): + text = text[1:].split("r") + frequency = float(text[0]) + return_loss = float(text[1]) + self.module.model.add_data_point(frequency, return_loss) + else: + self.module.view.add_info_text(text) diff --git a/src/nqrduck_autotm/model.py b/src/nqrduck_autotm/model.py index 50d485c..e57aad2 100644 --- a/src/nqrduck_autotm/model.py +++ b/src/nqrduck_autotm/model.py @@ -1,10 +1,17 @@ import serial from PyQt6.QtCore import pyqtSignal +from PyQt6.QtSerialPort import QSerialPort from nqrduck.module.module_model import ModuleModel class AutoTMModel(ModuleModel): available_devices_changed = pyqtSignal(list) - serial_changed = pyqtSignal(serial.Serial) + serial_changed = pyqtSignal(QSerialPort) + data_points_changed = pyqtSignal(list) + + + def __init__(self, module) -> None: + super().__init__(module) + self.data_points = [] @property def available_devices(self): @@ -23,3 +30,13 @@ class AutoTMModel(ModuleModel): def serial(self, value): self._serial = value self.serial_changed.emit(value) + + def add_data_point(self, frequency : float, return_loss : float) -> None: + """Add a data point to the model. """ + self.data_points.append((frequency, return_loss)) + self.data_points_changed.emit(self.data_points) + + def clear_data_points(self) -> None: + """Clear all data points from the model. """ + self.data_points.clear() + self.data_points_changed.emit(self.data_points) diff --git a/src/nqrduck_autotm/resources/autotm_widget.ui b/src/nqrduck_autotm/resources/autotm_widget.ui index d49e1be..6fadc68 100644 --- a/src/nqrduck_autotm/resources/autotm_widget.ui +++ b/src/nqrduck_autotm/resources/autotm_widget.ui @@ -156,12 +156,19 @@ - + Start Sweep + + + + Calibrate + + + @@ -193,7 +200,7 @@ 0 0 273 - 229 + 189 diff --git a/src/nqrduck_autotm/view.py b/src/nqrduck_autotm/view.py index 6213bef..db44efc 100644 --- a/src/nqrduck_autotm/view.py +++ b/src/nqrduck_autotm/view.py @@ -1,8 +1,8 @@ import logging -import serial from datetime import datetime -from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout -from PyQt6.QtCore import pyqtSlot, Qt +from PyQt6.QtSerialPort import QSerialPort +from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication +from PyQt6.QtCore import pyqtSlot, Qt from nqrduck.module.module_view import ModuleView from .widget import Ui_Form @@ -33,6 +33,15 @@ class AutoTMView(ModuleView): # On clicking of the connect button call the connect method self._ui_form.connectButton.clicked.connect(self.on_connect_button_clicked) + # On clicking of the start button call the start_frequency_sweep method + self._ui_form.startButton.clicked.connect(lambda: self.module.controller.start_frequency_sweep( + float(self._ui_form.startEdit.text()), + float(self._ui_form.stopEdit.text()) + )) + + # Connect the data points changed signal to the on_data_points_changed slot + self.module.model.data_points_changed.connect(self.on_data_points_changed) + # Add a vertical layout to the info box self._ui_form.scrollAreaWidgetContents.setLayout(QVBoxLayout()) self._ui_form.scrollAreaWidgetContents.layout().setAlignment(Qt.AlignmentFlag.AlignTop) @@ -81,20 +90,43 @@ class AutoTMView(ModuleView): selected_device = self._ui_form.portBox.currentText() self.module.controller.connect(selected_device) - @pyqtSlot(serial.Serial) - def on_serial_changed(self, serial : serial.Serial) -> None: + @pyqtSlot(QSerialPort) + def on_serial_changed(self, serial : QSerialPort) -> None: """Update the serial 'connectionLabel' according to the current serial connection. Args: serial (serial.Serial): The current serial connection.""" logger.debug("Updating serial connection label") - if serial.is_open: - self._ui_form.connectionLabel.setText(serial.port) - self.add_info_text("Connected to device %s" % serial.port) + if serial.isOpen(): + self._ui_form.connectionLabel.setText(serial.portName()) + self.add_info_text("Connected to device %s" % serial.portName()) else: self._ui_form.connectionLabel.setText("Disconnected") logger.debug("Updated serial connection label") + @pyqtSlot(list) + def on_data_points_changed(self, data_points : list) -> None: + """Update the S11 plot with the current data points. + + Args: + data_points (list): List of data points to plot. + """ + x = [data_point[0] for data_point in data_points] + y = [data_point[1] for data_point in data_points] + ax = self._ui_form.S11Plot.canvas.ax + ax.clear() + ax.set_xlabel("Frequency (MHz)") + ax.set_ylabel("S11 (dB)") + ax.set_title("S11") + ax.grid(True) + ax.plot(x, y) + # make the y axis go down instead of up + ax.invert_yaxis() + self._ui_form.S11Plot.canvas.draw() + self._ui_form.S11Plot.canvas.flush_events() + # Wait for the signals to be processed before adding the info text + QApplication.processEvents() + def add_info_text(self, text : str) -> None: """ Adds text to the info text box. diff --git a/src/nqrduck_autotm/widget.py b/src/nqrduck_autotm/widget.py index a07b76e..6bb0d9f 100644 --- a/src/nqrduck_autotm/widget.py +++ b/src/nqrduck_autotm/widget.py @@ -1,6 +1,6 @@ # Form implementation generated from reading ui file '../Modules/nqrduck-autotm/src/nqrduck_autotm/resources/autotm_widget.ui' # -# Created by: PyQt6 UI code generator 6.5.2 +# Created by: PyQt6 UI code generator 6.5.1 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -91,9 +91,9 @@ class Ui_Form(object): self.label_5.setObjectName("label_5") self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1) self.verticalLayout_2.addLayout(self.gridLayout) - self.pushButton_2 = QtWidgets.QPushButton(parent=Form) - self.pushButton_2.setObjectName("pushButton_2") - self.verticalLayout_2.addWidget(self.pushButton_2) + self.startButton = QtWidgets.QPushButton(parent=Form) + self.startButton.setObjectName("startButton") + self.verticalLayout_2.addWidget(self.startButton) self.pushButton_3 = QtWidgets.QPushButton(parent=Form) self.pushButton_3.setObjectName("pushButton_3") self.verticalLayout_2.addWidget(self.pushButton_3) @@ -145,7 +145,7 @@ class Ui_Form(object): self.label_6.setText(_translate("Form", "MHz")) self.label_7.setText(_translate("Form", "Stop Frequency:")) self.label_5.setText(_translate("Form", "Start Frequency:")) - self.pushButton_2.setText(_translate("Form", "Start Sweep")) + self.startButton.setText(_translate("Form", "Start Sweep")) self.pushButton_3.setText(_translate("Form", "T&M Settings")) self.titleinfoLabel.setText(_translate("Form", "Info Box:")) from nqrduck.contrib.mplwidget import MplWidget