Merge branch 'development'

pull/401/head^2
Holger Müller 2020-07-25 16:39:38 +02:00
commit 6c4fbe6c8d
20 zmienionych plików z 416 dodań i 140 usunięć

Wyświetl plik

@ -5,19 +5,16 @@ omit =
NanoVNASaver/Analysis/*.py
NanoVNASaver/Calibration.py
NanoVNASaver/Charts/*.py
NanoVNASaver/Controls/*.py
NanoVNASaver/Hardware/*.py
NanoVNASaver/Inputs.py
NanoVNASaver/Marker/Settings.py
NanoVNASaver/Marker/Values.py
NanoVNASaver/Marker/Widget.py
NanoVNASaver/Marker/__init__.py
NanoVNASaver/Marker/*.py
NanoVNASaver/NanoVNASaver.py
NanoVNASaver/Settings.py
NanoVNASaver/SweepWorker.py
NanoVNASaver/Windows/*.py
NanoVNASaver/__init__.py
**/__init__.py
NanoVNASaver/__main__.py
NanoVNASaver/about.py
[report]
fail_under = 90.0
show_missing = True

Wyświetl plik

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
VERSION = "0.3.6"
VERSION = "0.3.7-pre01"
VERSION_URL = (
"https://raw.githubusercontent.com/"
"NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py")

Wyświetl plik

@ -0,0 +1,81 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020 NanoVNA-Saver Authors
#
# 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/>.
import logging
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QCheckBox
from NanoVNASaver.Marker import Marker
logger = logging.getLogger(__name__)
class MarkerControl(QtWidgets.QGroupBox):
updated = pyqtSignal(object)
def __init__(self, app: QtWidgets.QWidget, title: str = "Markers"):
super().__init__()
self.app = app
self.setMaximumWidth(250)
self.setTitle(title)
self.layout = QtWidgets.QFormLayout(self)
marker_count = max(self.app.settings.value("MarkerCount", 3, int), 1)
for i in range(marker_count):
marker = Marker("", self.app.settings)
marker.updated.connect(self.app.markerUpdated)
label, layout = marker.getRow()
self.layout.addRow(label, layout)
self.app.markers.append(marker)
if i == 0:
marker.isMouseControlledRadioButton.setChecked(True)
self.check_delta = QCheckBox("Enable Delta Marker")
self.check_delta.toggled.connect(self.toggle_delta)
self.layout.addRow(self.check_delta)
self.showMarkerButton = QtWidgets.QPushButton()
if self.app.marker_frame.isHidden():
self.showMarkerButton.setText("Show data")
else:
self.showMarkerButton.setText("Hide data")
self.showMarkerButton.clicked.connect(self.toggle_frame)
lock_radiobutton = QtWidgets.QRadioButton("Locked")
lock_radiobutton.setLayoutDirection(QtCore.Qt.RightToLeft)
lock_radiobutton.setSizePolicy(
QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.showMarkerButton)
hbox.addWidget(lock_radiobutton)
self.layout.addRow(hbox)
def toggle_frame(self):
if self.app.marker_frame.isHidden():
self.app.marker_frame.setHidden(False)
self.app.settings.setValue("MarkersVisible", True)
self.showMarkerButton.setText("Hide data")
else:
self.app.marker_frame.setHidden(True)
self.app.settings.setValue("MarkersVisible", False)
self.showMarkerButton.setText("Show data")
def toggle_delta(self):
self.app.delta_marker_layout.setVisible(self.check_delta.isChecked())

Wyświetl plik

