Refactored Serial Control Widget

pull/421/head
Holger Müller 2021-07-05 16:16:33 +02:00
rodzic 2d96aaa2f3
commit 00293fe204
3 zmienionych plików z 65 dodań i 792 usunięć

Wyświetl plik

@ -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()

Wyświetl plik

@ -1,2 +1,3 @@
from .MarkerControl import MarkerControl
from .SweepControl import SweepControl
from .SerialControl import SerialControl

Wyświetl plik

@ -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():