kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Refactored Serial Control Widget
rodzic
2d96aaa2f3
commit
00293fe204
|
@ -17,460 +17,73 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from time import sleep, strftime, localtime
|
||||
from time import sleep
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
from .Windows import (
|
||||
AboutWindow, AnalysisWindow, CalibrationWindow,
|
||||
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
|
||||
TDRWindow, FilesWindow
|
||||
)
|
||||
from .Controls import MarkerControl, SweepControl
|
||||
from .Formatting import format_frequency, format_vswr, format_gain
|
||||
from .Hardware.Hardware import Interface, get_interfaces, get_VNA
|
||||
from .Hardware.VNA import VNA
|
||||
from .RFTools import corr_att_data
|
||||
from .Charts.Chart import Chart
|
||||
from .Charts import (
|
||||
CapacitanceChart,
|
||||
CombinedLogMagChart, GroupDelayChart, InductanceChart,
|
||||
LogMagChart, PhaseChart,
|
||||
MagnitudeChart, MagnitudeZChart, MagnitudeZShuntChart, MagnitudeZSeriesChart,
|
||||
QualityFactorChart, VSWRChart, PermeabilityChart, PolarChart,
|
||||
RealImaginaryChart, RealImaginaryShuntChart, RealImaginarySeriesChart,
|
||||
SmithChart, SParameterChart, TDRChart,
|
||||
)
|
||||
from .Calibration import Calibration
|
||||
from .Marker import Marker, DeltaMarker
|
||||
from .SweepWorker import SweepWorker
|
||||
from .Settings import BandsModel, Sweep
|
||||
from .Touchstone import Touchstone
|
||||
from .About import VERSION
|
||||
from NanoVNASaver.Hardware.Hardware import Interface, get_interfaces, get_VNA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SerialControl(QtWidgets.QGroupBox):
|
||||
updated = QtCore.pyqtSignal(object)
|
||||
|
||||
class NanoVNASaver(QtWidgets.QWidget):
|
||||
version = VERSION
|
||||
dataAvailable = QtCore.pyqtSignal()
|
||||
scaleFactor = 1
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, app: QtWidgets.QWidget, title: str="Serial port control"):
|
||||
super().__init__()
|
||||
self.s21att = 0.0
|
||||
if getattr(sys, 'frozen', False):
|
||||
logger.debug("Running from pyinstaller bundle")
|
||||
self.icon = QtGui.QIcon(f"{sys._MEIPASS}/icon_48x48.png") # pylint: disable=no-member
|
||||
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(f"Settings: {self.settings.fileName()}")
|
||||
self.threadpool = QtCore.QThreadPool()
|
||||
self.sweep = Sweep()
|
||||
self.worker = SweepWorker(self)
|
||||
self.app = app
|
||||
self.interface = Interface("serial", "none")
|
||||
self.setTitle(title)
|
||||
self.setMaximumWidth(240)
|
||||
|
||||
self.worker.signals.updated.connect(self.dataUpdated)
|
||||
self.worker.signals.finished.connect(self.sweepFinished)
|
||||
self.worker.signals.sweepError.connect(self.showSweepError)
|
||||
self.worker.signals.fatalSweepError.connect(self.showFatalSweepError)
|
||||
|
||||
self.markers = []
|
||||
|
||||
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)
|
||||
|
||||
self.sweep_control = SweepControl(self)
|
||||
self.marker_control = MarkerControl(self)
|
||||
|
||||
self.bands = BandsModel()
|
||||
|
||||
self.interface = Interface("serial", "None")
|
||||
try:
|
||||
self.vna = VNA(self.interface)
|
||||
except IOError as exc:
|
||||
self.showError(f"{exc}\n\nPlease try reconnect")
|
||||
|
||||
self.dataLock = threading.Lock()
|
||||
self.data = Touchstone()
|
||||
self.ref_data = Touchstone()
|
||||
|
||||
self.sweepSource = ""
|
||||
self.referenceSource = ""
|
||||
|
||||
self.calibration = Calibration()
|
||||
|
||||
logger.debug("Building user interface")
|
||||
|
||||
self.baseTitle = f"NanoVNA Saver {NanoVNASaver.version}"
|
||||
self.updateTitle()
|
||||
layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
self.charts = {
|
||||
"s11": OrderedDict((
|
||||
("capacitance", CapacitanceChart("S11 Serial C")),
|
||||
("group_delay", GroupDelayChart("S11 Group Delay")),
|
||||
("inductance", InductanceChart("S11 Serial L")),
|
||||
("log_mag", LogMagChart("S11 Return Loss")),
|
||||
("magnitude", MagnitudeChart("|S11|")),
|
||||
("magnitude_z", MagnitudeZChart("S11 |Z|")),
|
||||
("permeability", PermeabilityChart(
|
||||
"S11 R/\N{GREEK SMALL LETTER OMEGA} &"
|
||||
" X/\N{GREEK SMALL LETTER OMEGA}")),
|
||||
("phase", PhaseChart("S11 Phase")),
|
||||
("q_factor", QualityFactorChart("S11 Quality Factor")),
|
||||
("real_imag", RealImaginaryChart("S11 R+jX")),
|
||||
("smith", SmithChart("S11 Smith Chart")),
|
||||
("s_parameter", SParameterChart("S11 Real/Imaginary")),
|
||||
("vswr", VSWRChart("S11 VSWR")),
|
||||
)),
|
||||
"s21": OrderedDict((
|
||||
("group_delay", GroupDelayChart("S21 Group Delay",
|
||||
reflective=False)),
|
||||
("log_mag", LogMagChart("S21 Gain")),
|
||||
("magnitude", MagnitudeChart("|S21|")),
|
||||
("magnitude_z_shunt", MagnitudeZShuntChart("S21 |Z| shunt")),
|
||||
("magnitude_z_series", MagnitudeZSeriesChart("S21 |Z| series")),
|
||||
("real_imag_shunt", RealImaginaryShuntChart("S21 R+jX shunt")),
|
||||
("real_imag_series", RealImaginarySeriesChart("S21 R+jX series")),
|
||||
("phase", PhaseChart("S21 Phase")),
|
||||
("polar", PolarChart("S21 Polar Plot")),
|
||||
("s_parameter", SParameterChart("S21 Real/Imaginary")),
|
||||
)),
|
||||
"combined": OrderedDict((
|
||||
("log_mag", CombinedLogMagChart("S11 & S21 LogMag")),
|
||||
)),
|
||||
}
|
||||
self.tdr_chart = TDRChart("TDR")
|
||||
self.tdr_mainwindow_chart = TDRChart("TDR")
|
||||
|
||||
# List of all the S11 charts, for selecting
|
||||
self.s11charts = list(self.charts["s11"].values())
|
||||
|
||||
# List of all the S21 charts, for selecting
|
||||
self.s21charts = list(self.charts["s21"].values())
|
||||
|
||||
# List of all charts that use both S11 and S21
|
||||
self.combinedCharts = list(self.charts["combined"].values())
|
||||
|
||||
# List of all charts that can be selected for display
|
||||
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()
|
||||
|
||||
QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Q"), self, self.close)
|
||||
|
||||
###############################################################
|
||||
# Create main layout
|
||||
###############################################################
|
||||
|
||||
left_column = QtWidgets.QVBoxLayout()
|
||||
right_column = QtWidgets.QVBoxLayout()
|
||||
right_column.addLayout(self.charts_layout)
|
||||
self.marker_frame.setHidden(
|
||||
not self.settings.value("MarkersVisible", True, bool))
|
||||
chart_widget = QtWidgets.QWidget()
|
||||
chart_widget.setLayout(right_column)
|
||||
self.splitter = QtWidgets.QSplitter()
|
||||
self.splitter.addWidget(self.marker_frame)
|
||||
self.splitter.addWidget(chart_widget)
|
||||
|
||||
try:
|
||||
self.splitter.restoreState(self.settings.value("SplitterSizes"))
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
layout.addLayout(left_column)
|
||||
layout.addWidget(self.splitter, 2)
|
||||
|
||||
###############################################################
|
||||
# Windows
|
||||
###############################################################
|
||||
|
||||
self.windows = {
|
||||
"about": AboutWindow(self),
|
||||
# "analysis": AnalysisWindow(self),
|
||||
"calibration": CalibrationWindow(self),
|
||||
"device_settings": DeviceSettingsWindow(self),
|
||||
"file": FilesWindow(self),
|
||||
"sweep_settings": SweepSettingsWindow(self),
|
||||
"setup": DisplaySettingsWindow(self),
|
||||
"tdr": TDRWindow(self),
|
||||
}
|
||||
|
||||
###############################################################
|
||||
# Sweep control
|
||||
###############################################################
|
||||
|
||||
left_column.addWidget(self.sweep_control)
|
||||
|
||||
# ###############################################################
|
||||
# Marker control
|
||||
###############################################################
|
||||
|
||||
left_column.addWidget(self.marker_control)
|
||||
|
||||
for c in self.subscribing_charts:
|
||||
c.setMarkers(self.markers)
|
||||
c.setBands(self.bands)
|
||||
|
||||
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.get_data_layout())
|
||||
|
||||
scroll2 = QtWidgets.QScrollArea()
|
||||
#scroll2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
scroll2.setWidgetResizable(True)
|
||||
scroll2.setVisible(True)
|
||||
|
||||
widget2 = QtWidgets.QWidget()
|
||||
widget2.setLayout(self.marker_data_layout)
|
||||
scroll2.setWidget(widget2)
|
||||
self.marker_column.addWidget(scroll2)
|
||||
|
||||
# init delta marker (but assume only one marker exists)
|
||||
self.delta_marker = DeltaMarker("Delta Marker 2 - Marker 1")
|
||||
self.delta_marker_layout = self.delta_marker.get_data_layout()
|
||||
self.delta_marker_layout.hide()
|
||||
self.marker_column.addWidget(self.delta_marker_layout)
|
||||
|
||||
###############################################################
|
||||
# Statistics/analysis
|
||||
###############################################################
|
||||
|
||||
s11_control_box = QtWidgets.QGroupBox()
|
||||
s11_control_box.setTitle("S11")
|
||||
s11_control_layout = QtWidgets.QFormLayout()
|
||||
s11_control_layout.setVerticalSpacing(0)
|
||||
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_layout.setVerticalSpacing(0)
|
||||
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)
|
||||
|
||||
self.windows["analysis"] = AnalysisWindow(self)
|
||||
btn_show_analysis = QtWidgets.QPushButton("Analysis ...")
|
||||
btn_show_analysis.setMinimumHeight(20)
|
||||
btn_show_analysis.clicked.connect(
|
||||
lambda: self.display_window("analysis"))
|
||||
self.marker_column.addWidget(btn_show_analysis)
|
||||
|
||||
###############################################################
|
||||
# TDR
|
||||
###############################################################
|
||||
|
||||
self.tdr_chart.tdrWindow = self.windows["tdr"]
|
||||
self.tdr_mainwindow_chart.tdrWindow = self.windows["tdr"]
|
||||
self.windows["tdr"].updated.connect(self.tdr_chart.update)
|
||||
self.windows["tdr"].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(240)
|
||||
|
||||
self.tdr_result_label = QtWidgets.QLabel()
|
||||
self.tdr_result_label.setMinimumHeight(20)
|
||||
tdr_control_layout.addRow(
|
||||
"Estimated cable length:", self.tdr_result_label)
|
||||
|
||||
self.tdr_button = QtWidgets.QPushButton(
|
||||
"Time Domain Reflectometry ...")
|
||||
self.tdr_button.setMinimumHeight(20)
|
||||
self.tdr_button.clicked.connect(lambda: self.display_window("tdr"))
|
||||
|
||||
tdr_control_layout.addRow(self.tdr_button)
|
||||
|
||||
left_column.addWidget(tdr_control_box)
|
||||
|
||||
###############################################################
|
||||
# Spacer
|
||||
###############################################################
|
||||
|
||||
left_column.addSpacerItem(
|
||||
QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed,
|
||||
QtWidgets.QSizePolicy.Expanding))
|
||||
|
||||
###############################################################
|
||||
# Reference control
|
||||
###############################################################
|
||||
|
||||
reference_control_box = QtWidgets.QGroupBox()
|
||||
reference_control_box.setMaximumWidth(240)
|
||||
reference_control_box.setTitle("Reference sweep")
|
||||
reference_control_layout = QtWidgets.QFormLayout(reference_control_box)
|
||||
|
||||
btn_set_reference = QtWidgets.QPushButton("Set current as reference")
|
||||
btn_set_reference.setMinimumHeight(20)
|
||||
btn_set_reference.clicked.connect(self.setReference)
|
||||
self.btnResetReference = QtWidgets.QPushButton("Reset reference")
|
||||
self.btnResetReference.setMinimumHeight(20)
|
||||
self.btnResetReference.clicked.connect(self.resetReference)
|
||||
self.btnResetReference.setDisabled(True)
|
||||
|
||||
reference_control_layout.addRow(btn_set_reference)
|
||||
reference_control_layout.addRow(self.btnResetReference)
|
||||
|
||||
left_column.addWidget(reference_control_box)
|
||||
|
||||
###############################################################
|
||||
# Serial control
|
||||
###############################################################
|
||||
|
||||
serial_control_box = QtWidgets.QGroupBox()
|
||||
serial_control_box.setMaximumWidth(240)
|
||||
serial_control_box.setTitle("Serial port control")
|
||||
serial_control_layout = QtWidgets.QFormLayout(serial_control_box)
|
||||
self.serialPortInput = QtWidgets.QComboBox()
|
||||
self.serialPortInput.setMinimumHeight(20)
|
||||
control_layout = QtWidgets.QFormLayout(self)
|
||||
self.inp_port = QtWidgets.QComboBox()
|
||||
self.inp_port.setMinimumHeight(20)
|
||||
self.rescanSerialPort()
|
||||
self.serialPortInput.setEditable(True)
|
||||
btn_rescan_serial_port = QtWidgets.QPushButton("Rescan")
|
||||
btn_rescan_serial_port.setMinimumHeight(20)
|
||||
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(QtWidgets.QLabel("Port"), stretch=0)
|
||||
serial_port_input_layout.addWidget(self.serialPortInput, stretch=1)
|
||||
serial_port_input_layout.addWidget(btn_rescan_serial_port, stretch=0)
|
||||
serial_control_layout.addRow(serial_port_input_layout)
|
||||
self.inp_port.setEditable(True)
|
||||
self.btn_rescan = QtWidgets.QPushButton("Rescan")
|
||||
self.btn_rescan.setMinimumHeight(20)
|
||||
self.btn_rescan.setFixedWidth(60)
|
||||
self.btn_rescan.clicked.connect(self.rescanSerialPort)
|
||||
intput_layout = QtWidgets.QHBoxLayout()
|
||||
intput_layout.addWidget(QtWidgets.QLabel("Port"), stretch=0)
|
||||
intput_layout.addWidget(self.inp_port, stretch=1)
|
||||
intput_layout.addWidget(self.btn_rescan, stretch=0)
|
||||
control_layout.addRow(intput_layout)
|
||||
|
||||
serial_button_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.btnSerialToggle = QtWidgets.QPushButton("Connect to device")
|
||||
self.btnSerialToggle.setMinimumHeight(20)
|
||||
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
||||
serial_button_layout.addWidget(self.btnSerialToggle, stretch=1)
|
||||
self.btn_toggle = QtWidgets.QPushButton("Connect to device")
|
||||
self.btn_toggle.setMinimumHeight(20)
|
||||
self.btn_toggle.clicked.connect(self.serialButtonClick)
|
||||
button_layout.addWidget(self.btn_toggle, stretch=1)
|
||||
|
||||
self.btnDeviceSettings = QtWidgets.QPushButton("Manage")
|
||||
self.btnDeviceSettings.setMinimumHeight(20)
|
||||
self.btnDeviceSettings.setFixedWidth(60)
|
||||
self.btnDeviceSettings.clicked.connect(
|
||||
lambda: self.display_window("device_settings"))
|
||||
serial_button_layout.addWidget(self.btnDeviceSettings, stretch=0)
|
||||
serial_control_layout.addRow(serial_button_layout)
|
||||
left_column.addWidget(serial_control_box)
|
||||
self.btn_settings = QtWidgets.QPushButton("Manage")
|
||||
self.btn_settings.setMinimumHeight(20)
|
||||
self.btn_settings.setFixedWidth(60)
|
||||
self.btn_settings.clicked.connect(
|
||||
lambda: self.app.display_window("device_settings"))
|
||||
|
||||
###############################################################
|
||||
# Calibration
|
||||
###############################################################
|
||||
|
||||
btnOpenCalibrationWindow = QtWidgets.QPushButton("Calibration ...")
|
||||
btnOpenCalibrationWindow.setMinimumHeight(20)
|
||||
self.calibrationWindow = CalibrationWindow(self)
|
||||
btnOpenCalibrationWindow.clicked.connect(
|
||||
lambda: self.display_window("calibration"))
|
||||
|
||||
###############################################################
|
||||
# Display setup
|
||||
###############################################################
|
||||
|
||||
btn_display_setup = QtWidgets.QPushButton("Display setup ...")
|
||||
btn_display_setup.setMinimumHeight(20)
|
||||
btn_display_setup.setMaximumWidth(240)
|
||||
btn_display_setup.clicked.connect(
|
||||
lambda: self.display_window("setup"))
|
||||
|
||||
btn_about = QtWidgets.QPushButton("About ...")
|
||||
btn_about.setMinimumHeight(20)
|
||||
btn_about.setMaximumWidth(240)
|
||||
|
||||
btn_about.clicked.connect(
|
||||
lambda: self.display_window("about"))
|
||||
|
||||
|
||||
btn_open_file_window = QtWidgets.QPushButton("Files")
|
||||
btn_open_file_window.setMinimumHeight(20)
|
||||
btn_open_file_window.setMaximumWidth(240)
|
||||
|
||||
btn_open_file_window.clicked.connect(
|
||||
lambda: self.display_window("file"))
|
||||
|
||||
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)
|
||||
|
||||
logger.debug("Finished building interface")
|
||||
button_layout.addWidget(self.btn_settings, stretch=0)
|
||||
control_layout.addRow(button_layout)
|
||||
|
||||
def rescanSerialPort(self):
|
||||
self.serialPortInput.clear()
|
||||
self.inp_port.clear()
|
||||
for iface in get_interfaces():
|
||||
self.serialPortInput.insertItem(1, f"{iface}", iface)
|
||||
self.serialPortInput.repaint()
|
||||
self.inp_port.insertItem(1, f"{iface}", iface)
|
||||
self.inp_port.repaint()
|
||||
|
||||
def serialButtonClick(self):
|
||||
if not self.vna.connected():
|
||||
if not self.app.vna.connected():
|
||||
self.connect_device()
|
||||
else:
|
||||
self.disconnect_device()
|
||||
|
||||
def connect_device(self):
|
||||
if not self.interface:
|
||||
return
|
||||
with self.interface.lock:
|
||||
self.interface = self.serialPortInput.currentData()
|
||||
self.interface = self.inp_port.currentData()
|
||||
logger.info("Connection %s", self.interface)
|
||||
try:
|
||||
self.interface.open()
|
||||
|
||||
except (IOError, AttributeError) as exc:
|
||||
logger.error("Tried to open %s and failed: %s",
|
||||
self.interface, exc)
|
||||
|
@ -481,282 +94,43 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
self.interface.timeout = 0.05
|
||||
sleep(0.1)
|
||||
try:
|
||||
self.vna = get_VNA(self.interface)
|
||||
self.app.vna = get_VNA(self.interface)
|
||||
except IOError as exc:
|
||||
logger.error("Unable to connect to VNA: %s", exc)
|
||||
|
||||
self.vna.validateInput = self.settings.value(
|
||||
self.app.vna.validateInput = self.app.settings.value(
|
||||
"SerialInputValidation", True, bool)
|
||||
|
||||
# connected
|
||||
self.btnSerialToggle.setText("Disconnect")
|
||||
self.btnSerialToggle.repaint()
|
||||
self.btn_toggle.setText("Disconnect")
|
||||
self.btn_toggle.repaint()
|
||||
|
||||
frequencies = self.vna.readFrequencies()
|
||||
frequencies = self.app.vna.readFrequencies()
|
||||
if not frequencies:
|
||||
logger.warning("No frequencies read")
|
||||
return
|
||||
logger.info("Read starting frequency %s and end frequency %s",
|
||||
frequencies[0], frequencies[-1])
|
||||
self.sweep_control.set_start(frequencies[0])
|
||||
self.app.sweep_control.set_start(frequencies[0])
|
||||
if frequencies[0] < frequencies[-1]:
|
||||
self.sweep_control.set_end(frequencies[-1])
|
||||
self.app.sweep_control.set_end(frequencies[-1])
|
||||
else:
|
||||
self.sweep_control.set_end(
|
||||
self.app.sweep_control.set_end(
|
||||
frequencies[0] +
|
||||
self.vna.datapoints * self.sweep_control.get_segments())
|
||||
self.app.vna.datapoints * self.app.sweep_control.get_segments())
|
||||
|
||||
self.sweep_control.set_segments(1) # speed up things
|
||||
self.sweep_control.update_center_span()
|
||||
self.sweep_control.update_step_size()
|
||||
self.app.sweep_control.set_segments(1) # speed up things
|
||||
self.app.sweep_control.update_center_span()
|
||||
self.app.sweep_control.update_step_size()
|
||||
|
||||
self.windows["sweep_settings"].vna_connected()
|
||||
self.app.windows["sweep_settings"].vna_connected()
|
||||
|
||||
logger.debug("Starting initial sweep")
|
||||
self.sweep_start()
|
||||
self.app.sweep_start()
|
||||
|
||||
def disconnect_device(self):
|
||||
with self.interface.lock:
|
||||
logger.info("Closing connection to %s", self.interface)
|
||||
self.interface.close()
|
||||
self.btnSerialToggle.setText("Connect to device")
|
||||
self.btnSerialToggle.repaint()
|
||||
|
||||
def sweep_start(self):
|
||||
# Run the device data update
|
||||
if not self.vna.connected():
|
||||
return
|
||||
self.worker.stopped = False
|
||||
|
||||
self.sweep_control.progress_bar.setValue(0)
|
||||
self.sweep_control.btn_start.setDisabled(True)
|
||||
self.sweep_control.btn_stop.setDisabled(False)
|
||||
self.sweep_control.toggle_settings(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("")
|
||||
|
||||
self.settings.setValue("Segments", self.sweep_control.get_segments())
|
||||
|
||||
logger.debug("Starting worker thread")
|
||||
self.threadpool.start(self.worker)
|
||||
|
||||
def sweep_stop(self):
|
||||
self.worker.stopped = True
|
||||
|
||||
def saveData(self, data, data21, source=None):
|
||||
with self.dataLock:
|
||||
self.data.s11 = data
|
||||
self.data.s21 = data21
|
||||
if self.s21att > 0:
|
||||
self.data.s21 = corr_att_data(self.data.s21, self.s21att)
|
||||
if source is not None:
|
||||
self.sweepSource = source
|
||||
else:
|
||||
self.sweepSource = (
|
||||
f"{self.sweep.properties.name}"
|
||||
f" {strftime('%Y-%m-%d %H:%M:%S', localtime())}"
|
||||
).lstrip()
|
||||
|
||||
def markerUpdated(self, marker: Marker):
|
||||
with self.dataLock:
|
||||
marker.findLocation(self.data.s11)
|
||||
marker.resetLabels()
|
||||
marker.updateLabels(self.data.s11, self.data.s21)
|
||||
for c in self.subscribing_charts:
|
||||
c.update()
|
||||
if Marker.count() >= 2 and not self.delta_marker_layout.isHidden():
|
||||
self.delta_marker.set_markers(self.markers[0], self.markers[1])
|
||||
self.delta_marker.resetLabels()
|
||||
try:
|
||||
self.delta_marker.updateLabels()
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def dataUpdated(self):
|
||||
with self.dataLock:
|
||||
s11 = self.data.s11[:]
|
||||
s21 = self.data.s21[:]
|
||||
|
||||
for m in self.markers:
|
||||
m.resetLabels()
|
||||
m.updateLabels(s11, s21)
|
||||
|
||||
for c in self.s11charts:
|
||||
c.setData(s11)
|
||||
|
||||
for c in self.s21charts:
|
||||
c.setData(s21)
|
||||
|
||||
for c in self.combinedCharts:
|
||||
c.setCombinedData(s11, s21)
|
||||
|
||||
self.sweep_control.progress_bar.setValue(self.worker.percentage)
|
||||
self.windows["tdr"].updateTDR()
|
||||
|
||||
if s11:
|
||||
min_vswr = min(s11, key=lambda data: data.vswr)
|
||||
self.s11_min_swr_label.setText(
|
||||
f"{format_vswr(min_vswr.vswr)} @ {format_frequency(min_vswr.freq)}")
|
||||
self.s11_min_rl_label.setText(format_gain(min_vswr.gain))
|
||||
else:
|
||||
self.s11_min_swr_label.setText("")
|
||||
self.s11_min_rl_label.setText("")
|
||||
|
||||
if s21:
|
||||
min_gain = min(s21, key=lambda data: data.gain)
|
||||
max_gain = max(s21, key=lambda data: data.gain)
|
||||
self.s21_min_gain_label.setText(
|
||||
f"{format_gain(min_gain.gain)}"
|
||||
f" @ {format_frequency(min_gain.freq)}")
|
||||
self.s21_max_gain_label.setText(
|
||||
f"{format_gain(max_gain.gain)}"
|
||||
f" @ {format_frequency(max_gain.freq)}")
|
||||
else:
|
||||
self.s21_min_gain_label.setText("")
|
||||
self.s21_max_gain_label.setText("")
|
||||
|
||||
self.updateTitle()
|
||||
self.dataAvailable.emit()
|
||||
|
||||
def sweepFinished(self):
|
||||
self.sweep_control.progress_bar.setValue(100)
|
||||
self.sweep_control.btn_start.setDisabled(False)
|
||||
self.sweep_control.btn_stop.setDisabled(True)
|
||||
self.sweep_control.toggle_settings(False)
|
||||
|
||||
for marker in self.markers:
|
||||
marker.frequencyInput.textEdited.emit(
|
||||
marker.frequencyInput.text())
|
||||
|
||||
def setReference(self, s11=None, s21=None, source=None):
|
||||
if not s11:
|
||||
with self.dataLock:
|
||||
s11 = self.data.s11[:]
|
||||
s21 = self.data.s21[:]
|
||||
|
||||
self.ref_data.s11 = s11
|
||||
for c in self.s11charts:
|
||||
c.setReference(s11)
|
||||
|
||||
self.ref_data.s21 = s21
|
||||
for c in self.s21charts:
|
||||
c.setReference(s21)
|
||||
|
||||
for c in self.combinedCharts:
|
||||
c.setCombinedReference(s11, s21)
|
||||
|
||||
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):
|
||||
insert = "("
|
||||
if self.sweepSource != "":
|
||||
insert += (
|
||||
f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points"
|
||||
f"{', ' if self.referenceSource else ''}")
|
||||
if self.referenceSource != "":
|
||||
insert += (
|
||||
f"Reference: {self.referenceSource} @"
|
||||
f" {len(self.ref_data.s11)} points")
|
||||
insert += ")"
|
||||
title = f"{self.baseTitle} {insert if insert else ''}"
|
||||
self.setWindowTitle(title)
|
||||
|
||||
def resetReference(self):
|
||||
self.ref_data = Touchstone()
|
||||
self.referenceSource = ""
|
||||
self.updateTitle()
|
||||
for c in self.subscribing_charts:
|
||||
c.resetReference()
|
||||
self.btnResetReference.setDisabled(True)
|
||||
|
||||
def sizeHint(self) -> QtCore.QSize:
|
||||
return QtCore.QSize(1100, 950)
|
||||
|
||||
def display_window(self, name):
|
||||
self.windows[name].show()
|
||||
QtWidgets.QApplication.setActiveWindow(self.windows[name])
|
||||
|
||||
def showError(self, text):
|
||||
QtWidgets.QMessageBox.warning(self, "Error", text)
|
||||
|
||||
def showFatalSweepError(self):
|
||||
self.showError(self.worker.error_message)
|
||||
self.stopSerial()
|
||||
|
||||
def showSweepError(self):
|
||||
self.showError(self.worker.error_message)
|
||||
try:
|
||||
self.vna.flushSerialBuffers() # Remove any left-over data
|
||||
self.vna.reconnect() # try reconnection
|
||||
except IOError:
|
||||
pass
|
||||
self.sweepFinished()
|
||||
|
||||
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)
|
||||
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", Marker.count())
|
||||
for marker in self.markers:
|
||||
marker.update_settings()
|
||||
|
||||
self.settings.setValue("WindowHeight", self.height())
|
||||
self.settings.setValue("WindowWidth", self.width())
|
||||
self.settings.setValue("SplitterSizes", self.splitter.saveState())
|
||||
|
||||
self.settings.sync()
|
||||
self.bands.saveSettings()
|
||||
self.threadpool.waitForDone(2500)
|
||||
a0.accept()
|
||||
sys.exit()
|
||||
|
||||
def changeFont(self, font: QtGui.QFont) -> None:
|
||||
qf_new = QtGui.QFontMetricsF(font)
|
||||
normal_font = QtGui.QFont(font)
|
||||
normal_font.setPointSize(8)
|
||||
qf_normal = QtGui.QFontMetricsF(normal_font)
|
||||
# Characters we would normally display
|
||||
standard_string = "0.123456789 0.123456789 MHz \N{OHM SIGN}"
|
||||
new_width = qf_new.boundingRect(standard_string).width()
|
||||
old_width = qf_normal.boundingRect(standard_string).width()
|
||||
self.scaleFactor = new_width / old_width
|
||||
logger.debug("New font width: %f, normal font: %f, factor: %f",
|
||||
new_width, old_width, self.scaleFactor)
|
||||
# TODO: Update all the fixed widths to account for the scaling
|
||||
for m in self.markers:
|
||||
m.get_data_layout().setFont(font)
|
||||
m.setScale(self.scaleFactor)
|
||||
|
||||
def update_sweep_title(self):
|
||||
for c in self.subscribing_charts:
|
||||
c.setSweepTitle(self.sweep.properties.name)
|
||||
self.btn_toggle.setText("Connect to device")
|
||||
self.btn_toggle.repaint()
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
from .MarkerControl import MarkerControl
|
||||
from .SweepControl import SweepControl
|
||||
from .SerialControl import SerialControl
|
||||
|
|
|
@ -29,9 +29,9 @@ from .Windows import (
|
|||
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
|
||||
TDRWindow, FilesWindow
|
||||
)
|
||||
from .Controls import MarkerControl, SweepControl
|
||||
from .Controls import MarkerControl, SweepControl, SerialControl
|
||||
from .Formatting import format_frequency, format_vswr, format_gain
|
||||
from .Hardware.Hardware import Interface, get_interfaces, get_VNA
|
||||
from .Hardware.Hardware import Interface
|
||||
from .Hardware.VNA import VNA
|
||||
from .RFTools import corr_att_data
|
||||
from .Charts.Chart import Chart
|
||||
|
@ -71,7 +71,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
||||
QtCore.QSettings.UserScope,
|
||||
"NanoVNASaver", "NanoVNASaver")
|
||||
print(f"Settings: {self.settings.fileName()}")
|
||||
logger.info("Settings from: %s", self.settings.fileName())
|
||||
self.threadpool = QtCore.QThreadPool()
|
||||
self.sweep = Sweep()
|
||||
self.worker = SweepWorker(self)
|
||||
|
@ -90,6 +90,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
self.sweep_control = SweepControl(self)
|
||||
self.marker_control = MarkerControl(self)
|
||||
self.serial_control = SerialControl(self)
|
||||
|
||||
self.bands = BandsModel()
|
||||
|
||||
|
@ -178,8 +179,9 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
self.combinedCharts = list(self.charts["combined"].values())
|
||||
|
||||
# List of all charts that can be selected for display
|
||||
self.selectable_charts = self.s11charts + self.s21charts + self.combinedCharts
|
||||
self.selectable_charts.append(self.tdr_mainwindow_chart)
|
||||
self.selectable_charts = (
|
||||
self.s11charts + self.s21charts +
|
||||
self.combinedCharts + [self.tdr_mainwindow_chart, ])
|
||||
|
||||
# List of all charts that subscribe to updates (including duplicates!)
|
||||
self.subscribing_charts = []
|
||||
|
@ -372,39 +374,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
# Serial control
|
||||
###############################################################
|
||||
|
||||
serial_control_box = QtWidgets.QGroupBox()
|
||||
serial_control_box.setMaximumWidth(240)
|
||||
serial_control_box.setTitle("Serial port control")
|
||||
serial_control_layout = QtWidgets.QFormLayout(serial_control_box)
|
||||
self.serialPortInput = QtWidgets.QComboBox()
|
||||
self.serialPortInput.setMinimumHeight(20)
|
||||
self.rescanSerialPort()
|
||||
self.serialPortInput.setEditable(True)
|
||||
btn_rescan_serial_port = QtWidgets.QPushButton("Rescan")
|
||||
btn_rescan_serial_port.setMinimumHeight(20)
|
||||
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(QtWidgets.QLabel("Port"), stretch=0)
|
||||
serial_port_input_layout.addWidget(self.serialPortInput, stretch=1)
|
||||
serial_port_input_layout.addWidget(btn_rescan_serial_port, stretch=0)
|
||||
serial_control_layout.addRow(serial_port_input_layout)
|
||||
|
||||
serial_button_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.btnSerialToggle = QtWidgets.QPushButton("Connect to device")
|
||||
self.btnSerialToggle.setMinimumHeight(20)
|
||||
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
||||
serial_button_layout.addWidget(self.btnSerialToggle, stretch=1)
|
||||
|
||||
self.btnDeviceSettings = QtWidgets.QPushButton("Manage")
|
||||
self.btnDeviceSettings.setMinimumHeight(20)
|
||||
self.btnDeviceSettings.setFixedWidth(60)
|
||||
self.btnDeviceSettings.clicked.connect(
|
||||
lambda: self.display_window("device_settings"))
|
||||
serial_button_layout.addWidget(self.btnDeviceSettings, stretch=0)
|
||||
serial_control_layout.addRow(serial_button_layout)
|
||||
left_column.addWidget(serial_control_box)
|
||||
left_column.addWidget(self.serial_control)
|
||||
|
||||
###############################################################
|
||||
# Calibration
|
||||
|
@ -450,78 +420,6 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
logger.debug("Finished building interface")
|
||||
|
||||
def rescanSerialPort(self):
|
||||
self.serialPortInput.clear()
|
||||
for iface in get_interfaces():
|
||||
self.serialPortInput.insertItem(1, f"{iface}", iface)
|
||||
self.serialPortInput.repaint()
|
||||
|
||||
def serialButtonClick(self):
|
||||
if not self.vna.connected():
|
||||
self.connect_device()
|
||||
else:
|
||||
self.disconnect_device()
|
||||
|
||||
def connect_device(self):
|
||||
if not self.interface:
|
||||
return
|
||||
with self.interface.lock:
|
||||
self.interface = self.serialPortInput.currentData()
|
||||
logger.info("Connection %s", self.interface)
|
||||
try:
|
||||
self.interface.open()
|
||||
|
||||
except (IOError, AttributeError) as exc:
|
||||
logger.error("Tried to open %s and failed: %s",
|
||||
self.interface, exc)
|
||||
return
|
||||
if not self.interface.isOpen():
|
||||
logger.error("Unable to open port %s", self.interface)
|
||||
return
|
||||
self.interface.timeout = 0.05
|
||||
sleep(0.1)
|
||||
try:
|
||||
self.vna = get_VNA(self.interface)
|
||||
except IOError as exc:
|
||||
logger.error("Unable to connect to VNA: %s", exc)
|
||||
|
||||
self.vna.validateInput = self.settings.value(
|
||||
"SerialInputValidation", True, bool)
|
||||
|
||||
# connected
|
||||
self.btnSerialToggle.setText("Disconnect")
|
||||
self.btnSerialToggle.repaint()
|
||||
|
||||
frequencies = self.vna.readFrequencies()
|
||||
if not frequencies:
|
||||
logger.warning("No frequencies read")
|
||||
return
|
||||
logger.info("Read starting frequency %s and end frequency %s",
|
||||
frequencies[0], frequencies[-1])
|
||||
self.sweep_control.set_start(frequencies[0])
|
||||
if frequencies[0] < frequencies[-1]:
|
||||
self.sweep_control.set_end(frequencies[-1])
|
||||
else:
|
||||
self.sweep_control.set_end(
|
||||
frequencies[0] +
|
||||
self.vna.datapoints * self.sweep_control.get_segments())
|
||||
|
||||
self.sweep_control.set_segments(1) # speed up things
|
||||
self.sweep_control.update_center_span()
|
||||
self.sweep_control.update_step_size()
|
||||
|
||||
self.windows["sweep_settings"].vna_connected()
|
||||
|
||||
logger.debug("Starting initial sweep")
|
||||
self.sweep_start()
|
||||
|
||||
def disconnect_device(self):
|
||||
with self.interface.lock:
|
||||
logger.info("Closing connection to %s", self.interface)
|
||||
self.interface.close()
|
||||
self.btnSerialToggle.setText("Connect to device")
|
||||
self.btnSerialToggle.repaint()
|
||||
|
||||
def sweep_start(self):
|
||||
# Run the device data update
|
||||
if not self.vna.connected():
|
||||
|
|
Ładowanie…
Reference in New Issue