@ -33,11 +33,11 @@ logger = logging.getLogger(__name__)
class SweepControl(QtWidgets.QGroupBox):
updated = pyqtSignal(object)
def __init__(self, app: QtWidgets.QWidget):
def __init__(self, app: QtWidgets.QWidget, title: str = "Sweep control"):
super().__init__()
self.app = app
self.setMaximumWidth(250)
self.setTitle("Sweep control")
self.setTitle(title)
control_layout = QtWidgets.QFormLayout(self)
line = QtWidgets.QFrame()
@ -77,16 +77,16 @@ class SweepControl(QtWidgets.QGroupBox):
input_right_layout.addRow(QtWidgets.QLabel("Span"), self.input_span)
self.input_count = QtWidgets.QLineEdit(self.app.settings.value("Segments", "1"))
self.input_count.setAlignment(QtCore.Qt.AlignRight)
self.input_count.setFixedWidth(60)
self.input_count.textEdited.connect(self.update_step_size)
self.input_segments = QtWidgets.QLineEdit(self.app.settings.value("Segments", "1"))
self.input_segments.setAlignment(QtCore.Qt.AlignRight)
self.input_segments.setFixedWidth(60)
self.input_segments.textEdited.connect(self.update_step_size)
self.label_step = QtWidgets.QLabel("Hz/step")
self.label_step.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
segment_layout = QtWidgets.QHBoxLayout()
segment_layout.addWidget(self.input_count)
segment_layout.addWidget(self.input_segments)
segment_layout.addWidget(self.label_step)
control_layout.addRow(QtWidgets.QLabel("Segments"), segment_layout)
@ -143,16 +143,16 @@ class SweepControl(QtWidgets.QGroupBox):
self.input_center.textEdited.emit(self.input_center.text())
self.updated.emit(self)
def get_count(self) -> int:
def get_segments(self) -> int:
try:
result = int(self.input_count.text())
result = int(self.input_segments.text())
except ValueError:
result = 1
return result
def set_count(self, count: int):
self.input_count.setText(str(count))
self.input_count.textEdited.emit(self.input_count.text())
def set_segments(self, count: int):
self.input_segments.setText(str(count))
self.input_segments.textEdited.emit(self.input_segments.text())
self.updated.emit(self)
def get_span(self) -> int:
@ -168,7 +168,7 @@ class SweepControl(QtWidgets.QGroupBox):
self.input_end.setDisabled(disabled)
self.input_span.setDisabled(disabled)
self.input_center.setDisabled(disabled)
self.input_count.setDisabled(disabled)
self.input_segments.setDisabled(disabled)
def update_center_span(self):
fstart = self.get_start()
@ -196,7 +196,7 @@ class SweepControl(QtWidgets.QGroupBox):
fspan = self.get_span()
if fspan < 0:
return
segments = self.get_count()
segments = self.get_segments()
if segments > 0:
fstep = fspan / (segments * self.app.vna.datapoints - 1)
self.label_step.setText(

Wyświetl plik

@ -0,0 +1,2 @@
from .MarkerControl import MarkerControl
from .SweepControl import SweepControl

Wyświetl plik

@ -35,6 +35,7 @@ FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ")
FMT_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True)
FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True,
printable_min=0, unprintable_under="- ")
FMT_COMPLEX_NEG = SITools.Format(max_nr_digits=3, allow_strip=True)
FMT_PARSE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True,
parse_clamp_min=0)
@ -65,8 +66,8 @@ def format_gain(val: float, invert: bool = False) -> str:
return f"{val:.3f} dB"
def format_q_factor(val: float) -> str:
if val < 0 or val > 10000.0:
def format_q_factor(val: float, allow_negative: bool = False) -> str:
if (not allow_negative and val < 0) or abs(val > 10000.0):
return "\N{INFINITY}"
return str(SITools.Value(val, fmt=FMT_Q_FACTOR))
@ -79,8 +80,8 @@ def format_magnitude(val: float) -> str:
return f"{val:.3f}"
def format_resistance(val: float) -> str:
if val < 0:
def format_resistance(val: float, allow_negative: bool = False) -> str:
if not allow_negative and val < 0:
return "- \N{OHM SIGN}"
return str(SITools.Value(val, "\N{OHM SIGN}", FMT_REACT))
@ -105,8 +106,11 @@ def format_phase(val: float) -> str:
return f"{math.degrees(val):.2f}\N{DEGREE SIGN}"
def format_complex_imp(z: complex) -> str:
re = SITools.Value(z.real, fmt=FMT_COMPLEX)
def format_complex_imp(z: complex, allow_negative: bool = False) -> str:
fmt_re = FMT_COMPLEX
if allow_negative:
fmt_re = FMT_COMPLEX_NEG
re = SITools.Value(z.real, fmt=fmt_re)
im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX)
return f"{re}{'-' if z.imag < 0 else '+'}j{im} \N{OHM SIGN}"

Wyświetl plik

@ -58,7 +58,7 @@ WRITE_SLEEP = 0.05
class NanoVNA_V2(VNA):
name = "NanoVNA-V2"
valid_datapoints = (101, 51, 202, 303, 505, 1023)
valid_datapoints = (101, 10, 50, 202, 303, 505, 1023)
screenwidth = 320
screenheight = 240

Wyświetl plik

@ -36,15 +36,18 @@ DISLORD_BW = OrderedDict((
(1000, 1),
(2000, 0),
))
WAIT = 0.05
def _max_retries(bandwidth: int, datapoints: int) -> int:
return 20 * (datapoints / 101) + round(
(1000 / bandwidth) ** 1.2 * (datapoints / 101))
return round(5 + 20 * (datapoints / 101) +
(1000 / bandwidth) ** 1.30 * (datapoints / 101))
class VNA:
name = "VNA"
valid_datapoints = (101, )
valid_datapoints = (101, 50, 10)
wait = 0.05
def __init__(self, iface: Interface):
self.serial = iface
@ -63,7 +66,23 @@ class VNA:
if "Bandwidth" in self.features:
self.set_bandwidth(self.get_bandwidths()[-1])
def exec_command(self, command: str, wait: float = 0.05) -> Iterator[str]:
def connect(self):
logger.info("connect %s", self.serial)
with self.serial.lock:
self.serial.open()
def disconnect(self):
logger.info("disconnect %s", self.serial)
with self.serial.lock:
self.serial.close()
def reconnect(self):
self.disconnect()
sleep(WAIT)
self.connect()
sleep(WAIT)
def exec_command(self, command: str, wait: float = WAIT) -> Iterator[str]:
logger.debug("exec_command(%s)", command)
with self.serial.lock:
drain_serial(self.serial)

Wyświetl plik

