Merge branch 'Development' into serial-to-parallel-test-fix

pull/162/head
mihtjel 2020-02-29 09:05:08 +01:00 zatwierdzone przez GitHub
commit 2a36fc80be
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
27 zmienionych plików z 5665 dodań i 433 usunięć

20
.coveragerc 100644
Wyświetl plik

@ -0,0 +1,20 @@
[run]
# ignore GUI code atm.
omit =
NanoVNASaver/Analysis.py
NanoVNASaver/Calibration.py
NanoVNASaver/Chart.py
NanoVNASaver/Hardware.py
NanoVNASaver/Inputs.py
NanoVNASaver/Marker/Settings.py
NanoVNASaver/Marker/Values.py
NanoVNASaver/Marker/Widget.py
NanoVNASaver/Marker/__init__.py
NanoVNASaver/NanoVNASaver.py
NanoVNASaver/SweepWorker.py
NanoVNASaver/__init__.py
NanoVNASaver/__main__.py
NanoVNASaver/about.py
[report]
fail_under = 90.0
show_missing = True

Wyświetl plik

@ -21,8 +21,7 @@ jobs:
run: |
pip install pylint
pylint --exit-zero NanoVNASaver
- name: Unittests
- name: Unittests / Coverage
run: |
pip install coverage
coverage run ./test_master.py
coverage report --fail-under=90 -m --skip-covered
pip install pytest-cov
pytest --cov=NanoVNASaver

1
.gitignore vendored
Wyświetl plik

@ -9,3 +9,4 @@
*.cal
settings.json
.gitignore
.coverage

Wyświetl plik

@ -764,11 +764,15 @@ class FrequencyChart(Chart):
for i in range(len(data)):
x = self.getXPosition(data[i])
y = y_function(data[i])
if y is None:
continue
if self.isPlotable(x, y):
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(data[i - 1])
prevy = y_function(data[i - 1])
if prevy is None:
continue
qp.setPen(line_pen)
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
qp.drawLine(x, y, prevx, prevy)
@ -795,7 +799,8 @@ class FrequencyChart(Chart):
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
def isPlotable(self, x, y):
return self.leftMargin <= x <= self.leftMargin + self.chartWidth and \
return y is not None and x is not None and \
self.leftMargin <= x <= self.leftMargin + self.chartWidth and \
self.topMargin <= y <= self.topMargin + self.chartHeight
def getPlotable(self, x, y, distantx, distanty):
@ -1569,6 +1574,8 @@ class LogMagChart(FrequencyChart):
maxValue = -100
for d in self.data:
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
@ -1577,6 +1584,8 @@ class LogMagChart(FrequencyChart):
if d.freq < self.fstart or d.freq > self.fstop:
continue
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
@ -1673,6 +1682,8 @@ class LogMagChart(FrequencyChart):
def getYPosition(self, d: Datapoint) -> int:
logMag = self.logMag(d)
if math.isinf(logMag):
return None
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
@ -1946,29 +1957,37 @@ class CombinedLogMagChart(FrequencyChart):
maxValue = 0
for d in self.data11:
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
minValue = logmag
for d in self.data21:
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
minValue = logmag
for d in self.reference11: # Also check min/max for the reference sweep
for d in self.reference11:
if d.freq < self.fstart or d.freq > self.fstop:
continue
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
minValue = logmag
for d in self.reference21: # Also check min/max for the reference sweep
for d in self.reference21:
if d.freq < self.fstart or d.freq > self.fstop:
continue
logmag = self.logMag(d)
if math.isinf(logmag):
continue
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
@ -2096,6 +2115,8 @@ class CombinedLogMagChart(FrequencyChart):
def getYPosition(self, d: Datapoint) -> int:
logMag = self.logMag(d)
if math.isinf(logMag):
return None
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
@ -3818,7 +3839,7 @@ class GroupDelayChart(FrequencyChart):
for d in self.reference:
rawReference.append(d.phase)
if len(self.data) > 0:
if len(self.data) > 1:
unwrappedData = np.degrees(np.unwrap(rawData))
self.groupDelay = []
for i in range(len(self.data)):
@ -3838,7 +3859,7 @@ class GroupDelayChart(FrequencyChart):
delay /= 2
self.groupDelay.append(delay)
if len(self.reference) > 0:
if len(self.reference) > 1:
unwrappedReference = np.degrees(np.unwrap(rawReference))
self.groupDelayReference = []
for i in range(len(self.reference)):
@ -4096,7 +4117,7 @@ class CapacitanceChart(FrequencyChart):
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
fmt = Format(max_nr_digits=3)
fmt = Format(max_nr_digits=1)
for i in range(target_ticks):
val = minValue + (i / target_ticks) * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
@ -4223,7 +4244,7 @@ class InductanceChart(FrequencyChart):
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
fmt = Format(max_nr_digits=3)
fmt = Format(max_nr_digits=1)
for i in range(target_ticks):
val = minValue + (i / target_ticks) * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)

Wyświetl plik

@ -23,6 +23,8 @@ FMT_FREQ_INPUTS = SITools.Format(max_nr_digits=10, allow_strip=True, printable_m
FMT_Q_FACTOR = SITools.Format(max_nr_digits=4, assume_infinity=False, min_offset=0, max_offset=0, allow_strip=True)
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="- ")
def format_frequency(freq: float, fmt=FMT_FREQ) -> str:
@ -48,6 +50,8 @@ def format_q_factor(val: float) -> str:
def format_vswr(val: float) -> str:
return f"{val:.3f}"
def format_magnitude(val: float) -> str:
return f"{val:.3f}"
def format_resistance(val: float) -> str:
if val < 0:
@ -68,7 +72,7 @@ def format_inductance(val: float, allow_negative: bool = True) -> str:
def format_group_delay(val: float) -> str:
return str(SITools.Value(val, "s", fmt=FMT_GROUP_DELAY))
return str(SITools.Value(val, "s", FMT_GROUP_DELAY))
def format_phase(val: float) -> str:
@ -76,21 +80,6 @@ def format_phase(val: float) -> str:
def format_complex_imp(z: complex) -> str:
if z.real > 0:
if z.real >= 1000:
s = f"{z.real/1000:.3g}k"
else:
s = f"{z.real:.4g}"
else:
s = "- "
if z.imag < 0:
s += " -j"
else:
s += " +j"
if abs(z.imag) >= 1000:
s += f"{abs(z.imag)/1000:.3g}k"
elif abs(z.imag) < 0.1:
s += f"{abs(z.imag)*1000:.3g}m"
else:
s += f"{abs(z.imag):.3g}"
return s + " \N{OHM SIGN}"
re = SITools.Value(z.real, fmt=FMT_COMPLEX)
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

