kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge branch 'development'
commit
6c4fbe6c8d
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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())
|
|
@ -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(
|
|
@ -0,0 +1,2 @@
|
|||
from .MarkerControl import MarkerControl
|
||||
from .SweepControl import SweepControl
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}")
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from .SweepControl import SweepControl
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue