2019-08-29 13:10:35 +00:00
|
|
|
# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
|
|
|
|
# Copyright (C) 2019. Rune B. Broberg
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2019-08-28 18:20:07 +00:00
|
|
|
import collections
|
2019-09-16 13:47:37 +00:00
|
|
|
import logging
|
2019-08-29 13:42:35 +00:00
|
|
|
import math
|
2019-09-13 15:32:13 +00:00
|
|
|
import sys
|
2019-08-28 18:20:07 +00:00
|
|
|
import threading
|
2019-10-02 07:54:24 +00:00
|
|
|
from time import sleep, strftime, localtime
|
2019-09-26 20:57:34 +00:00
|
|
|
from typing import List, Tuple
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-01 13:14:25 +00:00
|
|
|
import numpy as np
|
2019-08-28 18:20:07 +00:00
|
|
|
import serial
|
2019-09-26 20:57:34 +00:00
|
|
|
import typing
|
2019-08-28 18:20:07 +00:00
|
|
|
from PyQt5 import QtWidgets, QtCore, QtGui
|
2019-09-26 20:57:34 +00:00
|
|
|
from PyQt5.QtCore import QModelIndex
|
2019-09-02 19:03:53 +00:00
|
|
|
from serial.tools import list_ports
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-22 11:42:05 +00:00
|
|
|
from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \
|
|
|
|
RealImaginaryChart
|
2019-09-04 16:12:38 +00:00
|
|
|
from .Calibration import CalibrationWindow, Calibration
|
|
|
|
from .Marker import Marker
|
|
|
|
from .SweepWorker import SweepWorker
|
|
|
|
from .Touchstone import Touchstone
|
2019-09-17 08:55:32 +00:00
|
|
|
from .about import version as ver
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
Datapoint = collections.namedtuple('Datapoint', 'freq re im')
|
|
|
|
|
2019-09-02 19:03:53 +00:00
|
|
|
VID = 1155
|
|
|
|
PID = 22336
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-16 13:47:37 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2019-09-03 20:04:08 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
class NanoVNASaver(QtWidgets.QWidget):
|
2019-09-17 08:55:32 +00:00
|
|
|
version = ver
|
2019-09-05 12:56:40 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__()
|
2019-09-11 18:26:57 +00:00
|
|
|
self.setWindowIcon(QtGui.QIcon("icon_48x48.png"))
|
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
|
|
|
QtCore.QSettings.UserScope,
|
|
|
|
"NanoVNASaver", "NanoVNASaver")
|
|
|
|
print("Settings: " + self.settings.fileName())
|
2019-08-28 18:20:07 +00:00
|
|
|
self.threadpool = QtCore.QThreadPool()
|
2019-08-28 22:56:54 +00:00
|
|
|
self.worker = SweepWorker(self)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
self.bands = BandsModel()
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
self.noSweeps = 1 # Number of sweeps to run
|
|
|
|
|
|
|
|
self.serialLock = threading.Lock()
|
|
|
|
self.serial = serial.Serial()
|
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
self.dataLock = threading.Lock()
|
2019-09-04 08:29:13 +00:00
|
|
|
self.data: List[Datapoint] = []
|
|
|
|
self.data21: List[Datapoint] = []
|
|
|
|
self.referenceS11data: List[Datapoint] = []
|
|
|
|
self.referenceS21data: List[Datapoint] = []
|
2019-09-02 13:20:02 +00:00
|
|
|
|
2019-09-05 18:42:02 +00:00
|
|
|
self.sweepSource = ""
|
|
|
|
self.referenceSource = ""
|
|
|
|
|
2019-09-03 13:36:42 +00:00
|
|
|
self.calibration = Calibration()
|
|
|
|
|
2019-08-29 13:04:40 +00:00
|
|
|
self.markers = []
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-16 13:47:37 +00:00
|
|
|
self.serialPort = self.getPort()
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Building user interface")
|
|
|
|
|
2019-09-05 18:42:02 +00:00
|
|
|
self.baseTitle = "NanoVNA Saver " + NanoVNASaver.version
|
|
|
|
self.updateTitle()
|
2019-08-28 18:20:07 +00:00
|
|
|
layout = QtWidgets.QGridLayout()
|
2019-09-03 18:34:32 +00:00
|
|
|
scrollarea = QtWidgets.QScrollArea()
|
|
|
|
outer = QtWidgets.QVBoxLayout()
|
|
|
|
outer.addWidget(scrollarea)
|
|
|
|
self.setLayout(outer)
|
|
|
|
scrollarea.setWidgetResizable(True)
|
2019-09-09 18:06:01 +00:00
|
|
|
window_width = self.settings.value("WindowWidth", 1350, type=int)
|
|
|
|
window_height = self.settings.value("WindowHeight", 950, type=int)
|
|
|
|
self.resize(window_width, window_height)
|
2019-09-03 18:34:32 +00:00
|
|
|
scrollarea.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
|
|
|
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
|
|
|
widget = QtWidgets.QWidget()
|
|
|
|
widget.setLayout(layout)
|
|
|
|
scrollarea.setWidget(widget)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
self.s11SmithChart = SmithChart("S11 Smith Chart")
|
2019-09-07 06:12:13 +00:00
|
|
|
self.s21PolarChart = PolarChart("S21 Polar Plot")
|
2019-09-01 14:47:12 +00:00
|
|
|
self.s11LogMag = LogMagChart("S11 Return Loss")
|
|
|
|
self.s21LogMag = LogMagChart("S21 Gain")
|
2019-09-05 12:56:40 +00:00
|
|
|
self.s11Phase = PhaseChart("S11 Phase")
|
|
|
|
self.s21Phase = PhaseChart("S21 Phase")
|
2019-09-05 13:51:01 +00:00
|
|
|
self.s11VSWR = VSWRChart("S11 VSWR")
|
2019-09-10 17:51:21 +00:00
|
|
|
self.s11QualityFactor = QualityFactorChart("S11 Quality Factor")
|
2019-09-22 11:42:05 +00:00
|
|
|
self.s11RealImaginary = RealImaginaryChart("S11 R+jX")
|
2019-09-05 12:56:40 +00:00
|
|
|
|
|
|
|
self.s11charts: List[Chart] = []
|
|
|
|
self.s11charts.append(self.s11SmithChart)
|
|
|
|
self.s11charts.append(self.s11LogMag)
|
|
|
|
self.s11charts.append(self.s11Phase)
|
2019-09-05 13:51:01 +00:00
|
|
|
self.s11charts.append(self.s11VSWR)
|
2019-09-22 11:42:05 +00:00
|
|
|
self.s11charts.append(self.s11RealImaginary)
|
2019-09-10 17:51:21 +00:00
|
|
|
self.s11charts.append(self.s11QualityFactor)
|
2019-09-05 12:56:40 +00:00
|
|
|
|
|
|
|
self.s21charts: List[Chart] = []
|
2019-09-07 06:12:13 +00:00
|
|
|
self.s21charts.append(self.s21PolarChart)
|
2019-09-05 12:56:40 +00:00
|
|
|
self.s21charts.append(self.s21LogMag)
|
|
|
|
self.s21charts.append(self.s21Phase)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
self.charts = self.s11charts + self.s21charts
|
2019-09-01 21:13:21 +00:00
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_chart = TDRChart("TDR")
|
|
|
|
self.charts.append(self.tdr_chart)
|
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
self.charts_layout = QtWidgets.QGridLayout()
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
left_column = QtWidgets.QVBoxLayout()
|
2019-09-04 12:40:37 +00:00
|
|
|
marker_column = QtWidgets.QVBoxLayout()
|
2019-09-13 09:17:37 +00:00
|
|
|
self.marker_frame = QtWidgets.QFrame()
|
|
|
|
marker_column.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.marker_frame.setLayout(marker_column)
|
2019-08-28 18:20:07 +00:00
|
|
|
right_column = QtWidgets.QVBoxLayout()
|
2019-09-09 18:06:01 +00:00
|
|
|
right_column.addLayout(self.charts_layout)
|
2019-09-13 09:17:37 +00:00
|
|
|
self.marker_frame.setHidden(not self.settings.value("MarkersVisible", True, bool))
|
2019-09-09 18:06:01 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
layout.addLayout(left_column, 0, 0)
|
2019-09-13 09:17:37 +00:00
|
|
|
layout.addWidget(self.marker_frame, 0, 1)
|
2019-09-04 12:40:37 +00:00
|
|
|
layout.addLayout(right_column, 0, 2)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
################################################################################################################
|
|
|
|
# Sweep control
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
sweep_control_box = QtWidgets.QGroupBox()
|
2019-09-04 12:40:37 +00:00
|
|
|
sweep_control_box.setMaximumWidth(250)
|
2019-08-28 18:20:07 +00:00
|
|
|
sweep_control_box.setTitle("Sweep control")
|
|
|
|
sweep_control_layout = QtWidgets.QFormLayout(sweep_control_box)
|
|
|
|
|
2019-09-12 21:12:32 +00:00
|
|
|
line = QtWidgets.QFrame()
|
|
|
|
line.setFrameShape(QtWidgets.QFrame.VLine)
|
|
|
|
|
|
|
|
sweep_input_layout = QtWidgets.QHBoxLayout()
|
|
|
|
sweep_input_left_layout = QtWidgets.QFormLayout()
|
|
|
|
sweep_input_right_layout = QtWidgets.QFormLayout()
|
|
|
|
sweep_input_layout.addLayout(sweep_input_left_layout)
|
|
|
|
sweep_input_layout.addWidget(line)
|
|
|
|
sweep_input_layout.addLayout(sweep_input_right_layout)
|
|
|
|
sweep_control_layout.addRow(sweep_input_layout)
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
self.sweepStartInput = QtWidgets.QLineEdit("")
|
2019-09-17 09:13:42 +00:00
|
|
|
self.sweepStartInput.setMinimumWidth(60)
|
2019-08-28 18:20:07 +00:00
|
|
|
self.sweepStartInput.setAlignment(QtCore.Qt.AlignRight)
|
2019-09-12 21:12:32 +00:00
|
|
|
self.sweepStartInput.textEdited.connect(self.updateCenterSpan)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-12 21:12:32 +00:00
|
|
|
sweep_input_left_layout.addRow(QtWidgets.QLabel("Start"), self.sweepStartInput)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
self.sweepEndInput = QtWidgets.QLineEdit("")
|
|
|
|
self.sweepEndInput.setAlignment(QtCore.Qt.AlignRight)
|
2019-09-12 21:12:32 +00:00
|
|
|
self.sweepEndInput.textEdited.connect(self.updateCenterSpan)
|
|
|
|
|
|
|
|
sweep_input_left_layout.addRow(QtWidgets.QLabel("Stop"), self.sweepEndInput)
|
|
|
|
|
|
|
|
self.sweepCenterInput = QtWidgets.QLineEdit("")
|
2019-09-17 09:13:42 +00:00
|
|
|
self.sweepCenterInput.setMinimumWidth(60)
|
2019-09-12 21:12:32 +00:00
|
|
|
self.sweepCenterInput.setAlignment(QtCore.Qt.AlignRight)
|
|
|
|
self.sweepCenterInput.textEdited.connect(self.updateStartEnd)
|
|
|
|
|
|
|
|
sweep_input_right_layout.addRow(QtWidgets.QLabel("Center"), self.sweepCenterInput)
|
|
|
|
|
|
|
|
self.sweepSpanInput = QtWidgets.QLineEdit("")
|
|
|
|
self.sweepSpanInput.setAlignment(QtCore.Qt.AlignRight)
|
|
|
|
self.sweepSpanInput.textEdited.connect(self.updateStartEnd)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-12 21:12:32 +00:00
|
|
|
sweep_input_right_layout.addRow(QtWidgets.QLabel("Span"), self.sweepSpanInput)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-18 11:45:53 +00:00
|
|
|
self.sweepCountInput = QtWidgets.QLineEdit(self.settings.value("Segments", "1"))
|
2019-08-28 18:20:07 +00:00
|
|
|
self.sweepCountInput.setAlignment(QtCore.Qt.AlignRight)
|
2019-09-23 19:10:43 +00:00
|
|
|
self.sweepCountInput.setFixedWidth(60)
|
|
|
|
self.sweepCountInput.textEdited.connect(self.updateStepSize)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-23 19:10:43 +00:00
|
|
|
self.sweepStepLabel = QtWidgets.QLabel("Hz/step")
|
|
|
|
self.sweepStepLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
|
|
|
|
|
|
|
segment_layout = QtWidgets.QHBoxLayout()
|
|
|
|
segment_layout.addWidget(self.sweepCountInput)
|
|
|
|
segment_layout.addWidget(self.sweepStepLabel)
|
|
|
|
sweep_control_layout.addRow(QtWidgets.QLabel("Segments"), segment_layout)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-21 10:55:22 +00:00
|
|
|
self.sweepSettingsWindow = SweepSettingsWindow(self)
|
|
|
|
btn_sweep_settings_window = QtWidgets.QPushButton("Sweep settings")
|
|
|
|
btn_sweep_settings_window.clicked.connect(self.displaySweepSettingsWindow)
|
2019-09-10 10:47:18 +00:00
|
|
|
|
2019-09-21 10:55:22 +00:00
|
|
|
sweep_control_layout.addRow(btn_sweep_settings_window)
|
2019-09-10 10:47:18 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
self.sweepProgressBar = QtWidgets.QProgressBar()
|
2019-08-28 22:56:54 +00:00
|
|
|
self.sweepProgressBar.setMaximum(100)
|
|
|
|
self.sweepProgressBar.setValue(0)
|
2019-08-28 18:20:07 +00:00
|
|
|
sweep_control_layout.addRow(self.sweepProgressBar)
|
|
|
|
|
|
|
|
self.btnSweep = QtWidgets.QPushButton("Sweep")
|
|
|
|
self.btnSweep.clicked.connect(self.sweep)
|
2019-09-10 10:47:18 +00:00
|
|
|
self.btnStopSweep = QtWidgets.QPushButton("Stop")
|
|
|
|
self.btnStopSweep.clicked.connect(self.stopSweep)
|
|
|
|
self.btnStopSweep.setDisabled(True)
|
2019-09-26 11:14:42 +00:00
|
|
|
btn_layout = QtWidgets.QHBoxLayout()
|
|
|
|
btn_layout.addWidget(self.btnSweep)
|
|
|
|
btn_layout.addWidget(self.btnStopSweep)
|
|
|
|
btn_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
btn_layout_widget = QtWidgets.QWidget()
|
|
|
|
btn_layout_widget.setLayout(btn_layout)
|
|
|
|
sweep_control_layout.addRow(btn_layout_widget)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
left_column.addWidget(sweep_control_box)
|
|
|
|
|
|
|
|
################################################################################################################
|
|
|
|
# Marker control
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
marker_control_box = QtWidgets.QGroupBox()
|
|
|
|
marker_control_box.setTitle("Markers")
|
2019-09-13 09:17:37 +00:00
|
|
|
marker_control_box.setMaximumWidth(250)
|
2019-08-28 18:20:07 +00:00
|
|
|
marker_control_layout = QtWidgets.QFormLayout(marker_control_box)
|
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
marker1_color = self.settings.value("Marker1Color", QtGui.QColor(255, 0, 20), QtGui.QColor)
|
|
|
|
marker1 = Marker("Marker 1", marker1_color)
|
2019-08-29 13:42:35 +00:00
|
|
|
marker1.updated.connect(self.dataUpdated)
|
2019-08-29 13:04:40 +00:00
|
|
|
label, layout = marker1.getRow()
|
2019-08-29 10:19:58 +00:00
|
|
|
marker_control_layout.addRow(label, layout)
|
2019-08-29 13:04:40 +00:00
|
|
|
self.markers.append(marker1)
|
2019-09-11 19:59:48 +00:00
|
|
|
marker1.isMouseControlledRadioButton.setChecked(True)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
marker2_color = self.settings.value("Marker2Color", QtGui.QColor(20, 0, 255), QtGui.QColor)
|
|
|
|
marker2 = Marker("Marker 2", marker2_color)
|
2019-08-29 13:42:35 +00:00
|
|
|
marker2.updated.connect(self.dataUpdated)
|
2019-08-29 13:04:40 +00:00
|
|
|
label, layout = marker2.getRow()
|
2019-08-29 10:19:58 +00:00
|
|
|
marker_control_layout.addRow(label, layout)
|
2019-08-29 13:04:40 +00:00
|
|
|
self.markers.append(marker2)
|
|
|
|
|
2019-09-11 19:59:48 +00:00
|
|
|
marker3_color = self.settings.value("Marker3Color", QtGui.QColor(20, 255, 20), QtGui.QColor)
|
|
|
|
marker3 = Marker("Marker 3", marker3_color)
|
|
|
|
marker3.updated.connect(self.dataUpdated)
|
|
|
|
label, layout = marker3.getRow()
|
|
|
|
marker_control_layout.addRow(label, layout)
|
2019-09-13 09:17:37 +00:00
|
|
|
|
2019-09-11 19:59:48 +00:00
|
|
|
self.markers.append(marker3)
|
|
|
|
|
2019-09-13 09:17:37 +00:00
|
|
|
self.showMarkerButton = QtWidgets.QPushButton()
|
|
|
|
if self.marker_frame.isHidden():
|
|
|
|
self.showMarkerButton.setText("Show data")
|
|
|
|
else:
|
|
|
|
self.showMarkerButton.setText("Hide data")
|
|
|
|
self.showMarkerButton.clicked.connect(self.toggleMarkerFrame)
|
|
|
|
marker_control_layout.addRow(self.showMarkerButton)
|
2019-09-11 19:59:48 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
for c in self.charts:
|
|
|
|
c.setMarkers(self.markers)
|
2019-09-26 20:57:34 +00:00
|
|
|
c.setBands(self.bands)
|
2019-09-13 09:17:37 +00:00
|
|
|
left_column.addWidget(marker_control_box)
|
2019-09-04 12:40:37 +00:00
|
|
|
|
2019-09-11 19:59:48 +00:00
|
|
|
marker_column.addWidget(self.markers[0].getGroupBox())
|
2019-09-09 11:21:54 +00:00
|
|
|
marker_column.addWidget(self.markers[1].getGroupBox())
|
|
|
|
marker_column.addWidget(self.markers[2].getGroupBox())
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-08-30 16:41:22 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Statistics/analysis
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
s11_control_box = QtWidgets.QGroupBox()
|
|
|
|
s11_control_box.setTitle("S11")
|
|
|
|
s11_control_layout = QtWidgets.QFormLayout()
|
|
|
|
s11_control_box.setLayout(s11_control_layout)
|
|
|
|
|
|
|
|
self.s11_min_swr_label = QtWidgets.QLabel()
|
|
|
|
s11_control_layout.addRow("Min VSWR:", self.s11_min_swr_label)
|
|
|
|
self.s11_min_rl_label = QtWidgets.QLabel()
|
|
|
|
s11_control_layout.addRow("Return loss:", self.s11_min_rl_label)
|
|
|
|
|
2019-09-19 12:15:47 +00:00
|
|
|
marker_column.addWidget(s11_control_box)
|
2019-08-30 16:41:22 +00:00
|
|
|
|
|
|
|
s21_control_box = QtWidgets.QGroupBox()
|
|
|
|
s21_control_box.setTitle("S21")
|
|
|
|
s21_control_layout = QtWidgets.QFormLayout()
|
|
|
|
s21_control_box.setLayout(s21_control_layout)
|
|
|
|
|
|
|
|
self.s21_min_gain_label = QtWidgets.QLabel()
|
|
|
|
s21_control_layout.addRow("Min gain:", self.s21_min_gain_label)
|
|
|
|
|
|
|
|
self.s21_max_gain_label = QtWidgets.QLabel()
|
|
|
|
s21_control_layout.addRow("Max gain:", self.s21_max_gain_label)
|
|
|
|
|
2019-09-19 12:15:47 +00:00
|
|
|
marker_column.addWidget(s21_control_box)
|
2019-08-30 16:41:22 +00:00
|
|
|
|
2019-09-04 12:40:37 +00:00
|
|
|
marker_column.addSpacerItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))
|
|
|
|
|
|
|
|
################################################################################################################
|
|
|
|
# TDR
|
|
|
|
################################################################################################################
|
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_window = TDRWindow(self)
|
|
|
|
self.tdr_chart.tdrWindow = self.tdr_window
|
|
|
|
|
2019-09-01 13:14:25 +00:00
|
|
|
tdr_control_box = QtWidgets.QGroupBox()
|
|
|
|
tdr_control_box.setTitle("TDR")
|
|
|
|
tdr_control_layout = QtWidgets.QFormLayout()
|
|
|
|
tdr_control_box.setLayout(tdr_control_layout)
|
2019-09-04 12:40:37 +00:00
|
|
|
tdr_control_box.setMaximumWidth(250)
|
2019-09-01 13:14:25 +00:00
|
|
|
|
|
|
|
self.tdr_result_label = QtWidgets.QLabel()
|
|
|
|
tdr_control_layout.addRow("Estimated cable length:", self.tdr_result_label)
|
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_button = QtWidgets.QPushButton("Time Domain Reflectometry ...")
|
|
|
|
self.tdr_button.clicked.connect(self.displayTDRWindow)
|
|
|
|
|
|
|
|
tdr_control_layout.addRow(self.tdr_button)
|
|
|
|
|
2019-09-01 13:14:25 +00:00
|
|
|
left_column.addWidget(tdr_control_box)
|
|
|
|
|
2019-08-30 16:41:22 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Spacer
|
|
|
|
################################################################################################################
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
left_column.addSpacerItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))
|
|
|
|
|
2019-09-01 21:13:21 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Reference control
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
reference_control_box = QtWidgets.QGroupBox()
|
2019-09-04 12:40:37 +00:00
|
|
|
reference_control_box.setMaximumWidth(250)
|
2019-09-01 21:13:21 +00:00
|
|
|
reference_control_box.setTitle("Reference sweep")
|
|
|
|
reference_control_layout = QtWidgets.QFormLayout(reference_control_box)
|
|
|
|
|
2019-09-02 14:06:15 +00:00
|
|
|
btnSetReference = QtWidgets.QPushButton("Set current as reference")
|
|
|
|
btnSetReference.clicked.connect(self.setReference)
|
2019-09-01 21:13:21 +00:00
|
|
|
self.btnResetReference = QtWidgets.QPushButton("Reset reference")
|
|
|
|
self.btnResetReference.clicked.connect(self.resetReference)
|
|
|
|
self.btnResetReference.setDisabled(True)
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
reference_control_layout.addRow(btnSetReference)
|
2019-09-01 21:13:21 +00:00
|
|
|
reference_control_layout.addRow(self.btnResetReference)
|
|
|
|
|
|
|
|
left_column.addWidget(reference_control_box)
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Serial control
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
serial_control_box = QtWidgets.QGroupBox()
|
2019-09-04 12:40:37 +00:00
|
|
|
serial_control_box.setMaximumWidth(250)
|
2019-08-28 18:20:07 +00:00
|
|
|
serial_control_box.setTitle("Serial port control")
|
|
|
|
serial_control_layout = QtWidgets.QFormLayout(serial_control_box)
|
|
|
|
self.serialPortInput = QtWidgets.QLineEdit(self.serialPort)
|
|
|
|
self.serialPortInput.setAlignment(QtCore.Qt.AlignRight)
|
2019-09-25 19:50:27 +00:00
|
|
|
btn_rescan_serial_port = QtWidgets.QPushButton("Rescan")
|
|
|
|
btn_rescan_serial_port.setFixedWidth(60)
|
|
|
|
btn_rescan_serial_port.clicked.connect(self.rescanSerialPort)
|
|
|
|
serial_port_input_layout = QtWidgets.QHBoxLayout()
|
|
|
|
serial_port_input_layout.addWidget(self.serialPortInput)
|
|
|
|
serial_port_input_layout.addWidget(btn_rescan_serial_port)
|
|
|
|
serial_control_layout.addRow(QtWidgets.QLabel("Serial port"), serial_port_input_layout)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-07 06:12:13 +00:00
|
|
|
self.btnSerialToggle = QtWidgets.QPushButton("Connect to NanoVNA")
|
2019-08-28 18:20:07 +00:00
|
|
|
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
|
|
|
serial_control_layout.addRow(self.btnSerialToggle)
|
|
|
|
|
|
|
|
left_column.addWidget(serial_control_box)
|
|
|
|
|
|
|
|
################################################################################################################
|
|
|
|
# File control
|
|
|
|
################################################################################################################
|
|
|
|
|
2019-09-03 14:59:36 +00:00
|
|
|
self.fileWindow = QtWidgets.QWidget()
|
|
|
|
self.fileWindow.setWindowTitle("Files")
|
2019-09-21 11:13:33 +00:00
|
|
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self.fileWindow, self.fileWindow.hide)
|
2019-09-03 14:59:36 +00:00
|
|
|
file_window_layout = QtWidgets.QVBoxLayout()
|
|
|
|
self.fileWindow.setLayout(file_window_layout)
|
|
|
|
|
|
|
|
reference_file_control_box = QtWidgets.QGroupBox("Import file")
|
|
|
|
reference_file_control_layout = QtWidgets.QFormLayout(reference_file_control_box)
|
|
|
|
self.referenceFileNameInput = QtWidgets.QLineEdit("")
|
2019-09-26 11:14:42 +00:00
|
|
|
btn_reference_file_picker = QtWidgets.QPushButton("...")
|
|
|
|
btn_reference_file_picker.setMaximumWidth(25)
|
|
|
|
btn_reference_file_picker.clicked.connect(self.pickReferenceFile)
|
|
|
|
reference_file_name_layout = QtWidgets.QHBoxLayout()
|
|
|
|
reference_file_name_layout.addWidget(self.referenceFileNameInput)
|
|
|
|
reference_file_name_layout.addWidget(btn_reference_file_picker)
|
|
|
|
|
|
|
|
reference_file_control_layout.addRow(QtWidgets.QLabel("Filename"), reference_file_name_layout)
|
2019-09-03 14:59:36 +00:00
|
|
|
file_window_layout.addWidget(reference_file_control_box)
|
|
|
|
|
2019-09-26 11:14:42 +00:00
|
|
|
btn_load_reference = QtWidgets.QPushButton("Load reference")
|
|
|
|
btn_load_reference.clicked.connect(self.loadReferenceFile)
|
|
|
|
btn_load_sweep = QtWidgets.QPushButton("Load as sweep")
|
|
|
|
btn_load_sweep.clicked.connect(self.loadSweepFile)
|
|
|
|
reference_file_control_layout.addRow(btn_load_reference)
|
|
|
|
reference_file_control_layout.addRow(btn_load_sweep)
|
2019-09-03 14:59:36 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
file_control_box = QtWidgets.QGroupBox()
|
|
|
|
file_control_box.setTitle("Export file")
|
2019-09-04 12:40:37 +00:00
|
|
|
file_control_box.setMaximumWidth(300)
|
2019-08-28 18:20:07 +00:00
|
|
|
file_control_layout = QtWidgets.QFormLayout(file_control_box)
|
|
|
|
self.fileNameInput = QtWidgets.QLineEdit("")
|
2019-09-26 11:14:42 +00:00
|
|
|
btn_file_picker = QtWidgets.QPushButton("...")
|
|
|
|
btn_file_picker.setMaximumWidth(25)
|
|
|
|
btn_file_picker.clicked.connect(self.pickFile)
|
|
|
|
file_name_layout = QtWidgets.QHBoxLayout()
|
|
|
|
file_name_layout.addWidget(self.fileNameInput)
|
|
|
|
file_name_layout.addWidget(btn_file_picker)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-26 11:14:42 +00:00
|
|
|
file_control_layout.addRow(QtWidgets.QLabel("Filename"), file_name_layout)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-08-30 07:43:18 +00:00
|
|
|
self.btnExportFile = QtWidgets.QPushButton("Export data S1P")
|
2019-08-30 19:57:10 +00:00
|
|
|
self.btnExportFile.clicked.connect(self.exportFileS1P)
|
|
|
|
file_control_layout.addRow(self.btnExportFile)
|
|
|
|
|
|
|
|
self.btnExportFile = QtWidgets.QPushButton("Export data S2P")
|
|
|
|
self.btnExportFile.clicked.connect(self.exportFileS2P)
|
2019-08-28 18:20:07 +00:00
|
|
|
file_control_layout.addRow(self.btnExportFile)
|
|
|
|
|
2019-09-03 14:59:36 +00:00
|
|
|
file_window_layout.addWidget(file_control_box)
|
|
|
|
|
2019-09-26 11:14:42 +00:00
|
|
|
btn_open_file_window = QtWidgets.QPushButton("Files ...")
|
|
|
|
btn_open_file_window.clicked.connect(self.displayFileWindow)
|
2019-09-03 14:59:36 +00:00
|
|
|
|
2019-09-10 10:47:18 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Calibration
|
|
|
|
################################################################################################################
|
|
|
|
|
|
|
|
btnOpenCalibrationWindow = QtWidgets.QPushButton("Calibration ...")
|
|
|
|
self.calibrationWindow = CalibrationWindow(self)
|
|
|
|
btnOpenCalibrationWindow.clicked.connect(self.displayCalibrationWindow)
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Display setup
|
|
|
|
################################################################################################################
|
|
|
|
|
2019-09-19 12:15:47 +00:00
|
|
|
btn_display_setup = QtWidgets.QPushButton("Display setup ...")
|
|
|
|
btn_display_setup.setMaximumWidth(250)
|
2019-09-21 10:55:22 +00:00
|
|
|
self.displaySetupWindow = DisplaySettingsWindow(self)
|
2019-09-19 12:15:47 +00:00
|
|
|
btn_display_setup.clicked.connect(self.displaySettingsWindow)
|
2019-09-05 12:56:40 +00:00
|
|
|
|
|
|
|
btn_about = QtWidgets.QPushButton("About ...")
|
|
|
|
btn_about.setMaximumWidth(250)
|
|
|
|
btn_about.clicked.connect(lambda: QtWidgets.QMessageBox.about(self, "About NanoVNASaver",
|
|
|
|
"NanoVNASaver version "
|
|
|
|
+ NanoVNASaver.version +
|
|
|
|
"\n\n\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg\n" +
|
|
|
|
"This program comes with ABSOLUTELY NO WARRANTY\n" +
|
|
|
|
"This program is licensed under the GNU General Public License version 3\n\n" +
|
2019-09-26 11:14:42 +00:00
|
|
|
"See https://mihtjel.github.io/nanovna-saver/ for further details"))
|
2019-09-19 12:15:47 +00:00
|
|
|
|
|
|
|
button_grid = QtWidgets.QGridLayout()
|
2019-09-26 11:14:42 +00:00
|
|
|
button_grid.addWidget(btn_open_file_window, 0, 0)
|
2019-09-19 12:15:47 +00:00
|
|
|
button_grid.addWidget(btnOpenCalibrationWindow, 0, 1)
|
|
|
|
button_grid.addWidget(btn_display_setup, 1, 0)
|
|
|
|
button_grid.addWidget(btn_about, 1, 1)
|
|
|
|
left_column.addLayout(button_grid)
|
2019-09-05 12:56:40 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
################################################################################################################
|
|
|
|
# Right side
|
|
|
|
################################################################################################################
|
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
self.worker.signals.updated.connect(self.dataUpdated)
|
2019-08-29 13:04:40 +00:00
|
|
|
self.worker.signals.finished.connect(self.sweepFinished)
|
2019-09-26 13:14:28 +00:00
|
|
|
self.worker.signals.sweepError.connect(self.showSweepError)
|
2019-08-28 22:56:54 +00:00
|
|
|
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Finished building interface")
|
|
|
|
|
2019-09-25 19:50:27 +00:00
|
|
|
def rescanSerialPort(self):
|
|
|
|
serial_port = self.getPort()
|
|
|
|
self.serialPort = serial_port
|
|
|
|
self.serialPortInput.setText(serial_port)
|
|
|
|
|
2019-09-02 19:03:53 +00:00
|
|
|
# Get that windows port
|
2019-09-04 08:29:13 +00:00
|
|
|
@staticmethod
|
2019-09-16 13:47:37 +00:00
|
|
|
def getPort() -> str:
|
2019-09-02 19:03:53 +00:00
|
|
|
device_list = list_ports.comports()
|
|
|
|
for d in device_list:
|
|
|
|
if (d.vid == VID and
|
|
|
|
d.pid == PID):
|
|
|
|
port = d.device
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.info("Found NanoVNA (%04x %04x) on port %s", d.vid, d.pid, d.device)
|
2019-09-02 19:03:53 +00:00
|
|
|
return port
|
2019-09-25 19:50:27 +00:00
|
|
|
return ""
|
2019-09-02 19:03:53 +00:00
|
|
|
|
2019-09-02 13:20:02 +00:00
|
|
|
def pickReferenceFile(self):
|
2019-09-04 08:29:13 +00:00
|
|
|
filename, _ = QtWidgets.QFileDialog.getOpenFileName(directory=self.referenceFileNameInput.text(),
|
|
|
|
filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
|
2019-09-03 14:59:36 +00:00
|
|
|
if filename != "":
|
|
|
|
self.referenceFileNameInput.setText(filename)
|
2019-09-02 13:20:02 +00:00
|
|
|
|
2019-08-30 07:43:18 +00:00
|
|
|
def pickFile(self):
|
2019-09-04 08:29:13 +00:00
|
|
|
filename, _ = QtWidgets.QFileDialog.getSaveFileName(directory=self.fileNameInput.text(),
|
|
|
|
filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
|
2019-09-03 14:59:36 +00:00
|
|
|
if filename != "":
|
|
|
|
self.fileNameInput.setText(filename)
|
2019-08-30 07:43:18 +00:00
|
|
|
|
2019-08-30 19:57:10 +00:00
|
|
|
def exportFileS1P(self):
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Save S1P file to %s", self.fileNameInput.text())
|
2019-08-30 19:57:10 +00:00
|
|
|
if len(self.data) == 0:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.warning("No data stored, nothing written.")
|
2019-08-30 07:43:18 +00:00
|
|
|
return
|
2019-08-28 18:20:07 +00:00
|
|
|
filename = self.fileNameInput.text()
|
2019-08-30 07:43:18 +00:00
|
|
|
if filename == "":
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.warning("No filename entered, nothing saved.")
|
2019-08-30 07:43:18 +00:00
|
|
|
return
|
|
|
|
try:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Opening %s for writing", filename)
|
2019-08-30 07:43:18 +00:00
|
|
|
file = open(filename, "w+")
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Writing file")
|
2019-08-30 07:43:18 +00:00
|
|
|
file.write("# Hz S RI R 50\n")
|
2019-08-30 09:00:48 +00:00
|
|
|
for i in range(len(self.data)):
|
2019-08-30 19:57:10 +00:00
|
|
|
if i == 0 or self.data[i].freq != self.data[i-1].freq:
|
2019-08-30 09:00:48 +00:00
|
|
|
file.write(str(self.data[i].freq) + " " + str(self.data[i].re) + " " + str(self.data[i].im) + "\n")
|
2019-08-30 07:43:18 +00:00
|
|
|
file.close()
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("File written")
|
2019-08-30 07:43:18 +00:00
|
|
|
except Exception as e:
|
2019-09-16 20:49:21 +00:00
|
|
|
logger.exception("Error during file export: %s", e)
|
2019-08-30 07:43:18 +00:00
|
|
|
return
|
|
|
|
|
2019-08-30 19:57:10 +00:00
|
|
|
def exportFileS2P(self):
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Save S2P file to %s", self.fileNameInput.text())
|
|
|
|
if len(self.data) == 0 or len(self.data21) == 0:
|
|
|
|
logger.warning("No data stored, nothing written.")
|
2019-08-30 19:57:10 +00:00
|
|
|
return
|
|
|
|
filename = self.fileNameInput.text()
|
|
|
|
if filename == "":
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.warning("No filename entered, nothing saved.")
|
2019-08-30 19:57:10 +00:00
|
|
|
return
|
|
|
|
try:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Opening %s for writing", filename)
|
2019-08-30 19:57:10 +00:00
|
|
|
file = open(filename, "w+")
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Writing file")
|
2019-08-30 19:57:10 +00:00
|
|
|
file.write("# Hz S RI R 50\n")
|
|
|
|
for i in range(len(self.data)):
|
|
|
|
if i == 0 or self.data[i].freq != self.data[i-1].freq:
|
|
|
|
file.write(str(self.data[i].freq) + " " + str(self.data[i].re) + " " + str(self.data[i].im) + " " +
|
|
|
|
str(self.data21[i].re) + " " + str(self.data21[i].im) + " 0 0 0 0\n")
|
|
|
|
file.close()
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("File written")
|
2019-08-30 19:57:10 +00:00
|
|
|
except Exception as e:
|
2019-09-16 20:49:21 +00:00
|
|
|
logger.exception("Error during file export: %s", e)
|
2019-08-30 19:57:10 +00:00
|
|
|
return
|
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
def serialButtonClick(self):
|
|
|
|
if self.serial.is_open:
|
|
|
|
self.stopSerial()
|
|
|
|
else:
|
|
|
|
self.startSerial()
|
|
|
|
return
|
|
|
|
|
2019-09-04 13:15:53 +00:00
|
|
|
def flushSerialBuffers(self):
|
2019-09-04 16:40:45 +00:00
|
|
|
if self.serialLock.acquire():
|
|
|
|
self.serial.write(b"\r\n\r\n")
|
|
|
|
sleep(0.1)
|
|
|
|
self.serial.reset_input_buffer()
|
|
|
|
self.serial.reset_output_buffer()
|
|
|
|
sleep(0.1)
|
|
|
|
self.serialLock.release()
|
2019-09-04 13:15:53 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
def startSerial(self):
|
|
|
|
if self.serialLock.acquire():
|
|
|
|
self.serialPort = self.serialPortInput.text()
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.info("Opening serial port %s", self.serialPort)
|
2019-08-28 18:20:07 +00:00
|
|
|
try:
|
|
|
|
self.serial = serial.Serial(port=self.serialPort, baudrate=115200)
|
2019-08-30 09:00:48 +00:00
|
|
|
self.serial.timeout = 0.05
|
2019-08-28 18:20:07 +00:00
|
|
|
except serial.SerialException as exc:
|
2019-09-22 11:42:05 +00:00
|
|
|
logger.error("Tried to open %s and failed: %s", self.serialPort, exc)
|
2019-08-28 18:20:07 +00:00
|
|
|
self.serialLock.release()
|
|
|
|
return
|
2019-09-07 06:12:13 +00:00
|
|
|
self.btnSerialToggle.setText("Disconnect")
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
self.serialLock.release()
|
2019-08-29 13:04:40 +00:00
|
|
|
sleep(0.05)
|
|
|
|
|
2019-09-04 13:15:53 +00:00
|
|
|
self.flushSerialBuffers()
|
2019-09-04 16:40:45 +00:00
|
|
|
sleep(0.05)
|
|
|
|
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.info(self.readFirmware())
|
2019-09-04 13:15:53 +00:00
|
|
|
|
2019-08-29 13:04:40 +00:00
|
|
|
frequencies = self.readValues("frequencies")
|
2019-10-01 11:23:38 +00:00
|
|
|
if frequencies:
|
|
|
|
logger.info("Read starting frequency %s and end frequency %s", frequencies[0], frequencies[100])
|
|
|
|
if int(frequencies[0]) == int(frequencies[100]) and (self.sweepStartInput.text() == "" or self.sweepEndInput.text() == ""):
|
|
|
|
self.sweepStartInput.setText(frequencies[0])
|
|
|
|
self.sweepEndInput.setText(str(int(frequencies[100]) + 100000))
|
|
|
|
elif self.sweepStartInput.text() == "" or self.sweepEndInput.text() == "":
|
|
|
|
self.sweepStartInput.setText(frequencies[0])
|
|
|
|
self.sweepEndInput.setText(frequencies[100])
|
|
|
|
else:
|
|
|
|
logger.warning("No frequencies read")
|
|
|
|
return
|
2019-08-29 13:04:40 +00:00
|
|
|
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.debug("Starting initial sweep")
|
2019-08-28 18:20:07 +00:00
|
|
|
self.sweep()
|
|
|
|
return
|
|
|
|
|
|
|
|
def stopSerial(self):
|
|
|
|
if self.serialLock.acquire():
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.info("Closing connection to NanoVNA")
|
2019-08-28 18:20:07 +00:00
|
|
|
self.serial.close()
|
|
|
|
self.serialLock.release()
|
2019-09-07 06:12:13 +00:00
|
|
|
self.btnSerialToggle.setText("Connect to NanoVNA")
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
def writeSerial(self, command):
|
|
|
|
if not self.serial.is_open:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.warning("Writing without serial port being opened (%s)", command)
|
2019-08-28 18:20:07 +00:00
|
|
|
return
|
|
|
|
if self.serialLock.acquire():
|
|
|
|
try:
|
|
|
|
self.serial.write(str(command + "\r").encode('ascii'))
|
|
|
|
self.serial.readline()
|
|
|
|
except serial.SerialException as exc:
|
2019-09-16 20:49:21 +00:00
|
|
|
logger.exception("Exception while writing to serial port (%s): %s", command, exc)
|
2019-08-28 18:20:07 +00:00
|
|
|
self.serialLock.release()
|
|
|
|
return
|
|
|
|
|
|
|
|
def setSweep(self, start, stop):
|
|
|
|
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
|
|
|
|
|
|
|
|
def sweep(self):
|
|
|
|
# Run the serial port update
|
|
|
|
if not self.serial.is_open:
|
|
|
|
return
|
2019-09-10 10:47:18 +00:00
|
|
|
self.worker.stopped = False
|
2019-08-28 18:20:07 +00:00
|
|
|
|
|
|
|
self.sweepProgressBar.setValue(0)
|
|
|
|
self.btnSweep.setDisabled(True)
|
2019-09-10 10:47:18 +00:00
|
|
|
self.btnStopSweep.setDisabled(False)
|
2019-09-09 11:21:54 +00:00
|
|
|
for m in self.markers:
|
|
|
|
m.resetLabels()
|
2019-09-01 17:38:01 +00:00
|
|
|
self.s11_min_rl_label.setText("")
|
|
|
|
self.s11_min_swr_label.setText("")
|
|
|
|
self.s21_min_gain_label.setText("")
|
|
|
|
self.s21_max_gain_label.setText("")
|
|
|
|
self.tdr_result_label.setText("")
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-18 11:45:53 +00:00
|
|
|
if self.sweepCountInput.text().isdigit():
|
|
|
|
self.settings.setValue("Segments", self.sweepCountInput.text())
|
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
self.threadpool.start(self.worker)
|
|
|
|
|
2019-09-10 10:47:18 +00:00
|
|
|
def stopSweep(self):
|
|
|
|
self.worker.stopped = True
|
|
|
|
|
2019-09-04 16:40:45 +00:00
|
|
|
def readFirmware(self):
|
|
|
|
if self.serialLock.acquire():
|
2019-09-16 19:41:01 +00:00
|
|
|
result = ""
|
2019-09-04 16:40:45 +00:00
|
|
|
try:
|
|
|
|
data = "a"
|
|
|
|
while data != "":
|
|
|
|
data = self.serial.readline().decode('ascii')
|
|
|
|
# Then send the command to read data
|
|
|
|
self.serial.write("info\r".encode('ascii'))
|
2019-09-16 19:41:01 +00:00
|
|
|
result = ""
|
|
|
|
data = ""
|
|
|
|
sleep(0.01)
|
|
|
|
while "ch>" not in data:
|
|
|
|
data = self.serial.readline().decode('ascii')
|
|
|
|
result += data
|
2019-09-04 16:40:45 +00:00
|
|
|
except serial.SerialException as exc:
|
2019-09-16 20:49:21 +00:00
|
|
|
logger.exception("Exception while reading firmware data: %s", exc)
|
2019-09-04 16:40:45 +00:00
|
|
|
self.serialLock.release()
|
|
|
|
return result
|
2019-09-16 19:41:01 +00:00
|
|
|
else:
|
|
|
|
logger.error("Unable to acquire serial lock to read firmware.")
|
|
|
|
return ""
|
2019-09-04 16:40:45 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
def readValues(self, value):
|
|
|
|
if self.serialLock.acquire():
|
|
|
|
try:
|
|
|
|
data = "a"
|
|
|
|
while data != "":
|
|
|
|
data = self.serial.readline().decode('ascii')
|
|
|
|
|
|
|
|
# Then send the command to read data
|
|
|
|
self.serial.write(str(value + "\r").encode('ascii'))
|
2019-09-16 19:41:01 +00:00
|
|
|
result = ""
|
|
|
|
data = ""
|
|
|
|
sleep(0.05)
|
|
|
|
while "ch>" not in data:
|
|
|
|
data = self.serial.readline().decode('ascii')
|
|
|
|
result += data
|
|
|
|
values = result.split("\r\n")
|
2019-08-28 18:20:07 +00:00
|
|
|
except serial.SerialException as exc:
|
2019-09-16 20:49:21 +00:00
|
|
|
logger.exception("Exception while reading %s: %s", value, exc)
|
2019-10-01 11:23:38 +00:00
|
|
|
self.serialLock.release()
|
|
|
|
return
|
2019-09-16 19:41:01 +00:00
|
|
|
|
2019-08-28 18:20:07 +00:00
|
|
|
self.serialLock.release()
|
|
|
|
return values[1:102]
|
2019-09-16 19:41:01 +00:00
|
|
|
else:
|
|
|
|
logger.error("Unable to acquire serial lock to read %s", value)
|
|
|
|
return
|
2019-08-28 18:20:07 +00:00
|
|
|
|
2019-09-05 18:42:02 +00:00
|
|
|
def saveData(self, data, data12, source=None):
|
2019-08-28 22:56:54 +00:00
|
|
|
if self.dataLock.acquire(blocking=True):
|
|
|
|
self.data = data
|
2019-08-30 16:41:22 +00:00
|
|
|
self.data21 = data12
|
2019-08-28 22:56:54 +00:00
|
|
|
else:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.error("Failed acquiring data lock while saving.")
|
2019-08-28 22:56:54 +00:00
|
|
|
self.dataLock.release()
|
2019-09-05 18:42:02 +00:00
|
|
|
if source is not None:
|
|
|
|
self.sweepSource = source
|
|
|
|
else:
|
2019-10-02 07:54:24 +00:00
|
|
|
self.sweepSource = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
2019-08-28 22:56:54 +00:00
|
|
|
|
|
|
|
def dataUpdated(self):
|
|
|
|
if self.dataLock.acquire(blocking=True):
|
2019-08-29 13:04:40 +00:00
|
|
|
for m in self.markers:
|
|
|
|
m.findLocation(self.data)
|
2019-09-09 11:21:54 +00:00
|
|
|
m.resetLabels()
|
|
|
|
m.updateLabels(self.data, self.data21)
|
2019-08-29 13:42:35 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
for c in self.s11charts:
|
|
|
|
c.setData(self.data)
|
|
|
|
|
|
|
|
for c in self.s21charts:
|
|
|
|
c.setData(self.data21)
|
2019-09-13 15:08:20 +00:00
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
self.sweepProgressBar.setValue(self.worker.percentage)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_window.updateTDR()
|
|
|
|
|
2019-08-30 16:41:22 +00:00
|
|
|
# Find the minimum S11 VSWR:
|
|
|
|
minVSWR = 100
|
|
|
|
minVSWRfreq = -1
|
|
|
|
for d in self.data:
|
|
|
|
_, _, vswr = self.vswr(d)
|
2019-09-04 08:29:13 +00:00
|
|
|
if minVSWR > vswr > 0:
|
2019-08-30 16:41:22 +00:00
|
|
|
minVSWR = vswr
|
|
|
|
minVSWRfreq = d.freq
|
|
|
|
|
|
|
|
if minVSWRfreq > -1:
|
2019-09-01 17:38:01 +00:00
|
|
|
self.s11_min_swr_label.setText(str(round(minVSWR, 3)) + " @ " + self.formatFrequency(minVSWRfreq))
|
2019-09-30 08:37:58 +00:00
|
|
|
if minVSWR > 1:
|
|
|
|
self.s11_min_rl_label.setText(str(round(20*math.log10((minVSWR-1)/(minVSWR+1)), 3)) + " dB")
|
|
|
|
else:
|
|
|
|
# Infinite return loss?
|
|
|
|
self.s11_min_rl_label.setText("\N{INFINITY} dB")
|
2019-09-09 11:21:54 +00:00
|
|
|
else:
|
|
|
|
self.s11_min_swr_label.setText("")
|
|
|
|
self.s11_min_rl_label.setText("")
|
2019-08-30 16:41:22 +00:00
|
|
|
minGain = 100
|
|
|
|
minGainFreq = -1
|
|
|
|
maxGain = -100
|
|
|
|
maxGainFreq = -1
|
|
|
|
for d in self.data21:
|
|
|
|
gain = self.gain(d)
|
|
|
|
if gain > maxGain:
|
|
|
|
maxGain = gain
|
|
|
|
maxGainFreq = d.freq
|
|
|
|
if gain < minGain:
|
|
|
|
minGain = gain
|
|
|
|
minGainFreq = d.freq
|
|
|
|
|
|
|
|
if maxGainFreq > -1:
|
2019-09-01 17:38:01 +00:00
|
|
|
self.s21_min_gain_label.setText(str(round(minGain, 3)) + " dB @ " + self.formatFrequency(minGainFreq))
|
|
|
|
self.s21_max_gain_label.setText(str(round(maxGain, 3)) + " dB @ " + self.formatFrequency(maxGainFreq))
|
2019-09-09 11:21:54 +00:00
|
|
|
else:
|
|
|
|
self.s21_min_gain_label.setText("")
|
|
|
|
self.s21_max_gain_label.setText("")
|
2019-08-30 16:41:22 +00:00
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
else:
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.error("Failed acquiring data lock while updating.")
|
2019-09-08 18:30:20 +00:00
|
|
|
self.updateTitle()
|
2019-08-28 22:56:54 +00:00
|
|
|
self.dataLock.release()
|
|
|
|
|
2019-08-30 16:41:22 +00:00
|
|
|
@staticmethod
|
|
|
|
def vswr(data: Datapoint):
|
2019-09-22 11:42:05 +00:00
|
|
|
re50, im50 = NanoVNASaver.normalize50(data)
|
2019-09-30 08:37:58 +00:00
|
|
|
try:
|
|
|
|
mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / math.sqrt((re50 + 50) * (re50 + 50) + im50 * im50)
|
|
|
|
vswr = (1 + mag) / (1 - mag)
|
|
|
|
except ZeroDivisionError as e:
|
|
|
|
vswr = 1
|
2019-08-30 16:41:22 +00:00
|
|
|
return im50, re50, vswr
|
|
|
|
|
2019-09-10 17:51:21 +00:00
|
|
|
@staticmethod
|
|
|
|
def qualifyFactor(data: Datapoint):
|
|
|
|
im50, re50, _ = NanoVNASaver.vswr(data)
|
|
|
|
if re50 != 0:
|
2019-10-01 12:19:07 +00:00
|
|
|
Q = abs(im50 / re50)
|
2019-09-10 17:51:21 +00:00
|
|
|
else:
|
2019-10-01 12:19:07 +00:00
|
|
|
Q = -1
|
|
|
|
return Q
|
2019-09-10 17:51:21 +00:00
|
|
|
|
2019-09-05 19:38:45 +00:00
|
|
|
@staticmethod
|
2019-09-11 18:26:57 +00:00
|
|
|
def capacitanceEquivalent(im50, freq) -> str:
|
2019-09-26 10:54:20 +00:00
|
|
|
if im50 == 0 or freq == 0:
|
|
|
|
return "- pF"
|
2019-09-08 07:36:30 +00:00
|
|
|
capacitance = 10**12/(freq * 2 * math.pi * im50)
|
2019-09-29 06:33:03 +00:00
|
|
|
if abs(capacitance) > 10000:
|
|
|
|
return str(round(-capacitance/1000, 2)) + " nF"
|
|
|
|
elif abs(capacitance) > 1000:
|
|
|
|
return str(round(-capacitance/1000, 3)) + " nF"
|
|
|
|
elif abs(capacitance) > 10:
|
|
|
|
return str(round(-capacitance, 2)) + " pF"
|
|
|
|
else:
|
|
|
|
return str(round(-capacitance, 3)) + " pF"
|
2019-09-11 18:26:57 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def inductanceEquivalent(im50, freq) -> str:
|
2019-09-26 10:54:20 +00:00
|
|
|
if freq == 0:
|
|
|
|
return "- nH"
|
2019-10-01 12:19:07 +00:00
|
|
|
inductance = im50 * 1000000000 / (freq * 2 * math.pi)
|
2019-09-29 06:33:03 +00:00
|
|
|
if abs(inductance) > 10000:
|
|
|
|
return str(round(inductance / 1000, 2)) + " μH"
|
|
|
|
elif abs(inductance) > 1000:
|
|
|
|
return str(round(inductance/1000, 3)) + " μH"
|
|
|
|
elif abs(inductance) > 10:
|
|
|
|
return str(round(inductance, 2)) + " nH"
|
|
|
|
else:
|
|
|
|
return str(round(inductance, 3)) + " nH"
|
2019-09-05 19:38:45 +00:00
|
|
|
|
2019-08-30 16:41:22 +00:00
|
|
|
@staticmethod
|
|
|
|
def gain(data: Datapoint):
|
2019-09-22 11:42:05 +00:00
|
|
|
re50, im50 = NanoVNASaver.normalize50(data)
|
2019-08-30 16:41:22 +00:00
|
|
|
# Calculate the gain / reflection coefficient
|
|
|
|
mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / math.sqrt(
|
|
|
|
(re50 + 50) * (re50 + 50) + im50 * im50)
|
2019-09-30 08:37:58 +00:00
|
|
|
if mag > 0:
|
|
|
|
return 20 * math.log10(mag)
|
|
|
|
else:
|
|
|
|
return 0
|
2019-08-30 16:41:22 +00:00
|
|
|
|
2019-09-13 09:17:37 +00:00
|
|
|
@staticmethod
|
|
|
|
def normalize50(data):
|
|
|
|
re = data.re
|
|
|
|
im = data.im
|
|
|
|
re50 = 50 * (1 - re * re - im * im) / (1 + re * re + im * im - 2 * re)
|
|
|
|
im50 = 50 * (2 * im) / (1 + re * re + im * im - 2 * re)
|
2019-09-22 11:42:05 +00:00
|
|
|
return re50, im50
|
2019-09-13 09:17:37 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def admittance(data):
|
|
|
|
re50, im50 = NanoVNASaver.normalize50(data)
|
|
|
|
rp = re50 / (re50**2 + im50**2)
|
|
|
|
xp = - im50 / (re50**2 + im50**2)
|
|
|
|
return rp, xp
|
|
|
|
|
2019-08-28 22:56:54 +00:00
|
|
|
def sweepFinished(self):
|
|
|
|
self.sweepProgressBar.setValue(100)
|
2019-08-30 07:43:18 +00:00
|
|
|
self.btnSweep.setDisabled(False)
|
2019-09-10 10:47:18 +00:00
|
|
|
self.btnStopSweep.setDisabled(True)
|
2019-08-30 07:43:18 +00:00
|
|
|
|
2019-09-12 21:12:32 +00:00
|
|
|
def updateCenterSpan(self):
|
|
|
|
fstart = self.parseFrequency(self.sweepStartInput.text())
|
|
|
|
fstop = self.parseFrequency(self.sweepEndInput.text())
|
|
|
|
fspan = fstop - fstart
|
|
|
|
fcenter = int(round((fstart+fstop)/2))
|
|
|
|
if fspan < 0 or fstart < 0 or fstop < 0:
|
|
|
|
return
|
|
|
|
self.sweepSpanInput.setText(str(fspan))
|
|
|
|
self.sweepCenterInput.setText(str(fcenter))
|
2019-09-23 19:10:43 +00:00
|
|
|
self.updateStepSize()
|
2019-09-12 21:12:32 +00:00
|
|
|
|
|
|
|
def updateStartEnd(self):
|
|
|
|
fcenter = self.parseFrequency(self.sweepCenterInput.text())
|
|
|
|
fspan = self.parseFrequency(self.sweepSpanInput.text())
|
|
|
|
if fspan < 0 or fcenter < 0:
|
|
|
|
return
|
|
|
|
fstart = int(round(fcenter - fspan/2))
|
|
|
|
fstop = int(round(fcenter + fspan/2))
|
|
|
|
if fstart < 0 or fstop < 0:
|
|
|
|
return
|
|
|
|
self.sweepStartInput.setText(str(fstart))
|
|
|
|
self.sweepEndInput.setText(str(fstop))
|
2019-09-23 19:10:43 +00:00
|
|
|
self.updateStepSize()
|
|
|
|
|
|
|
|
def updateStepSize(self):
|
|
|
|
fspan = self.parseFrequency(self.sweepSpanInput.text())
|
|
|
|
if fspan < 0:
|
|
|
|
return
|
|
|
|
if self.sweepCountInput.text().isdigit():
|
|
|
|
segments = int(self.sweepCountInput.text())
|
|
|
|
fstep = fspan / (segments * 101)
|
2019-09-26 13:14:28 +00:00
|
|
|
self.sweepStepLabel.setText(self.formatShortFrequency(fstep) + "/step")
|
2019-09-12 21:12:32 +00:00
|
|
|
|
2019-09-01 17:38:01 +00:00
|
|
|
@staticmethod
|
|
|
|
def formatFrequency(freq):
|
2019-09-26 13:14:28 +00:00
|
|
|
if freq < 1:
|
|
|
|
return "- Hz"
|
2019-09-01 17:38:01 +00:00
|
|
|
if math.log10(freq) < 3:
|
2019-09-23 19:10:43 +00:00
|
|
|
return str(round(freq)) + " Hz"
|
2019-09-01 17:38:01 +00:00
|
|
|
elif math.log10(freq) < 7:
|
|
|
|
return "{:.3f}".format(freq/1000) + " kHz"
|
|
|
|
elif math.log10(freq) < 8:
|
|
|
|
return "{:.4f}".format(freq/1000000) + " MHz"
|
|
|
|
else:
|
|
|
|
return "{:.3f}".format(freq/1000000) + " MHz"
|
|
|
|
|
2019-09-26 13:14:28 +00:00
|
|
|
@staticmethod
|
|
|
|
def formatShortFrequency(freq):
|
|
|
|
if freq < 1:
|
|
|
|
return "- Hz"
|
|
|
|
if math.log10(freq) < 3:
|
|
|
|
return str(round(freq)) + " Hz"
|
|
|
|
elif math.log10(freq) < 5:
|
|
|
|
return "{:.3f}".format(freq/1000) + " kHz"
|
|
|
|
elif math.log10(freq) < 6:
|
|
|
|
return "{:.2f}".format(freq/1000) + " kHz"
|
|
|
|
elif math.log10(freq) < 7:
|
|
|
|
return "{:.1f}".format(freq/1000) + " kHz"
|
|
|
|
elif math.log10(freq) < 8:
|
|
|
|
return "{:.3f}".format(freq/1000000) + " MHz"
|
|
|
|
elif math.log10(freq) < 9:
|
|
|
|
return "{:.2f}".format(freq/1000000) + " MHz"
|
|
|
|
else:
|
|
|
|
return "{:.1f}".format(freq/1000000) + " MHz"
|
|
|
|
|
2019-09-01 17:38:01 +00:00
|
|
|
@staticmethod
|
2019-09-04 08:29:13 +00:00
|
|
|
def parseFrequency(freq: str):
|
2019-09-01 17:38:01 +00:00
|
|
|
freq = freq.replace(" ", "") # People put all sorts of weird whitespace in.
|
|
|
|
if freq.isnumeric():
|
|
|
|
return int(freq)
|
|
|
|
|
|
|
|
multiplier = 1
|
|
|
|
freq = freq.lower()
|
|
|
|
|
|
|
|
if freq.endswith("k"):
|
|
|
|
multiplier = 1000
|
|
|
|
freq = freq[:-1]
|
|
|
|
elif freq.endswith("m"):
|
|
|
|
multiplier = 1000000
|
|
|
|
freq = freq[:-1]
|
|
|
|
|
|
|
|
if freq.isnumeric():
|
|
|
|
return int(freq) * multiplier
|
|
|
|
|
|
|
|
try:
|
|
|
|
f = float(freq)
|
|
|
|
return int(round(multiplier * f))
|
|
|
|
except ValueError:
|
|
|
|
# Okay, we couldn't parse this however much we tried.
|
2019-09-01 21:13:21 +00:00
|
|
|
return -1
|
|
|
|
|
2019-09-05 18:42:02 +00:00
|
|
|
def setReference(self, s11data=None, s21data=None, source=None):
|
2019-09-04 06:57:33 +00:00
|
|
|
if not s11data:
|
|
|
|
s11data = self.data
|
|
|
|
if not s21data:
|
|
|
|
s21data = self.data21
|
2019-09-02 13:20:02 +00:00
|
|
|
self.referenceS11data = s11data
|
2019-09-05 12:56:40 +00:00
|
|
|
for c in self.s11charts:
|
|
|
|
c.setReference(s11data)
|
2019-09-01 21:13:21 +00:00
|
|
|
|
2019-09-02 13:20:02 +00:00
|
|
|
self.referenceS21data = s21data
|
2019-09-05 12:56:40 +00:00
|
|
|
for c in self.s21charts:
|
|
|
|
c.setReference(s21data)
|
2019-09-01 21:13:21 +00:00
|
|
|
self.btnResetReference.setDisabled(False)
|
|
|
|
|
2019-09-05 18:42:02 +00:00
|
|
|
if source is not None:
|
|
|
|
# Save the reference source info
|
|
|
|
self.referenceSource = source
|
|
|
|
else:
|
|
|
|
self.referenceSource = self.sweepSource
|
|
|
|
self.updateTitle()
|
|
|
|
|
|
|
|
def updateTitle(self):
|
|
|
|
title = self.baseTitle
|
|
|
|
insert = ""
|
|
|
|
if self.sweepSource != "":
|
|
|
|
insert += "Sweep: " + self.sweepSource + " @ " + str(len(self.data)) + " points"
|
|
|
|
if self.referenceSource != "":
|
|
|
|
if insert != "":
|
|
|
|
insert += ", "
|
|
|
|
insert += "Reference: " + self.referenceSource + " @ " + str(len(self.referenceS11data)) + " points"
|
|
|
|
if insert != "":
|
|
|
|
title = title + " (" + insert + ")"
|
|
|
|
self.setWindowTitle(title)
|
|
|
|
|
2019-09-13 09:17:37 +00:00
|
|
|
def toggleMarkerFrame(self):
|
|
|
|
if self.marker_frame.isHidden():
|
|
|
|
self.marker_frame.setHidden(False)
|
|
|
|
self.settings.setValue("MarkersVisible", True)
|
|
|
|
self.showMarkerButton.setText("Hide data")
|
|
|
|
else:
|
|
|
|
self.marker_frame.setHidden(True)
|
|
|
|
self.settings.setValue("MarkersVisible", False)
|
|
|
|
self.showMarkerButton.setText("Show data")
|
|
|
|
|
2019-09-01 21:13:21 +00:00
|
|
|
def resetReference(self):
|
2019-09-02 13:20:02 +00:00
|
|
|
self.referenceS11data = []
|
|
|
|
self.referenceS21data = []
|
2019-09-05 18:42:02 +00:00
|
|
|
self.referenceSource = ""
|
2019-09-21 10:55:22 +00:00
|
|
|
self.updateTitle()
|
2019-09-01 21:13:21 +00:00
|
|
|
for c in self.charts:
|
|
|
|
c.resetReference()
|
|
|
|
self.btnResetReference.setDisabled(True)
|
|
|
|
|
2019-09-02 13:20:02 +00:00
|
|
|
def loadReferenceFile(self):
|
|
|
|
filename = self.referenceFileNameInput.text()
|
2019-09-07 11:47:13 +00:00
|
|
|
if filename is not "":
|
|
|
|
self.resetReference()
|
|
|
|
t = Touchstone(filename)
|
|
|
|
t.load()
|
|
|
|
self.setReference(t.s11data, t.s21data, filename)
|
2019-09-02 17:25:17 +00:00
|
|
|
|
|
|
|
def loadSweepFile(self):
|
|
|
|
filename = self.referenceFileNameInput.text()
|
2019-09-07 11:47:13 +00:00
|
|
|
if filename is not "":
|
|
|
|
self.data = []
|
|
|
|
self.data21 = []
|
|
|
|
t = Touchstone(filename)
|
|
|
|
t.load()
|
|
|
|
self.saveData(t.s11data, t.s21data, filename)
|
|
|
|
self.dataUpdated()
|
2019-09-03 18:34:32 +00:00
|
|
|
|
|
|
|
def sizeHint(self) -> QtCore.QSize:
|
|
|
|
return QtCore.QSize(1100, 950)
|
2019-09-05 12:56:40 +00:00
|
|
|
|
2019-09-07 06:12:13 +00:00
|
|
|
def displaySettingsWindow(self):
|
2019-09-21 10:55:22 +00:00
|
|
|
self.displaySetupWindow.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.displaySetupWindow)
|
|
|
|
|
|
|
|
def displaySweepSettingsWindow(self):
|
|
|
|
self.sweepSettingsWindow.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.sweepSettingsWindow)
|
2019-09-07 06:12:13 +00:00
|
|
|
|
|
|
|
def displayCalibrationWindow(self):
|
|
|
|
self.calibrationWindow.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.calibrationWindow)
|
|
|
|
|
|
|
|
def displayFileWindow(self):
|
|
|
|
self.fileWindow.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.fileWindow)
|
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
def displayTDRWindow(self):
|
|
|
|
self.tdr_window.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.tdr_window)
|
|
|
|
|
2019-09-22 19:19:08 +00:00
|
|
|
def showError(self, text):
|
2019-09-26 13:14:28 +00:00
|
|
|
error_message = QtWidgets.QErrorMessage(self)
|
|
|
|
error_message.showMessage(text)
|
|
|
|
|
|
|
|
def showSweepError(self):
|
|
|
|
self.showError(self.worker.error_message)
|
2019-09-22 19:19:08 +00:00
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
2019-09-10 10:47:18 +00:00
|
|
|
self.worker.stopped = True
|
2019-09-11 19:59:48 +00:00
|
|
|
self.settings.setValue("Marker1Color", self.markers[0].color)
|
|
|
|
self.settings.setValue("Marker2Color", self.markers[1].color)
|
|
|
|
self.settings.setValue("Marker3Color", self.markers[2].color)
|
2019-09-09 18:06:01 +00:00
|
|
|
|
|
|
|
self.settings.setValue("WindowHeight", self.height())
|
|
|
|
self.settings.setValue("WindowWidth", self.width())
|
|
|
|
self.settings.sync()
|
2019-09-26 20:57:34 +00:00
|
|
|
self.bands.saveSettings()
|
2019-09-10 10:47:18 +00:00
|
|
|
self.threadpool.waitForDone(2500)
|
2019-09-09 18:06:01 +00:00
|
|
|
a0.accept()
|
2019-09-13 15:32:13 +00:00
|
|
|
sys.exit()
|
2019-09-09 18:06:01 +00:00
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
|
|
|
|
class DisplaySettingsWindow(QtWidgets.QWidget):
|
|
|
|
def __init__(self, app: NanoVNASaver):
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
self.app = app
|
|
|
|
self.setWindowTitle("Display settings")
|
|
|
|
|
2019-09-21 11:13:33 +00:00
|
|
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
layout = QtWidgets.QVBoxLayout()
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
2019-09-07 09:43:06 +00:00
|
|
|
display_options_box = QtWidgets.QGroupBox("Options")
|
|
|
|
display_options_layout = QtWidgets.QFormLayout(display_options_box)
|
|
|
|
|
2019-09-07 10:40:10 +00:00
|
|
|
self.show_lines_option = QtWidgets.QCheckBox("Show lines")
|
|
|
|
show_lines_label = QtWidgets.QLabel("Displays a thin line between data points")
|
2019-09-07 09:43:06 +00:00
|
|
|
self.show_lines_option.stateChanged.connect(self.changeShowLines)
|
2019-09-07 10:40:10 +00:00
|
|
|
display_options_layout.addRow(self.show_lines_option, show_lines_label)
|
|
|
|
|
|
|
|
self.dark_mode_option = QtWidgets.QCheckBox("Dark mode")
|
|
|
|
dark_mode_label = QtWidgets.QLabel("Black background with white text")
|
|
|
|
self.dark_mode_option.stateChanged.connect(self.changeDarkMode)
|
|
|
|
display_options_layout.addRow(self.dark_mode_option, dark_mode_label)
|
2019-09-07 09:43:06 +00:00
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
self.btnColorPicker = QtWidgets.QPushButton("█")
|
|
|
|
self.btnColorPicker.setFixedWidth(20)
|
|
|
|
self.sweepColor = self.app.settings.value("SweepColor", defaultValue=QtGui.QColor(160, 140, 20, 128),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.setSweepColor(self.sweepColor)
|
|
|
|
self.btnColorPicker.clicked.connect(lambda: self.setSweepColor(
|
|
|
|
QtWidgets.QColorDialog.getColor(self.sweepColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
display_options_layout.addRow("Sweep color", self.btnColorPicker)
|
|
|
|
|
2019-09-22 11:42:05 +00:00
|
|
|
self.btnSecondaryColorPicker = QtWidgets.QPushButton("█")
|
|
|
|
self.btnSecondaryColorPicker.setFixedWidth(20)
|
|
|
|
self.secondarySweepColor = self.app.settings.value("SecondarySweepColor",
|
|
|
|
defaultValue=QtGui.QColor(20, 160, 140, 128),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.setSecondarySweepColor(self.secondarySweepColor)
|
|
|
|
self.btnSecondaryColorPicker.clicked.connect(lambda: self.setSecondarySweepColor(
|
|
|
|
QtWidgets.QColorDialog.getColor(self.secondarySweepColor,
|
|
|
|
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
display_options_layout.addRow("Second sweep color", self.btnSecondaryColorPicker)
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
self.btnReferenceColorPicker = QtWidgets.QPushButton("█")
|
|
|
|
self.btnReferenceColorPicker.setFixedWidth(20)
|
|
|
|
self.referenceColor = self.app.settings.value("ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 32),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.setReferenceColor(self.referenceColor)
|
|
|
|
self.btnReferenceColorPicker.clicked.connect(lambda: self.setReferenceColor(
|
|
|
|
QtWidgets.QColorDialog.getColor(self.referenceColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
display_options_layout.addRow("Reference color", self.btnReferenceColorPicker)
|
|
|
|
|
2019-09-07 09:43:06 +00:00
|
|
|
layout.addWidget(display_options_box)
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
color_options_box = QtWidgets.QGroupBox("Chart colors")
|
2019-09-17 12:37:12 +00:00
|
|
|
color_options_layout = QtWidgets.QFormLayout(color_options_box)
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
self.use_custom_colors = QtWidgets.QCheckBox("Use custom chart colors")
|
2019-09-17 12:37:12 +00:00
|
|
|
self.use_custom_colors.stateChanged.connect(self.changeCustomColors)
|
|
|
|
color_options_layout.addRow(self.use_custom_colors)
|
|
|
|
|
|
|
|
self.btn_background_picker = QtWidgets.QPushButton("█")
|
|
|
|
self.btn_background_picker.setFixedWidth(20)
|
|
|
|
self.btn_background_picker.clicked.connect(lambda: self.setColor("background", QtWidgets.QColorDialog.getColor(self.backgroundColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
color_options_layout.addRow("Chart background", self.btn_background_picker)
|
|
|
|
|
|
|
|
self.btn_foreground_picker = QtWidgets.QPushButton("█")
|
|
|
|
self.btn_foreground_picker.setFixedWidth(20)
|
|
|
|
self.btn_foreground_picker.clicked.connect(lambda: self.setColor("foreground", QtWidgets.QColorDialog.getColor(self.foregroundColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
color_options_layout.addRow("Chart foreground", self.btn_foreground_picker)
|
|
|
|
|
|
|
|
self.btn_text_picker = QtWidgets.QPushButton("█")
|
|
|
|
self.btn_text_picker.setFixedWidth(20)
|
|
|
|
self.btn_text_picker.clicked.connect(lambda: self.setColor("text", QtWidgets.QColorDialog.getColor(self.textColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
color_options_layout.addRow("Chart text", self.btn_text_picker)
|
|
|
|
|
|
|
|
layout.addWidget(color_options_box)
|
|
|
|
|
2019-09-17 19:59:06 +00:00
|
|
|
font_options_box = QtWidgets.QGroupBox("Font")
|
|
|
|
font_options_layout = QtWidgets.QFormLayout(font_options_box)
|
|
|
|
self.font_dropdown = QtWidgets.QComboBox()
|
|
|
|
self.font_dropdown.addItems(["7", "8", "9", "10", "11", "12"])
|
|
|
|
font_size = self.app.settings.value("FontSize",
|
|
|
|
defaultValue=str(QtWidgets.QApplication.instance().font().pointSize()),
|
|
|
|
type=str)
|
|
|
|
self.font_dropdown.setCurrentText(font_size)
|
|
|
|
self.changeFont()
|
|
|
|
|
|
|
|
self.font_dropdown.currentTextChanged.connect(self.changeFont)
|
|
|
|
font_options_layout.addRow("Font size", self.font_dropdown)
|
|
|
|
|
|
|
|
layout.addWidget(font_options_box)
|
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
bands_box = QtWidgets.QGroupBox("Bands")
|
|
|
|
bands_layout = QtWidgets.QFormLayout(bands_box)
|
|
|
|
|
|
|
|
self.show_bands = QtWidgets.QCheckBox("Show bands")
|
2019-09-27 08:56:09 +00:00
|
|
|
self.show_bands.setChecked(self.app.bands.enabled)
|
2019-09-27 08:30:00 +00:00
|
|
|
self.show_bands.stateChanged.connect(lambda: self.setShowBands(self.show_bands.isChecked()))
|
2019-09-26 20:57:34 +00:00
|
|
|
bands_layout.addRow(self.show_bands)
|
|
|
|
|
2019-09-27 08:56:09 +00:00
|
|
|
self.btn_bands_picker = QtWidgets.QPushButton("█")
|
|
|
|
self.btn_bands_picker.setFixedWidth(20)
|
|
|
|
self.btn_bands_picker.clicked.connect(lambda: self.setColor("bands", QtWidgets.QColorDialog.getColor(self.bandsColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
|
|
|
|
|
|
|
|
bands_layout.addRow("Chart bands", self.btn_bands_picker)
|
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
self.btn_manage_bands = QtWidgets.QPushButton("Manage bands")
|
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
self.bandsWindow = BandsWindow(self.app)
|
|
|
|
self.btn_manage_bands.clicked.connect(self.displayBandsWindow)
|
2019-09-26 20:57:34 +00:00
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
bands_layout.addRow(self.btn_manage_bands)
|
2019-09-26 20:57:34 +00:00
|
|
|
|
|
|
|
layout.addWidget(bands_box)
|
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
charts_box = QtWidgets.QGroupBox("Displayed charts")
|
|
|
|
charts_layout = QtWidgets.QGridLayout(charts_box)
|
|
|
|
|
|
|
|
# selections = ["S11 Smith chart",
|
|
|
|
# "S11 LogMag",
|
|
|
|
# "S11 VSWR",
|
|
|
|
# "S11 Phase",
|
|
|
|
# "S21 Smith chart",
|
|
|
|
# "S21 LogMag",
|
|
|
|
# "S21 Phase",
|
|
|
|
# "None"]
|
|
|
|
|
|
|
|
selections = []
|
|
|
|
|
|
|
|
for c in self.app.charts:
|
|
|
|
selections.append(c.name)
|
|
|
|
|
|
|
|
selections.append("None")
|
|
|
|
|
|
|
|
chart00_selection = QtWidgets.QComboBox()
|
|
|
|
chart00_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart00_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart00", "S11 Smith Chart")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart00_selection.currentTextChanged.connect(lambda: self.changeChart(0, 0, chart00_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart00_selection, 0, 0)
|
|
|
|
|
|
|
|
chart01_selection = QtWidgets.QComboBox()
|
|
|
|
chart01_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart01_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart01", "S11 Return Loss")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart01_selection.currentTextChanged.connect(lambda: self.changeChart(0, 1, chart01_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart01_selection, 0, 1)
|
|
|
|
|
|
|
|
chart02_selection = QtWidgets.QComboBox()
|
|
|
|
chart02_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart02_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart02", "None")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart02_selection.currentTextChanged.connect(lambda: self.changeChart(0, 2, chart02_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart02_selection, 0, 2)
|
|
|
|
|
|
|
|
chart10_selection = QtWidgets.QComboBox()
|
|
|
|
chart10_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart10_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart10", "S21 Polar Plot")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart10_selection.currentTextChanged.connect(lambda: self.changeChart(1, 0, chart10_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart10_selection, 1, 0)
|
|
|
|
|
|
|
|
chart11_selection = QtWidgets.QComboBox()
|
|
|
|
chart11_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart11_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart11", "S21 Gain")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart11_selection.currentTextChanged.connect(lambda: self.changeChart(1, 1, chart11_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart11_selection, 1, 1)
|
|
|
|
|
|
|
|
chart12_selection = QtWidgets.QComboBox()
|
|
|
|
chart12_selection.addItems(selections)
|
2019-09-09 18:06:01 +00:00
|
|
|
chart12_selection.setCurrentIndex(selections.index(self.app.settings.value("Chart12", "None")))
|
2019-09-05 12:56:40 +00:00
|
|
|
chart12_selection.currentTextChanged.connect(lambda: self.changeChart(1, 2, chart12_selection.currentText()))
|
|
|
|
charts_layout.addWidget(chart12_selection, 1, 2)
|
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
self.changeChart(0, 0, chart00_selection.currentText())
|
|
|
|
self.changeChart(0, 1, chart01_selection.currentText())
|
|
|
|
self.changeChart(0, 2, chart02_selection.currentText())
|
|
|
|
self.changeChart(1, 0, chart10_selection.currentText())
|
|
|
|
self.changeChart(1, 1, chart11_selection.currentText())
|
|
|
|
self.changeChart(1, 2, chart12_selection.currentText())
|
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
layout.addWidget(charts_box)
|
2019-09-09 18:06:01 +00:00
|
|
|
self.dark_mode_option.setChecked(self.app.settings.value("DarkMode", False, bool))
|
|
|
|
self.show_lines_option.setChecked(self.app.settings.value("ShowLines", False, bool))
|
2019-09-05 12:56:40 +00:00
|
|
|
|
2019-09-17 12:37:12 +00:00
|
|
|
self.backgroundColor = self.app.settings.value("BackgroundColor", defaultValue=QtGui.QColor("white"),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.foregroundColor = self.app.settings.value("ForegroundColor", defaultValue=QtGui.QColor("lightgray"),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.textColor = self.app.settings.value("TextColor", defaultValue=QtGui.QColor("black"),
|
|
|
|
type=QtGui.QColor)
|
2019-09-27 08:56:09 +00:00
|
|
|
self.bandsColor = self.app.settings.value("BandsColor", defaultValue=QtGui.QColor(128, 128, 128, 48),
|
|
|
|
type=QtGui.QColor)
|
|
|
|
self.app.bands.color = self.bandsColor
|
2019-09-17 12:37:12 +00:00
|
|
|
|
|
|
|
if self.app.settings.value("UseCustomColors", defaultValue=False, type=bool):
|
|
|
|
self.dark_mode_option.setDisabled(True)
|
|
|
|
self.dark_mode_option.setChecked(False)
|
|
|
|
self.use_custom_colors.setChecked(True)
|
|
|
|
else:
|
|
|
|
self.btn_background_picker.setDisabled(True)
|
|
|
|
self.btn_foreground_picker.setDisabled(True)
|
|
|
|
self.btn_text_picker.setDisabled(True)
|
|
|
|
|
|
|
|
p = self.btn_background_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, self.backgroundColor)
|
|
|
|
self.btn_background_picker.setPalette(p)
|
|
|
|
|
|
|
|
p = self.btn_foreground_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, self.foregroundColor)
|
|
|
|
self.btn_foreground_picker.setPalette(p)
|
|
|
|
|
|
|
|
p = self.btn_text_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, self.textColor)
|
|
|
|
self.btn_text_picker.setPalette(p)
|
|
|
|
|
2019-09-27 08:56:09 +00:00
|
|
|
p = self.btn_bands_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, self.bandsColor)
|
|
|
|
self.btn_bands_picker.setPalette(p)
|
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
def changeChart(self, x, y, chart):
|
|
|
|
found = None
|
|
|
|
for c in self.app.charts:
|
|
|
|
if c.name == chart:
|
|
|
|
found = c
|
|
|
|
|
2019-09-09 18:06:01 +00:00
|
|
|
self.app.settings.setValue("Chart" + str(x) + str(y), chart)
|
|
|
|
|
2019-09-05 12:56:40 +00:00
|
|
|
oldWidget = self.app.charts_layout.itemAtPosition(x, y)
|
|
|
|
if oldWidget is not None:
|
|
|
|
w = oldWidget.widget()
|
|
|
|
self.app.charts_layout.removeWidget(w)
|
|
|
|
w.hide()
|
|
|
|
if found is not None:
|
|
|
|
self.app.charts_layout.addWidget(found, x, y)
|
|
|
|
if found.isHidden():
|
|
|
|
found.show()
|
2019-09-07 09:43:06 +00:00
|
|
|
|
|
|
|
def changeShowLines(self):
|
|
|
|
state = self.show_lines_option.isChecked()
|
2019-09-09 18:06:01 +00:00
|
|
|
self.app.settings.setValue("ShowLines", state)
|
2019-09-07 09:43:06 +00:00
|
|
|
for c in self.app.charts:
|
2019-09-07 10:40:10 +00:00
|
|
|
c.setDrawLines(state)
|
|
|
|
|
|
|
|
def changeDarkMode(self):
|
|
|
|
state = self.dark_mode_option.isChecked()
|
2019-09-09 18:06:01 +00:00
|
|
|
self.app.settings.setValue("DarkMode", state)
|
2019-09-07 10:40:10 +00:00
|
|
|
if state:
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setBackgroundColor(QtGui.QColor(QtCore.Qt.black))
|
2019-09-17 12:37:12 +00:00
|
|
|
c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray))
|
2019-09-07 10:40:10 +00:00
|
|
|
c.setTextColor(QtGui.QColor(QtCore.Qt.white))
|
|
|
|
else:
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setBackgroundColor(QtGui.QColor(QtCore.Qt.white))
|
2019-09-17 12:37:12 +00:00
|
|
|
c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray))
|
2019-09-07 10:40:10 +00:00
|
|
|
c.setTextColor(QtGui.QColor(QtCore.Qt.black))
|
2019-09-13 15:08:20 +00:00
|
|
|
|
2019-09-17 12:37:12 +00:00
|
|
|
def changeCustomColors(self):
|
|
|
|
self.app.settings.setValue("UseCustomColors", self.use_custom_colors.isChecked())
|
|
|
|
if self.use_custom_colors.isChecked():
|
|
|
|
self.dark_mode_option.setDisabled(True)
|
|
|
|
self.dark_mode_option.setChecked(False)
|
|
|
|
self.btn_background_picker.setDisabled(False)
|
|
|
|
self.btn_foreground_picker.setDisabled(False)
|
|
|
|
self.btn_text_picker.setDisabled(False)
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setBackgroundColor(self.backgroundColor)
|
|
|
|
c.setForegroundColor(self.foregroundColor)
|
|
|
|
c.setTextColor(self.textColor)
|
|
|
|
else:
|
|
|
|
self.dark_mode_option.setDisabled(False)
|
|
|
|
self.btn_background_picker.setDisabled(True)
|
|
|
|
self.btn_foreground_picker.setDisabled(True)
|
|
|
|
self.btn_text_picker.setDisabled(True)
|
|
|
|
self.changeDarkMode() # Reset to the default colors depending on Dark Mode setting
|
|
|
|
|
|
|
|
def setColor(self, name: str, color: QtGui.QColor):
|
|
|
|
if name == "background":
|
|
|
|
p = self.btn_background_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btn_background_picker.setPalette(p)
|
|
|
|
self.backgroundColor = color
|
|
|
|
self.app.settings.setValue("BackgroundColor", color)
|
|
|
|
elif name == "foreground":
|
|
|
|
p = self.btn_foreground_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btn_foreground_picker.setPalette(p)
|
|
|
|
self.foregroundColor = color
|
|
|
|
self.app.settings.setValue("ForegroundColor", color)
|
|
|
|
elif name == "text":
|
|
|
|
p = self.btn_text_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btn_text_picker.setPalette(p)
|
|
|
|
self.textColor = color
|
|
|
|
self.app.settings.setValue("TextColor", color)
|
2019-09-27 08:56:09 +00:00
|
|
|
elif name == "bands":
|
|
|
|
p = self.btn_bands_picker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btn_bands_picker.setPalette(p)
|
|
|
|
self.bandsColor = color
|
|
|
|
self.app.settings.setValue("BandsColor", color)
|
|
|
|
self.app.bands.setColor(color)
|
2019-09-17 12:37:12 +00:00
|
|
|
self.changeCustomColors()
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
def setSweepColor(self, color: QtGui.QColor):
|
|
|
|
if color.isValid():
|
|
|
|
self.sweepColor = color
|
|
|
|
p = self.btnColorPicker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btnColorPicker.setPalette(p)
|
|
|
|
self.app.settings.setValue("SweepColor", color)
|
|
|
|
self.app.settings.sync()
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setSweepColor(color)
|
|
|
|
|
2019-09-22 11:42:05 +00:00
|
|
|
def setSecondarySweepColor(self, color: QtGui.QColor):
|
|
|
|
if color.isValid():
|
|
|
|
self.secondarySweepColor = color
|
|
|
|
p = self.btnSecondaryColorPicker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btnSecondaryColorPicker.setPalette(p)
|
|
|
|
self.app.settings.setValue("SecondarySweepColor", color)
|
|
|
|
self.app.settings.sync()
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setSecondarySweepColor(color)
|
|
|
|
|
2019-09-19 15:32:36 +00:00
|
|
|
def setReferenceColor(self, color):
|
|
|
|
if color.isValid():
|
|
|
|
self.referenceColor = color
|
|
|
|
p = self.btnReferenceColorPicker.palette()
|
|
|
|
p.setColor(QtGui.QPalette.ButtonText, color)
|
|
|
|
self.btnReferenceColorPicker.setPalette(p)
|
|
|
|
self.app.settings.setValue("ReferenceColor", color)
|
|
|
|
self.app.settings.sync()
|
|
|
|
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.setReferenceColor(color)
|
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
def setShowBands(self, show_bands):
|
|
|
|
self.app.bands.enabled = show_bands
|
|
|
|
self.app.bands.settings.setValue("ShowBands", show_bands)
|
|
|
|
self.app.bands.settings.sync()
|
|
|
|
for c in self.app.charts:
|
|
|
|
c.update()
|
|
|
|
|
2019-09-17 19:59:06 +00:00
|
|
|
def changeFont(self):
|
|
|
|
font_size = self.font_dropdown.currentText()
|
|
|
|
self.app.settings.setValue("FontSize", font_size)
|
|
|
|
app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
|
|
|
|
font = app.font()
|
|
|
|
font.setPointSize(int(font_size))
|
|
|
|
app.setFont(font)
|
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
def displayBandsWindow(self):
|
|
|
|
self.bandsWindow.show()
|
|
|
|
QtWidgets.QApplication.setActiveWindow(self.bandsWindow)
|
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
|
|
|
|
class TDRWindow(QtWidgets.QWidget):
|
|
|
|
def __init__(self, app: NanoVNASaver):
|
|
|
|
super().__init__()
|
|
|
|
self.app = app
|
|
|
|
|
|
|
|
self.td = []
|
|
|
|
self.distance_axis = []
|
|
|
|
|
|
|
|
self.setWindowTitle("TDR")
|
|
|
|
|
2019-09-21 11:13:33 +00:00
|
|
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
|
|
|
|
2019-09-13 15:08:20 +00:00
|
|
|
layout = QtWidgets.QFormLayout()
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
self.tdr_velocity_dropdown = QtWidgets.QComboBox()
|
|
|
|
self.tdr_velocity_dropdown.addItem("Jelly filled (0.64)", 0.64)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Polyethylene (0.66)", 0.66)
|
|
|
|
self.tdr_velocity_dropdown.addItem("PTFE (Teflon) (0.70)", 0.70)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Pulp Insulation (0.72)", 0.72)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Foam or Cellular PE (0.78)", 0.78)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Semi-solid PE (SSPE) (0.84)", 0.84)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Air (Helical spacers) (0.94)", 0.94)
|
|
|
|
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
2019-09-26 11:14:42 +00:00
|
|
|
# Lots of cable types added by Larry Goga, AE5CZ
|
2019-09-22 11:42:05 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75\N{OHM SIGN} (Belden 8215) (0.66)", 0.66)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75\N{OHM SIGN} (Belden 9290) (0.81)", 0.81)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG-8/U Foam (Belden 8214) (0.78)", 0.78)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-8/U (Belden 9913) (0.84)", 0.84)
|
2019-09-15 20:20:47 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82)
|
2019-09-22 11:42:05 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-58A/U 54\N{OHM SIGN} Foam (Belden 8219) (0.73)", 0.73)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-59A/U PE 75\N{OHM SIGN} (Belden 8241) (0.66)", 0.66)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-59A/U Foam 75\N{OHM SIGN} (Belden 8241F) (0.78)", 0.78)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG-174 PE (Belden 8216)(0.66)", 0.66)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-174 Foam (Belden 7805R) (0.735)", 0.735)
|
|
|
|
self.tdr_velocity_dropdown.addItem("RG-213/U PE (Belden 8267) (0.66)", 0.66)
|
2019-09-15 20:20:47 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG316 (0.695)", 0.695)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("RG402 (0.695)", 0.695)
|
|
|
|
self.tdr_velocity_dropdown.addItem("LMR-240 (0.84)", 0.84)
|
|
|
|
self.tdr_velocity_dropdown.addItem("LMR-240UF (0.80)", 0.80)
|
|
|
|
self.tdr_velocity_dropdown.addItem("LMR-400 (0.85)", 0.85)
|
2019-09-15 20:20:47 +00:00
|
|
|
self.tdr_velocity_dropdown.addItem("LMR400UF (0.83)", 0.83)
|
|
|
|
self.tdr_velocity_dropdown.addItem("Davis Bury-FLEX (0.82)", 0.82)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
|
|
|
self.tdr_velocity_dropdown.addItem("Custom", -1)
|
|
|
|
|
|
|
|
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
|
|
|
|
|
|
|
|
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
|
|
|
|
|
|
|
|
layout.addRow(self.tdr_velocity_dropdown)
|
|
|
|
|
|
|
|
self.tdr_velocity_input = QtWidgets.QLineEdit()
|
|
|
|
self.tdr_velocity_input.setDisabled(True)
|
|
|
|
self.tdr_velocity_input.setText("0.66")
|
|
|
|
self.tdr_velocity_input.textChanged.connect(self.app.dataUpdated)
|
|
|
|
|
|
|
|
layout.addRow("Velocity factor", self.tdr_velocity_input)
|
|
|
|
|
|
|
|
self.tdr_result_label = QtWidgets.QLabel()
|
|
|
|
layout.addRow("Estimated cable length:", self.tdr_result_label)
|
|
|
|
|
|
|
|
layout.addRow(self.app.tdr_chart)
|
|
|
|
|
|
|
|
def updateTDR(self):
|
|
|
|
c = 299792458
|
|
|
|
if len(self.app.data) < 2:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.tdr_velocity_dropdown.currentData() == -1:
|
|
|
|
self.tdr_velocity_input.setDisabled(False)
|
|
|
|
else:
|
|
|
|
self.tdr_velocity_input.setDisabled(True)
|
|
|
|
self.tdr_velocity_input.setText(str(self.tdr_velocity_dropdown.currentData()))
|
|
|
|
|
|
|
|
try:
|
|
|
|
v = float(self.tdr_velocity_input.text())
|
|
|
|
except ValueError:
|
|
|
|
return
|
|
|
|
|
|
|
|
step_size = self.app.data[1].freq - self.app.data[0].freq
|
|
|
|
if step_size == 0:
|
|
|
|
self.tdr_result_label.setText("")
|
2019-09-16 19:41:01 +00:00
|
|
|
logger.info("Cannot compute cable length at 0 span")
|
2019-09-13 15:08:20 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
s11 = []
|
|
|
|
for d in self.app.data:
|
|
|
|
s11.append(np.complex(d.re, d.im))
|
|
|
|
|
|
|
|
window = np.blackman(len(self.app.data))
|
|
|
|
|
|
|
|
windowed_s11 = window * s11
|
|
|
|
|
2019-09-26 21:01:19 +00:00
|
|
|
self.td = np.abs(np.fft.ifft(windowed_s11, 2**16))
|
2019-09-13 15:08:20 +00:00
|
|
|
|
2019-09-26 21:01:19 +00:00
|
|
|
time_axis = np.linspace(0, 1/step_size, 2**16)
|
2019-09-13 15:08:20 +00:00
|
|
|
self.distance_axis = time_axis * v * c
|
|
|
|
|
|
|
|
# peak = np.max(td) # We should check that this is an actual *peak*, and not just a vague maximum
|
|
|
|
index_peak = np.argmax(self.td)
|
|
|
|
|
2019-09-15 13:29:09 +00:00
|
|
|
cable_len = round(self.distance_axis[index_peak]/2, 3)
|
|
|
|
feet = math.floor(cable_len / 0.3048)
|
|
|
|
inches = round(((cable_len / 0.3048) - feet)*12, 1)
|
|
|
|
|
|
|
|
self.tdr_result_label.setText(str(cable_len) + " m (" + str(feet) + "ft " + str(inches) + "in)")
|
|
|
|
self.app.tdr_result_label.setText(str(cable_len) + " m")
|
|
|
|
self.app.tdr_chart.update()
|
2019-09-21 10:55:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SweepSettingsWindow(QtWidgets.QWidget):
|
|
|
|
def __init__(self, app: NanoVNASaver):
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
self.app = app
|
|
|
|
self.setWindowTitle("Sweep settings")
|
|
|
|
|
2019-09-21 11:13:33 +00:00
|
|
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
|
|
|
|
2019-09-21 10:55:22 +00:00
|
|
|
layout = QtWidgets.QFormLayout()
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
|
self.single_sweep_radiobutton = QtWidgets.QRadioButton("Single sweep")
|
|
|
|
self.continuous_sweep_radiobutton = QtWidgets.QRadioButton("Continuous sweep")
|
|
|
|
self.averaged_sweep_radiobutton = QtWidgets.QRadioButton("Averaged sweep")
|
|
|
|
|
|
|
|
layout.addWidget(self.single_sweep_radiobutton)
|
|
|
|
self.single_sweep_radiobutton.setChecked(True)
|
|
|
|
layout.addWidget(self.continuous_sweep_radiobutton)
|
|
|
|
layout.addWidget(self.averaged_sweep_radiobutton)
|
|
|
|
|
|
|
|
self.averages = QtWidgets.QLineEdit("3")
|
|
|
|
self.truncates = QtWidgets.QLineEdit("0")
|
|
|
|
|
|
|
|
layout.addRow("Number of measurements to average", self.averages)
|
|
|
|
layout.addRow("Number to discard", self.truncates)
|
|
|
|
layout.addRow(QtWidgets.QLabel("Averaging allows discarding outlying samples to get better averages."))
|
|
|
|
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.averaged_sweep_radiobutton.toggled.connect(self.updateAveraging)
|
|
|
|
self.averages.textEdited.connect(self.updateAveraging)
|
|
|
|
self.truncates.textEdited.connect(self.updateAveraging)
|
|
|
|
|
|
|
|
def updateAveraging(self):
|
|
|
|
self.app.worker.setAveraging(self.averaged_sweep_radiobutton.isChecked(),
|
|
|
|
self.averages.text(),
|
|
|
|
self.truncates.text())
|
2019-09-26 20:57:34 +00:00
|
|
|
|
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
class BandsWindow(QtWidgets.QWidget):
|
|
|
|
def __init__(self, app):
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
self.app: NanoVNASaver = app
|
|
|
|
self.setWindowTitle("Manage bands")
|
|
|
|
|
|
|
|
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
|
|
|
|
|
|
|
|
layout = QtWidgets.QVBoxLayout()
|
|
|
|
self.setLayout(layout)
|
|
|
|
self.setMinimumSize(500, 300)
|
|
|
|
|
|
|
|
self.bands_table = QtWidgets.QTableView()
|
|
|
|
self.bands_table.setModel(self.app.bands)
|
|
|
|
self.bands_table.horizontalHeader().setStretchLastSection(True)
|
|
|
|
|
|
|
|
layout.addWidget(self.bands_table)
|
|
|
|
|
|
|
|
btn_add_row = QtWidgets.QPushButton("Add row")
|
|
|
|
btn_delete_row = QtWidgets.QPushButton("Delete row")
|
|
|
|
btn_reset_bands = QtWidgets.QPushButton("Reset bands")
|
|
|
|
btn_layout = QtWidgets.QHBoxLayout()
|
|
|
|
btn_layout.addWidget(btn_add_row)
|
|
|
|
btn_layout.addWidget(btn_delete_row)
|
|
|
|
btn_layout.addWidget(btn_reset_bands)
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
|
|
|
|
|
btn_add_row.clicked.connect(self.app.bands.addRow)
|
|
|
|
btn_delete_row.clicked.connect(self.deleteRows)
|
|
|
|
btn_reset_bands.clicked.connect(self.resetBands)
|
|
|
|
|
|
|
|
def deleteRows(self):
|
|
|
|
rows = self.bands_table.selectedIndexes()
|
|
|
|
for row in rows:
|
|
|
|
self.app.bands.removeRow(row.row())
|
|
|
|
|
|
|
|
def resetBands(self):
|
|
|
|
confirm = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning,
|
|
|
|
"Confirm reset",
|
|
|
|
"Are you sure you want to reset the bands to default?",
|
|
|
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel).exec()
|
|
|
|
if confirm == QtWidgets.QMessageBox.Yes:
|
|
|
|
self.app.bands.resetBands()
|
|
|
|
|
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
class BandsModel(QtCore.QAbstractTableModel):
|
|
|
|
bands: List[Tuple[str, int, int]] = []
|
2019-09-27 07:40:08 +00:00
|
|
|
enabled = False
|
2019-09-27 08:56:09 +00:00
|
|
|
color = QtGui.QColor(128, 128, 128, 48)
|
2019-09-26 20:57:34 +00:00
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
# These bands correspond broadly to the Danish Amateur Radio allocation
|
2019-09-29 06:33:03 +00:00
|
|
|
default_bands = ["2200 m;135700;137800",
|
|
|
|
"630 m;472000;479000",
|
|
|
|
"160 m;1800000;2000000",
|
|
|
|
"80 m;3500000;3800000",
|
|
|
|
"60 m;5250000;5450000",
|
|
|
|
"40 m;7000000;7200000",
|
|
|
|
"30 m;10100000;10150000",
|
|
|
|
"20 m;14000000;14350000",
|
|
|
|
"17 m;18068000;18168000",
|
|
|
|
"15 m;21000000;21450000",
|
|
|
|
"12 m;24890000;24990000",
|
|
|
|
"10 m;28000000;29700000",
|
|
|
|
"6 m;50000000;52000000",
|
|
|
|
"4 m;69887500;70512500",
|
|
|
|
"2 m;144000000;146000000",
|
|
|
|
"70 cm;432000000;438000000",
|
|
|
|
"23 cm;1240000000;1300000000"]
|
2019-09-27 08:30:00 +00:00
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__()
|
|
|
|
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
|
|
|
QtCore.QSettings.UserScope,
|
|
|
|
"NanoVNASaver", "Bands")
|
|
|
|
self.settings.setIniCodec("UTF-8")
|
2019-09-27 08:56:09 +00:00
|
|
|
self.enabled = self.settings.value("ShowBands", False, bool)
|
2019-09-27 08:30:00 +00:00
|
|
|
|
|
|
|
stored_bands: List[str] = self.settings.value("bands", self.default_bands)
|
2019-09-27 07:40:08 +00:00
|
|
|
if stored_bands:
|
|
|
|
for b in stored_bands:
|
|
|
|
(name, start, end) = b.split(";")
|
|
|
|
self.bands.append((name, int(start), int(end)))
|
2019-09-26 20:57:34 +00:00
|
|
|
|
|
|
|
def saveSettings(self):
|
2019-09-27 07:40:08 +00:00
|
|
|
stored_bands = []
|
|
|
|
for b in self.bands:
|
|
|
|
stored_bands.append(b[0] + ";" + str(b[1]) + ";" + str(b[2]))
|
|
|
|
self.settings.setValue("bands", stored_bands)
|
2019-09-26 20:57:34 +00:00
|
|
|
self.settings.sync()
|
|
|
|
|
2019-09-27 08:30:00 +00:00
|
|
|
def resetBands(self):
|
|
|
|
self.bands = []
|
|
|
|
for b in self.default_bands:
|
|
|
|
(name, start, end) = b.split(";")
|
|
|
|
self.bands.append((name, int(start), int(end)))
|
|
|
|
self.layoutChanged.emit()
|
|
|
|
self.saveSettings()
|
|
|
|
|
2019-09-26 20:57:34 +00:00
|
|
|
def columnCount(self, parent: QModelIndex = ...) -> int:
|
|
|
|
return 3
|
|
|
|
|
|
|
|
def rowCount(self, parent: QModelIndex = ...) -> int:
|
|
|
|
return len(self.bands)
|
|
|
|
|
|
|
|
def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant:
|
|
|
|
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.ItemDataRole or role == QtCore.Qt.EditRole:
|
|
|
|
return QtCore.QVariant(self.bands[index.row()][index.column()])
|
|
|
|
elif role == QtCore.Qt.TextAlignmentRole:
|
|
|
|
if index.column() == 0:
|
|
|
|
return QtCore.QVariant(QtCore.Qt.AlignCenter)
|
|
|
|
else:
|
2019-09-27 08:30:00 +00:00
|
|
|
return QtCore.QVariant(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
2019-09-26 20:57:34 +00:00
|
|
|
else:
|
|
|
|
return QtCore.QVariant()
|
|
|
|
|
|
|
|
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
|
|
|
|
if role == QtCore.Qt.EditRole and index.isValid():
|
|
|
|
t = self.bands[index.row()]
|
|
|
|
name = t[0]
|
|
|
|
start = t[1]
|
|
|
|
end = t[2]
|
|
|
|
if index.column() == 0:
|
|
|
|
name = value
|
|
|
|
elif index.column() == 1:
|
|
|
|
start = value
|
|
|
|
elif index.column() == 2:
|
|
|
|
end = value
|
|
|
|
self.bands[index.row()] = (name, start, end)
|
|
|
|
self.dataChanged.emit(index, index)
|
2019-09-27 08:30:00 +00:00
|
|
|
self.saveSettings()
|
2019-09-26 20:57:34 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
|
|
|
|
return self.createIndex(row, column)
|
|
|
|
|
|
|
|
def addRow(self):
|
|
|
|
self.bands.append(("New", 0, 0))
|
|
|
|
self.dataChanged.emit(self.index(len(self.bands), 0), self.index(len(self.bands), 2))
|
|
|
|
self.layoutChanged.emit()
|
|
|
|
|
|
|
|
def removeRow(self, row: int, parent: QModelIndex = ...) -> bool:
|
|
|
|
self.bands.remove(self.bands[row])
|
|
|
|
self.layoutChanged.emit()
|
2019-09-27 08:30:00 +00:00
|
|
|
self.saveSettings()
|
2019-09-26 20:57:34 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = ...):
|
|
|
|
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
|
|
|
|
if section == 0:
|
|
|
|
return "Band"
|
|
|
|
if section == 1:
|
|
|
|
return "Start (Hz)"
|
|
|
|
if section == 2:
|
|
|
|
return "End (Hz)"
|
|
|
|
else:
|
|
|
|
return "Invalid"
|
|
|
|
else:
|
|
|
|
super().headerData(section, orientation, role)
|
|
|
|
|
|
|
|
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:
|
|
|
|
if index.isValid():
|
|
|
|
return QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
|
|
|
|
else:
|
|
|
|
super().flags(index)
|
2019-09-27 08:56:09 +00:00
|
|
|
|
|
|
|
def setColor(self, color):
|
|
|
|
self.color = color
|