@ -31,6 +31,7 @@ class VNA:
name = "VNA"
validateInput = True
features = []
datapoints = 101
def __init__(self, app, serial_port: serial.Serial):
from NanoVNASaver.NanoVNASaver import NanoVNASaver
@ -44,6 +45,9 @@ class VNA:
tmp_vna = VNA(app, serial_port)
tmp_vna.flushSerialBuffers()
firmware = tmp_vna.readFirmware()
if firmware.find("AVNA + Teensy") > 0:
logger.info("Type: AVNA")
return AVNA(app, serial_port)
if firmware.find("NanoVNA-H") > 0:
logger.info("Type: NanoVNA-H")
return NanoVNA_H(app, serial_port)
@ -190,11 +194,12 @@ class VNA:
return
def setSweep(self, start, stop):
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
class InvalidVNA(VNA):
name = "Invalid"
datapoints = 0
def __init__(self):
pass
@ -227,8 +232,92 @@ class InvalidVNA(VNA):
return
class AVNA(VNA):
name = "AVNA"
datapoints = 101
def __init__(self, app, serial_port):
super().__init__(app, serial_port)
self.version = Version(self.readVersion())
self.features = []
self.features.append("Customizable data points")
def isValid(self):
return True
def getCalibration(self) -> str:
logger.debug("Reading calibration info.")
if not self.serial.is_open:
return "Not connected."
if self.app.serialLock.acquire():
try:
data = "a"
while data != "":
data = self.serial.readline().decode('ascii')
self.serial.write("cal\r".encode('ascii'))
result = ""
data = ""
sleep(0.1)
while "ch>" not in data:
data = self.serial.readline().decode('ascii')
result += data
values = result.splitlines()
return values[1]
except serial.SerialException as exc:
logger.exception("Exception while reading calibration info: %s", exc)
finally:
self.app.serialLock.release()
return "Unknown"
def readFrequencies(self) -> List[str]:
return self.readValues("frequencies")
def readValues11(self) -> List[str]:
return self.readValues("data 0")
def readValues21(self) -> List[str]:
return self.readValues("data 1")
def resetSweep(self, start: int, stop: int):
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
self.writeSerial("resume")
def readVersion(self):
logger.debug("Reading version info.")
if not self.serial.is_open:
return
if self.app.serialLock.acquire():
try:
data = "a"
while data != "":
data = self.serial.readline().decode('ascii')
self.serial.write("version\r".encode('ascii'))
result = ""
data = ""
sleep(0.1)
while "ch>" not in data:
data = self.serial.readline().decode('ascii')
result += data
values = result.splitlines()
logger.debug("Found version info: %s", values[1])
return values[1]
except serial.SerialException as exc:
logger.exception("Exception while reading firmware version: %s", exc)
finally:
self.app.serialLock.release()
return
def setSweep(self, start, stop):
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
sleep(1)
class NanoVNA(VNA):
name = "NanoVNA"
datapoints = 101
screenwidth = 320
screenheight = 240
def __init__(self, app, serial_port):
super().__init__(app, serial_port)
@ -289,17 +378,17 @@ class NanoVNA(VNA):
data = self.serial.readline().decode('ascii')
self.serial.write("capture\r".encode('ascii'))
timeout = self.serial.timeout
self.serial.timeout = 2
self.serial.timeout = 4
self.serial.readline()
image_data = self.serial.read(320 * 240 * 2)
image_data = self.serial.read(self.screenwidth * self.screenheight * 2)
self.serial.timeout = timeout
rgb_data = struct.unpack(">76800H", image_data)
rgb_data = struct.unpack(">" + str(self.screenwidth * self.screenheight) + "H", image_data)
rgb_array = np.array(rgb_data, dtype=np.uint32)
rgba_array = (0xFF000000 +
((rgb_array & 0xF800) << 8) +
((rgb_array & 0x07E0) << 5) +
((rgb_array & 0x001F) << 3))
image = QtGui.QImage(rgba_array, 320, 240, QtGui.QImage.Format_ARGB32)
image = QtGui.QImage(rgba_array, self.screenwidth, self.screenheight, QtGui.QImage.Format_ARGB32)
logger.debug("Captured screenshot")
return QtGui.QPixmap(image)
except serial.SerialException as exc:
@ -318,7 +407,7 @@ class NanoVNA(VNA):
return self.readValues("data 1")
def resetSweep(self, start: int, stop: int):
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
self.writeSerial("resume")
def readVersion(self):
@ -348,9 +437,9 @@ class NanoVNA(VNA):
def setSweep(self, start, stop):
if self.useScan:
self.writeSerial("scan " + str(start) + " " + str(stop) + " 101")
self.writeSerial("scan " + str(start) + " " + str(stop) + " " + str(self.datapoints))
else:
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
sleep(1)
@ -358,8 +447,75 @@ class NanoVNA_H(NanoVNA):
name = "NanoVNA-H"
class NanoVNA_H4(NanoVNA):
name = "NanoVNA-H4"
screenwidth = 640
screenheight = 240
class NanoVNA_F(NanoVNA):
name = "NanoVNA-F"
screenwidth = 800
screenheight = 480
def getScreenshot(self) -> QtGui.QPixmap:
logger.debug("Capturing screenshot...")
if not self.serial.is_open:
return QtGui.QPixmap()
if self.app.serialLock.acquire():
try:
data = "a"
while data != "":
data = self.serial.readline().decode('ascii')
self.serial.write("capture\r".encode('ascii'))
timeout = self.serial.timeout
self.serial.timeout = 4
self.serial.readline()
image_data = self.serial.read(self.screenwidth * self.screenheight * 2)
self.serial.timeout = timeout
rgb_data = struct.unpack("<" + str(self.screenwidth * self.screenheight) + "H", image_data)
rgb_array = np.array(rgb_data, dtype=np.uint32)
rgba_array = (0xFF000000 +
((rgb_array & 0xF800) << 8) + # G?!
((rgb_array & 0x07E0) >> 3) + # B
((rgb_array & 0x001F) << 11)) # G
# logger.debug("Value yellow: %s", hex(rgb_array[10*400+36])) # This ought to be yellow
# logger.debug("Value white: %s", hex(rgb_array[50*400+261])) # This ought to be white
# logger.debug("Value cyan: %s", hex(rgb_array[10*400+252])) # This ought to be cyan
#
# rgba_array = (0xFF000000 + ((rgb_array & 0x001F) << 11)) # Exclusively green?
# rgba_array[10*400+36] = 0xFFFF0000
# rgba_array[50*400+261] = 0xFFFF0000
# rgba_array[10*400+252] = 0xFFFF0000
# At this point, the RGBA array is structured as 4 small images:
# 13
# 24
# each of which represents the pixels in a differently structured larger image:
# 12
# 34
# Let us unwrap.
unwrapped_array = np.empty(self.screenwidth*self.screenheight, dtype=np.uint32)
for y in range(self.screenheight//2):
for x in range(self.screenwidth//2):
unwrapped_array[2 * x + 2 * y * self.screenwidth] = rgba_array[x + y * self.screenwidth]
unwrapped_array[(2 * x) + 1 + 2 * y * self.screenwidth] = \
rgba_array[x + (self.screenheight//2 + y) * self.screenwidth]
unwrapped_array[2 * x + (2 * y + 1) * self.screenwidth] = \
rgba_array[x + self.screenwidth//2 + y * self.screenwidth]
unwrapped_array[(2 * x) + 1 + (2 * y + 1) * self.screenwidth] = \
rgba_array[x + self.screenwidth//2 + (self.screenheight//2 + y) * self.screenwidth]
image = QtGui.QImage(unwrapped_array, self.screenwidth, self.screenheight, QtGui.QImage.Format_ARGB32)
logger.debug("Captured screenshot")
return QtGui.QPixmap(image)
except serial.SerialException as exc:
logger.exception("Exception while capturing screenshot: %s", exc)
finally:
self.app.serialLock.release()
return QtGui.QPixmap()
class Version:

Wyświetl plik

@ -0,0 +1,154 @@
# NanoVNASaver
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from PyQt5 import QtWidgets, QtCore, QtGui
from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Marker import Marker
from NanoVNASaver.Marker.Values import TYPES, default_label_ids
logger = logging.getLogger(__name__)
class MarkerSettingsWindow(QtWidgets.QWidget):
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
Datapoint(123500000, 0.9, -0.1),
Datapoint(124000000, 0.91, -0.95)]
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
Datapoint(123456000, -0.3, 0.5),
Datapoint(124000000, -0.2, 0.5)]
def __init__(self, app: QtWidgets.QWidget):
super().__init__()
self.app = app
self.setWindowTitle("Marker settings")
self.setWindowIcon(self.app.icon)
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
self.exampleMarker = Marker("Example marker")
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
settings_group_box = QtWidgets.QGroupBox("Settings")
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
settings_group_box_layout.addRow(self.checkboxColouredMarker)
fields_group_box = QtWidgets.QGroupBox("Displayed data")
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
self.savedFieldSelection = self.app.settings.value(
"MarkerFields", defaultValue=default_label_ids()
)
if self.savedFieldSelection == "":
self.savedFieldSelection = []
self.currentFieldSelection = self.savedFieldSelection[:]
self.active_labels_view = QtWidgets.QListView()
self.update_displayed_data_form()
fields_group_box_layout.addRow(self.active_labels_view)
layout.addWidget(settings_group_box)
layout.addWidget(fields_group_box)
layout.addWidget(self.exampleMarker.getGroupBox())
btn_layout = QtWidgets.QHBoxLayout()
layout.addLayout(btn_layout)
btn_ok = QtWidgets.QPushButton("OK")
btn_apply = QtWidgets.QPushButton("Apply")
btn_default = QtWidgets.QPushButton("Defaults")
btn_cancel = QtWidgets.QPushButton("Cancel")
btn_ok.clicked.connect(self.okButtonClick)
btn_apply.clicked.connect(self.applyButtonClick)
btn_default.clicked.connect(self.defaultButtonClick)
btn_cancel.clicked.connect(self.cancelButtonClick)
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_apply)
btn_layout.addWidget(btn_default)
btn_layout.addWidget(btn_cancel)
self.updateMarker()
for m in self.app.markers:
m.setFieldSelection(self.currentFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def updateMarker(self):
self.exampleMarker.setFrequency(123456000)
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
self.exampleMarker.findLocation(self.exampleData11)
self.exampleMarker.resetLabels()
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
def updateField(self, field: QtGui.QStandardItem):
if field.checkState() == QtCore.Qt.Checked:
if not field.data() in self.currentFieldSelection:
self.currentFieldSelection = []
for i in range(self.model.rowCount()):
field = self.model.item(i, 0)
if field.checkState() == QtCore.Qt.Checked:
self.currentFieldSelection.append(field.data())
else:
if field.data() in self.currentFieldSelection:
self.currentFieldSelection.remove(field.data())
self.updateMarker()
def applyButtonClick(self):
self.savedFieldSelection = self.currentFieldSelection[:]
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
for m in self.app.markers:
m.setFieldSelection(self.savedFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def okButtonClick(self):
self.applyButtonClick()
self.close()
def cancelButtonClick(self):
self.currentFieldSelection = self.savedFieldSelection[:]
self.update_displayed_data_form()
self.updateMarker()
self.close()
def defaultButtonClick(self):
self.currentFieldSelection = default_label_ids()
self.update_displayed_data_form()
self.updateMarker()
def update_displayed_data_form(self):
self.model = QtGui.QStandardItemModel()
for label in TYPES:
item = QtGui.QStandardItem(label.description)
item.setData(label.label_id)
item.setCheckable(True)
item.setEditable(False)
if label.label_id in self.currentFieldSelection:
item.setCheckState(QtCore.Qt.Checked)
self.model.appendRow(item)
self.active_labels_view.setModel(self.model)
self.model.itemChanged.connect(self.updateField)

Wyświetl plik

@ -0,0 +1,86 @@
# NanoVNASaver
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import List, NamedTuple
from NanoVNASaver.RFTools import Datapoint
class Label(NamedTuple):
label_id: str
name: str
description: str
default_active: bool
TYPES = (
Label("actualfreq", "Frequency", "Actual frequency", True),
Label("impedance", "Impedance", "Impedance", True),
Label("admittance", "Admittance", "Admittance", False),
Label("serr", "Series R", "Series R", False),
Label("serlc", "Series X", "Series equivalent L/C", False),
Label("serl", "Series L", "Series equivalent L", True),
Label("serc", "Series C", "Series equivalent C", True),
Label("parr", "Parallel R", "Parallel R", True),
Label("parlc", "Parallel X", "Parallel equivalent L/C", True),
Label("parl", "Parallel L", "Parallel equivalent L", False),
Label("parc", "Parallel C", "Parallel equivalent C", False),
Label("vswr", "VSWR", "VSWR", True),
Label("returnloss", "Return loss", "Return loss", True),
Label("s11mag", "|S11|", "S11 Magnitude", False),
Label("s11q", "Quality factor", "S11 Quality factor", True),
Label("s11z", "S11 |Z|", "S11 Z Magnitude", False),
Label("s11phase", "S11 Phase", "S11 Phase", True),
Label("s11polar", "S11 Polar", "S11 Polar", False),
Label("s11groupdelay", "S11 Group Delay", "S11 Group Delay", False),
Label("s21gain", "S21 Gain", "S21 Gain", True),
Label("s21mag", "|S21|", "S21 Magnitude", False),
Label("s21phase", "S21 Phase", "S21 Phase", True),
Label("s21polar", "S21 Polar", "S21 Polar", False),
Label("s21groupdelay", "S21 Group Delay", "S21 Group Delay", False),
)
def default_label_ids() -> str:
return [l.label_id for l in TYPES if l.default_active]
class Value():
"""Contains the data area to calculate marker values from"""
def __init__(self, freq: int = 0,
s11data: List[Datapoint] = None,
s21data: List[Datapoint] = None):
self.freq = freq
self.s11data = [] if s11data is None else s11data[:]
self.s21data = [] if s21data is None else s21data[:]
def store(self, index: int,
s11data: List[Datapoint],
s21data: List[Datapoint]):
# handle boundaries
if index == 0:
index = 1
s11data = [s11data[0], ] + s11data
if s21data:
s21data = [s21data[0], ] + s21data
if index == len(s11data):
s11data = s11data + [s11data[-1], ]
if s21data:
s21data = s21data + [s21data[-1], ]
self.freq = s11data[1].freq
self.s11data = s11data[index-1:index+2]
if s21data:
self.s21data = s21data[index-1:index+2]

Wyświetl plik

@ -21,86 +21,84 @@ from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal
from NanoVNASaver import RFTools
from NanoVNASaver.Formatting import format_frequency, format_capacitance, format_inductance, format_complex_imp, \
format_resistance, format_vswr, format_phase, format_q_factor, format_gain, format_group_delay
from NanoVNASaver.Formatting import (
format_capacitance,
format_complex_imp,
format_frequency,
format_gain,
format_group_delay,
format_inductance,
format_magnitude,
format_phase,
format_q_factor,
format_resistance,
format_vswr,
)
from NanoVNASaver.Inputs import MarkerFrequencyInputWidget as FrequencyInput
from NanoVNASaver.Marker.Values import TYPES, Value, default_label_ids
COLORS = (
QtGui.QColor(QtCore.Qt.darkGray),
QtGui.QColor(255, 0, 0),
QtGui.QColor(0, 255, 0),
QtGui.QColor(0, 0, 255),
QtGui.QColor(0, 255, 255),
QtGui.QColor(255, 0, 255),
QtGui.QColor(255, 255, 0),
)
class Marker(QtCore.QObject):
name = "Marker"
frequency = 0
class MarkerLabel(QtWidgets.QLabel):
def __init__(self, name):
super().__init__("")
self.name = name
class Marker(QtCore.QObject, Value):
_instances = 0
color: QtGui.QColor = QtGui.QColor()
coloredText = True
location = -1
returnloss_is_positive = False
updated = pyqtSignal(object)
active_labels = []
fieldSelection = []
@classmethod
def count(cls):
return cls._instances
def __init__(self, name, initialColor, frequency=""):
super().__init__()
def __init__(self, name: str = "", qsettings: QtCore.QSettings = None):
super(QtCore.QObject, self).__init__()
self.name = name
self.qsettings = qsettings
self.name = name
self.color = QtGui.QColor()
self.index = 0
self.frequency = RFTools.RFTools.parseFrequency(frequency)
if self.qsettings:
Marker._instances += 1
Marker.active_labels = self.qsettings.value(
"MarkerFields", defaultValue=default_label_ids())
self.index = Marker._instances
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)
################################################################################################################
###############################################################
# Data display labels
################################################################################################################
###############################################################
self.frequency_label = QtWidgets.QLabel("")
self.frequency_label.setMinimumWidth(100)
self.impedance_label = QtWidgets.QLabel("")
self.admittance_label = QtWidgets.QLabel("")
self.parallel_r_label = QtWidgets.QLabel("")
self.parallel_x_label = QtWidgets.QLabel("")
self.parallel_c_label = QtWidgets.QLabel("")
self.parallel_l_label = QtWidgets.QLabel("")
self.returnloss_label = QtWidgets.QLabel("")
self.returnloss_label.setMinimumWidth(80)
self.vswr_label = QtWidgets.QLabel("")
self.series_r_label = QtWidgets.QLabel("")
self.series_x_label = QtWidgets.QLabel("")
self.inductance_label = QtWidgets.QLabel("")
self.capacitance_label = QtWidgets.QLabel("")
self.gain_label = QtWidgets.QLabel("")
self.s11_phase_label = QtWidgets.QLabel("")
self.s21_phase_label = QtWidgets.QLabel("")
self.s11_group_delay_label = QtWidgets.QLabel("")
self.s21_group_delay_label = QtWidgets.QLabel("")
self.s11_polar_label = QtWidgets.QLabel("")
self.s21_polar_label = QtWidgets.QLabel("")
self.quality_factor_label = QtWidgets.QLabel("")
self.fields = {
"actualfreq": ("Frequency:", self.frequency_label),
"impedance": ("Impedance:", self.impedance_label),
"admittance": ("Admittance:", self.admittance_label),
"s11polar": ("S11 Polar:", self.s11_polar_label),
"s21polar": ("S21 Polar:", self.s21_polar_label),
"serr": ("Series R:", self.series_r_label),
"serl": ("Series L:", self.inductance_label),
"serc": ("Series C:", self.capacitance_label),
"serlc": ("Series X:", self.series_x_label),
"parr": ("Parallel R:", self.parallel_r_label),
"parc": ("Parallel C:", self.parallel_c_label),
"parl": ("Parallel L:", self.parallel_l_label),
"parlc": ("Parallel X:", self.parallel_x_label),
"returnloss": ("Return loss:", self.returnloss_label),
"vswr": ("VSWR:", self.vswr_label),
"s11q": ("Quality factor:", self.quality_factor_label),
"s11phase": ("S11 Phase:", self.s11_phase_label),
"s11groupdelay": ("S11 Group Delay:", self.s11_group_delay_label),
"s21gain": ("S21 Gain:", self.gain_label),
"s21phase": ("S21 Phase:", self.s21_phase_label),
"s21groupdelay": ("S21 Group Delay:", self.s21_group_delay_label),
}
self.label = {}
for l in TYPES:
self.label[l.label_id] = MarkerLabel(l.name)
self.label['actualfreq'].setMinimumWidth(100)
self.label['returnloss'].setMinimumWidth(80)
###############################################################
# Marker control layout
@ -127,11 +125,19 @@ class Marker(QtCore.QObject):
self.group_box.setMaximumWidth(340)
box_layout = QtWidgets.QHBoxLayout(self.group_box)
self.setColor(initialColor)
try:
self.setColor(
self.qsettings.value(
f"Marker{self.count()}Color", COLORS[self.count()]))
except AttributeError: # happens when qsettings == None
self.setColor(COLORS[1])
except IndexError:
self.setColor(COLORS[0])
line = QtWidgets.QFrame()
line.setFrameShape(QtWidgets.QFrame.VLine)
# line only if more then 3 selected
self.left_form = QtWidgets.QFormLayout()
self.right_form = QtWidgets.QFormLayout()
box_layout.addLayout(self.left_form)
@ -140,13 +146,27 @@ class Marker(QtCore.QObject):
self.buildForm()
def __del__(self):
if self.qsettings:
Marker._instances -= 1
def _add_active_labels(self, label_id, form):
if label_id in self.label:
form.addRow(
f"{self.label[label_id].name}:", self.label[label_id])
self.label[label_id].show()
def _size_str(self) -> str:
return str(self.group_box.font().pointSize())
def update_settings(self):
self.qsettings.setValue(f"Marker{self.index}Color", self.color)
def setScale(self, scale):
self.group_box.setMaximumWidth(int(340 * scale))
self.frequency_label.setMinimumWidth(int(100 * scale))
self.returnloss_label.setMinimumWidth(int(80 * scale))
self.label['actualfreq'].setMinimumWidth(int(100 * scale))
self.label['actualfreq'].setMinimumWidth(int(100 * scale))
self.label['returnloss'].setMinimumWidth(int(80 * scale))
if self.coloredText:
color_string = QtCore.QVariant(self.color)
color_string.convert(QtCore.QVariant.String)
@ -170,34 +190,25 @@ class Marker(QtCore.QObject):
old_row.fieldItem.widget().hide()
old_row.labelItem.widget().hide()
if len(self.fieldSelection) <= 3:
for field in self.fieldSelection:
if field in self.fields:
label, value = self.fields[field]
self.left_form.addRow(label, value)
value.show()
if len(self.active_labels) <= 3:
for label_id in self.active_labels:
self._add_active_labels(label_id, self.left_form)
else:
left_half = math.ceil(len(self.fieldSelection)/2)
right_half = len(self.fieldSelection)
left_half = math.ceil(len(self.active_labels)/2)
right_half = len(self.active_labels)
for i in range(left_half):
field = self.fieldSelection[i]
if field in self.fields:
label, value = self.fields[field]
self.left_form.addRow(label, value)
value.show()
label_id = self.active_labels[i]
self._add_active_labels(label_id, self.left_form)
for i in range(left_half, right_half):
field = self.fieldSelection[i]
if field in self.fields:
label, value = self.fields[field]
self.right_form.addRow(label, value)
value.show()
label_id = self.active_labels[i]
self._add_active_labels(label_id, self.right_form)
def setFrequency(self, frequency):
self.frequency = RFTools.RFTools.parseFrequency(frequency)
self.freq = RFTools.RFTools.parseFrequency(frequency)
self.updated.emit(self)
def setFieldSelection(self, fields):
self.fieldSelection: List[str] = fields.copy()
self.active_labels = fields[:]
self.buildForm()
def setColor(self, color):
@ -229,9 +240,6 @@ class Marker(QtCore.QObject):
self.location = -1
self.frequencyInput.nextFrequency = -1
self.frequencyInput.previousFrequency = -1
if self.frequency <= 0:
# No frequency set for this marker
return
datasize = len(data)
if datasize == 0:
# Set the frequency before loading any data
@ -243,13 +251,14 @@ class Marker(QtCore.QObject):
upper_stepsize = data[-1].freq - data[-2].freq
# We are outside the bounds of the data, so we can't put in a marker
if self.frequency + lower_stepsize/2 < min_freq or self.frequency - upper_stepsize/2 > max_freq:
if (self.freq + lower_stepsize/2 < min_freq or
self.freq - upper_stepsize/2 > max_freq):
return
min_distance = max_freq
for i, item in enumerate(data):
if abs(item.freq - self.frequency) <= min_distance:
min_distance = abs(item.freq - self.frequency)
if abs(item.freq - self.freq) <= min_distance:
min_distance = abs(item.freq - self.freq)
else:
# We have now started moving away from the nearest point
self.location = i-1
@ -266,33 +275,16 @@ class Marker(QtCore.QObject):
return self.group_box
def resetLabels(self):
self.frequency_label.setText("")
self.impedance_label.setText("")
self.admittance_label.setText("")
self.s11_polar_label.setText("")
self.s21_polar_label.setText("")
self.parallel_r_label.setText("")
self.parallel_x_label.setText("")
self.parallel_l_label.setText("")
self.parallel_c_label.setText("")
self.series_x_label.setText("")
self.series_r_label.setText("")
self.inductance_label.setText("")
self.capacitance_label.setText("")
self.vswr_label.setText("")
self.returnloss_label.setText("")
self.gain_label.setText("")
self.s11_phase_label.setText("")
self.s21_phase_label.setText("")
self.s11_group_delay_label.setText("")
self.s21_group_delay_label.setText("")
self.quality_factor_label.setText("")
for v in self.label.values():
v.setText("")
def updateLabels(self,
s11data: List[RFTools.Datapoint],
s21data: List[RFTools.Datapoint]):
if self.location == -1:
return
self.store(self.location, s11data, s21data)
s11 = s11data[self.location]
imp = s11.impedance()
@ -313,35 +305,35 @@ class Marker(QtCore.QObject):
else:
x_p_str = ind_p_str
self.frequency_label.setText(format_frequency(s11.freq))
self.impedance_label.setText(format_complex_imp(imp))
self.series_r_label.setText(format_resistance(imp.real))
self.series_x_label.setText(x_str)
self.capacitance_label.setText(cap_str)
self.inductance_label.setText(ind_str)
self.admittance_label.setText(format_complex_imp(imp_p))
self.parallel_r_label.setText(format_resistance(imp_p.real))
self.parallel_x_label.setText(x_p_str)
self.parallel_c_label.setText(cap_p_str)
self.parallel_l_label.setText(ind_p_str)
self.vswr_label.setText(format_vswr(s11.vswr))
self.s11_phase_label.setText(format_phase(s11.phase))
self.quality_factor_label.setText(format_q_factor(s11.qFactor()))
self.returnloss_label.setText(format_gain(s11.gain, self.returnloss_is_positive))
self.s11_group_delay_label.setText(format_group_delay(RFTools.groupDelay(s11data, self.location)))
self.s11_polar_label.setText(str(round(abs(s11.z), 2)) + "" + format_phase(s11.phase))
self.label['actualfreq'].setText(format_frequency(s11.freq))
self.label['admittance'].setText(format_complex_imp(imp_p))
self.label['impedance'].setText(format_complex_imp(imp))
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))
self.label['returnloss'].setText(
format_gain(s11.gain, self.returnloss_is_positive))
self.label['s11groupdelay'].setText(
format_group_delay(RFTools.groupDelay(s11data, self.location)))
self.label['s11mag'].setText(format_magnitude(abs(s11.z)))
self.label['s11phase'].setText(format_phase(s11.phase))
self.label['s11polar'].setText(
str(round(abs(s11.z), 2)) + "" + format_phase(s11.phase))
self.label['s11q'].setText(format_q_factor(s11.qFactor()))
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))
self.label['vswr'].setText(format_vswr(s11.vswr))
if len(s21data) == len(s11data):
s21 = s21data[self.location]
self.s21_phase_label.setText(format_phase(s21.phase))
self.gain_label.setText(format_gain(s21.gain))
self.s21_group_delay_label.setText(format_group_delay(RFTools.groupDelay(s21data, self.location) / 2))
self.s21_polar_label.setText(str(round(abs(s21.z), 2)) + "" + format_phase(s21.phase))
self.label['s21gain'].setText(format_gain(s21.gain))
self.label['s21groupdelay'].setText(
format_group_delay(RFTools.groupDelay(s21data, self.location) / 2))
self.label['s21mag'].setText(format_magnitude(abs(s21.z)))
self.label['s21phase'].setText(format_phase(s21.phase))
self.label['s21polar'].setText(
str(round(abs(s21.z), 2)) + "" + format_phase(s21.phase))

Wyświetl plik

@ -0,0 +1,3 @@
from .Widget import Marker # noqa
from .Settings import MarkerSettingsWindow # noqa
from .Values import Value, default_label_ids # noqa

Wyświetl plik

@ -35,7 +35,7 @@ from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagC
GroupDelayChart, CapacitanceChart, InductanceChart
from .Calibration import CalibrationWindow, Calibration
from .Inputs import FrequencyInputWidget
from .Marker import Marker
from .Marker import Marker, MarkerSettingsWindow
from .SweepWorker import SweepWorker
from .Touchstone import Touchstone
from .Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, BandPassAnalysis, BandStopAnalysis, \
@ -50,13 +50,6 @@ logger = logging.getLogger(__name__)
class NanoVNASaver(QtWidgets.QWidget):
version = ver
default_marker_colors = [QtGui.QColor(255, 0, 0),
QtGui.QColor(0, 255, 0),
QtGui.QColor(0, 0, 255),
QtGui.QColor(0, 255, 255),
QtGui.QColor(255, 0, 255),
QtGui.QColor(255, 255, 0)]
dataAvailable = QtCore.pyqtSignal()
scaleFactor = 1
@ -303,12 +296,7 @@ class NanoVNASaver(QtWidgets.QWidget):
marker_count = max(self.settings.value("MarkerCount", 3, int), 1)
for i in range(marker_count):
if i < len(self.default_marker_colors):
default_color = self.default_marker_colors[i]
else:
default_color = QtGui.QColor(QtCore.Qt.darkGray)
color = self.settings.value("Marker" + str(i+1) + "Color", default_color)
marker = Marker("Marker " + str(i+1), color)
marker = Marker("", self.settings)
marker.updated.connect(self.markerUpdated)
label, layout = marker.getRow()
self.marker_control_layout.addRow(label, layout)
@ -493,11 +481,11 @@ class NanoVNASaver(QtWidgets.QWidget):
save_file_control_box.setMaximumWidth(300)
save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box)
btn_export_file = QtWidgets.QPushButton("Save file (S1P)")
btn_export_file = QtWidgets.QPushButton("Save 1-Port file (S1P)")
btn_export_file.clicked.connect(self.exportFileS1P)
save_file_control_layout.addRow(btn_export_file)
btn_export_file = QtWidgets.QPushButton("Save file (S2P)")
btn_export_file = QtWidgets.QPushButton("Save 2-Port file (S2P)")
btn_export_file.clicked.connect(self.exportFileS2P)
save_file_control_layout.addRow(btn_export_file)
@ -565,7 +553,7 @@ class NanoVNASaver(QtWidgets.QWidget):
filedialog = QtWidgets.QFileDialog(self)
filedialog.setDefaultSuffix("s1p")
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
filedialog.setNameFilter("Touchstone 1-Port Files (*.s1p);;All files (*.*)")
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
selected = filedialog.exec()
if selected:
@ -601,7 +589,7 @@ class NanoVNASaver(QtWidgets.QWidget):
filedialog = QtWidgets.QFileDialog(self)
filedialog.setDefaultSuffix("s2p")
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
filedialog.setNameFilter("Touchstone 2-Port Files (*.s2p);;All files (*.*)")
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
selected = filedialog.exec()
if selected:
@ -991,9 +979,9 @@ class NanoVNASaver(QtWidgets.QWidget):
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.worker.stopped = True
self.settings.setValue("MarkerCount", len(self.markers))
for i in range(len(self.markers)):
self.settings.setValue("Marker" + str(i+1) + "Color", self.markers[i].color)
self.settings.setValue("MarkerCount", Marker.count())
for marker in self.markers:
marker.update_settings()
self.settings.setValue("WindowHeight", self.height())
self.settings.setValue("WindowWidth", self.width())
@ -1671,38 +1659,27 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
QtWidgets.QApplication.setActiveWindow(self.marker_window)
def addMarker(self):
marker_count = len(self.app.markers)
if marker_count < 6:
color = NanoVNASaver.default_marker_colors[marker_count]
else:
color = QtGui.QColor(QtCore.Qt.darkGray)
new_marker = Marker("Marker " + str(marker_count+1), color)
new_marker.setColoredText(self.app.settings.value("ColoredMarkerNames", True, bool))
new_marker.setFieldSelection(self.app.settings.value("MarkerFields",
defaultValue=self.marker_window.defaultValue))
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())
new_marker.updated.connect(self.app.markerUpdated)
label, layout = new_marker.getRow()
self.app.marker_control_layout.insertRow(marker_count, label, layout)
if marker_count == 0:
new_marker.isMouseControlledRadioButton.setChecked(True)
self.app.marker_control_layout.insertRow(Marker.count() - 1, label, layout)
self.btn_remove_marker.setDisabled(False)
def removeMarker(self):
# keep at least one marker
if len(self.app.markers) <= 1:
if Marker.count() <= 1:
return
if len(self.app.markers) == 2:
if Marker.count() == 2:
self.btn_remove_marker.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(len(self.app.markers))
self.app.marker_control_layout.removeRow(Marker.count()-1)
last_marker.getGroupBox().hide()
last_marker.getGroupBox().destroy()
label, layout = last_marker.getRow()
@ -2409,186 +2386,6 @@ class AnalysisWindow(QtWidgets.QWidget):
self.app.dataAvailable.disconnect(self.runAnalysis)
class MarkerSettingsWindow(QtWidgets.QWidget):
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
Datapoint(123500000, 0.9, -0.1),
Datapoint(124000000, 0.91, -0.95)]
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
Datapoint(123456000, -0.3, 0.5),
Datapoint(124000000, -0.2, 0.5)]
fieldList = {"actualfreq": "Actual frequency",
"impedance": "Impedance",
"admittance": "Admittance",
"s11polar": "S11 Polar Form",
"s21polar": "S21 Polar Form",
"serr": "Series R",
"serlc": "Series equivalent L/C",
"serl": "Series equivalent L",
"serc": "Series equivalent C",
"parr": "Parallel R",
"parlc": "Parallel equivalent L/C",
"parl": "Parallel equivalent L",
"parc": "Parallel equivalent C",
"vswr": "VSWR",
"returnloss": "Return loss",
"s11q": "S11 Quality factor",
"s11phase": "S11 Phase",
"s11groupdelay": "S11 Group Delay",
"s21gain": "S21 Gain",
"s21phase": "S21 Phase",
"s21groupdelay": "S21 Group Delay",
}
defaultValue = ["actualfreq",
"impedance",
"serl",
"serc",
"parr",
"parlc",
"vswr",
"returnloss",
"s11q",
"s11phase",
"s21gain",
"s21phase"
]
def __init__(self, app: NanoVNASaver):
super().__init__()
self.app = app
self.setWindowTitle("Marker settings")
self.setWindowIcon(self.app.icon)
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
if len(self.app.markers) > 0:
color = self.app.markers[0].color
else:
color = self.app.default_marker_colors[0]
self.exampleMarker = Marker("Example marker", initialColor=color, frequency="123456000")
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
settings_group_box = QtWidgets.QGroupBox("Settings")
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
settings_group_box_layout.addRow(self.checkboxColouredMarker)
fields_group_box = QtWidgets.QGroupBox("Displayed data")
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
self.savedFieldSelection = self.app.settings.value("MarkerFields", defaultValue=self.defaultValue)
if self.savedFieldSelection == "":
self.savedFieldSelection = []
self.currentFieldSelection = self.savedFieldSelection.copy()
self.fieldSelectionView = QtWidgets.QListView()
self.model = QtGui.QStandardItemModel()
for field in self.fieldList:
item = QtGui.QStandardItem(self.fieldList[field])
item.setData(field)
item.setCheckable(True)
item.setEditable(False)
if field in self.currentFieldSelection:
item.setCheckState(QtCore.Qt.Checked)
self.model.appendRow(item)
self.fieldSelectionView.setModel(self.model)
self.model.itemChanged.connect(self.updateField)
fields_group_box_layout.addRow(self.fieldSelectionView)
layout.addWidget(settings_group_box)
layout.addWidget(fields_group_box)
layout.addWidget(self.exampleMarker.getGroupBox())
btn_layout = QtWidgets.QHBoxLayout()
layout.addLayout(btn_layout)
btn_ok = QtWidgets.QPushButton("OK")
btn_apply = QtWidgets.QPushButton("Apply")
btn_default = QtWidgets.QPushButton("Defaults")
btn_cancel = QtWidgets.QPushButton("Cancel")
btn_ok.clicked.connect(self.okButtonClick)
btn_apply.clicked.connect(self.applyButtonClick)
btn_default.clicked.connect(self.defaultButtonClick)
btn_cancel.clicked.connect(self.cancelButtonClick)
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_apply)
btn_layout.addWidget(btn_default)
btn_layout.addWidget(btn_cancel)
self.updateMarker()
for m in self.app.markers:
m.setFieldSelection(self.currentFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def updateMarker(self):
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
self.exampleMarker.findLocation(self.exampleData11)
self.exampleMarker.resetLabels()
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
def updateField(self, field: QtGui.QStandardItem):
if field.checkState() == QtCore.Qt.Checked:
if not field.data() in self.currentFieldSelection:
self.currentFieldSelection = []
for i in range(self.model.rowCount()):
field = self.model.item(i, 0)
if field.checkState() == QtCore.Qt.Checked:
self.currentFieldSelection.append(field.data())
else:
if field.data() in self.currentFieldSelection:
self.currentFieldSelection.remove(field.data())
self.updateMarker()
def applyButtonClick(self):
self.savedFieldSelection = self.currentFieldSelection.copy()
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
for m in self.app.markers:
m.setFieldSelection(self.savedFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked())
def okButtonClick(self):
self.applyButtonClick()
self.close()
def cancelButtonClick(self):
self.currentFieldSelection = self.savedFieldSelection.copy()
self.resetModel()
self.updateMarker()
self.close()
def defaultButtonClick(self):
self.currentFieldSelection = self.defaultValue.copy()
self.resetModel()
self.updateMarker()
def resetModel(self):
self.model = QtGui.QStandardItemModel()
for field in self.fieldList:
item = QtGui.QStandardItem(self.fieldList[field])
item.setData(field)
item.setCheckable(True)
item.setEditable(False)
if field in self.currentFieldSelection:
item.setCheckState(QtCore.Qt.Checked)
self.model.appendRow(item)
self.fieldSelectionView.setModel(self.model)
self.model.itemChanged.connect(self.updateField)
class DeviceSettingsWindow(QtWidgets.QWidget):
def __init__(self, app: NanoVNASaver):
super().__init__()

Wyświetl plik

@ -38,20 +38,15 @@ def parallel_to_serial(z: complex) -> complex:
def serial_to_parallel(z: complex) -> complex:
"""Convert serial impedance to parallel impedance equivalent"""
z_sq_sum = z.real ** 2 + z.imag ** 2
if z.real != 0 and z.imag != 0:
return complex(z_sq_sum / z.real, z_sq_sum / z.imag)
elif z.real != 0 and z_sq_sum > 0:
return complex(z_sq_sum / z.real, math.inf)
elif z.real != 0 and z_sq_sum < 0:
return complex(z_sq_sum / z.real, -math.inf)
elif z.imag != 0 and z_sq_sum > 0:
return complex(math.inf, z_sq_sum / z.real)
elif z.imag != 0 and z_sq_sum < 0:
return complex(-math.inf, z_sq_sum / z.real)
elif z_sq_sum == 0:
return complex(0, 0)
else:
if z.real == 0 and z.imag == 0:
return complex(math.inf, math.inf)
if z_sq_sum == 0:
return complex(0, 0)
if z.imag == 0:
return complex(z_sq_sum / z.real, math.copysign(math.inf, z_sq_sum))
if z.real == 0:
return complex(math.copysign(math.inf, z_sq_sum), z_sq_sum / z.real)
return complex(z_sq_sum / z.real, z_sq_sum / z.imag)
def impedance_to_capacitance(z: complex, freq: float) -> float:
@ -87,7 +82,10 @@ def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex:
def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex:
"""Calculate impedance from gamma"""
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
try:
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
except ZeroDivisionError:
return math.inf
def parseFrequency(freq: str) -> int:
@ -117,7 +115,7 @@ class Datapoint(NamedTuple):
mag = abs(self.z)
if mag > 0:
return 20 * math.log10(mag)
return 0
return -math.inf
@property
def vswr(self) -> float:
@ -174,4 +172,4 @@ class RFTools:
@staticmethod
def parseFrequency(freq: str) -> int:
return parseFrequency(freq)
return parseFrequency(freq)

Wyświetl plik

@ -59,7 +59,7 @@ class Value:
value: Union[Number, str] = 0,
unit: str = "",
fmt=Format()):
assert 3 <= fmt.max_nr_digits <= 30
assert 1 <= fmt.max_nr_digits <= 30
assert -8 <= fmt.min_offset <= fmt.max_offset <= 8
assert fmt.parse_clamp_min < fmt.parse_clamp_max
assert fmt.printable_min < fmt.printable_max
@ -91,7 +91,7 @@ class Value:
real = float(self._value) / (10 ** (offset * 3))
if fmt.max_nr_digits < 4:
if fmt.max_nr_digits < 3:
formstr = ".0f"
else:
max_digits = fmt.max_nr_digits + (

Wyświetl plik

@ -96,7 +96,7 @@ class SweepWorker(QtCore.QRunnable):
return
span = sweep_to - sweep_from
stepsize = int(span / (100 + (self.noSweeps-1)*101))
stepsize = int(span / (self.vna.datapoints-1 + (self.noSweeps-1)*self.vna.datapoints))
# Setup complete
@ -110,14 +110,14 @@ class SweepWorker(QtCore.QRunnable):
if self.stopped:
logger.debug("Stopping sweeping as signalled")
break
start = sweep_from + i * 101 * stepsize
freq, val11, val21 = self.readAveragedSegment(start, start + 100 * stepsize, self.averages)
start = sweep_from + i * self.vna.datapoints * stepsize
freq, val11, val21 = self.readAveragedSegment(start, start + (self.vna.datapoints-1) * stepsize, self.averages)
frequencies += freq
values += val11
values21 += val21
self.percentage = (i + 1) * 100 / self.noSweeps
self.percentage = (i + 1) * (self.vna.datapoints-1) / self.noSweeps
logger.debug("Saving acquired data")
self.saveData(frequencies, values, values21)
@ -127,9 +127,9 @@ class SweepWorker(QtCore.QRunnable):
if self.stopped:
logger.debug("Stopping sweeping as signalled")
break
start = sweep_from + i*101*stepsize
start = sweep_from + i*self.vna.datapoints*stepsize
try:
freq, val11, val21 = self.readSegment(start, start+100*stepsize)
freq, val11, val21 = self.readSegment(start, start+(self.vna.datapoints-1)*stepsize)
frequencies += freq
values += val11
@ -156,11 +156,11 @@ class SweepWorker(QtCore.QRunnable):
if self.stopped:
logger.debug("Stopping sweeping as signalled")
break
start = sweep_from + i * 101 * stepsize
start = sweep_from + i * self.vna.datapoints * stepsize
try:
_, values, values21 = self.readSegment(start, start + 100 * stepsize)
_, values, values21 = self.readSegment(start, start + (self.vna.datapoints-1) * stepsize)
logger.debug("Updating acquired data")
self.updateData(values, values21, i)
self.updateData(values, values21, i, self.vna.datapoints)
except NanoVNAValueException as e:
self.error_message = str(e)
self.stopped = True
@ -196,8 +196,8 @@ class SweepWorker(QtCore.QRunnable):
raw_data21 = Datapoint(freq, re21, im21)
data11, data21 = self.applyCalibration([raw_data11], [raw_data21])
self.data11[offset * segment_size + i] = data11
self.data21[offset * segment_size + i] = data21
self.data11[offset * segment_size + i] = data11[0]
self.data21[offset * segment_size + i] = data21[0]
self.rawData11[offset * segment_size + i] = raw_data11
self.rawData21[offset * segment_size + i] = raw_data21
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))

