kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge branch 'upstream_Development' into feature/refactor_marker
commit
6a4800d361
|
@ -15,9 +15,13 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import struct
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PyQt5 import QtGui
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -25,28 +29,33 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class VNA:
|
class VNA:
|
||||||
name = "VNA"
|
name = "VNA"
|
||||||
|
validateInput = True
|
||||||
|
features = []
|
||||||
|
|
||||||
def __init__(self, app, serialPort: serial.Serial):
|
def __init__(self, app, serial_port: serial.Serial):
|
||||||
from NanoVNASaver.NanoVNASaver import NanoVNASaver
|
from NanoVNASaver.NanoVNASaver import NanoVNASaver
|
||||||
self.app: NanoVNASaver = app
|
self.app: NanoVNASaver = app
|
||||||
self.serial = serialPort
|
self.serial = serial_port
|
||||||
self.version: Version = Version("0.0.0")
|
self.version: Version = Version("0.0.0")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getVNA(app, serialPort: serial.Serial) -> 'VNA':
|
def getVNA(app, serial_port: serial.Serial) -> 'VNA':
|
||||||
logger.info("Finding correct VNA type")
|
logger.info("Finding correct VNA type")
|
||||||
tmp_vna = VNA(app, serialPort)
|
tmp_vna = VNA(app, serial_port)
|
||||||
tmp_vna.flushSerialBuffers()
|
tmp_vna.flushSerialBuffers()
|
||||||
firmware = tmp_vna.readFirmware()
|
firmware = tmp_vna.readFirmware()
|
||||||
if firmware.find("NanoVNA-H") > 0:
|
if firmware.find("NanoVNA-H") > 0:
|
||||||
return NanoVNA_H(app, serialPort)
|
logger.info("Type: NanoVNA-H")
|
||||||
|
return NanoVNA_H(app, serial_port)
|
||||||
if firmware.find("NanoVNA-F") > 0:
|
if firmware.find("NanoVNA-F") > 0:
|
||||||
return NanoVNA_F(app, serialPort)
|
logger.info("Type: NanoVNA-F")
|
||||||
|
return NanoVNA_F(app, serial_port)
|
||||||
elif firmware.find("NanoVNA") > 0:
|
elif firmware.find("NanoVNA") > 0:
|
||||||
return NanoVNA(app, serialPort)
|
logger.info("Type: Generic NanoVNA")
|
||||||
|
return NanoVNA(app, serial_port)
|
||||||
else:
|
else:
|
||||||
logger.warning("Did not recognize NanoVNA type from firmware.")
|
logger.warning("Did not recognize NanoVNA type from firmware.")
|
||||||
return NanoVNA(app, serialPort)
|
return NanoVNA(app, serial_port)
|
||||||
|
|
||||||
def readFrequencies(self) -> List[str]:
|
def readFrequencies(self) -> List[str]:
|
||||||
pass
|
pass
|
||||||
|
@ -63,6 +72,15 @@ class VNA:
|
||||||
def isValid(self):
|
def isValid(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getFeatures(self) -> List[str]:
|
||||||
|
return self.features
|
||||||
|
|
||||||
|
def getCalibration(self) -> str:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def getScreenshot(self) -> QtGui.QPixmap:
|
||||||
|
return QtGui.QPixmap()
|
||||||
|
|
||||||
def flushSerialBuffers(self):
|
def flushSerialBuffers(self):
|
||||||
if self.app.serialLock.acquire():
|
if self.app.serialLock.acquire():
|
||||||
self.serial.write(b"\r\n\r\n")
|
self.serial.write(b"\r\n\r\n")
|
||||||
|
@ -177,24 +195,81 @@ class InvalidVNA(VNA):
|
||||||
class NanoVNA(VNA):
|
class NanoVNA(VNA):
|
||||||
name = "NanoVNA"
|
name = "NanoVNA"
|
||||||
|
|
||||||
def __init__(self, app, serialPort):
|
def __init__(self, app, serial_port):
|
||||||
super().__init__(app, serialPort)
|
super().__init__(app, serial_port)
|
||||||
self.version = Version(self.readVersion())
|
self.version = Version(self.readVersion())
|
||||||
|
|
||||||
logger.debug("Testing against 0.2.0")
|
logger.debug("Testing against 0.2.0")
|
||||||
if self.version.version_string.find("extended with scan") > 0:
|
if self.version.version_string.find("extended with scan") > 0:
|
||||||
logger.debug("Incompatible scan command detected.")
|
logger.debug("Incompatible scan command detected.")
|
||||||
|
self.features.append("Incompatible scan command")
|
||||||
self.useScan = False
|
self.useScan = False
|
||||||
elif self.version >= Version("0.2.0"):
|
elif self.version >= Version("0.2.0"):
|
||||||
logger.debug("Newer than 0.2.0, using new scan command.")
|
logger.debug("Newer than 0.2.0, using new scan command.")
|
||||||
|
self.features.append("New scan command")
|
||||||
self.useScan = True
|
self.useScan = True
|
||||||
else:
|
else:
|
||||||
logger.debug("Older than 0.2.0, using old sweep command.")
|
logger.debug("Older than 0.2.0, using old sweep command.")
|
||||||
|
self.features.append("Original sweep method")
|
||||||
self.useScan = False
|
self.useScan = False
|
||||||
|
|
||||||
def isValid(self):
|
def isValid(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getCalibration(self) -> str:
|
||||||
|
logger.debug("Reading calibration info.")
|
||||||
|
if not self.serial.is_open:
|
||||||
|
return "Not connected."
|
||||||
|
if self.app.serialLock.acquire():
|
||||||
|
try:
|
||||||
|
data = "a"
|
||||||
|
while data != "":
|
||||||
|
data = self.serial.readline().decode('ascii')
|
||||||
|
self.serial.write("cal\r".encode('ascii'))
|
||||||
|
result = ""
|
||||||
|
data = ""
|
||||||
|
sleep(0.1)
|
||||||
|
while "ch>" not in data:
|
||||||
|
data = self.serial.readline().decode('ascii')
|
||||||
|
result += data
|
||||||
|
values = result.splitlines()
|
||||||
|
return values[1]
|
||||||
|
except serial.SerialException as exc:
|
||||||
|
logger.exception("Exception while reading calibration info: %s", exc)
|
||||||
|
finally:
|
||||||
|
self.app.serialLock.release()
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
def getScreenshot(self) -> QtGui.QPixmap:
|
||||||
|
logger.debug("Capturing screenshot...")
|
||||||
|
if not self.serial.is_open:
|
||||||
|
return QtGui.QPixmap()
|
||||||
|
if self.app.serialLock.acquire():
|
||||||
|
try:
|
||||||
|
data = "a"
|
||||||
|
while data != "":
|
||||||
|
data = self.serial.readline().decode('ascii')
|
||||||
|
self.serial.write("capture\r".encode('ascii'))
|
||||||
|
timeout = self.serial.timeout
|
||||||
|
self.serial.timeout = 2
|
||||||
|
self.serial.readline()
|
||||||
|
image_data = self.serial.read(320 * 240 * 2)
|
||||||
|
self.serial.timeout = timeout
|
||||||
|
rgb_data = struct.unpack(">76800H", image_data)
|
||||||
|
rgb_array = np.array(rgb_data, dtype=np.uint32)
|
||||||
|
rgba_array = (0xFF000000 +
|
||||||
|
((rgb_array & 0xF800) << 8) +
|
||||||
|
((rgb_array & 0x07E0) << 5) +
|
||||||
|
((rgb_array & 0x001F) << 3))
|
||||||
|
image = QtGui.QImage(rgba_array, 320, 240, QtGui.QImage.Format_ARGB32)
|
||||||
|
logger.debug("Captured screenshot")
|
||||||
|
return QtGui.QPixmap(image)
|
||||||
|
except serial.SerialException as exc:
|
||||||
|
logger.exception("Exception while capturing screenshot: %s", exc)
|
||||||
|
finally:
|
||||||
|
self.app.serialLock.release()
|
||||||
|
return QtGui.QPixmap()
|
||||||
|
|
||||||
def readFrequencies(self) -> List[str]:
|
def readFrequencies(self) -> List[str]:
|
||||||
return self.readValues("frequencies")
|
return self.readValues("frequencies")
|
||||||
|
|
||||||
|
@ -254,7 +329,7 @@ class Version:
|
||||||
minor = 0
|
minor = 0
|
||||||
revision = 0
|
revision = 0
|
||||||
note = ""
|
note = ""
|
||||||
version_string =""
|
version_string = ""
|
||||||
|
|
||||||
def __init__(self, version_string):
|
def __init__(self, version_string):
|
||||||
self.version_string = version_string
|
self.version_string = version_string
|
||||||
|
@ -286,3 +361,6 @@ class Version:
|
||||||
def __eq__(self, other: "Version"):
|
def __eq__(self, other: "Version"):
|
||||||
return self.major == other.major and self.minor == other.minor and self.revision == other.revision and \
|
return self.major == other.major and self.minor == other.minor and self.revision == other.revision and \
|
||||||
self.note == other.note
|
self.note == other.note
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.major) + "." + str(self.minor) + "." + str(self.revision) + str(self.note)
|
||||||
|
|
|
@ -448,10 +448,17 @@ class NanoVNASaver(QtWidgets.QWidget):
|
||||||
serial_port_input_layout.addWidget(btn_rescan_serial_port)
|
serial_port_input_layout.addWidget(btn_rescan_serial_port)
|
||||||
serial_control_layout.addRow(QtWidgets.QLabel("Serial port"), serial_port_input_layout)
|
serial_control_layout.addRow(QtWidgets.QLabel("Serial port"), serial_port_input_layout)
|
||||||
|
|
||||||
|
serial_button_layout = QtWidgets.QHBoxLayout()
|
||||||
|
|
||||||
self.btnSerialToggle = QtWidgets.QPushButton("Connect to NanoVNA")
|
self.btnSerialToggle = QtWidgets.QPushButton("Connect to NanoVNA")
|
||||||
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
||||||
serial_control_layout.addRow(self.btnSerialToggle)
|
serial_button_layout.addWidget(self.btnSerialToggle, stretch=1)
|
||||||
|
|
||||||
|
self.deviceSettingsWindow = DeviceSettingsWindow(self)
|
||||||
|
self.btnDeviceSettings = QtWidgets.QPushButton("Manage")
|
||||||
|
self.btnDeviceSettings.clicked.connect(self.displayDeviceSettingsWindow)
|
||||||
|
serial_button_layout.addWidget(self.btnDeviceSettings, stretch=0)
|
||||||
|
serial_control_layout.addRow(serial_button_layout)
|
||||||
left_column.addWidget(serial_control_box)
|
left_column.addWidget(serial_control_box)
|
||||||
|
|
||||||
################################################################################################################
|
################################################################################################################
|
||||||
|
@ -783,8 +790,10 @@ class NanoVNASaver(QtWidgets.QWidget):
|
||||||
min_gain_freq = d.freq
|
min_gain_freq = d.freq
|
||||||
|
|
||||||
if max_gain_freq > -1:
|
if max_gain_freq > -1:
|
||||||
self.s21_min_gain_label.setText(str(round(min_gain, 3)) + " dB @ " + RFTools.formatFrequency(min_gain_freq))
|
self.s21_min_gain_label.setText(
|
||||||
self.s21_max_gain_label.setText(str(round(max_gain, 3)) + " dB @ " + RFTools.formatFrequency(max_gain_freq))
|
str(round(min_gain, 3)) + " dB @ " + RFTools.formatFrequency(min_gain_freq))
|
||||||
|
self.s21_max_gain_label.setText(
|
||||||
|
str(round(max_gain, 3)) + " dB @ " + RFTools.formatFrequency(max_gain_freq))
|
||||||
else:
|
else:
|
||||||
self.s21_min_gain_label.setText("")
|
self.s21_min_gain_label.setText("")
|
||||||
self.s21_max_gain_label.setText("")
|
self.s21_max_gain_label.setText("")
|
||||||
|
@ -830,7 +839,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
||||||
if self.sweepCountInput.text().isdigit():
|
if self.sweepCountInput.text().isdigit():
|
||||||
segments = int(self.sweepCountInput.text())
|
segments = int(self.sweepCountInput.text())
|
||||||
if segments > 0:
|
if segments > 0:
|
||||||
fstep = fspan / (segments * 101)
|
fstep = fspan / (segments * 101 - 1)
|
||||||
self.sweepStepLabel.setText(RFTools.formatShortFrequency(fstep) + "/step")
|
self.sweepStepLabel.setText(RFTools.formatShortFrequency(fstep) + "/step")
|
||||||
|
|
||||||
def setReference(self, s11data=None, s21data=None, source=None):
|
def setReference(self, s11data=None, s21data=None, source=None):
|
||||||
|
@ -919,6 +928,10 @@ class NanoVNASaver(QtWidgets.QWidget):
|
||||||
self.sweepSettingsWindow.show()
|
self.sweepSettingsWindow.show()
|
||||||
QtWidgets.QApplication.setActiveWindow(self.sweepSettingsWindow)
|
QtWidgets.QApplication.setActiveWindow(self.sweepSettingsWindow)
|
||||||
|
|
||||||
|
def displayDeviceSettingsWindow(self):
|
||||||
|
self.deviceSettingsWindow.show()
|
||||||
|
QtWidgets.QApplication.setActiveWindow(self.deviceSettingsWindow)
|
||||||
|
|
||||||
def displayCalibrationWindow(self):
|
def displayCalibrationWindow(self):
|
||||||
self.calibrationWindow.show()
|
self.calibrationWindow.show()
|
||||||
QtWidgets.QApplication.setActiveWindow(self.calibrationWindow)
|
QtWidgets.QApplication.setActiveWindow(self.calibrationWindow)
|
||||||
|
@ -2027,7 +2040,8 @@ class SweepSettingsWindow(QtWidgets.QWidget):
|
||||||
settings_layout.addRow(QtWidgets.QLabel("Averaging allows discarding outlying samples to get better averages."))
|
settings_layout.addRow(QtWidgets.QLabel("Averaging allows discarding outlying samples to get better averages."))
|
||||||
settings_layout.addRow(QtWidgets.QLabel("Common values are 3/0, 5/2, 9/4 and 25/6."))
|
settings_layout.addRow(QtWidgets.QLabel("Common values are 3/0, 5/2, 9/4 and 25/6."))
|
||||||
|
|
||||||
self.continuous_sweep_radiobutton.toggled.connect(lambda: self.app.worker.setContinuousSweep(self.continuous_sweep_radiobutton.isChecked()))
|
self.continuous_sweep_radiobutton.toggled.connect(
|
||||||
|
lambda: self.app.worker.setContinuousSweep(self.continuous_sweep_radiobutton.isChecked()))
|
||||||
self.averaged_sweep_radiobutton.toggled.connect(self.updateAveraging)
|
self.averaged_sweep_radiobutton.toggled.connect(self.updateAveraging)
|
||||||
self.averages.textEdited.connect(self.updateAveraging)
|
self.averages.textEdited.connect(self.updateAveraging)
|
||||||
self.truncates.textEdited.connect(self.updateAveraging)
|
self.truncates.textEdited.connect(self.updateAveraging)
|
||||||
|
@ -2546,3 +2560,151 @@ class MarkerSettingsWindow(QtWidgets.QWidget):
|
||||||
item.setCheckState(QtCore.Qt.Checked)
|
item.setCheckState(QtCore.Qt.Checked)
|
||||||
self.model.appendRow(item)
|
self.model.appendRow(item)
|
||||||
self.fieldSelectionView.setModel(self.model)
|
self.fieldSelectionView.setModel(self.model)
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceSettingsWindow(QtWidgets.QWidget):
|
||||||
|
def __init__(self, app: NanoVNASaver):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.app = app
|
||||||
|
self.setWindowTitle("Device settings")
|
||||||
|
self.setWindowIcon(self.app.icon)
|
||||||
|
|
||||||
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
status_box = QtWidgets.QGroupBox("Status")
|
||||||
|
status_layout = QtWidgets.QFormLayout(status_box)
|
||||||
|
self.statusLabel = QtWidgets.QLabel("Not connected.")
|
||||||
|
status_layout.addRow("Status:", self.statusLabel)
|
||||||
|
|
||||||
|
self.calibrationStatusLabel = QtWidgets.QLabel("Not connected.")
|
||||||
|
status_layout.addRow("Calibration:", self.calibrationStatusLabel)
|
||||||
|
|
||||||
|
status_layout.addRow(QtWidgets.QLabel("Features:"))
|
||||||
|
self.featureList = QtWidgets.QListWidget()
|
||||||
|
status_layout.addRow(self.featureList)
|
||||||
|
|
||||||
|
settings_box = QtWidgets.QGroupBox("Settings")
|
||||||
|
settings_layout = QtWidgets.QFormLayout(settings_box)
|
||||||
|
|
||||||
|
self.chkValidateInputData = QtWidgets.QCheckBox("Validate received data")
|
||||||
|
self.chkValidateInputData.setChecked(True)
|
||||||
|
self.chkValidateInputData.stateChanged.connect(self.updateValidation)
|
||||||
|
settings_layout.addRow("Validation", self.chkValidateInputData)
|
||||||
|
|
||||||
|
control_layout = QtWidgets.QHBoxLayout()
|
||||||
|
self.btnRefresh = QtWidgets.QPushButton("Refresh")
|
||||||
|
self.btnRefresh.clicked.connect(self.updateFields)
|
||||||
|
control_layout.addWidget(self.btnRefresh)
|
||||||
|
|
||||||
|
self.screenshotWindow = ScreenshotWindow()
|
||||||
|
self.btnCaptureScreenshot = QtWidgets.QPushButton("Screenshot")
|
||||||
|
self.btnCaptureScreenshot.clicked.connect(self.captureScreenshot)
|
||||||
|
control_layout.addWidget(self.btnCaptureScreenshot)
|
||||||
|
|
||||||
|
layout.addWidget(status_box)
|
||||||
|
layout.addWidget(settings_box)
|
||||||
|
layout.addLayout(control_layout)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
super().show()
|
||||||
|
self.updateFields()
|
||||||
|
|
||||||
|
def updateFields(self):
|
||||||
|
if self.app.vna.isValid():
|
||||||
|
self.statusLabel.setText("Connected to " + self.app.vna.name + ".")
|
||||||
|
if self.app.worker.running:
|
||||||
|
self.calibrationStatusLabel.setText("(Sweep running)")
|
||||||
|
else:
|
||||||
|
self.calibrationStatusLabel.setText(self.app.vna.getCalibration())
|
||||||
|
|
||||||
|
self.featureList.clear()
|
||||||
|
self.featureList.addItem(self.app.vna.name + " v" + str(self.app.vna.version))
|
||||||
|
for item in self.app.vna.getFeatures():
|
||||||
|
self.featureList.addItem(item)
|
||||||
|
else:
|
||||||
|
self.statusLabel.setText("Not connected.")
|
||||||
|
self.calibrationStatusLabel.setText("Not connected.")
|
||||||
|
self.featureList.clear()
|
||||||
|
self.featureList.addItem("Not connected.")
|
||||||
|
|
||||||
|
def updateValidation(self, validate_data: bool):
|
||||||
|
self.app.vna.validateInput = validate_data
|
||||||
|
|
||||||
|
def captureScreenshot(self):
|
||||||
|
if not self.app.worker.running:
|
||||||
|
pixmap = self.app.vna.getScreenshot()
|
||||||
|
self.screenshotWindow.setScreenshot(pixmap)
|
||||||
|
self.screenshotWindow.show()
|
||||||
|
else:
|
||||||
|
# TODO: Tell the user no screenshots while sweep is running?
|
||||||
|
# TODO: Consider having a list of widgets that want to be disabled when a sweep is running?
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenshotWindow(QtWidgets.QLabel):
|
||||||
|
pix = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Screenshot")
|
||||||
|
# TODO : self.setWindowIcon(self.app.icon)
|
||||||
|
|
||||||
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||||
|
|
||||||
|
self.action_original_size = QtWidgets.QAction("Original size")
|
||||||
|
self.action_original_size.triggered.connect(lambda: self.setScale(1))
|
||||||
|
self.action_2x_size = QtWidgets.QAction("2x size")
|
||||||
|
self.action_2x_size.triggered.connect(lambda: self.setScale(2))
|
||||||
|
self.action_3x_size = QtWidgets.QAction("3x size")
|
||||||
|
self.action_3x_size.triggered.connect(lambda: self.setScale(3))
|
||||||
|
self.action_4x_size = QtWidgets.QAction("4x size")
|
||||||
|
self.action_4x_size.triggered.connect(lambda: self.setScale(4))
|
||||||
|
self.action_5x_size = QtWidgets.QAction("5x size")
|
||||||
|
self.action_5x_size.triggered.connect(lambda: self.setScale(5))
|
||||||
|
|
||||||
|
self.addAction(self.action_original_size)
|
||||||
|
self.addAction(self.action_2x_size)
|
||||||
|
self.addAction(self.action_3x_size)
|
||||||
|
self.addAction(self.action_4x_size)
|
||||||
|
self.addAction(self.action_5x_size)
|
||||||
|
self.action_save_screenshot = QtWidgets.QAction("Save image")
|
||||||
|
self.action_save_screenshot.triggered.connect(self.saveScreenshot)
|
||||||
|
self.addAction(self.action_save_screenshot)
|
||||||
|
|
||||||
|
def setScreenshot(self, pixmap: QtGui.QPixmap):
|
||||||
|
if self.pix is None:
|
||||||
|
self.resize(pixmap.size())
|
||||||
|
self.pix = pixmap
|
||||||
|
self.setPixmap(self.pix.scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.FastTransformation))
|
||||||
|
w, h = pixmap.width(), pixmap.height()
|
||||||
|
self.action_original_size.setText("Original size (" + str(w) + "x" + str(h) + ")")
|
||||||
|
self.action_2x_size.setText("2x size (" + str(w * 2) + "x" + str(h * 2) + ")")
|
||||||
|
self.action_3x_size.setText("3x size (" + str(w * 3) + "x" + str(h * 3) + ")")
|
||||||
|
self.action_4x_size.setText("4x size (" + str(w * 4) + "x" + str(h * 4) + ")")
|
||||||
|
self.action_5x_size.setText("5x size (" + str(w * 5) + "x" + str(h * 5) + ")")
|
||||||
|
|
||||||
|
def saveScreenshot(self):
|
||||||
|
if self.pix is not None:
|
||||||
|
logger.info("Saving screenshot to file...")
|
||||||
|
filename, _ = QtWidgets.QFileDialog.getSaveFileName(parent=self, caption="Save image",
|
||||||
|
filter="PNG (*.png);;All files (*.*)")
|
||||||
|
|
||||||
|
logger.debug("Filename: %s", filename)
|
||||||
|
if filename != "":
|
||||||
|
self.pixmap().save(filename)
|
||||||
|
else:
|
||||||
|
logger.warning("The user got shown an empty screenshot window?")
|
||||||
|
|
||||||
|
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
|
||||||
|
super().resizeEvent(a0)
|
||||||
|
if self.pixmap() is not None:
|
||||||
|
self.setPixmap(self.pix.scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.FastTransformation))
|
||||||
|
|
||||||
|
def setScale(self, scale):
|
||||||
|
width, height = self.pix.size().width() * scale, self.pix.size().height() * scale
|
||||||
|
self.resize(width, height)
|
||||||
|
|
|
@ -51,6 +51,7 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
self.rawData11: List[Datapoint] = []
|
self.rawData11: List[Datapoint] = []
|
||||||
self.rawData21: List[Datapoint] = []
|
self.rawData21: List[Datapoint] = []
|
||||||
self.stopped = False
|
self.stopped = False
|
||||||
|
self.running = False
|
||||||
self.continuousSweep = False
|
self.continuousSweep = False
|
||||||
self.averaging = False
|
self.averaging = False
|
||||||
self.averages = 3
|
self.averages = 3
|
||||||
|
@ -61,9 +62,11 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def run(self):
|
def run(self):
|
||||||
logger.info("Initializing SweepWorker")
|
logger.info("Initializing SweepWorker")
|
||||||
|
self.running = True
|
||||||
self.percentage = 0
|
self.percentage = 0
|
||||||
if not self.app.serial.is_open:
|
if not self.app.serial.is_open:
|
||||||
logger.debug("Attempted to run without being connected to the NanoVNA")
|
logger.debug("Attempted to run without being connected to the NanoVNA")
|
||||||
|
self.running = False
|
||||||
return
|
return
|
||||||
|
|
||||||
if int(self.app.sweepCountInput.text()) > 0:
|
if int(self.app.sweepCountInput.text()) > 0:
|
||||||
|
@ -88,6 +91,7 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
self.app.sweepEndInput.text())
|
self.app.sweepEndInput.text())
|
||||||
self.error_message = "Unable to parse frequency inputs - check start and stop fields."
|
self.error_message = "Unable to parse frequency inputs - check start and stop fields."
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
self.running = False
|
||||||
self.signals.sweepError.emit()
|
self.signals.sweepError.emit()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -137,6 +141,7 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
except NanoVNAValueException as e:
|
except NanoVNAValueException as e:
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
self.running = False
|
||||||
self.signals.fatalSweepError.emit()
|
self.signals.fatalSweepError.emit()
|
||||||
|
|
||||||
while self.continuousSweep and not self.stopped:
|
while self.continuousSweep and not self.stopped:
|
||||||
|
@ -154,6 +159,7 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
except NanoVNAValueException as e:
|
except NanoVNAValueException as e:
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
self.running = False
|
||||||
self.signals.fatalSweepError.emit()
|
self.signals.fatalSweepError.emit()
|
||||||
|
|
||||||
# Reset the device to show the full range
|
# Reset the device to show the full range
|
||||||
|
@ -166,45 +172,42 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
self.percentage = 100
|
self.percentage = 100
|
||||||
logger.debug("Sending \"finished\" signal")
|
logger.debug("Sending \"finished\" signal")
|
||||||
self.signals.finished.emit()
|
self.signals.finished.emit()
|
||||||
|
self.running = False
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateData(self, values11, values21, offset, segment_size = 101):
|
def updateData(self, values11, values21, offset, segment_size=101):
|
||||||
# Update the data from (i*101) to (i+1)*101
|
# Update the data from (i*101) to (i+1)*101
|
||||||
logger.debug("Calculating data and inserting in existing data at offset %d", offset)
|
logger.debug("Calculating data and inserting in existing data at offset %d", offset)
|
||||||
for i in range(len(values11)):
|
for i in range(len(values11)):
|
||||||
re, im = values11[i]
|
re, im = values11[i]
|
||||||
re21, im21 = values21[i]
|
re21, im21 = values21[i]
|
||||||
freq = self.data11[offset * segment_size + i].freq
|
freq = self.data11[offset * segment_size + i].freq
|
||||||
rawData11 = Datapoint(freq, re, im)
|
raw_data11 = Datapoint(freq, re, im)
|
||||||
rawData21 = Datapoint(freq, re21, im21)
|
raw_data21 = Datapoint(freq, re21, im21)
|
||||||
# TODO: Use applyCalibration instead
|
data11, data21 = self.applyCalibration([raw_data11], [raw_data21])
|
||||||
if self.app.calibration.isCalculated:
|
|
||||||
re, im = self.app.calibration.correct11(re, im, freq)
|
|
||||||
if self.app.calibration.isValid2Port():
|
|
||||||
re21, im21 = self.app.calibration.correct21(re21, im21, freq)
|
|
||||||
|
|
||||||
self.data11[offset * segment_size + i] = Datapoint(freq, re, im)
|
self.data11[offset * segment_size + i] = data11
|
||||||
self.data21[offset * segment_size + i] = Datapoint(freq, re21, im21)
|
self.data21[offset * segment_size + i] = data21
|
||||||
self.rawData11[offset * segment_size + i] = rawData11
|
self.rawData11[offset * segment_size + i] = raw_data11
|
||||||
self.rawData21[offset * segment_size + i] = rawData21
|
self.rawData21[offset * segment_size + i] = raw_data21
|
||||||
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))
|
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))
|
||||||
self.app.saveData(self.data11, self.data21)
|
self.app.saveData(self.data11, self.data21)
|
||||||
logger.debug("Sending \"updated\" signal")
|
logger.debug("Sending \"updated\" signal")
|
||||||
self.signals.updated.emit()
|
self.signals.updated.emit()
|
||||||
|
|
||||||
def saveData(self, frequencies, values11, values21):
|
def saveData(self, frequencies, values11, values21):
|
||||||
rawData11 = []
|
raw_data11 = []
|
||||||
rawData21 = []
|
raw_data21 = []
|
||||||
logger.debug("Calculating data including corrections")
|
logger.debug("Calculating data including corrections")
|
||||||
for i in range(len(values11)):
|
for i in range(len(values11)):
|
||||||
re, im = values11[i]
|
re, im = values11[i]
|
||||||
re21, im21 = values21[i]
|
re21, im21 = values21[i]
|
||||||
freq = frequencies[i]
|
freq = frequencies[i]
|
||||||
rawData11 += [Datapoint(freq, re, im)]
|
raw_data11 += [Datapoint(freq, re, im)]
|
||||||
rawData21 += [Datapoint(freq, re21, im21)]
|
raw_data21 += [Datapoint(freq, re21, im21)]
|
||||||
self.data11, self.data21 = self.applyCalibration(rawData11, rawData21)
|
self.data11, self.data21 = self.applyCalibration(raw_data11, raw_data21)
|
||||||
self.rawData11 = rawData11
|
self.rawData11 = raw_data11
|
||||||
self.rawData21 = rawData21
|
self.rawData21 = raw_data21
|
||||||
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))
|
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))
|
||||||
self.app.saveData(self.data11, self.data21)
|
self.app.saveData(self.data11, self.data21)
|
||||||
logger.debug("Sending \"updated\" signal")
|
logger.debug("Sending \"updated\" signal")
|
||||||
|
@ -213,7 +216,6 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
def applyCalibration(self, raw_data11: List[Datapoint], raw_data21: List[Datapoint]) ->\
|
def applyCalibration(self, raw_data11: List[Datapoint], raw_data21: List[Datapoint]) ->\
|
||||||
(List[Datapoint], List[Datapoint]):
|
(List[Datapoint], List[Datapoint]):
|
||||||
if self.offsetDelay != 0:
|
if self.offsetDelay != 0:
|
||||||
logger.debug("Applying offset delay of %f ps.", self.offsetDelay * 10e12)
|
|
||||||
tmp = []
|
tmp = []
|
||||||
for d in raw_data11:
|
for d in raw_data11:
|
||||||
tmp.append(Calibration.correctDelay11(d, self.offsetDelay))
|
tmp.append(Calibration.correctDelay11(d, self.offsetDelay))
|
||||||
|
@ -230,7 +232,6 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
data21: List[Datapoint] = []
|
data21: List[Datapoint] = []
|
||||||
|
|
||||||
if self.app.calibration.isValid1Port():
|
if self.app.calibration.isValid1Port():
|
||||||
logger.debug("Applying S11 calibration.")
|
|
||||||
for d in raw_data11:
|
for d in raw_data11:
|
||||||
re, im = self.app.calibration.correct11(d.re, d.im, d.freq)
|
re, im = self.app.calibration.correct11(d.re, d.im, d.freq)
|
||||||
data11.append(Datapoint(d.freq, re, im))
|
data11.append(Datapoint(d.freq, re, im))
|
||||||
|
@ -238,7 +239,6 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
data11 = raw_data11
|
data11 = raw_data11
|
||||||
|
|
||||||
if self.app.calibration.isValid2Port():
|
if self.app.calibration.isValid2Port():
|
||||||
logger.debug("Applying S21 calibration.")
|
|
||||||
for d in raw_data21:
|
for d in raw_data21:
|
||||||
re, im = self.app.calibration.correct21(d.re, d.im, d.freq)
|
re, im = self.app.calibration.correct21(d.re, d.im, d.freq)
|
||||||
data21.append(Datapoint(d.freq, re, im))
|
data21.append(Datapoint(d.freq, re, im))
|
||||||
|
@ -273,7 +273,8 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
|
|
||||||
return freq, return11, return21
|
return freq, return11, return21
|
||||||
|
|
||||||
def truncate(self, values: List[List[tuple]], count):
|
@staticmethod
|
||||||
|
def truncate(values: List[List[tuple]], count):
|
||||||
logger.debug("Truncating from %d values to %d", len(values), len(values) - count)
|
logger.debug("Truncating from %d values to %d", len(values), len(values) - count)
|
||||||
if count < 1:
|
if count < 1:
|
||||||
return values
|
return values
|
||||||
|
@ -336,11 +337,11 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
for d in tmpdata:
|
for d in tmpdata:
|
||||||
a, b = d.split(" ")
|
a, b = d.split(" ")
|
||||||
try:
|
try:
|
||||||
if float(a) < -9.5 or float(a) > 9.5:
|
if self.vna.validateInput and float(a) < -9.5 or float(a) > 9.5:
|
||||||
logger.warning("Got a non-float data value: %s (%s)", d, a)
|
logger.warning("Got a non-float data value: %s (%s)", d, a)
|
||||||
logger.debug("Re-reading %s", data)
|
logger.debug("Re-reading %s", data)
|
||||||
done = False
|
done = False
|
||||||
elif float(b) < -9.5 or float(b) > 9.5:
|
elif self.vna.validateInput and float(b) < -9.5 or float(b) > 9.5:
|
||||||
logger.warning("Got a non-float data value: %s (%s)", d, b)
|
logger.warning("Got a non-float data value: %s (%s)", d, b)
|
||||||
logger.debug("Re-reading %s", data)
|
logger.debug("Re-reading %s", data)
|
||||||
done = False
|
done = False
|
||||||
|
@ -389,15 +390,15 @@ class SweepWorker(QtCore.QRunnable):
|
||||||
returnfreq.append(int(f))
|
returnfreq.append(int(f))
|
||||||
return returnfreq
|
return returnfreq
|
||||||
|
|
||||||
def setContinuousSweep(self, continuousSweep: bool):
|
def setContinuousSweep(self, continuous_sweep: bool):
|
||||||
self.continuousSweep = continuousSweep
|
self.continuousSweep = continuous_sweep
|
||||||
|
|
||||||
def setAveraging(self, averaging: bool, averages: str, truncates: str):
|
def setAveraging(self, averaging: bool, averages: str, truncates: str):
|
||||||
self.averaging = averaging
|
self.averaging = averaging
|
||||||
try:
|
try:
|
||||||
self.averages = int(averages)
|
self.averages = int(averages)
|
||||||
self.truncates = int(truncates)
|
self.truncates = int(truncates)
|
||||||
except:
|
except ValueError:
|
||||||
return
|
return
|
||||||
|
|
||||||
def setVNA(self, vna):
|
def setVNA(self, vna):
|
||||||
|
|
|
@ -14,5 +14,5 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
version = '0.2.1'
|
version = '0.2.2alpha'
|
||||||
debug = False
|
debug = False
|
||||||
|
|
Ładowanie…
Reference in New Issue