nanovna-saver/NanoVNASaver/NanoVNASaver.py

2456 wiersze
105 KiB
Python
Czysty Zwykły widok Historia

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-09-16 13:47:37 +00:00
import logging
import math
2019-09-13 15:32:13 +00:00
import sys
2019-08-28 18:20:07 +00:00
import threading
from time import sleep, strftime, localtime
from typing import List, Tuple
2019-08-28 18:20:07 +00:00
import numpy as np
import scipy.signal as signal
2019-08-28 18:20:07 +00:00
import serial
import typing
2019-08-28 18:20:07 +00:00
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QModelIndex
from serial.tools import list_ports
2019-08-28 18:20:07 +00:00
2019-10-20 10:19:05 +00:00
from .Hardware import VNA, InvalidVNA, Version
from .RFTools import RFTools, Datapoint
from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \
2019-10-21 14:46:30 +00:00
RealImaginaryChart, MagnitudeChart, MagnitudeZChart, CombinedLogMagChart, SParameterChart
from .Calibration import CalibrationWindow, Calibration
from .Marker import Marker
from .SweepWorker import SweepWorker
from .Touchstone import Touchstone
from .Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, BandPassAnalysis, BandStopAnalysis, \
2019-11-02 21:40:54 +00:00
PeakSearchAnalysis, VSWRAnalysis, SimplePeakSearchAnalysis
from .about import version as ver
2019-08-28 18:20:07 +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-08-28 18:20:07 +00:00
class NanoVNASaver(QtWidgets.QWidget):
version = ver
default_marker_colors = [QtGui.QColor(255, 0, 0),
QtGui.QColor(0, 255, 0),
QtGui.QColor(0, 0, 255),
QtGui.QColor(0, 255, 255),
QtGui.QColor(255, 0, 255),
QtGui.QColor(255, 255, 0)]
dataAvailable = QtCore.pyqtSignal()
2019-08-28 18:20:07 +00:00
def __init__(self):
super().__init__()
2019-10-02 08:07:38 +00:00
if getattr(sys, 'frozen', False):
logger.debug("Running from pyinstaller bundle")
self.icon = QtGui.QIcon(sys._MEIPASS + "/icon_48x48.png")
else:
self.icon = QtGui.QIcon("icon_48x48.png")
self.setWindowIcon(self.icon)
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()
self.vna: VNA = InvalidVNA()
self.worker = SweepWorker(self)
2019-08-28 18:20:07 +00:00
self.worker.signals.updated.connect(self.dataUpdated)
self.worker.signals.finished.connect(self.sweepFinished)
self.worker.signals.sweepError.connect(self.showSweepError)
2019-08-28 18:20:07 +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()
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] = []
self.sweepSource = ""
self.referenceSource = ""
self.calibration = Calibration()
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")
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)
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
# outer.setContentsMargins(2, 2, 2, 2) # Small screen mode, reduce margins?
self.s11SmithChart = SmithChart("S11 Smith Chart")
self.s21PolarChart = PolarChart("S21 Polar Plot")
2019-10-21 14:46:30 +00:00
self.s11SParameterChart = SParameterChart("S11 Real/Imaginary")
self.s21SParameterChart = SParameterChart("S21 Real/Imaginary")
self.s11LogMag = LogMagChart("S11 Return Loss")
self.s21LogMag = LogMagChart("S21 Gain")
self.s11Mag = MagnitudeChart("|S11|")
self.s21Mag = MagnitudeChart("|S21|")
self.s11MagZ = MagnitudeZChart("S11 |Z|")
self.s11Phase = PhaseChart("S11 Phase")
self.s21Phase = PhaseChart("S21 Phase")
self.s11VSWR = VSWRChart("S11 VSWR")
2019-09-10 17:51:21 +00:00
self.s11QualityFactor = QualityFactorChart("S11 Quality Factor")
self.s11RealImaginary = RealImaginaryChart("S11 R+jX")
self.tdr_chart = TDRChart("TDR")
self.tdr_mainwindow_chart = TDRChart("TDR")
2019-10-20 21:28:52 +00:00
self.combinedLogMag = CombinedLogMagChart("S11 & S21 LogMag")
# List of all the S11 charts, for selecting
self.s11charts: List[Chart] = []
self.s11charts.append(self.s11SmithChart)
self.s11charts.append(self.s11LogMag)
self.s11charts.append(self.s11Mag)
self.s11charts.append(self.s11MagZ)
self.s11charts.append(self.s11Phase)
self.s11charts.append(self.s11VSWR)
self.s11charts.append(self.s11RealImaginary)
2019-09-10 17:51:21 +00:00
self.s11charts.append(self.s11QualityFactor)
2019-10-21 14:46:30 +00:00
self.s11charts.append(self.s11SParameterChart)
# List of all the S21 charts, for selecting
self.s21charts: List[Chart] = []
self.s21charts.append(self.s21PolarChart)
self.s21charts.append(self.s21LogMag)
self.s21charts.append(self.s21Mag)
self.s21charts.append(self.s21Phase)
2019-10-21 14:46:30 +00:00
self.s21charts.append(self.s21SParameterChart)
2019-08-28 18:20:07 +00:00
2019-10-20 21:28:52 +00:00
# List of all charts that use both S11 and S21
self.combinedCharts: List[Chart] = []
self.combinedCharts.append(self.combinedLogMag)
# List of all charts that can be selected for display
2019-10-20 21:28:52 +00:00
self.selectable_charts = self.s11charts + self.s21charts + self.combinedCharts
self.selectable_charts.append(self.tdr_mainwindow_chart)
# List of all charts that subscribe to updates (including duplicates!)
self.subscribing_charts = []
self.subscribing_charts.extend(self.selectable_charts)
self.subscribing_charts.append(self.tdr_chart)
for c in self.subscribing_charts:
c.popoutRequested.connect(self.popoutChart)
self.charts_layout = QtWidgets.QGridLayout()
2019-08-28 18:20:07 +00:00
left_column = QtWidgets.QVBoxLayout()
self.marker_column = QtWidgets.QVBoxLayout()
self.marker_frame = QtWidgets.QFrame()
self.marker_column.setContentsMargins(0, 0, 0, 0)
self.marker_frame.setLayout(self.marker_column)
2019-08-28 18:20:07 +00:00
right_column = QtWidgets.QVBoxLayout()
right_column.addLayout(self.charts_layout)
self.marker_frame.setHidden(not self.settings.value("MarkersVisible", True, bool))
2019-08-28 18:20:07 +00:00
layout.addLayout(left_column, 0, 0)
layout.addWidget(self.marker_frame, 0, 1)
layout.addLayout(right_column, 0, 2)
2019-08-28 18:20:07 +00:00
################################################################################################################
# Sweep control
################################################################################################################
sweep_control_box = QtWidgets.QGroupBox()
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)
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("")
self.sweepStartInput.setMinimumWidth(60)
2019-08-28 18:20:07 +00:00
self.sweepStartInput.setAlignment(QtCore.Qt.AlignRight)
self.sweepStartInput.textEdited.connect(self.updateCenterSpan)
2019-10-12 20:48:17 +00:00
self.sweepStartInput.textChanged.connect(self.updateStepSize)
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)
self.sweepEndInput.textEdited.connect(self.updateCenterSpan)
2019-10-12 20:48:17 +00:00
self.sweepEndInput.textChanged.connect(self.updateStepSize)
sweep_input_left_layout.addRow(QtWidgets.QLabel("Stop"), self.sweepEndInput)
self.sweepCenterInput = QtWidgets.QLineEdit("")
self.sweepCenterInput.setMinimumWidth(60)
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
sweep_input_right_layout.addRow(QtWidgets.QLabel("Span"), self.sweepSpanInput)
2019-08-28 18:20:07 +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)
self.sweepCountInput.setFixedWidth(60)
self.sweepCountInput.textEdited.connect(self.updateStepSize)
2019-08-28 18:20:07 +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
self.sweepSettingsWindow = SweepSettingsWindow(self)
btn_sweep_settings_window = QtWidgets.QPushButton("Sweep settings ...")
btn_sweep_settings_window.clicked.connect(self.displaySweepSettingsWindow)
sweep_control_layout.addRow(btn_sweep_settings_window)
2019-08-28 18:20:07 +00:00
self.sweepProgressBar = QtWidgets.QProgressBar()
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)
self.btnSweep.setShortcut(QtCore.Qt.Key_W | QtCore.Qt.CTRL)
self.btnStopSweep = QtWidgets.QPushButton("Stop")
self.btnStopSweep.clicked.connect(self.stopSweep)
self.btnStopSweep.setShortcut(QtCore.Qt.Key_Escape)
self.btnStopSweep.setDisabled(True)
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")
marker_control_box.setMaximumWidth(250)
self.marker_control_layout = QtWidgets.QFormLayout(marker_control_box)
marker_count = self.settings.value("MarkerCount", 3, int)
for i in range(marker_count):
if i < len(self.default_marker_colors):
default_color = self.default_marker_colors[i]
else:
default_color = QtGui.QColor(QtCore.Qt.darkGray)
color = self.settings.value("Marker" + str(i+1) + "Color", default_color)
marker = Marker("Marker " + str(i+1), color)
marker.updated.connect(self.markerUpdated)
label, layout = marker.getRow()
self.marker_control_layout.addRow(label, layout)
self.markers.append(marker)
if i == 0:
marker.isMouseControlledRadioButton.setChecked(True)
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)
self.marker_control_layout.addRow(self.showMarkerButton)
for c in self.subscribing_charts:
c.setMarkers(self.markers)
c.setBands(self.bands)
left_column.addWidget(marker_control_box)
self.marker_data_layout = QtWidgets.QVBoxLayout()
self.marker_data_layout.setContentsMargins(0, 0, 0, 0)
for m in self.markers:
self.marker_data_layout.addWidget(m.getGroupBox())
self.marker_column.addLayout(self.marker_data_layout)
2019-08-28 18:20:07 +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)
self.marker_column.addWidget(s11_control_box)
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)
self.marker_column.addWidget(s21_control_box)
self.marker_column.addStretch(1)
2019-10-04 10:51:20 +00:00
self.analysis_window = AnalysisWindow(self)
btn_show_analysis = QtWidgets.QPushButton("Analysis ...")
btn_show_analysis.clicked.connect(self.displayAnalysisWindow)
self.marker_column.addWidget(btn_show_analysis)
2019-10-04 10:51:20 +00:00
################################################################################################################
# TDR
################################################################################################################
self.tdr_window = TDRWindow(self)
self.tdr_chart.tdrWindow = self.tdr_window
self.tdr_mainwindow_chart.tdrWindow = self.tdr_window
self.tdr_window.updated.connect(self.tdr_chart.update)
self.tdr_window.updated.connect(self.tdr_mainwindow_chart.update)
tdr_control_box = QtWidgets.QGroupBox()
tdr_control_box.setTitle("TDR")
tdr_control_layout = QtWidgets.QFormLayout()
tdr_control_box.setLayout(tdr_control_layout)
tdr_control_box.setMaximumWidth(250)
self.tdr_result_label = QtWidgets.QLabel()
tdr_control_layout.addRow("Estimated cable length:", self.tdr_result_label)
self.tdr_button = QtWidgets.QPushButton("Time Domain Reflectometry ...")
self.tdr_button.clicked.connect(self.displayTDRWindow)
tdr_control_layout.addRow(self.tdr_button)
left_column.addWidget(tdr_control_box)
################################################################################################################
# Spacer
################################################################################################################
2019-08-28 18:20:07 +00:00
left_column.addSpacerItem(QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding))
################################################################################################################
# Reference control
################################################################################################################
reference_control_box = QtWidgets.QGroupBox()
reference_control_box.setMaximumWidth(250)
reference_control_box.setTitle("Reference sweep")
reference_control_layout = QtWidgets.QFormLayout(reference_control_box)
btnSetReference = QtWidgets.QPushButton("Set current as reference")
btnSetReference.clicked.connect(self.setReference)
self.btnResetReference = QtWidgets.QPushButton("Reset reference")
self.btnResetReference.clicked.connect(self.resetReference)
self.btnResetReference.setDisabled(True)
reference_control_layout.addRow(btnSetReference)
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()
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
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
################################################################################################################
self.fileWindow = QtWidgets.QWidget()
self.fileWindow.setWindowTitle("Files")
2019-10-02 08:07:38 +00:00
self.fileWindow.setWindowIcon(self.icon)
self.fileWindow.setMinimumWidth(200)
2019-09-21 11:13:33 +00:00
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self.fileWindow, self.fileWindow.hide)
file_window_layout = QtWidgets.QVBoxLayout()
self.fileWindow.setLayout(file_window_layout)
load_file_control_box = QtWidgets.QGroupBox("Import file")
load_file_control_box.setMaximumWidth(300)
load_file_control_layout = QtWidgets.QFormLayout(load_file_control_box)
btn_load_sweep = QtWidgets.QPushButton("Load as sweep")
btn_load_sweep.clicked.connect(self.loadSweepFile)
btn_load_reference = QtWidgets.QPushButton("Load reference")
btn_load_reference.clicked.connect(self.loadReferenceFile)
load_file_control_layout.addRow(btn_load_sweep)
load_file_control_layout.addRow(btn_load_reference)
file_window_layout.addWidget(load_file_control_box)
2019-08-28 18:20:07 +00:00
save_file_control_box = QtWidgets.QGroupBox("Export file")
save_file_control_box.setMaximumWidth(300)
save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box)
2019-08-28 18:20:07 +00:00
btnExportFile = QtWidgets.QPushButton("Save file (S1P)")
btnExportFile.clicked.connect(self.exportFileS1P)
save_file_control_layout.addRow(btnExportFile)
btnExportFile = QtWidgets.QPushButton("Save file (S2P)")
btnExportFile.clicked.connect(self.exportFileS2P)
save_file_control_layout.addRow(btnExportFile)
2019-08-28 18:20:07 +00:00
file_window_layout.addWidget(save_file_control_box)
btn_open_file_window = QtWidgets.QPushButton("Files ...")
btn_open_file_window.clicked.connect(self.displayFileWindow)
################################################################################################################
# Calibration
################################################################################################################
btnOpenCalibrationWindow = QtWidgets.QPushButton("Calibration ...")
self.calibrationWindow = CalibrationWindow(self)
btnOpenCalibrationWindow.clicked.connect(self.displayCalibrationWindow)
2019-08-28 18:20:07 +00:00
################################################################################################################
# Display setup
################################################################################################################
btn_display_setup = QtWidgets.QPushButton("Display setup ...")
btn_display_setup.setMaximumWidth(250)
self.displaySetupWindow = DisplaySettingsWindow(self)
btn_display_setup.clicked.connect(self.displaySettingsWindow)
self.aboutWindow = AboutWindow(self)
btn_about = QtWidgets.QPushButton("About ...")
btn_about.setMaximumWidth(250)
btn_about.clicked.connect(self.displayAboutWindow)
button_grid = QtWidgets.QGridLayout()
button_grid.addWidget(btn_open_file_window, 0, 0)
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-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)
# Get that windows port
2019-09-04 08:29:13 +00:00
@staticmethod
2019-09-16 13:47:37 +00:00
def getPort() -> str:
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)
return port
2019-09-25 19:50:27 +00:00
return ""
def exportFileS1P(self):
if len(self.data) == 0:
# No data to save, alert the user
QtWidgets.QMessageBox.warning(self, "No data to save", "There is no data to save.")
return
filedialog = QtWidgets.QFileDialog(self)
filedialog.setDefaultSuffix("s1p")
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
selected = filedialog.exec()
if selected:
filename = filedialog.selectedFiles()[0]
else:
return
if filename == "":
logger.debug("No file name selected.")
return
logger.debug("Save S1P file to %s", filename)
if len(self.data) == 0:
logger.warning("No data stored, nothing written.")
return
try:
2019-09-16 19:41:01 +00:00
logger.debug("Opening %s for writing", filename)
file = open(filename, "w+")
2019-09-16 19:41:01 +00:00
logger.debug("Writing file")
file.write("# Hz S RI R 50\n")
2019-08-30 09:00:48 +00:00
for i in range(len(self.data)):
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")
file.close()
2019-09-16 19:41:01 +00:00
logger.debug("File written")
except Exception as e:
logger.exception("Error during file export: %s", e)
return
def exportFileS2P(self):
if len(self.data21) == 0:
# No S21 data to save, alert the user
QtWidgets.QMessageBox.warning(self, "No S21 data to save", "There is no S21 data to save.")
return
filedialog = QtWidgets.QFileDialog(self)
filedialog.setDefaultSuffix("s2p")
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
selected = filedialog.exec()
if selected:
filename = filedialog.selectedFiles()[0]
else:
return
if filename == "":
logger.debug("No file name selected.")
return
logger.debug("Save S2P file to %s", filename)
if len(self.data) == 0 or len(self.data21) == 0:
logger.warning("No data stored, nothing written.")
return
try:
2019-09-16 19:41:01 +00:00
logger.debug("Opening %s for writing", filename)
file = open(filename, "w+")
2019-09-16 19:41:01 +00:00
logger.debug("Writing file")
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")
except Exception as e:
logger.exception("Error during file export: %s", e)
return
2019-08-28 18:20:07 +00:00
def serialButtonClick(self):
if self.serial.is_open:
self.stopSerial()
else:
self.startSerial()
return
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:
logger.error("Tried to open %s and failed: %s", self.serialPort, exc)
2019-08-28 18:20:07 +00:00
self.serialLock.release()
return
self.btnSerialToggle.setText("Disconnect")
2019-08-28 18:20:07 +00:00
self.serialLock.release()
sleep(0.05)
self.vna = VNA.getVNA(self, self.serial)
self.worker.setVNA(self.vna)
logger.info(self.vna.readFirmware())
2019-09-04 13:15:53 +00:00
frequencies = self.vna.readFrequencies()
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(RFTools.formatFixedFrequency(int(frequencies[0])))
self.sweepEndInput.setText(RFTools.formatFixedFrequency(int(frequencies[100]) + 100000))
elif self.sweepStartInput.text() == "" or self.sweepEndInput.text() == "":
self.sweepStartInput.setText(RFTools.formatFixedFrequency(int(frequencies[0])))
self.sweepEndInput.setText(RFTools.formatFixedFrequency(int(frequencies[100])))
self.sweepStartInput.textEdited.emit(self.sweepStartInput.text())
self.sweepStartInput.textChanged.emit(self.sweepStartInput.text())
else:
logger.warning("No frequencies read")
return
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()
self.btnSerialToggle.setText("Connect to NanoVNA")
2019-08-28 18:20:07 +00:00
def toggleSweepSettings(self, disabled):
self.sweepStartInput.setDisabled(disabled)
self.sweepEndInput.setDisabled(disabled)
self.sweepSpanInput.setDisabled(disabled)
self.sweepCenterInput.setDisabled(disabled)
self.sweepCountInput.setDisabled(disabled)
2019-08-28 18:20:07 +00:00
def sweep(self):
# Run the serial port update
if not self.serial.is_open:
return
self.worker.stopped = False
2019-08-28 18:20:07 +00:00
self.sweepProgressBar.setValue(0)
self.btnSweep.setDisabled(True)
self.btnStopSweep.setDisabled(False)
self.toggleSweepSettings(True)
for m in self.markers:
m.resetLabels()
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
if self.sweepCountInput.text().isdigit():
self.settings.setValue("Segments", self.sweepCountInput.text())
logger.debug("Starting worker thread")
self.threadpool.start(self.worker)
def stopSweep(self):
self.worker.stopped = True
def saveData(self, data, data12, source=None):
if self.dataLock.acquire(blocking=True):
self.data = data
self.data21 = data12
else:
2019-09-16 19:41:01 +00:00
logger.error("Failed acquiring data lock while saving.")
self.dataLock.release()
if source is not None:
self.sweepSource = source
else:
self.sweepSource = strftime("%Y-%m-%d %H:%M:%S", localtime())
def markerUpdated(self, marker: Marker):
if self.dataLock.acquire(blocking=True):
marker.findLocation(self.data)
for m in self.markers:
m.resetLabels()
m.updateLabels(self.data, self.data21)
for c in self.subscribing_charts:
c.update()
self.dataLock.release()
def dataUpdated(self):
if self.dataLock.acquire(blocking=True):
for m in self.markers:
m.findLocation(self.data)
m.resetLabels()
m.updateLabels(self.data, self.data21)
for c in self.s11charts:
c.setData(self.data)
for c in self.s21charts:
c.setData(self.data21)
2019-10-20 21:28:52 +00:00
for c in self.combinedCharts:
c.setCombinedData(self.data, self.data21)
self.sweepProgressBar.setValue(self.worker.percentage)
self.tdr_window.updateTDR()
# Find the minimum S11 VSWR:
minVSWR = 100
minVSWRfreq = -1
for d in self.data:
vswr = RFTools.calculateVSWR(d)
2019-09-04 08:29:13 +00:00
if minVSWR > vswr > 0:
minVSWR = vswr
minVSWRfreq = d.freq
if minVSWRfreq > -1:
self.s11_min_swr_label.setText(str(round(minVSWR, 3)) + " @ " + RFTools.formatFrequency(minVSWRfreq))
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")
else:
self.s11_min_swr_label.setText("")
self.s11_min_rl_label.setText("")
minGain = 100
minGainFreq = -1
maxGain = -100
maxGainFreq = -1
for d in self.data21:
gain = RFTools.gain(d)
if gain > maxGain:
maxGain = gain
maxGainFreq = d.freq
if gain < minGain:
minGain = gain
minGainFreq = d.freq
if maxGainFreq > -1:
self.s21_min_gain_label.setText(str(round(minGain, 3)) + " dB @ " + RFTools.formatFrequency(minGainFreq))
self.s21_max_gain_label.setText(str(round(maxGain, 3)) + " dB @ " + RFTools.formatFrequency(maxGainFreq))
else:
self.s21_min_gain_label.setText("")
self.s21_max_gain_label.setText("")
else:
2019-09-16 19:41:01 +00:00
logger.error("Failed acquiring data lock while updating.")
self.updateTitle()
self.dataLock.release()
self.dataAvailable.emit()
def sweepFinished(self):
self.sweepProgressBar.setValue(100)
self.btnSweep.setDisabled(False)
self.btnStopSweep.setDisabled(True)
self.toggleSweepSettings(False)
def updateCenterSpan(self):
fstart = RFTools.parseFrequency(self.sweepStartInput.text())
fstop = RFTools.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(RFTools.formatFixedFrequency(fspan))
self.sweepCenterInput.setText(RFTools.formatFixedFrequency(fcenter))
def updateStartEnd(self):
fcenter = RFTools.parseFrequency(self.sweepCenterInput.text())
fspan = RFTools.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(RFTools.formatFixedFrequency(fstart))
self.sweepEndInput.setText(RFTools.formatFixedFrequency(fstop))
def updateStepSize(self):
fspan = RFTools.parseFrequency(self.sweepSpanInput.text())
if fspan < 0:
return
if self.sweepCountInput.text().isdigit():
segments = int(self.sweepCountInput.text())
if segments > 0:
fstep = fspan / (segments * 101)
self.sweepStepLabel.setText(RFTools.formatShortFrequency(fstep) + "/step")
def setReference(self, s11data=None, s21data=None, source=None):
if not s11data:
s11data = self.data
if not s21data:
s21data = self.data21
self.referenceS11data = s11data
for c in self.s11charts:
c.setReference(s11data)
self.referenceS21data = s21data
for c in self.s21charts:
c.setReference(s21data)
2019-10-20 21:28:52 +00:00
for c in self.combinedCharts:
c.setCombinedReference(s11data, s21data)
self.btnResetReference.setDisabled(False)
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)
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")
def resetReference(self):
self.referenceS11data = []
self.referenceS21data = []
self.referenceSource = ""
self.updateTitle()
for c in self.subscribing_charts:
c.resetReference()
self.btnResetReference.setDisabled(True)
def loadReferenceFile(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
if filename != "":
self.resetReference()
t = Touchstone(filename)
t.load()
self.setReference(t.s11data, t.s21data, filename)
def loadSweepFile(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)")
if filename != "":
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)
def displaySettingsWindow(self):
self.displaySetupWindow.show()
QtWidgets.QApplication.setActiveWindow(self.displaySetupWindow)
def displaySweepSettingsWindow(self):
self.sweepSettingsWindow.show()
QtWidgets.QApplication.setActiveWindow(self.sweepSettingsWindow)
def displayCalibrationWindow(self):
self.calibrationWindow.show()
QtWidgets.QApplication.setActiveWindow(self.calibrationWindow)
def displayFileWindow(self):
self.fileWindow.show()
QtWidgets.QApplication.setActiveWindow(self.fileWindow)
def displayTDRWindow(self):
self.tdr_window.show()
QtWidgets.QApplication.setActiveWindow(self.tdr_window)
2019-10-04 10:51:20 +00:00
def displayAnalysisWindow(self):
self.analysis_window.show()
QtWidgets.QApplication.setActiveWindow(self.analysis_window)
def displayAboutWindow(self):
self.aboutWindow.show()
QtWidgets.QApplication.setActiveWindow(self.aboutWindow)
def showError(self, text):
QtWidgets.QMessageBox.warning(self, "Error", text)
def showSweepError(self):
self.showError(self.worker.error_message)
self.stopSerial()
def popoutChart(self, chart: Chart):
logger.debug("Requested popout for chart: %s", chart.name)
new_chart = self.copyChart(chart)
new_chart.isPopout = True
new_chart.show()
new_chart.setWindowTitle(new_chart.name)
def copyChart(self, chart: Chart):
new_chart = chart.copy()
self.subscribing_charts.append(new_chart)
if chart in self.s11charts:
self.s11charts.append(new_chart)
if chart in self.s21charts:
self.s21charts.append(new_chart)
2019-10-20 21:28:52 +00:00
if chart in self.combinedCharts:
self.combinedCharts.append(new_chart)
new_chart.popoutRequested.connect(self.popoutChart)
return new_chart
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.worker.stopped = True
self.settings.setValue("MarkerCount", len(self.markers))
for i in range(len(self.markers)):
self.settings.setValue("Marker" + str(i+1) + "Color", self.markers[i].color)
self.settings.setValue("WindowHeight", self.height())
self.settings.setValue("WindowWidth", self.width())
self.settings.sync()
self.bands.saveSettings()
self.threadpool.waitForDone(2500)
a0.accept()
sys.exit()
class DisplaySettingsWindow(QtWidgets.QWidget):
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.setWindowTitle("Display settings")
2019-10-02 08:07:38 +00:00
self.setWindowIcon(self.app.icon)
2019-09-21 11:13:33 +00:00
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
2019-10-13 15:35:32 +00:00
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
2019-10-13 15:35:32 +00:00
left_layout = QtWidgets.QVBoxLayout()
layout.addLayout(left_layout)
display_options_box = QtWidgets.QGroupBox("Options")
display_options_layout = QtWidgets.QFormLayout(display_options_box)
returnloss_group = QtWidgets.QButtonGroup()
self.returnloss_is_negative = QtWidgets.QRadioButton("Negative")
self.returnloss_is_positive = QtWidgets.QRadioButton("Positive")
returnloss_group.addButton(self.returnloss_is_positive)
returnloss_group.addButton(self.returnloss_is_negative)
display_options_layout.addRow("Return loss is:", self.returnloss_is_negative)
display_options_layout.addRow("", self.returnloss_is_positive)
if self.app.settings.value("ReturnLossPositive", False):
self.returnloss_is_positive.setChecked(True)
else:
self.returnloss_is_negative.setChecked(True)
self.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
self.show_lines_option = QtWidgets.QCheckBox("Show lines")
show_lines_label = QtWidgets.QLabel("Displays a thin line between data points")
self.show_lines_option.stateChanged.connect(self.changeShowLines)
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)
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)
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)
self.btnReferenceColorPicker = QtWidgets.QPushButton("")
self.btnReferenceColorPicker.setFixedWidth(20)
self.referenceColor = self.app.settings.value("ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 48),
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)
self.btnSecondaryReferenceColorPicker = QtWidgets.QPushButton("")
self.btnSecondaryReferenceColorPicker.setFixedWidth(20)
self.secondaryReferenceColor = self.app.settings.value("SecondaryReferenceColor",
defaultValue=QtGui.QColor(0, 0, 255, 48),
type=QtGui.QColor)
self.setSecondaryReferenceColor(self.secondaryReferenceColor)
self.btnSecondaryReferenceColorPicker.clicked.connect(lambda: self.setSecondaryReferenceColor(
QtWidgets.QColorDialog.getColor(self.secondaryReferenceColor,
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
display_options_layout.addRow("Second reference color", self.btnSecondaryReferenceColorPicker)
self.pointSizeInput = QtWidgets.QSpinBox()
pointsize = self.app.settings.value("PointSize", 2, int)
self.pointSizeInput.setValue(pointsize)
self.changePointSize(pointsize)
self.pointSizeInput.setMinimum(1)
self.pointSizeInput.setMaximum(10)
self.pointSizeInput.setSuffix(" px")
self.pointSizeInput.setAlignment(QtCore.Qt.AlignRight)
self.pointSizeInput.valueChanged.connect(self.changePointSize)
display_options_layout.addRow("Point size", self.pointSizeInput)
self.lineThicknessInput = QtWidgets.QSpinBox()
2019-10-29 11:47:30 +00:00
linethickness = self.app.settings.value("LineThickness", 1, int)
self.lineThicknessInput.setValue(linethickness)
self.changeLineThickness(linethickness)
self.lineThicknessInput.setMinimum(1)
self.lineThicknessInput.setMaximum(10)
self.lineThicknessInput.setSuffix(" px")
self.lineThicknessInput.setAlignment(QtCore.Qt.AlignRight)
self.lineThicknessInput.valueChanged.connect(self.changeLineThickness)
display_options_layout.addRow("Line thickness", self.lineThicknessInput)
2019-10-29 11:47:30 +00:00
self.markerSizeInput = QtWidgets.QSpinBox()
markersize = self.app.settings.value("MarkerSize", 6, int)
self.markerSizeInput.setValue(markersize)
self.changeMarkerSize(markersize)
self.markerSizeInput.setMinimum(4)
self.markerSizeInput.setMaximum(20)
self.markerSizeInput.setSingleStep(2)
self.markerSizeInput.setSuffix(" px")
self.markerSizeInput.setAlignment(QtCore.Qt.AlignRight)
self.markerSizeInput.valueChanged.connect(self.changeMarkerSize)
self.markerSizeInput.editingFinished.connect(self.validateMarkerSize)
display_options_layout.addRow("Marker size", self.markerSizeInput)
2019-10-29 13:21:22 +00:00
self.show_marker_number_option = QtWidgets.QCheckBox("Show marker numbers")
show_marker_number_label = QtWidgets.QLabel("Displays the marker number next to the marker")
self.show_marker_number_option.stateChanged.connect(self.changeShowMarkerNumber)
display_options_layout.addRow(self.show_marker_number_option, show_marker_number_label)
color_options_box = QtWidgets.QGroupBox("Chart colors")
color_options_layout = QtWidgets.QFormLayout(color_options_box)
2019-09-17 12:37:12 +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)
2019-09-17 12:37:12 +00:00
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)
2019-09-17 12:37:12 +00:00
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)
2019-09-17 12:37:12 +00:00
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)
2019-10-13 15:35:32 +00:00
right_layout = QtWidgets.QVBoxLayout()
layout.addLayout(right_layout)
2019-09-17 12:37:12 +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)
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)
self.show_bands.stateChanged.connect(lambda: self.setShowBands(self.show_bands.isChecked()))
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)
self.btn_manage_bands = QtWidgets.QPushButton("Manage bands")
self.bandsWindow = BandsWindow(self.app)
self.btn_manage_bands.clicked.connect(self.displayBandsWindow)
bands_layout.addRow(self.btn_manage_bands)
2019-10-13 15:35:32 +00:00
vswr_marker_box = QtWidgets.QGroupBox("VSWR Markers")
vswr_marker_layout = QtWidgets.QFormLayout(vswr_marker_box)
self.vswrMarkers: List[float] = self.app.settings.value("VSWRMarkers", [], float)
if isinstance(self.vswrMarkers, float):
if self.vswrMarkers == 0:
self.vswrMarkers = []
else:
# Single values from the .ini become floats rather than lists. Convert them.
self.vswrMarkers = [self.vswrMarkers]
2019-10-13 15:35:32 +00:00
self.btn_vswr_picker = QtWidgets.QPushButton("")
self.btn_vswr_picker.setFixedWidth(20)
self.btn_vswr_picker.clicked.connect(lambda: self.setColor("vswr", QtWidgets.QColorDialog.getColor(self.vswrColor, options=QtWidgets.QColorDialog.ShowAlphaChannel)))
vswr_marker_layout.addRow("VSWR Markers", self.btn_vswr_picker)
self.vswr_marker_dropdown = QtWidgets.QComboBox()
vswr_marker_layout.addRow(self.vswr_marker_dropdown)
if len(self.vswrMarkers) == 0:
self.vswr_marker_dropdown.addItem("None")
else:
for m in self.vswrMarkers:
self.vswr_marker_dropdown.addItem(str(m))
for c in self.app.s11charts:
c.addSWRMarker(m)
self.vswr_marker_dropdown.setCurrentIndex(0)
btn_add_vswr_marker = QtWidgets.QPushButton("Add ...")
btn_remove_vswr_marker = QtWidgets.QPushButton("Remove")
2019-10-13 15:35:32 +00:00
vswr_marker_btn_layout = QtWidgets.QHBoxLayout()
vswr_marker_btn_layout.addWidget(btn_add_vswr_marker)
vswr_marker_btn_layout.addWidget(btn_remove_vswr_marker)
2019-10-13 15:35:32 +00:00
vswr_marker_layout.addRow(vswr_marker_btn_layout)
btn_add_vswr_marker.clicked.connect(self.addVSWRMarker)
btn_remove_vswr_marker.clicked.connect(self.removeVSWRMarker)
markers_box = QtWidgets.QGroupBox("Markers")
markers_layout = QtWidgets.QFormLayout(markers_box)
self.marker_window = MarkerSettingsWindow(self.app)
btn_add_marker = QtWidgets.QPushButton("Add")
btn_add_marker.clicked.connect(self.addMarker)
self.btn_remove_marker = QtWidgets.QPushButton("Remove")
self.btn_remove_marker.clicked.connect(self.removeMarker)
btn_marker_settings = QtWidgets.QPushButton("Settings ...")
btn_marker_settings.clicked.connect(self.displayMarkerWindow)
marker_btn_layout = QtWidgets.QHBoxLayout()
marker_btn_layout.addWidget(btn_add_marker)
marker_btn_layout.addWidget(self.btn_remove_marker)
marker_btn_layout.addWidget(btn_marker_settings)
markers_layout.addRow(marker_btn_layout)
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.selectable_charts:
selections.append(c.name)
selections.append("None")
chart00_selection = QtWidgets.QComboBox()
chart00_selection.addItems(selections)
2019-10-29 10:16:33 +00:00
chart00 = self.app.settings.value("Chart00", "S11 Smith Chart")
if chart00_selection.findText(chart00) > -1:
chart00_selection.setCurrentText(chart00)
else:
chart00_selection.setCurrentText("S11 Smith Chart")
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-10-29 10:16:33 +00:00
chart01 = self.app.settings.value("Chart01", "S11 Return Loss")
if chart01_selection.findText(chart01) > -1:
chart01_selection.setCurrentText(chart01)
else:
chart01_selection.setCurrentText("S11 Return Loss")
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-10-29 10:16:33 +00:00
chart02 = self.app.settings.value("Chart02", "None")
if chart02_selection.findText(chart02) > -1:
chart02_selection.setCurrentText(chart02)
else:
chart02_selection.setCurrentText("None")
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-10-29 10:16:33 +00:00
chart10 = self.app.settings.value("Chart10", "S21 Polar Plot")
if chart10_selection.findText(chart10) > -1:
chart10_selection.setCurrentText(chart10)
else:
chart10_selection.setCurrentText("S21 Polar Plot")
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-10-29 10:16:33 +00:00
chart11 = self.app.settings.value("Chart11", "S21 Gain")
if chart11_selection.findText(chart11) > -1:
chart11_selection.setCurrentText(chart11)
else:
chart11_selection.setCurrentText("S21 Gain")
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-10-29 10:16:33 +00:00
chart12 = self.app.settings.value("Chart12", "None")
if chart12_selection.findText(chart12) > -1:
chart12_selection.setCurrentText(chart12)
else:
chart12_selection.setCurrentText("None")
chart12_selection.currentTextChanged.connect(lambda: self.changeChart(1, 2, chart12_selection.currentText()))
charts_layout.addWidget(chart12_selection, 1, 2)
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-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-10-13 15:35:32 +00:00
self.vswrColor = self.app.settings.value("VSWRColor", defaultValue=QtGui.QColor(192, 0, 0, 128),
type=QtGui.QColor)
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-10-29 13:21:22 +00:00
self.show_marker_number_option.setChecked(self.app.settings.value("ShowMarkerNumbers", False, bool))
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)
self.changeCustomColors() # Update all the colours of all the charts
2019-09-17 12:37:12 +00:00
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-10-13 15:35:32 +00:00
p = self.btn_vswr_picker.palette()
p.setColor(QtGui.QPalette.ButtonText, self.vswrColor)
self.btn_vswr_picker.setPalette(p)
left_layout.addWidget(display_options_box)
left_layout.addWidget(charts_box)
left_layout.addWidget(markers_box)
2019-10-13 15:35:32 +00:00
left_layout.addStretch(1)
right_layout.addWidget(color_options_box)
right_layout.addWidget(font_options_box)
right_layout.addWidget(bands_box)
right_layout.addWidget(vswr_marker_box)
right_layout.addStretch(1)
2019-10-13 15:35:32 +00:00
def changeChart(self, x, y, chart):
found = None
for c in self.app.selectable_charts:
if c.name == chart:
found = c
self.app.settings.setValue("Chart" + str(x) + str(y), chart)
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:
if self.app.charts_layout.indexOf(found) > -1:
logger.debug("%s is already shown, duplicating.", found.name)
found = self.app.copyChart(found)
self.app.charts_layout.addWidget(found, x, y)
if found.isHidden():
found.show()
def changeReturnLoss(self):
state = self.returnloss_is_positive.isChecked()
self.app.settings.setValue("ReturnLossPositive", state)
for m in self.app.markers:
m.returnloss_is_positive = state
m.updateLabels(self.app.data, self.app.data21)
self.app.s11LogMag.isInverted = state
self.app.s11LogMag.update()
def changeShowLines(self):
state = self.show_lines_option.isChecked()
self.app.settings.setValue("ShowLines", state)
for c in self.app.subscribing_charts:
c.setDrawLines(state)
2019-10-29 13:21:22 +00:00
def changeShowMarkerNumber(self):
state = self.show_marker_number_option.isChecked()
self.app.settings.setValue("ShowMarkerNumbers", state)
for c in self.app.subscribing_charts:
c.setDrawMarkerNumbers(state)
def changePointSize(self, size: int):
self.app.settings.setValue("PointSize", size)
for c in self.app.subscribing_charts:
c.setPointSize(size)
def changeLineThickness(self, size: int):
self.app.settings.setValue("LineThickness", size)
for c in self.app.subscribing_charts:
c.setLineThickness(size)
2019-10-29 11:47:30 +00:00
def changeMarkerSize(self, size: int):
if size % 2 == 0:
self.app.settings.setValue("MarkerSize", size)
for c in self.app.subscribing_charts:
c.setMarkerSize(int(size / 2))
def validateMarkerSize(self):
size = self.markerSizeInput.value()
if size % 2 != 0:
self.markerSizeInput.setValue(size + 1)
def changeDarkMode(self):
state = self.dark_mode_option.isChecked()
self.app.settings.setValue("DarkMode", state)
if state:
for c in self.app.subscribing_charts:
c.setBackgroundColor(QtGui.QColor(QtCore.Qt.black))
2019-09-17 12:37:12 +00:00
c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray))
c.setTextColor(QtGui.QColor(QtCore.Qt.white))
2019-10-13 15:35:32 +00:00
c.setSWRColor(self.vswrColor)
else:
for c in self.app.subscribing_charts:
c.setBackgroundColor(QtGui.QColor(QtCore.Qt.white))
2019-09-17 12:37:12 +00:00
c.setForegroundColor(QtGui.QColor(QtCore.Qt.lightGray))
c.setTextColor(QtGui.QColor(QtCore.Qt.black))
2019-10-13 15:35:32 +00:00
c.setSWRColor(self.vswrColor)
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():
2019-09-17 12:37:12 +00:00
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.subscribing_charts:
2019-09-17 12:37:12 +00:00
c.setBackgroundColor(self.backgroundColor)
c.setForegroundColor(self.foregroundColor)
c.setTextColor(self.textColor)
c.setSWRColor(self.vswrColor)
else:
2019-09-17 12:37:12 +00:00
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-10-13 15:35:32 +00:00
elif name == "vswr":
p = self.btn_vswr_picker.palette()
p.setColor(QtGui.QPalette.ButtonText, color)
self.btn_vswr_picker.setPalette(p)
self.vswrColor = color
self.app.settings.setValue("VSWRColor", color)
2019-09-17 12:37:12 +00:00
self.changeCustomColors()
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.subscribing_charts:
c.setSweepColor(color)
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.subscribing_charts:
c.setSecondarySweepColor(color)
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.subscribing_charts:
c.setReferenceColor(color)
def setSecondaryReferenceColor(self, color):
if color.isValid():
self.secondaryReferenceColor = color
p = self.btnSecondaryReferenceColorPicker.palette()
p.setColor(QtGui.QPalette.ButtonText, color)
self.btnSecondaryReferenceColorPicker.setPalette(p)
self.app.settings.setValue("SecondaryReferenceColor", color)
self.app.settings.sync()
for c in self.app.subscribing_charts:
c.setSecondaryReferenceColor(color)
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.subscribing_charts:
c.update()
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)
def displayBandsWindow(self):
self.bandsWindow.show()
QtWidgets.QApplication.setActiveWindow(self.bandsWindow)
def displayMarkerWindow(self):
self.marker_window.show()
QtWidgets.QApplication.setActiveWindow(self.marker_window)
def addMarker(self):
marker_count = len(self.app.markers)
if marker_count < 6:
color = NanoVNASaver.default_marker_colors[marker_count]
else:
color = QtGui.QColor(QtCore.Qt.darkGray)
new_marker = Marker("Marker " + str(marker_count+1), color)
self.app.markers.append(new_marker)
self.app.marker_data_layout.addWidget(new_marker.getGroupBox())
new_marker.updated.connect(self.app.markerUpdated)
label, layout = new_marker.getRow()
self.app.marker_control_layout.insertRow(marker_count, label, layout)
if marker_count == 0:
new_marker.isMouseControlledRadioButton.setChecked(True)
self.btn_remove_marker.setDisabled(False)
def removeMarker(self):
if len(self.app.markers) == 0:
# How did we even get here? Better handle it anyway.
self.btn_remove_marker.setDisabled(True)
return
last_marker = self.app.markers.pop()
if len(self.app.markers) == 0:
# Last marker removed.
self.btn_remove_marker.setDisabled(True)
last_marker.updated.disconnect(self.app.markerUpdated)
self.app.marker_data_layout.removeWidget(last_marker.getGroupBox())
self.app.marker_control_layout.removeRow(len(self.app.markers))
last_marker.getGroupBox().hide()
last_marker.getGroupBox().destroy()
label, layout = last_marker.getRow()
label.hide()
2019-10-13 15:35:32 +00:00
def addVSWRMarker(self):
value, selected = QtWidgets.QInputDialog.getDouble(self, "Add VSWR Marker",
"VSWR value to show:", min=1.001, decimals=3)
if selected:
self.vswrMarkers.append(value)
if self.vswr_marker_dropdown.itemText(0) == "None":
self.vswr_marker_dropdown.removeItem(0)
self.vswr_marker_dropdown.addItem(str(value))
self.vswr_marker_dropdown.setCurrentText(str(value))
for c in self.app.s11charts:
c.addSWRMarker(value)
self.app.settings.setValue("VSWRMarkers", self.vswrMarkers)
def removeVSWRMarker(self):
value_str = self.vswr_marker_dropdown.currentText()
if value_str != "None":
value = float(value_str)
self.vswrMarkers.remove(value)
self.vswr_marker_dropdown.removeItem(self.vswr_marker_dropdown.currentIndex())
if self.vswr_marker_dropdown.count() == 0:
self.vswr_marker_dropdown.addItem("None")
self.app.settings.remove("VSWRMarkers")
else:
self.app.settings.setValue("VSWRMarkers", self.vswrMarkers)
2019-10-13 15:35:32 +00:00
for c in self.app.s11charts:
c.removeSWRMarker(value)
class AboutWindow(QtWidgets.QWidget):
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.setWindowTitle("About NanoVNASaver")
self.setWindowIcon(self.app.icon)
top_layout = QtWidgets.QHBoxLayout()
self.setLayout(top_layout)
#self.setAutoFillBackground(True)
pal = self.palette()
pal.setColor(QtGui.QPalette.Background, QtGui.QColor("white"))
self.setPalette(pal)
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
icon_layout = QtWidgets.QVBoxLayout()
top_layout.addLayout(icon_layout)
icon = QtWidgets.QLabel()
icon.setPixmap(self.app.icon.pixmap(128, 128))
icon_layout.addWidget(icon)
icon_layout.addStretch()
layout = QtWidgets.QVBoxLayout()
top_layout.addLayout(layout)
layout.addWidget(QtWidgets.QLabel("NanoVNASaver version " + NanoVNASaver.version))
layout.addWidget(QtWidgets.QLabel(""))
layout.addWidget(QtWidgets.QLabel("\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg"))
layout.addWidget(QtWidgets.QLabel("This program comes with ABSOLUTELY NO WARRANTY"))
layout.addWidget(QtWidgets.QLabel("This program is licensed under the GNU General Public License version 3"))
layout.addWidget(QtWidgets.QLabel(""))
link_label = QtWidgets.QLabel("For further details, see: " +
"<a href=\"https://mihtjel.github.io/nanovna-saver/\">" +
"https://mihtjel.github.io/nanovna-saver/</a>")
link_label.setOpenExternalLinks(True)
layout.addWidget(link_label)
layout.addWidget(QtWidgets.QLabel(""))
self.versionLabel = QtWidgets.QLabel("NanoVNA Firmware Version: Not connected.")
layout.addWidget(self.versionLabel)
layout.addStretch()
2019-10-09 16:35:36 +00:00
btn_check_version = QtWidgets.QPushButton("Check for updates")
btn_check_version.clicked.connect(self.findUpdates)
self.updateLabel = QtWidgets.QLabel("Last checked: ")
self.updateCheckBox = QtWidgets.QCheckBox("Check for updates on startup")
self.updateCheckBox.toggled.connect(self.updateSettings)
check_for_updates = self.app.settings.value("CheckForUpdates", "Ask")
if check_for_updates == "Yes":
self.updateCheckBox.setChecked(True)
2019-10-13 15:35:32 +00:00
self.findUpdates(automatic=True)
2019-10-09 16:35:36 +00:00
elif check_for_updates == "No":
self.updateCheckBox.setChecked(False)
else:
logger.debug("Starting timer")
QtCore.QTimer.singleShot(2000, self.askAboutUpdates)
update_hbox = QtWidgets.QHBoxLayout()
update_hbox.addWidget(btn_check_version)
update_form = QtWidgets.QFormLayout()
update_hbox.addLayout(update_form)
update_hbox.addStretch()
update_form.addRow(self.updateLabel)
update_form.addRow(self.updateCheckBox)
layout.addLayout(update_hbox)
layout.addStretch()
btn_ok = QtWidgets.QPushButton("Ok")
btn_ok.clicked.connect(lambda: self.close())
layout.addWidget(btn_ok)
def show(self):
super().show()
self.updateLabels()
def updateLabels(self):
if self.app.vna.isValid():
logger.debug("Valid VNA")
v: Version = self.app.vna.version
self.versionLabel.setText("NanoVNA Firmware Version: " + self.app.vna.name + " " + v.version_string)
2019-10-09 16:35:36 +00:00
def updateSettings(self):
if self.updateCheckBox.isChecked():
self.app.settings.setValue("CheckForUpdates", "Yes")
else:
self.app.settings.setValue("CheckForUpdates", "No")
def askAboutUpdates(self):
logger.debug("Asking about automatic update checks")
selection = QtWidgets.QMessageBox.question(self.app, "Enable checking for updates?",
"Would you like NanoVNA-Saver to check for updates automatically?")
if selection == QtWidgets.QMessageBox.Yes:
self.updateCheckBox.setChecked(True)
self.app.settings.setValue("CheckForUpdates", "Yes")
self.findUpdates()
elif selection == QtWidgets.QMessageBox.No:
self.updateCheckBox.setChecked(False)
self.app.settings.setValue("CheckForUpdates", "No")
QtWidgets.QMessageBox.information(self.app, "Checking for updates disabled",
"You can check for updates using the \"About\" window.")
else:
self.app.settings.setValue("CheckForUpdates", "Ask")
def findUpdates(self, automatic=False):
2019-10-09 16:35:36 +00:00
from urllib import request, error
import json
update_url = "http://mihtjel.dk/nanovna-saver/latest.json"
try:
req = request.Request(update_url)
req.add_header('User-Agent', "NanoVNA-Saver/" + self.app.version)
updates = json.load(request.urlopen(req, timeout=3))
2019-10-09 16:35:36 +00:00
latest_version = Version(updates['version'])
latest_url = updates['url']
except error.HTTPError as e:
logger.exception("Checking for updates produced an HTTP exception: %s", e)
self.updateLabel.setText("Connection error.")
2019-10-09 16:35:36 +00:00
return
except json.JSONDecodeError as e:
logger.exception("Checking for updates provided an unparseable file: %s", e)
self.updateLabel.setText("Data error reading versions.")
2019-10-09 16:35:36 +00:00
return
except error.URLError as e:
logger.exception("Checking for updates produced a URL exception: %s", e)
self.updateLabel.setText("Connection error.")
return
2019-10-09 16:35:36 +00:00
logger.info("Latest version is " + latest_version.version_string)
this_version = Version(NanoVNASaver.version)
logger.info("This is " + this_version.version_string)
if latest_version > this_version:
logger.info("New update available: %s!", latest_version)
if automatic:
QtWidgets.QMessageBox.information(self, "Updates available",
"There is a new update for NanoVNA-Saver available!\n" +
"Version " + latest_version.version_string + "\n\n" +
"Press \"About\" to find the update.")
else:
QtWidgets.QMessageBox.information(self, "Updates available",
"There is a new update for NanoVNA-Saver available!")
self.updateLabel.setText("<a href=\"" + latest_url + "\">New version available</a>.")
self.updateLabel.setOpenExternalLinks(True)
else:
# Probably don't show a message box, just update the screen?
# Maybe consider showing it if not an automatic update.
2019-10-09 16:35:36 +00:00
#
# QtWidgets.QMessageBox.information(self, "No updates available", "There are no new updates available.")
#
self.updateLabel.setText("Last checked: " + strftime("%Y-%m-%d %H:%M:%S", localtime()))
return
class TDRWindow(QtWidgets.QWidget):
updated = QtCore.pyqtSignal()
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.td = []
self.distance_axis = []
self.step_response = []
self.step_response_Z = []
self.setWindowTitle("TDR")
2019-10-02 08:07:38 +00:00
self.setWindowIcon(self.app.icon)
2019-09-21 11:13:33 +00:00
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
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())
# Lots of cable types added by Larry Goga, AE5CZ
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)
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)
self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82)
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)
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)
self.tdr_velocity_dropdown.addItem("RG316 (0.695)", 0.695)
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)
self.tdr_velocity_dropdown.addItem("LMR400UF (0.83)", 0.83)
self.tdr_velocity_dropdown.addItem("Davis Bury-FLEX (0.82)", 0.82)
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
# TODO: Let the user select whether to use high or low resolution TDR?
FFT_POINTS = 2**14
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")
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
self.td = np.abs(np.fft.ifft(windowed_s11, FFT_POINTS))
step = np.ones(FFT_POINTS)
self.step_response = signal.convolve(self.td, step)
self.step_response_Z = 50 * (1 + self.step_response) / (1 - self.step_response)
time_axis = np.linspace(0, 1/step_size, FFT_POINTS)
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)
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.updated.emit()
class SweepSettingsWindow(QtWidgets.QWidget):
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.setWindowTitle("Sweep settings")
2019-10-02 08:07:38 +00:00
self.setWindowIcon(self.app.icon)
2019-09-21 11:13:33 +00:00
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
settings_box = QtWidgets.QGroupBox("Settings")
settings_layout = QtWidgets.QFormLayout(settings_box)
self.single_sweep_radiobutton = QtWidgets.QRadioButton("Single sweep")
self.continuous_sweep_radiobutton = QtWidgets.QRadioButton("Continuous sweep")
self.averaged_sweep_radiobutton = QtWidgets.QRadioButton("Averaged sweep")
settings_layout.addWidget(self.single_sweep_radiobutton)
self.single_sweep_radiobutton.setChecked(True)
settings_layout.addWidget(self.continuous_sweep_radiobutton)
settings_layout.addWidget(self.averaged_sweep_radiobutton)
self.averages = QtWidgets.QLineEdit("3")
self.truncates = QtWidgets.QLineEdit("0")
settings_layout.addRow("Number of measurements to average", self.averages)
settings_layout.addRow("Number to discard", self.truncates)
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."))
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)
layout.addWidget(settings_box)
band_sweep_box = QtWidgets.QGroupBox("Sweep band")
band_sweep_layout = QtWidgets.QFormLayout(band_sweep_box)
self.band_list = QtWidgets.QComboBox()
self.band_list.setModel(self.app.bands)
self.band_list.currentIndexChanged.connect(self.updateCurrentBand)
band_sweep_layout.addRow("Select band", self.band_list)
self.band_pad_group = QtWidgets.QButtonGroup()
self.band_pad_0 = QtWidgets.QRadioButton("None")
self.band_pad_10 = QtWidgets.QRadioButton("10%")
self.band_pad_25 = QtWidgets.QRadioButton("25%")
self.band_pad_100 = QtWidgets.QRadioButton("100%")
self.band_pad_0.setChecked(True)
self.band_pad_group.addButton(self.band_pad_0)
self.band_pad_group.addButton(self.band_pad_10)
self.band_pad_group.addButton(self.band_pad_25)
self.band_pad_group.addButton(self.band_pad_100)
self.band_pad_group.buttonClicked.connect(self.updateCurrentBand)
band_sweep_layout.addRow("Pad band limits", self.band_pad_0)
band_sweep_layout.addRow("", self.band_pad_10)
band_sweep_layout.addRow("", self.band_pad_25)
band_sweep_layout.addRow("", self.band_pad_100)
self.band_limit_label = QtWidgets.QLabel()
band_sweep_layout.addRow(self.band_limit_label)
btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep")
btn_set_band_sweep.clicked.connect(self.setBandSweep)
band_sweep_layout.addRow(btn_set_band_sweep)
self.updateCurrentBand()
layout.addWidget(band_sweep_box)
def updateCurrentBand(self):
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
if self.band_pad_10.isChecked():
padding = 10
elif self.band_pad_25.isChecked():
padding = 25
elif self.band_pad_100.isChecked():
padding = 100
else:
padding = 0
if padding > 0:
span = stop - start
start -= round(span * padding / 100)
start = max(1, start)
stop += round(span * padding / 100)
self.band_limit_label.setText("Sweep span: " + RFTools.formatShortFrequency(start) + " to " +
RFTools.formatShortFrequency(stop))
def setBandSweep(self):
index_start = self.band_list.model().index(self.band_list.currentIndex(), 1)
index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2)
start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value())
stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value())
if self.band_pad_10.isChecked():
padding = 10
elif self.band_pad_25.isChecked():
padding = 25
elif self.band_pad_100.isChecked():
padding = 100
else:
padding = 0
if padding > 0:
span = stop - start
start -= round(span * padding / 100)
2019-10-04 10:51:20 +00:00
start = max(1, start)
stop += round(span * padding / 100)
self.app.sweepStartInput.setText(RFTools.formatFixedFrequency(start))
self.app.sweepEndInput.setText(RFTools.formatFixedFrequency(stop))
self.app.sweepEndInput.textEdited.emit(self.app.sweepEndInput.text())
def updateAveraging(self):
self.app.worker.setAveraging(self.averaged_sweep_radiobutton.isChecked(),
self.averages.text(),
self.truncates.text())
class BandsWindow(QtWidgets.QWidget):
def __init__(self, app):
super().__init__()
self.app: NanoVNASaver = app
self.setWindowTitle("Manage bands")
2019-10-02 08:07:38 +00:00
self.setWindowIcon(self.app.icon)
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()
class BandsModel(QtCore.QAbstractTableModel):
bands: List[Tuple[str, int, int]] = []
enabled = False
2019-09-27 08:56:09 +00:00
color = QtGui.QColor(128, 128, 128, 48)
# These bands correspond broadly to the Danish Amateur Radio allocation
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"]
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)
stored_bands: List[str] = self.settings.value("bands", self.default_bands)
if stored_bands:
for b in stored_bands:
(name, start, end) = b.split(";")
self.bands.append((name, int(start), int(end)))
def saveSettings(self):
stored_bands = []
for b in self.bands:
stored_bands.append(b[0] + ";" + str(b[1]) + ";" + str(b[2]))
self.settings.setValue("bands", stored_bands)
self.settings.sync()
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()
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:
return QtCore.QVariant(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
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)
self.saveSettings()
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()
self.saveSettings()
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
2019-10-04 10:51:20 +00:00
class AnalysisWindow(QtWidgets.QWidget):
analyses = []
analysis: Analysis = None
def __init__(self, app):
super().__init__()
self.app: NanoVNASaver = app
self.setWindowTitle("Sweep analysis")
self.setWindowIcon(self.app.icon)
#self.setMinimumSize(400, 600)
#self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
2019-10-04 10:51:20 +00:00
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
select_analysis_box = QtWidgets.QGroupBox("Select analysis")
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
self.analysis_list = QtWidgets.QComboBox()
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app))
self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app))
2019-10-09 14:31:04 +00:00
self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app))
2019-11-02 21:40:54 +00:00
# self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
self.analysis_list.addItem("Peak search", SimplePeakSearchAnalysis(self.app))
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
select_analysis_layout.addRow("Analysis type", self.analysis_list)
self.analysis_list.currentIndexChanged.connect(self.updateSelection)
2019-10-04 10:51:20 +00:00
btn_run_analysis = QtWidgets.QPushButton("Run analysis")
btn_run_analysis.clicked.connect(self.runAnalysis)
select_analysis_layout.addRow(btn_run_analysis)
self.checkbox_run_automatically = QtWidgets.QCheckBox("Run automatically")
self.checkbox_run_automatically.stateChanged.connect(self.toggleAutomaticRun)
select_analysis_layout.addRow(self.checkbox_run_automatically)
2019-10-04 10:51:20 +00:00
analysis_box = QtWidgets.QGroupBox("Analysis")
analysis_box.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
2019-10-04 10:51:20 +00:00
self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box)
self.analysis_layout.setContentsMargins(0, 0, 0, 0)
2019-10-04 10:51:20 +00:00
layout.addWidget(select_analysis_box)
layout.addWidget(analysis_box)
self.updateSelection()
2019-10-04 10:51:20 +00:00
def runAnalysis(self):
if self.analysis is not None:
self.analysis.runAnalysis()
def updateSelection(self):
self.analysis = self.analysis_list.currentData()
old_item = self.analysis_layout.itemAt(0)
if old_item is not None:
old_widget = self.analysis_layout.itemAt(0).widget()
self.analysis_layout.replaceWidget(old_widget, self.analysis.widget())
old_widget.hide()
else:
self.analysis_layout.addWidget(self.analysis.widget())
self.analysis.widget().show()
self.update()
def toggleAutomaticRun(self, state: QtCore.Qt.CheckState):
if state == QtCore.Qt.Checked:
self.analysis_list.setDisabled(True)
self.app.dataAvailable.connect(self.runAnalysis)
else:
2019-11-03 11:56:37 +00:00
self.analysis_list.setDisabled(False)
self.app.dataAvailable.disconnect(self.runAnalysis)
class MarkerSettingsWindow(QtWidgets.QWidget):
exampleData11 = [Datapoint(123000000, 0, 0),
Datapoint(123500000, 0.9, -0.1),
Datapoint(124000000, 0, 0)]
exampleData21 = [Datapoint(123000000, 0, 0),
Datapoint(123456000, -0.3, 0.5),
Datapoint(124000000, 0, 0)]
fieldList = {"actualfreq": "Actual frequency",
"impedance": "Impedance",
"admittance": "Admittance",
"serr": "Series R",
"serlc": "Series equivalent L/C",
"serl": "Series equivalent L",
"serc": "Series equivalent C",
"parr": "Parallel R",
"parlc": "Parallel equivalent L/C",
"parl": "Parallel equivalent L",
"parc": "Parallel equivalent C",
"vswr": "VSWR",
"returnloss": "Return loss",
"s11q": "S11 Quality factor",
"s11phase": "S11 Phase",
"s21gain": "S21 Gain",
"s21phase": "S21 Phase",
}
defaultValue = ["actualfreq",
"impedance",
"serl",
"serc",
"parr",
"parlc",
"vswr",
"returnloss",
"s11q",
"s11phase",
"s21gain",
"s21phase"
]
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.setWindowTitle("Marker settings")
self.setWindowIcon(self.app.icon)
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
if len(self.app.markers) > 0:
color = self.app.markers[0].color
else:
color = self.app.default_marker_colors[0]
self.exampleMarker = Marker("Example marker", initialColor=color, frequency="123456000")
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
settings_group_box = QtWidgets.QGroupBox("Settings")
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
settings_group_box_layout.addRow(self.checkboxColouredMarker)
fields_group_box = QtWidgets.QGroupBox("Displayed data")
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
self.savedFieldSelection = self.app.settings.value("MarkerFields", defaultValue=self.defaultValue)
if self.savedFieldSelection == "":
self.savedFieldSelection = []
self.currentFieldSelection = self.savedFieldSelection.copy()
self.fieldSelectionView = QtWidgets.QListView()
self.model = QtGui.QStandardItemModel()
for field in self.fieldList:
item = QtGui.QStandardItem(self.fieldList[field])
item.setData(field)
item.setCheckable(True)
item.setEditable(False)
if field in self.currentFieldSelection:
item.setCheckState(QtCore.Qt.Checked)
self.model.appendRow(item)
self.fieldSelectionView.setModel(self.model)
self.model.itemChanged.connect(self.updateField)
fields_group_box_layout.addRow(self.fieldSelectionView)
layout.addWidget(settings_group_box)
layout.addWidget(fields_group_box)
layout.addWidget(self.exampleMarker.getGroupBox())
btn_layout = QtWidgets.QHBoxLayout()
layout.addLayout(btn_layout)
btn_ok = QtWidgets.QPushButton("OK")
btn_apply = QtWidgets.QPushButton("Apply")
btn_default = QtWidgets.QPushButton("Defaults")
btn_cancel = QtWidgets.QPushButton("Cancel")
btn_ok.clicked.connect(self.okButtonClick)
btn_apply.clicked.connect(self.applyButtonClick)
btn_default.clicked.connect(self.defaultButtonClick)
btn_cancel.clicked.connect(self.cancelButtonClick)
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_apply)
btn_layout.addWidget(btn_default)
btn_layout.addWidget(btn_cancel)
self.updateMarker()
for m in self.app.markers:
m.setFieldSelection(self.currentFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def updateMarker(self):
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
self.exampleMarker.findLocation(self.exampleData11)
self.exampleMarker.resetLabels()
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
def updateField(self, field: QtGui.QStandardItem):
if field.checkState() == QtCore.Qt.Checked:
if not field.data() in self.currentFieldSelection:
self.currentFieldSelection = []
for i in range(self.model.rowCount()):
field = self.model.item(i, 0)
if field.checkState() == QtCore.Qt.Checked:
self.currentFieldSelection.append(field.data())
else:
if field.data() in self.currentFieldSelection:
self.currentFieldSelection.remove(field.data())
self.updateMarker()
def applyButtonClick(self):
self.savedFieldSelection = self.currentFieldSelection.copy()
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
for m in self.app.markers:
m.setFieldSelection(self.savedFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def okButtonClick(self):
self.applyButtonClick()
self.close()
def cancelButtonClick(self):
self.currentFieldSelection = self.savedFieldSelection.copy()
self.resetModel()
self.updateMarker()
self.close()
def defaultButtonClick(self):
self.currentFieldSelection = self.defaultValue.copy()
self.resetModel()
self.updateMarker()
def resetModel(self):
self.model = QtGui.QStandardItemModel()
for field in self.fieldList:
item = QtGui.QStandardItem(self.fieldList[field])
item.setData(field)
item.setCheckable(True)
item.setEditable(False)
if field in self.currentFieldSelection:
item.setCheckState(QtCore.Qt.Checked)
self.model.appendRow(item)
self.fieldSelectionView.setModel(self.model)