Wyświetl plik

@ -139,7 +139,7 @@ class Touchstone:
z = cmath.rect(float(v), math.radians(float(next(vals))))
next(data_list).append(Datapoint(freq, z.real, z.imag))
if self.opts.format == "db":
z = cmath.rect(math.exp(float(v) / 20), math.radians(float(next(vals))))
z = cmath.rect(10 ** (float(v) / 20), math.radians(float(next(vals))))
next(data_list).append(Datapoint(freq, z.real, z.imag))
def load(self):

Wyświetl plik

@ -14,5 +14,5 @@
# 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.2.2'
version = '0.2.3'
debug = False

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,2 @@
# MHz S RI R 50
28.000000 0.0 0.0

11
test/data/db.s2p 100644
Wyświetl plik

@ -0,0 +1,11 @@
# HZ S DB R 50
1.465003750937734E9 -1.396696934098445E1 3.349568174026075 -2.261174260421613 1.620714227806657E2 -2.255142489055356 1.626927267495817E2 -1.442619251233750E1 4.509019031432469
1.465503875968992E9 -1.369876098541207E1 3.479938266374748 -2.224036324693064 1.631219183133622E2 -2.230062353322664 1.612120965710581E2 -1.353367611139944E1 1.954291818822123
1.466004001000250E9 -1.379085768177502E1 6.788018243134909 -2.234675921557394 1.616283462983217E2 -2.241155297558765 1.629287928899836E2 -1.404466816442995E1 2.475833229081635
1.466504126031508E9 -1.424627608260705E1 4.706658182189423 -2.267732265261066 1.630281926898599E2 -2.279113484903308 1.637911206096939E2 -1.403985522782244E1 6.258852806593705
1.467004251062766E9 -1.388823218559945E1 3.744481757023677 -2.243118570936963 1.619407156405345E2 -2.256689625298824 1.620401320385134E2 -1.437657711076861E1 4.378386297027917
1.467504376094024E9 -1.363131206363558E1 4.481745062167305 -2.234202921073654 1.609017179760186E2 -2.231499875527908 1.621150648113463E2 -1.403567006946793E1 6.008756896907650
1.468004501125281E9 -1.357308913865166E1 6.752417012204787 -2.190762960848804 1.592255394852820E2 -2.213893899730369 1.604919620912775E2 -1.392469613271226E1 5.802146694755130
1.468504626156539E9 -1.328017288789247E1 4.347321859152585 -2.211094613088734 1.590669733070027E2 -2.207418277887152 1.574936196343803E2 -1.424893658605293E1 2.965770345166085
1.469004751187797E9 -1.308760188829710E1 1.035833400693584E1 -2.213146425167909 1.605810413752405E2 -2.241527794979899 1.595688676701842E2 -1.413210094282936E1 7.337633110667883
1.469504876219055E9 -1.441098551455218E1 9.046042843197457 -2.219676590923017 1.602570163112450E2 -2.255210742654332 1.603381378830466E2 -1.369031345080353E1 7.167051366612957

11
test/data/ma.s2p 100644
Wyświetl plik

@ -0,0 +1,11 @@
# MHZ S MA R 50
10.0000 0.915 -63.193 57.033 142.886 0.013 51.862 0.525 -89.730
20.0000 0.830 -99.758 40.867 122.518 0.021 32.533 0.638 -123.891
30.0000 0.787 -121.018 30.176 110.861 0.022 20.814 0.684 -140.388
40.0000 0.765 -134.056 23.331 103.406 0.023 15.181 0.708 -149.561
50.0000 0.757 -141.913 19.148 98.789 0.023 10.360 0.719 -154.726
60.0000 0.752 -147.447 16.060 94.882 0.024 5.949 0.724 -158.168
70.0000 0.751 -151.523 13.785 91.746 0.024 2.789 0.732 -160.919
80.0000 0.750 -154.603 12.098 88.870 0.024 2.787 0.735 -162.634
90.0000 0.751 -156.729 10.716 86.488 0.024 -0.815 0.740 -164.178
100.000 0.750 -158.996 9.593 84.260 0.024 -3.226 0.742 -165.291

Wyświetl plik

@ -0,0 +1,112 @@
# NanoVNASaver
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import unittest
# Import targets to be tested
from NanoVNASaver import Formatting as fmt
class TestCases(unittest.TestCase):
def test_format_frequency(self):
self.assertEqual(fmt.format_frequency(1), '1.00000 Hz')
self.assertEqual(fmt.format_frequency(12), '12.0000 Hz')
self.assertEqual(fmt.format_frequency(123), '123.000 Hz')
self.assertEqual(fmt.format_frequency(1234), '1.23400 kHz')
self.assertEqual(fmt.format_frequency(1234567), '1.23457 MHz')
self.assertEqual(fmt.format_frequency(1234567890), '1.23457 GHz')
self.assertEqual(fmt.format_frequency(0), '0.00000 Hz')
self.assertEqual(fmt.format_frequency(-1), '-1.00000 Hz')
def test_format_frequency_inputs(self):
self.assertEqual(fmt.format_frequency_inputs(1), '1Hz')
self.assertEqual(fmt.format_frequency_inputs(12), '12Hz')
self.assertEqual(fmt.format_frequency_inputs(123), '123Hz')
self.assertEqual(fmt.format_frequency_inputs(1234), '1.234kHz')
self.assertEqual(fmt.format_frequency_inputs(1234567), '1.234567MHz')
self.assertEqual(fmt.format_frequency_inputs(1234567890), '1.23456789GHz')
self.assertEqual(fmt.format_frequency_inputs(0), '0Hz')
self.assertEqual(fmt.format_frequency_inputs(-1), '- Hz')
def test_format_gain(self):
self.assertEqual(fmt.format_gain(1), '1.000 dB')
self.assertEqual(fmt.format_gain(12), '12.000 dB')
self.assertEqual(fmt.format_gain(1.23456), '1.235 dB')
self.assertEqual(fmt.format_gain(-1), '-1.000 dB')
self.assertEqual(fmt.format_gain(-1, invert=True), '1.000 dB')
def test_format_q_factor(self):
self.assertEqual(fmt.format_q_factor(1), '1')
self.assertEqual(fmt.format_q_factor(12), '12')
self.assertEqual(fmt.format_q_factor(123), '123')
self.assertEqual(fmt.format_q_factor(1234), '1234')
self.assertEqual(fmt.format_q_factor(12345), '\N{INFINITY}')
self.assertEqual(fmt.format_q_factor(-1), '\N{INFINITY}')
self.assertEqual(fmt.format_q_factor(1.2345), '1.234')
def test_format_vswr(self):
self.assertEqual(fmt.format_vswr(1), '1.000')
self.assertEqual(fmt.format_vswr(1.234), '1.234')
self.assertEqual(fmt.format_vswr(12345.12345), '12345.123')
def test_format_resistance(self):
self.assertEqual(fmt.format_resistance(1), '1 \N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(12), '12 \N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(123), '123 \N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(1234), '1.234 k\N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(12345), '12.345 k\N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(123456), '123.46 k\N{OHM SIGN}')
self.assertEqual(fmt.format_resistance(-1), '- \N{OHM SIGN}')
def test_format_capacitance(self):
self.assertEqual(fmt.format_capacitance(1), '1 F')
self.assertEqual(fmt.format_capacitance(1e-3), '1 mF')
self.assertEqual(fmt.format_capacitance(1e-6), '1 µF')
self.assertEqual(fmt.format_capacitance(1e-9), '1 nF')
self.assertEqual(fmt.format_capacitance(1e-12), '1 pF')
self.assertEqual(fmt.format_capacitance(-1), '-1 F')
self.assertEqual(fmt.format_capacitance(-1, False), '- pF')
def test_format_inductance(self):
self.assertEqual(fmt.format_inductance(1), '1 H')
self.assertEqual(fmt.format_inductance(1e-3), '1 mH')
self.assertEqual(fmt.format_inductance(1e-6), '1 µH')
self.assertEqual(fmt.format_inductance(1e-9), '1 nH')
self.assertEqual(fmt.format_inductance(1e-12), '1 pH')
self.assertEqual(fmt.format_inductance(-1), '-1 H')
self.assertEqual(fmt.format_inductance(-1, False), '- nH')
def test_format_group_delay(self):
self.assertEqual(fmt.format_group_delay(1), '1.0000 s')
self.assertEqual(fmt.format_group_delay(1e-9), '1.0000 ns')
self.assertEqual(fmt.format_group_delay(1.23456e-9), '1.2346 ns')
def test_format_phase(self):
self.assertEqual(fmt.format_phase(0), '0.00°')
self.assertEqual(fmt.format_phase(1), '57.30°')
self.assertEqual(fmt.format_phase(-1), '-57.30°')
self.assertEqual(fmt.format_phase(3.1416), '180.00°')
self.assertEqual(fmt.format_phase(6.2831), '360.00°')
self.assertEqual(fmt.format_phase(9.4247), '540.00°')
self.assertEqual(fmt.format_phase(-3.1416), '-180.00°')
def test_format_complex_imp(self):
self.assertEqual(fmt.format_complex_imp(complex(1, 0)), '1+j0 \N{OHM SIGN}')
self.assertEqual(fmt.format_complex_imp(complex(1234, 1234)), '1.23k+j1.23k \N{OHM SIGN}')
self.assertEqual(fmt.format_complex_imp(complex(1234, -1234)), '1.23k-j1.23k \N{OHM SIGN}')
self.assertEqual(fmt.format_complex_imp(complex(1.234, 1234)), '1.23+j1.23k \N{OHM SIGN}')
self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3)), '- +j1.23m \N{OHM SIGN}')