@ -0,0 +1,133 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020 NanoVNA-Saver Authors
#
# 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/>.
from PyQt5 import QtCore
from NanoVNASaver import RFTools
from NanoVNASaver.Formatting import (
format_capacitance,
format_complex_imp,
format_frequency_space,
format_gain,
format_group_delay,
format_inductance,
format_magnitude,
format_phase,
format_q_factor,
format_resistance,
format_vswr,
)
from .Widget import Marker
class DeltaMarker(Marker):
def __init__(self, name: str = "", qsettings: QtCore.QSettings = None):
super().__init__(name, qsettings)
self.marker_a = None
self.marker_b = None
def set_markers(self, marker_a: Marker, marker_b: Marker):
self.marker_a = marker_a
self.marker_b = marker_b
self.name = f"Delta {marker_b.name} - {marker_a.name}"
self.group_box.setTitle(self.name)
def updateLabels(self): # pylint: disable=arguments-differ
a = self.marker_a
b = self.marker_b
s11_a = a.s11data[1]
s11_b = b.s11data[1]
imp_a = s11_a.impedance()
imp_b = s11_b.impedance()
imp = imp_b - imp_a
cap_str = format_capacitance(
RFTools.impedance_to_capacitance(imp_b, s11_b.freq) -
RFTools.impedance_to_capacitance(imp_a, s11_a.freq))
ind_str = format_inductance(
RFTools.impedance_to_inductance(imp_b, s11_b.freq) -
RFTools.impedance_to_inductance(imp_a, s11_a.freq))
imp_p_a = RFTools.serial_to_parallel(imp_a)
imp_p_b = RFTools.serial_to_parallel(imp_b)
imp_p = imp_p_b - imp_p_a
cap_p_str = format_capacitance(
RFTools.impedance_to_capacitance(imp_p_b, s11_b.freq)-
RFTools.impedance_to_capacitance(imp_p_a, s11_a.freq))
ind_p_str = format_inductance(
RFTools.impedance_to_inductance(imp_p_b, s11_b.freq)-
RFTools.impedance_to_inductance(imp_p_a, s11_a.freq))
if imp.imag < 0:
x_str = cap_str
else:
x_str = ind_str
if imp_p.imag < 0:
x_p_str = cap_p_str
else:
x_p_str = ind_p_str
self.label['actualfreq'].setText(
format_frequency_space(s11_b.freq - s11_a.freq))
self.label['admittance'].setText(format_complex_imp(imp_p, True))
self.label['impedance'].setText(format_complex_imp(imp, True))
self.label['parc'].setText(cap_p_str)
self.label['parl'].setText(ind_p_str)
self.label['parlc'].setText(x_p_str)
self.label['parr'].setText(format_resistance(imp_p.real, True))
self.label['returnloss'].setText(
format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive))
self.label['s11groupdelay'].setText(format_group_delay(
RFTools.groupDelay(b.s11data, 1) -
RFTools.groupDelay(a.s11data, 1)))
self.label['s11mag'].setText(
format_magnitude(abs(s11_b.z) - abs(s11_a.z)))
self.label['s11phase'].setText(format_phase(s11_b.phase - s11_a.phase))
self.label['s11polar'].setText(
f"{round(abs(s11_b.z) - abs(s11_a.z), 2)}"
f"{format_phase(s11_b.phase - s11_a.phase)}")
self.label['s11q'].setText(format_q_factor(
s11_b.qFactor() - s11_a.qFactor(), True))
self.label['s11z'].setText(format_resistance(abs(imp)))
self.label['serc'].setText(cap_str)
self.label['serl'].setText(ind_str)
self.label['serlc'].setText(x_str)
self.label['serr'].setText(format_resistance(imp.real, True))
self.label['vswr'].setText(format_vswr(s11_b.vswr - s11_a.vswr))
if len(a.s21data) == len(a.s11data):
s21_a = a.s21data[1]
s21_b = b.s21data[1]
self.label['s21gain'].setText(format_gain(
s21_b.gain - s21_a.gain))
self.label['s21groupdelay'].setText(format_group_delay(
(RFTools.groupDelay(b.s21data, 1) -
RFTools.groupDelay(a.s21data, 1)) / 2))
self.label['s21mag'].setText(format_magnitude(
abs(s21_b.z) - abs(s21_a.z)))
self.label['s21phase'].setText(format_phase(
s21_b.phase - s21_a.phase))
self.label['s21polar'].setText(
f"{round(abs(s21_b.z) - abs(s21_a.z) , 2)}"
f"{format_phase(s21_b.phase - s21_a.phase)}")

Wyświetl plik

@ -59,7 +59,6 @@ class MarkerLabel(QtWidgets.QLabel):
class Marker(QtCore.QObject, Value):
_instances = 0
color: QtGui.QColor = QtGui.QColor()
coloredText = True
location = -1
returnloss_is_positive = False
@ -72,7 +71,6 @@ class Marker(QtCore.QObject, Value):
def __init__(self, name: str = "", qsettings: QtCore.QSettings = None):
super().__init__()
self.name = name
self.qsettings = qsettings
self.name = name
self.color = QtGui.QColor()
@ -87,8 +85,6 @@ class Marker(QtCore.QObject, Value):
if not self.name:
self.name = f"Marker {Marker._instances}"
# self.freq = RFTools.RFTools.parseFrequency(frequency)
self.frequencyInput = FrequencyInput()
self.frequencyInput.setAlignment(QtCore.Qt.AlignRight)
self.frequencyInput.textEdited.connect(self.setFrequency)
@ -274,7 +270,7 @@ class Marker(QtCore.QObject, Value):
self.location = datasize - 1
self.frequencyInput.previousFrequency = data[-2].freq
def getGroupBox(self) -> QtWidgets.QGroupBox:
def get_data_layout(self) -> QtWidgets.QGroupBox:
return self.group_box
def resetLabels(self):
@ -284,6 +280,16 @@ class Marker(QtCore.QObject, Value):
def updateLabels(self,
s11data: List[RFTools.Datapoint],
s21data: List[RFTools.Datapoint]):
if not s11data:
return
if self.location == -1: # initial position
if self.index == 3:
self.location = len(s11data) - 1
elif self.index == 2:
self.location = round(len(s11data) / 2)
else:
self.location = 0
self.frequencyInput.setText(s11data[self.location].freq)
try:
s11 = s11data[self.location]
except IndexError:

Wyświetl plik

@ -1,2 +1,3 @@
from .Widget import Marker # noqa
from .Values import Value, default_label_ids # noqa
from .Widget import Marker
from .Delta import DeltaMarker
from .Values import Value, default_label_ids

Wyświetl plik

@ -31,7 +31,7 @@ from .Windows import (
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
TDRWindow
)
from .Widgets import SweepControl
from .Controls import MarkerControl, SweepControl
from .Formatting import format_frequency
from .Hardware.Hardware import Interface, get_interfaces, get_VNA
from .Hardware.VNA import VNA
@ -47,7 +47,7 @@ from .Charts import (
SmithChart, SParameterChart, TDRChart,
)
from .Calibration import Calibration
from .Marker import Marker
from .Marker import Marker, DeltaMarker
from .SweepWorker import SweepWorker
from .Settings import BandsModel
from .Touchstone import Touchstone
@ -84,12 +84,18 @@ class NanoVNASaver(QtWidgets.QWidget):
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.noSweeps = 1 # Number of sweeps to run
self.interface = Interface("serial", "None")
self.vna = VNA(self.interface)
@ -105,13 +111,13 @@ class NanoVNASaver(QtWidgets.QWidget):
self.calibration = Calibration()
self.markers = []
logger.debug("Building user interface")
self.baseTitle = f"NanoVNA Saver {NanoVNASaver.version}"
self.updateTitle()
layout = QtWidgets.QGridLayout()
layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight)
scrollarea = QtWidgets.QScrollArea()
outer = QtWidgets.QVBoxLayout()
outer.addWidget(scrollarea)
@ -128,8 +134,6 @@ class NanoVNASaver(QtWidgets.QWidget):
widget.setLayout(layout)
scrollarea.setWidget(widget)
# outer.setContentsMargins(2, 2, 2, 2) # Small screen mode, reduce margins?
self.charts = {
"s11": OrderedDict((
("capacitance", CapacitanceChart("S11 Serial C")),
@ -187,18 +191,22 @@ class NanoVNASaver(QtWidgets.QWidget):
self.charts_layout = QtWidgets.QGridLayout()
###############################################################
# Create main layout
###############################################################
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)
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)
splitter = QtWidgets.QSplitter()
splitter.addWidget(self.marker_frame)
splitter.addWidget(chart_widget)
layout.addLayout(left_column, 0, 0)
layout.addWidget(self.marker_frame, 0, 1)
layout.addLayout(right_column, 0, 2)
layout.addLayout(left_column)
layout.addWidget(splitter, 2)
###############################################################
# Windows
@ -225,48 +233,33 @@ class NanoVNASaver(QtWidgets.QWidget):
# 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 = max(self.settings.value("MarkerCount", 3, int), 1)
for i in range(marker_count):
marker = Marker("", self.settings)
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)
lock_radiobutton = QtWidgets.QRadioButton("Locked")
lock_radiobutton.setLayoutDirection(QtCore.Qt.RightToLeft)
lock_radiobutton.setSizePolicy(
QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.showMarkerButton)
hbox.addWidget(lock_radiobutton)
self.marker_control_layout.addRow(hbox)
left_column.addWidget(self.marker_control)
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_data_layout.addWidget(m.get_data_layout())
self.marker_column.addLayout(self.marker_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
@ -297,7 +290,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.marker_column.addWidget(s21_control_box)
self.marker_column.addStretch(1)
# self.marker_column.addStretch(1)
self.windows["analysis"] = AnalysisWindow(self)
btn_show_analysis = QtWidgets.QPushButton("Analysis ...")
@ -559,9 +552,9 @@ class NanoVNASaver(QtWidgets.QWidget):
else:
self.sweep_control.set_end(
frequencies[0] +
self.vna.datapoints * self.sweep_control.get_count())
self.vna.datapoints * self.sweep_control.get_segments())
self.sweep_control.set_count(1) # speed up things
self.sweep_control.set_segments(1) # speed up things
self.sweep_control.update_center_span()
self.sweep_control.update_step_size()
@ -593,8 +586,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s21_max_gain_label.setText("")
self.tdr_result_label.setText("")
if self.sweep_control.input_count.text().isdigit():
self.settings.setValue("Segments", self.sweep_control.input_count.text())
self.settings.setValue("Segments", self.sweep_control.get_segments())
logger.debug("Starting worker thread")
self.threadpool.start(self.worker)
@ -619,12 +611,17 @@ class NanoVNASaver(QtWidgets.QWidget):
def markerUpdated(self, marker: Marker):
with self.dataLock:
marker.findLocation(self.data11)
for m in self.markers:
m.resetLabels()
m.updateLabels(self.data11, self.data21)
marker.resetLabels()
marker.updateLabels(self.data11, self.data21)
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:
@ -738,16 +735,6 @@ class NanoVNASaver(QtWidgets.QWidget):
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 = []
@ -793,7 +780,11 @@ class NanoVNASaver(QtWidgets.QWidget):
def showSweepError(self):
self.showError(self.worker.error_message)
self.vna.flushSerialBuffers() # Remove any left-over data
try:
self.vna.flushSerialBuffers() # Remove any left-over data
self.vna.reconnect() # try reconnection
except IOError:
pass
self.sweepFinished()
def popoutChart(self, chart: Chart):
@ -843,7 +834,7 @@ class NanoVNASaver(QtWidgets.QWidget):
new_width, old_width, self.scaleFactor)
# TODO: Update all the fixed widths to account for the scaling
for m in self.markers:
m.getGroupBox().setFont(font)
m.get_data_layout().setFont(font)
m.setScale(self.scaleFactor)
def setSweepTitle(self, title):