Wyświetl plik

@ -21,7 +21,8 @@ from NanoVNASaver.RFTools import Datapoint, \
norm_to_impedance, impedance_to_norm, \
reflection_coefficient, gamma_to_impedance, clamp_value, \
parallel_to_serial, serial_to_parallel, \
impedance_to_capacitance, impedance_to_inductance
impedance_to_capacitance, impedance_to_inductance, \
groupDelay
import math
@ -63,6 +64,7 @@ class TestRFTools(unittest.TestCase):
def test_gamma_to_impedance(self):
self.assertEqual(gamma_to_impedance(0), 50)
self.assertAlmostEqual(gamma_to_impedance(0.2), 75)
self.assertEqual(gamma_to_impedance(1), math.inf)
# TODO: insert more test values here
def test_clamp_value(self):
@ -94,6 +96,20 @@ class TestRFTools(unittest.TestCase):
impedance_to_inductance(complex(50, 159.1549), 100000),
2.533e-4)
def test_groupDelay(self):
dpoints = [
Datapoint(100000, 0.1091, 0.3118),
Datapoint(100001, 0.1091, 0.3124),
Datapoint(100002, 0.1091, 0.3130),
]
dpoints0 = [
Datapoint(100000, 0.1091, 0.3118),
Datapoint(100000, 0.1091, 0.3124),
Datapoint(100000, 0.1091, 0.3130),
]
self.assertAlmostEqual(groupDelay(dpoints, 1), -9.514e-5)
self.assertEqual(groupDelay(dpoints0, 1), 0.0)
class TestRFToolsDatapoint(unittest.TestCase):
@ -101,11 +117,12 @@ class TestRFToolsDatapoint(unittest.TestCase):
self.dp = Datapoint(100000, 0.1091, 0.3118)
self.dp0 = Datapoint(100000, 0, 0)
self.dp50 = Datapoint(100000, 1, 0)
self.dp75 = Datapoint(100000, 0.2, 0)
def test_properties(self):
self.assertEqual(self.dp.z, complex(0.1091, 0.3118))
self.assertAlmostEqual(self.dp.phase, 1.23420722)
self.assertEqual(self.dp0.gain, 0.0)
self.assertEqual(self.dp0.gain, -math.inf)
self.assertAlmostEqual(self.dp.gain, -9.6208748)
self.assertEqual(self.dp50.vswr, 1.0)
self.assertAlmostEqual(self.dp.vswr, 1.9865736)
@ -114,4 +131,7 @@ class TestRFToolsDatapoint(unittest.TestCase):
self.assertAlmostEqual(self.dp.impedance(75),
complex(74.99628755, 52.49617517))
self.assertEqual(self.dp0.qFactor(), 0.0)
self.assertEqual(self.dp75.qFactor(), 0.0)
self.assertAlmostEqual(self.dp.qFactor(), 0.6999837)
self.assertAlmostEqual(self.dp.capacitiveEquivalent(), -4.54761539e-08)
self.assertAlmostEqual(self.dp.inductiveEquivalent(), 5.57001e-05)

Wyświetl plik

@ -23,13 +23,15 @@ from math import inf
F_DEFAULT = Format()
F_ASSERT_DIGITS_1 = Format(max_nr_digits=2)
F_ASSERT_DIGITS_0 = Format(max_nr_digits=0)
F_ASSERT_DIGITS_2 = Format(max_nr_digits=31)
F_ASSERT_OFFSET_1 = Format(min_offset=-9)
F_ASSERT_OFFSET_2 = Format(max_offset=9)
F_ASSERT_OFFSET_3 = Format(min_offset=9)
F_ASSERT_CLAMP = Format(parse_clamp_min=10, parse_clamp_max=9)
F_DIGITS_1 = Format(max_nr_digits=1, min_offset=-2,
max_offset=2, assume_infinity=False)
F_DIGITS_3 = Format(max_nr_digits=3, min_offset=-2,
max_offset=2, assume_infinity=False)
F_DIGITS_4 = Format(max_nr_digits=4)
@ -42,7 +44,7 @@ F_WITH_UNDERSCORE = Format(space_str="_")
class TestTSIToolsValue(unittest.TestCase):
def test_format_assertions(self):
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_1)
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_0)
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_2)
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_1)
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_2)
@ -95,8 +97,8 @@ class TestTSIToolsValue(unittest.TestCase):
self.assertEqual(str(Value(1e-30)), "0.00000")
self.assertEqual(float(Value(1e-30)), 1e-30)
def test_format_digits_3(self):
v = Value(fmt=F_DIGITS_3)
def test_format_digits_1(self):
v = Value(fmt=F_DIGITS_1)
self.assertEqual(str(v.parse("1")), "1")
self.assertEqual(str(v.parse("10")), "10")
self.assertEqual(str(v.parse("100")), "100")
@ -109,6 +111,20 @@ class TestTSIToolsValue(unittest.TestCase):
self.assertEqual(str(v.parse("1e-3")), "1m")
self.assertEqual(str(v.parse("1e-9")), "0")
def test_format_digits_3(self):
v = Value(fmt=F_DIGITS_3)
self.assertEqual(str(v.parse("1")), "1.00")
self.assertEqual(str(v.parse("10")), "10.0")
self.assertEqual(str(v.parse("100")), "100")
self.assertEqual(str(v.parse("1e3")), "1.00k")
self.assertEqual(str(v.parse("1e4")), "10.0k")
self.assertEqual(str(v.parse("1e5")), "100k")
self.assertEqual(str(v.parse("1e9")), "1000M")
self.assertEqual(str(v.parse("1e-1")), "100m")
self.assertEqual(str(v.parse("1e-2")), "10.0m")
self.assertEqual(str(v.parse("1e-3")), "1.00m")
self.assertEqual(str(v.parse("1e-9")), "0.00")
def test_format_digits_4(self):
v = Value(fmt=F_DIGITS_4)
self.assertEqual(str(v.parse("1")), "1.000")