Wyświetl plik

@ -142,8 +142,10 @@ class Value:
value = value[:-len(self._unit)]
factor = 1
if self.fmt.parse_sloppy_kilo and value[-1] == "K": # fix for e.g. KHz
value = value[:-1] + "k"
# fix for e.g. KHz, mHz gHz as milli-Hertz mostly makes no
# sense in NanoVNAs context
if self.fmt.parse_sloppy_kilo and value[-1] in ("K", "m", "g"):
value = value[:-1] + value[-1].swapcase()
if value[-1] in PREFIXES:
factor = 10 ** ((PREFIXES.index(value[-1]) - 8) * 3)
value = value[:-1]

Wyświetl plik

@ -187,7 +187,7 @@ class Version:
return False
def __lt__(self, other: "Version") -> bool:
return other < self
return other > self
def __ge__(self, other: "Version") -> bool:
return self > other or self == other

Wyświetl plik

@ -17,6 +17,7 @@
# 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
from math import log
from time import sleep
from typing import Iterator, List, Tuple
@ -56,44 +57,65 @@ class WorkerSignals(QtCore.QObject):
class Sweep():
def __init__(self, start: int = 3600000, end: int = 30000000,
points: int = 101, sweeps: int = 1):
points: int = 101, segments: int = 1,
logarithmic: bool = False):
self.start = start
self.end = end
self.points = points
self.sweeps = sweeps
self.segments = segments
self.logarithmic = logarithmic
self.span = self.end - self.start
self.step = self.stepsize()
self.check()
def __repr__(self) -> str:
return (
f"Sweep({self.start}, {self.end}, {self.points} {self.sweeps})")
f"Sweep({self.start}, {self.end}, {self.points}, {self.segments},"
f" {self.logarithmic})")
def __eq__(self, other) -> bool:
return(self.start == other.start and
self.end == other.end and
self.points == other.points and
self.sweeps == other.sweeps)
self.segments == other.segments)
def check(self):
if not(self.sweeps > 0 and
if not(self.segments > 0 and
self.points > 0 and
self.start > 0 and
self.end > 0 and
self.step >= 1):
self.stepsize() >= 1):
raise ValueError(f"Illegal sweep settings: {self}")
def stepsize(self) -> int:
return int(self.span / (self.points * self.sweeps - 1))
return round(self.span / ((self.points -1) * self.segments))
def _exp_factor(self, index: int) -> int:
return 1 - log(self.segments + 1 - index) / log(self.segments + 1)
def get_index_range(self, index: int) -> Tuple[int, int]:
start = self.start + index * self.points * self.step
end = start + (self.points -1) * self.step
if not self.logarithmic:
start = self.start + index * self.points * self.step
end = start + (self.points - 1) * self.step
else:
start = self.start + self.span * self._exp_factor(index)
end = self.start + self.span * self._exp_factor(index + 1)
logger.debug("get_index_range(%s) -> (%s, %s)", index, start, end)
return (start, end)
def get_frequencies(self) -> Iterator[int]:
for freq in range(self.start, self.end + 1, self.step):
yield freq
if not self.logarithmic:
for freq in range(self.start, self.end + 1, self.step):
yield freq
return
for i in range(self.segments):
start, stop = self.get_index_range(i)
step = (stop - start) / self.points
freq = start
for _ in range(self.points):
yield round(freq)
freq += step
class SweepWorker(QtCore.QRunnable):
@ -123,9 +145,11 @@ class SweepWorker(QtCore.QRunnable):
def run(self):
try:
self._run()
except BaseException as exc:
except BaseException as exc: # pylint: disable=broad-except
logger.exception("%s", exc)
raise exc
self.gui_error(f"ERROR during sweep\n\nStopped\n\n{exc}")
return
# raise exc
def _run(self):
logger.info("Initializing SweepWorker")
@ -141,7 +165,7 @@ class SweepWorker(QtCore.QRunnable):
self.app.sweep_control.get_start(),
self.app.sweep_control.get_end(),
self.app.vna.datapoints,
self.app.sweep_control.get_count(),
self.app.sweep_control.get_segments(),
)
except ValueError:
self.gui_error(
@ -160,7 +184,7 @@ class SweepWorker(QtCore.QRunnable):
finished = False
while not finished:
for i in range(self.sweep.sweeps):
for i in range(self.sweep.segments):
logger.debug("Sweep segment no %d", i)
if self.stopped:
logger.debug("Stopping sweeping as signalled")
@ -171,7 +195,7 @@ class SweepWorker(QtCore.QRunnable):
try:
freq, values11, values21 = self.readAveragedSegment(
start, stop, averages)
self.percentage = (i + 1) * 100 / self.sweep.sweeps
self.percentage = (i + 1) * 100 / self.sweep.segments
self.updateData(freq, values11, values21, i)
except ValueError as e:
self.error_message = str(e)
@ -182,7 +206,7 @@ class SweepWorker(QtCore.QRunnable):
if not self.continuousSweep:
finished = True
if self.sweep.sweeps > 1:
if self.sweep.segments > 1:
start = self.app.sweep_control.get_start()
end = self.app.sweep_control.get_end()
logger.debug("Resetting NanoVNA sweep to full range: %d to %d",
@ -284,7 +308,7 @@ class SweepWorker(QtCore.QRunnable):
freq, tmp11, tmp21 = self.readSegment(start, stop)
values11.append(tmp11)
values21.append(tmp21)
self.percentage += 100 / (self.sweep.sweeps * averages)
self.percentage += 100 / (self.sweep.segments * averages)
self.signals.updated.emit()
if self.truncates and averages > 1:
@ -309,7 +333,6 @@ class SweepWorker(QtCore.QRunnable):
if (len(frequencies) != len(values11) or
len(frequencies) != len(values21)):
logger.info("No valid data during this run")
# TODO: display gui warning
return [], [], []
return frequencies, values11, values21
@ -345,6 +368,8 @@ class SweepWorker(QtCore.QRunnable):
if count == 5:
logger.error("Tried and failed to read %s %d times.",
data, count)
logger.debug("trying to reconnect")
self.app.vna.reconnect()
if count >= 10:
logger.critical(
"Tried and failed to read %s %d times. Giving up.",

Wyświetl plik

@ -1 +0,0 @@
from .SweepControl import SweepControl

Wyświetl plik

@ -524,7 +524,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
for m in self.app.markers:
m.returnloss_is_positive = state
m.updateLabels(self.app.data, self.app.data21)
m.updateLabels(self.app.data11, self.app.data21)
self.marker_window.exampleMarker.returnloss_is_positive = state
self.marker_window.updateMarker()
self.app.charts["s11"]["log_mag"].isInverted = state
@ -719,26 +719,34 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
new_marker = Marker("", self.app.settings)
new_marker.setScale(self.app.scaleFactor)
self.app.markers.append(new_marker)
self.app.marker_data_layout.addWidget(new_marker.getGroupBox())
self.app.marker_data_layout.addWidget(new_marker.get_data_layout())
self.app.marker_frame.adjustSize()
new_marker.updated.connect(self.app.markerUpdated)
label, layout = new_marker.getRow()
self.app.marker_control_layout.insertRow(Marker.count() - 1, label, layout)
self.app.marker_control.layout.insertRow(Marker.count() - 1, label, layout)
self.btn_remove_marker.setDisabled(False)
if Marker.count() >= 2:
self.app.marker_control.check_delta.setDisabled(False)
def removeMarker(self):
# keep at least one marker
if Marker.count() <= 1:
return
if Marker.count() == 2:
self.btn_remove_marker.setDisabled(True)
self.app.delta_marker_layout.setVisible(False)
self.app.marker_control.check_delta.setDisabled(True)
last_marker = self.app.markers.pop()
last_marker.updated.disconnect(self.app.markerUpdated)
self.app.marker_data_layout.removeWidget(last_marker.getGroupBox())
self.app.marker_control_layout.removeRow(Marker.count()-1)
last_marker.getGroupBox().hide()
last_marker.getGroupBox().destroy()
self.app.marker_data_layout.removeWidget(last_marker.get_data_layout())
self.app.marker_control.layout.removeRow(Marker.count()-1)
self.app.marker_frame.adjustSize()
last_marker.get_data_layout().hide()
last_marker.get_data_layout().destroy()
label, _ = last_marker.getRow()
label.hide()

Wyświetl plik

@ -75,7 +75,7 @@ class MarkerSettingsWindow(QtWidgets.QWidget):
layout.addWidget(settings_group_box)
layout.addWidget(fields_group_box)
layout.addWidget(self.exampleMarker.getGroupBox())
layout.addWidget(self.exampleMarker.get_data_layout())
btn_layout = QtWidgets.QHBoxLayout()
layout.addLayout(btn_layout)

Wyświetl plik

@ -16,6 +16,12 @@ points, and generally display and analyze the resulting data.
# Latest Changes
## Changes in v0.3.7-pre
- Added a delta marker
- Scrollable marker column
- Better error handling whith communication errors during a sweep
## Changes in v0.3.6
- Added bandwidth setting in device manage dialog
@ -173,7 +179,7 @@ Via a MacPorts distribution maintained by @ra1nb0w.
3. NanoVNASaver Installation
git clone git clone https://github.com/NanoVNA-Saver/nanovna-saver
git clone https://github.com/NanoVNA-Saver/nanovna-saver
cd nanovna-saver
4. Install local pip packages

Wyświetl plik

@ -60,6 +60,9 @@ class TestCases(unittest.TestCase):
self.assertEqual(parse_frequency('123.kHz'), 123000)
self.assertEqual(parse_frequency('123.MHz'), 123000000)
self.assertEqual(parse_frequency('123.GHz'), 123000000000)
self.assertEqual(parse_frequency('123.KHz'), 123000)
self.assertEqual(parse_frequency('123.mHz'), 123000000)
self.assertEqual(parse_frequency('123.gHz'), 123000000000)
def test_unusualSIUnits(self):
#######################################################################
@ -76,7 +79,6 @@ class TestCases(unittest.TestCase):
self.assertEqual(parse_frequency('123ZHz'), 123000000000000000000000)
self.assertEqual(parse_frequency('123aHz'), 0)
self.assertEqual(parse_frequency('123fHz'), 0)
self.assertEqual(parse_frequency('123mHz'), 0)
self.assertEqual(parse_frequency('123nHz'), 0)
self.assertEqual(parse_frequency('123pHz'), 0)
self.assertEqual(parse_frequency('123yHz'), 0)