Wyświetl plik

@ -73,6 +73,29 @@ class TestTouchstoneTouchstone(unittest.TestCase):
self.assertEqual(len(ts.s22data), 1020)
self.assertIn("! Vector Network Analyzer VNA R2", ts.comments)
ts = Touchstone("./test/data/ma.s2p")
ts.load()
self.assertEqual(str(ts.opts), "# MHZ S MA R 50")
ts = Touchstone("./test/data/db.s2p")
ts.load()
self.assertEqual(str(ts.opts), "# HZ S DB R 50")
def test_db_conversation(self):
ts_db = Touchstone("./test/data/attenuator-0643_DB.s2p")
ts_db.load()
ts_ri = Touchstone("./test/data/attenuator-0643_RI.s2p")
ts_ri.load()
ts_ma = Touchstone("./test/data/attenuator-0643_MA.s2p")
ts_ma.load()
self.assertEqual(len(ts_db.s11data), len(ts_ri.s11data))
for dps_db, dps_ri in zip(ts_db.s11data, ts_ri.s11data):
self.assertAlmostEqual(dps_db.z, dps_ri.z, places=5)
self.assertEqual(len(ts_db.s11data), len(ts_ma.s11data))
for dps_db, dps_ma in zip(ts_db.s11data, ts_ma.s11data):
self.assertAlmostEqual(dps_db.z, dps_ma.z, places=5)
def test_load_scikit(self):
ts = Touchstone("./test/data/scikit_unordered.s2p")
with self.assertLogs(level=logging.WARNING) as cm:

Wyświetl plik

@ -31,7 +31,7 @@ import unittest
if __name__ == '__main__':
sys.path.append('.')
loader = unittest.TestLoader()
tests = loader.discover('.')
tests = loader.discover('./test')
testRunner = unittest.runner.TextTestRunner(
failfast=False,
verbosity=2)