diff --git a/.github/workflows/release_linux.yml b/.github/workflows/release_linux.yml
index aff68d41..da330909 100644
--- a/.github/workflows/release_linux.yml
+++ b/.github/workflows/release_linux.yml
@@ -28,9 +28,9 @@ jobs:
run: |
python3.11 -m venv build
. build/bin/activate
- python -m pip install pip==24.1 setuptools==70.1.0
+ python -m pip install pip==24.2 setuptools==75.2.0
pip install -r requirements.txt
- pip install PyInstaller==6.8.0
+ pip install PyInstaller==6.11.0
python3 -m pip install -U PyQt6-sip
python3 -m pip install -U PyQt6
- name: Build binary
diff --git a/.github/workflows/release_macos.yml b/.github/workflows/release_macos.yml
deleted file mode 100644
index ba5905dc..00000000
--- a/.github/workflows/release_macos.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: Mac Release
-
-on:
- push:
- tags:
- - v*
- workflow_dispatch:
-
-jobs:
- release:
- runs-on: macos-latest
-
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: 3.11
- - name: Install dependencies and pyinstall
- run: |
- python -m pip install pip==23.3.2 setuptools==69.0.3
- pip install -r requirements.txt
- pip install PyInstaller==6.3.0
- - name: Build binary
- run: |
- python setup.py -V
- pyinstaller --onefile -p src -n nanovna-saver nanovna-saver.py
-
- - name: Archive production artifacts
- uses: actions/upload-artifact@v1
- with:
- name: NanoVNASaver.macos
- path: dist/nanovna-saver
diff --git a/.github/workflows/release_macos_app.yml b/.github/workflows/release_macos_app.yml
deleted file mode 100644
index 135146ca..00000000
--- a/.github/workflows/release_macos_app.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-name: Mac Release App
-
-on:
- push:
- tags:
- - v*
- workflow_dispatch:
-
-jobs:
- release:
- runs-on: macos-latest
-
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: 3.11
- - name: Get Target Environment
- id: targetenv
- run: |
- echo "arch=`uname -m`" >> "$GITHUB_ENV"
-
- - name: Install dependencies and pyinstall
- run: |
- python -m pip install pip==23.3.2 setuptools==69.0.3
- pip install -r requirements.txt
- pip install PyInstaller==6.3.0
-
- - name: Build binary
- run: |
- python setup.py -V
- pyinstaller --onedir -p src -n NanoVNASaver nanovna-saver.py --window --clean -y -i icon_48x48.icns
- tar -C dist -zcf ./dist/NanoVNASaver.app-${{ env.arch }}.tar.gz NanoVNASaver.app
- echo "Created: NanoVNASaver.app-${{ env.arch }}.tar.gz"
-
- - name: Archive production artifacts
- uses: actions/upload-artifact@v1
- with:
- name: NanoVNASaver.app-${{ env.arch }}.tar.gz
- path: dist/NanoVNASaver.app-${{ env.arch }}.tar.gz
diff --git a/.github/workflows/release_win.yml b/.github/workflows/release_win.yml
index 536d4d28..1ed2d3b5 100644
--- a/.github/workflows/release_win.yml
+++ b/.github/workflows/release_win.yml
@@ -26,10 +26,10 @@ jobs:
run: |
python3 -m venv venv
.\venv\Scripts\activate
- python3 -m pip install pip==24.1
+ python3 -m pip install pip==24.2
python3 -m pip install -U setuptools setuptools-scm
python3 -m pip install -r requirements.txt
- python3 -m pip install PyInstaller==6.8.0
+ python3 -m pip install PyInstaller==6.11.0
python3 -m pip install -U PyQt6-sip
python3 -m pip install -U PyQt6
- name: Build binary
diff --git a/Makefile b/Makefile
index f480badf..781da4b0 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,7 @@ rpm: clean
# remove all package build artifacts (keep the *.deb)
.PHONY: clean
clean:
- python setup.py clean
+ python3 setup.py clean
-rm -rf build deb_dist dist *.tar.gz *.egg*
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 72385751..784a66c7 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -58,11 +58,11 @@ or
`make rpm` builds an (untested) rpm package that can be installed on your system the usual way.
-## Ubuntu 20.04 / 22.04
+## Ubuntu 20.04 / 22.04 / 24.04
1. Install python3 and pip
- sudo apt install python3 python3-pip
+ sudo apt install python3 python3-pip libxcb-cursor-dev
python3 -m venv ~/.venv_nano
. ~/.venv_nano/bin/activate
pip install -U pip
diff --git a/pyproject.toml b/pyproject.toml
index cd97f78a..7fe18585 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,3 +17,6 @@ write_to = "src/NanoVNASaver/_version.py"
pythonpath = [
".", "src",
]
+
+[tool.black]
+line-length = 80
diff --git a/requirements.txt b/requirements.txt
index 2d4f3809..81353295 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
pyserial==3.5
-PyQt6==6.7.0
-PyQt6-sip==13.6.0
-sip==6.8.4
-numpy==1.26.4
-scipy==1.13.1
-Cython==3.0.10
-setuptools==70.1.0
+PyQt6==6.7.1
+PyQt6-sip==13.8.0
+sip==6.8.6
+numpy==2.1.2
+scipy==1.14.1
+Cython==3.0.11
+setuptools==75.2.0
setuptools-scm==8.1.0
diff --git a/src/NanoVNASaver/About.py b/src/NanoVNASaver/About.py
index 8625a96d..af59ae9e 100644
--- a/src/NanoVNASaver/About.py
+++ b/src/NanoVNASaver/About.py
@@ -18,8 +18,9 @@
# along with this program. If not, see .
from setuptools_scm import get_version
+
try:
- version = get_version(root='..', relative_to=__file__)
+ version = get_version(root="../..", relative_to=__file__)
except LookupError:
from NanoVNASaver._version import version
diff --git a/src/NanoVNASaver/Charts/TDR.py b/src/NanoVNASaver/Charts/TDR.py
index a56faace..84fe88b9 100644
--- a/src/NanoVNASaver/Charts/TDR.py
+++ b/src/NanoVNASaver/Charts/TDR.py
@@ -37,14 +37,28 @@
logger = logging.getLogger(__name__)
+MIN_IMPEDANCE = 0
+MAX_IMPEDANCE = 1000
+
+MIN_S11 = -60
+MAX_S11 = 0
+
+MIN_VSWR = 1
+MAX_VSWR = 10
+
class TDRChart(Chart):
maxDisplayLength = 50
minDisplayLength = 0
fixedSpan = False
- minImpedance = 0
- maxImpedance = 1000
+ minYlim = 0
+ maxYlim = 1000
+
+ decimals = 1
+
+ formatString = ""
+
fixedValues = False
markerLocation = -1
@@ -105,7 +119,7 @@ def __init__(self, name):
self.x_menu.addAction(self.action_set_fixed_start)
self.x_menu.addAction(self.action_set_fixed_stop)
- self.y_menu = QMenu("Impedance axis")
+ self.y_menu = QMenu("Y axis")
self.y_mode_group = QActionGroup(self.y_menu)
self.y_action_automatic = QAction("Automatic")
self.y_action_automatic.setCheckable(True)
@@ -124,19 +138,11 @@ def __init__(self, name):
self.y_menu.addAction(self.y_action_fixed)
self.y_menu.addSeparator()
- self.y_action_set_fixed_maximum = QAction(
- f"Maximum ({self.maxImpedance})"
- )
- self.y_action_set_fixed_maximum.triggered.connect(
- self.setMaximumImpedance
- )
+ self.y_action_set_fixed_maximum = QAction(f"Maximum ({self.maxYlim})")
+ self.y_action_set_fixed_maximum.triggered.connect(self.setMaximumY)
- self.y_action_set_fixed_minimum = QAction(
- f"Minimum ({self.minImpedance})"
- )
- self.y_action_set_fixed_minimum.triggered.connect(
- self.setMinimumImpedance
- )
+ self.y_action_set_fixed_minimum = QAction(f"Minimum ({self.minYlim})")
+ self.y_action_set_fixed_minimum.triggered.connect(self.setMinimumY)
self.y_menu.addAction(self.y_action_set_fixed_maximum)
self.y_menu.addAction(self.y_action_set_fixed_minimum)
@@ -157,12 +163,8 @@ def __init__(self, name):
def contextMenuEvent(self, event):
self.action_set_fixed_start.setText(f"Start ({self.minDisplayLength})")
self.action_set_fixed_stop.setText(f"Stop ({self.maxDisplayLength})")
- self.y_action_set_fixed_minimum.setText(
- f"Minimum ({self.minImpedance})"
- )
- self.y_action_set_fixed_maximum.setText(
- f"Maximum ({self.maxImpedance})"
- )
+ self.y_action_set_fixed_minimum.setText(f"Minimum ({self.minYlim})")
+ self.y_action_set_fixed_maximum.setText(f"Maximum ({self.maxYlim})")
self.menu.exec(event.globalPos())
def isPlotable(self, x, y):
@@ -171,13 +173,30 @@ def isPlotable(self, x, y):
and self.topMargin <= y <= self.height() - self.bottomMargin
)
+ def _configureGraphFromFormat(self):
+ TDR_format = self.tdrWindow.format_dropdown.currentText()
+ if TDR_format == "|Z|":
+ self.minYlim = MIN_IMPEDANCE
+ self.maxYlim = MAX_IMPEDANCE
+ self.formatString = "impedance (\N{OHM SIGN})"
+ self.decimals = 1
+ elif TDR_format == "S11":
+ self.minYlim = MIN_S11
+ self.maxYlim = MAX_S11
+ self.formatString = "S11 (dB)"
+ self.decimals = 1
+ elif TDR_format == "VSWR":
+ self.minYlim = MIN_VSWR
+ self.maxYlim = MAX_VSWR
+ self.formatString = "VSWR"
+ self.decimals = 2
+
def resetDisplayLimits(self):
+ self._configureGraphFromFormat()
self.fixedSpan = False
self.minDisplayLength = 0
self.maxDisplayLength = 100
self.fixedValues = False
- self.minImpedance = 0
- self.maxImpedance = 1000
self.update()
def setFixedSpan(self, fixed_span):
@@ -205,7 +224,7 @@ def setMaximumLength(self):
self,
"Stop length (m)",
"Set stop length (m)",
- value=self.minDisplayLength,
+ value=self.maxDisplayLength,
min=0.1,
decimals=1,
)
@@ -220,35 +239,33 @@ def setFixedValues(self, fixed_values):
self.fixedValues = fixed_values
self.update()
- def setMinimumImpedance(self):
+ def setMinimumY(self):
min_val, selected = QInputDialog.getDouble(
self,
- "Minimum impedance (\N{OHM SIGN})",
- "Set minimum impedance (\N{OHM SIGN})",
- value=self.minDisplayLength,
- min=0,
- decimals=1,
+ "Minimum " + self.formatString,
+ "Set minimum " + self.formatString,
+ value=self.minYlim,
+ decimals=self.decimals,
)
if not selected:
return
- if not (self.fixedValues and min_val >= self.maxImpedance):
- self.minImpedance = min_val
+ if not (self.fixedValues and min_val >= self.maxYlim):
+ self.minYlim = min_val
if self.fixedValues:
self.update()
- def setMaximumImpedance(self):
+ def setMaximumY(self):
max_val, selected = QInputDialog.getDouble(
self,
- "Maximum impedance (\N{OHM SIGN})",
- "Set maximum impedance (\N{OHM SIGN})",
- value=self.minDisplayLength,
- min=0.1,
- decimals=1,
+ "Maximum " + self.formatString,
+ "Set maximum " + self.formatString,
+ value=self.maxYlim,
+ decimals=self.decimals,
)
if not selected:
return
- if not (self.fixedValues and max_val <= self.minImpedance):
- self.maxImpedance = max_val
+ if not (self.fixedValues and max_val <= self.minYlim):
+ self.maxYlim = max_val
if self.fixedValues:
self.update()
@@ -258,8 +275,8 @@ def copy(self):
new_chart.minDisplayLength = self.minDisplayLength
new_chart.maxDisplayLength = self.maxDisplayLength
new_chart.fixedSpan = self.fixedSpan
- new_chart.minImpedance = self.minImpedance
- new_chart.maxImpedance = self.maxImpedance
+ new_chart.minYlim = self.minYlim
+ new_chart.maxYlim = self.maxYlim
new_chart.fixedValues = self.fixedValues
self.tdrWindow.updated.connect(new_chart.update)
return new_chart
@@ -337,7 +354,7 @@ def _draw_ticks(self, height, width, x_step, min_index):
qp.drawText(
self.leftMargin - 10,
self.topMargin + height + 15,
- f"{str(round(self.tdrWindow.distance_axis[min_index] / 2, 1))}m",
+ f"{str(round(self.tdrWindow.distance_axis[min_index] / 2, self.decimals))}m",
)
def _draw_y_ticks(self, height, width, min_impedance, max_impedance):
@@ -351,10 +368,12 @@ def _draw_y_ticks(self, height, width, min_impedance, max_impedance):
qp.drawLine(self.leftMargin, y, self.leftMargin + width, y)
y_val = max_impedance - y_step * i * y_tick_step
qp.setPen(Chart.color.text)
- qp.drawText(3, y + 3, str(round(y_val, 1)))
+ qp.drawText(3, y + 3, str(round(y_val, self.decimals)))
qp.setPen(Chart.color.text)
qp.drawText(
- 3, self.topMargin + height + 3, f"{round(min_impedance, 1)}"
+ 3,
+ self.topMargin + height + 3,
+ f"{round(min_impedance, self.decimals)}",
)
def _draw_max_point(self, height, x_step, y_step, min_index):
@@ -412,11 +431,14 @@ def _draw_graph(self, height, width):
x_step = (max_index - min_index) / width
# TODO: Limit the search to the selected span?
- min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05)
- max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05)
+ min_Z = np.min(self.tdrWindow.step_response_Z)
+ max_Z = np.max(self.tdrWindow.step_response_Z)
+ # Ensure that everything works even if limits are negative
+ min_impedance = max(self.minYlim, min_Z - 0.05 * np.abs(min_Z))
+ max_impedance = min(self.maxYlim, max_Z + 0.05 * np.abs(max_Z))
if self.fixedValues:
- min_impedance = max(0, self.minImpedance)
- max_impedance = max(0.1, self.maxImpedance)
+ min_impedance = self.minYlim
+ max_impedance = self.maxYlim
y_step = max(self.tdrWindow.td) * 1.1 / height or 1.0e-30
@@ -495,15 +517,14 @@ def valueAtPosition(self, y):
height = self.height() - self.topMargin - self.bottomMargin
absy = (self.height() - y) - self.bottomMargin
if self.fixedValues:
- min_impedance = self.minImpedance
- max_impedance = self.maxImpedance
+ min_impedance = self.minYlim
+ max_impedance = self.maxYlim
else:
- min_impedance = max(
- 0, np.min(self.tdrWindow.step_response_Z) / 1.05
- )
- max_impedance = min(
- 1000, np.max(self.tdrWindow.step_response_Z) * 1.05
- )
+ min_Z = np.min(self.tdrWindow.step_response_Z)
+ max_Z = np.max(self.tdrWindow.step_response_Z)
+ # Ensure that everything works even if limits are negative
+ min_impedance = max(self.minYlim, min_Z - 0.05 * np.abs(min_Z))
+ max_impedance = min(self.maxYlim, max_Z + 0.05 * np.abs(max_Z))
y_step = (max_impedance - min_impedance) / height
return y_step * absy + min_impedance
return 0
@@ -540,8 +561,8 @@ def zoomTo(self, x1, y1, x2, y2):
val2 = self.valueAtPosition(y2)
if val1 != val2:
- self.minImpedance = round(min(val1, val2), 3)
- self.maxImpedance = round(max(val1, val2), 3)
+ self.minYlim = round(min(val1, val2), 3)
+ self.maxYlim = round(max(val1, val2), 3)
self.setFixedValues(True)
len1 = max(0, self.lengthAtPosition(x1, limit=False))
diff --git a/src/NanoVNASaver/Marker/Values.py b/src/NanoVNASaver/Marker/Values.py
index 96cb2030..a021756d 100644
--- a/src/NanoVNASaver/Marker/Values.py
+++ b/src/NanoVNASaver/Marker/Values.py
@@ -71,7 +71,7 @@ class Value:
"""Contains the data area to calculate marker values from"""
def __init__(
- self,
+ self,
):
self.freq: int = 0
self.s11: list[Datapoint] = []
diff --git a/src/NanoVNASaver/NanoVNASaver.py b/src/NanoVNASaver/NanoVNASaver.py
index 7c074956..26ecde0b 100644
--- a/src/NanoVNASaver/NanoVNASaver.py
+++ b/src/NanoVNASaver/NanoVNASaver.py
@@ -467,7 +467,9 @@ def __init__(self):
logger.debug("Finished building interface")
- def auto_connect(self): # connect if there is exactly one detected serial device
+ def auto_connect(
+ self,
+ ): # connect if there is exactly one detected serial device
if self.serial_control.inp_port.count() == 1:
self.serial_control.connect_device()
@@ -510,9 +512,9 @@ def saveData(self, data, data21, source=None):
if source is not None:
self.sweepSource = source
else:
- time = strftime('%Y-%m-%d %H:%M:%S', localtime())
- name = self.sweep.properties.name or 'nanovna'
- self.sweepSource = f'{name}_{time}'
+ time = strftime("%Y-%m-%d %H:%M:%S", localtime())
+ name = self.sweep.properties.name or "nanovna"
+ self.sweepSource = f"{name}_{time}"
def markerUpdated(self, marker: Marker):
with self.dataLock:
diff --git a/src/NanoVNASaver/RFTools.py b/src/NanoVNASaver/RFTools.py
index 3a253873..d6a09dc8 100644
--- a/src/NanoVNASaver/RFTools.py
+++ b/src/NanoVNASaver/RFTools.py
@@ -125,9 +125,7 @@ def norm_to_impedance(z: complex, ref_impedance: float = 50) -> complex:
def parallel_to_serial(z: complex) -> complex:
"""Convert parallel impedance to serial impedance equivalent"""
z_sq_sum = z.real**2 + z.imag**2 or 10.0e-30
- return complex(
- z.real * z.imag**2 / z_sq_sum, z.real**2 * z.imag / z_sq_sum
- )
+ return complex(z.real * z.imag**2 / z_sq_sum, z.real**2 * z.imag / z_sq_sum)
def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex:
diff --git a/src/NanoVNASaver/Settings/Sweep.py b/src/NanoVNASaver/Settings/Sweep.py
index 62759927..c717db27 100644
--- a/src/NanoVNASaver/Settings/Sweep.py
+++ b/src/NanoVNASaver/Settings/Sweep.py
@@ -39,13 +39,14 @@ class Properties(NamedTuple):
class Sweep:
- def __init__(self,
- start: int = 3600000,
- end: int = 30000000,
- points: int = 101,
- segments: int = 1,
- properties: "Properties" = Properties(),
- ):
+ def __init__(
+ self,
+ start: int = 3600000,
+ end: int = 30000000,
+ points: int = 101,
+ segments: int = 1,
+ properties: "Properties" = Properties(),
+ ):
self._start = start
self._end = end
self._points = points
@@ -56,21 +57,41 @@ def __init__(self,
logger.debug("%s", self)
def __repr__(self) -> str:
- return 'Sweep(' + ', '.join(map(str, (
- self.start, self.end, self.points, self.segments, self.properties
- ))) + ')'
+ return (
+ "Sweep("
+ + ", ".join(
+ map(
+ str,
+ (
+ self.start,
+ self.end,
+ self.points,
+ self.segments,
+ self.properties,
+ ),
+ )
+ )
+ + ")"
+ )
def __eq__(self, other) -> bool:
- return (self.start == other.start
- and self.end == other.end
- and self.points == other.points
- and self.segments == other.segments
- and self.properties == other.properties)
+ return (
+ self.start == other.start
+ and self.end == other.end
+ and self.points == other.points
+ and self.segments == other.segments
+ and self.properties == other.properties
+ )
def copy(self) -> "Sweep":
with self._lock:
- return Sweep(self.start, self.end, self.points, self.segments,
- self._properties)
+ return Sweep(
+ self.start,
+ self.end,
+ self.points,
+ self.segments,
+ self._properties,
+ )
# Getters for attributes, either private or computed.
@@ -128,7 +149,9 @@ def set_mode(self, mode: "SweepMode") -> None:
def set_averages(self, amount: int, truncates: int) -> None:
with self._lock:
- self._properties = self.properties._replace(averages=(amount, truncates))
+ self._properties = self.properties._replace(
+ averages=(amount, truncates)
+ )
def set_logarithmic(self, logarithmic: bool) -> None:
with self._lock:
@@ -145,7 +168,9 @@ def check(self):
raise ValueError(f"Illegal sweep settings: {self}")
def _exp_factor(self, index: int) -> float:
- return exp(log((self.start + self.span)/self.start) / self.segments * index)
+ return exp(
+ log((self.start + self.span) / self.start) / self.segments * index
+ )
def get_index_range(self, index: int) -> tuple[int, int]:
if self.properties.logarithmic:
diff --git a/src/NanoVNASaver/Version.py b/src/NanoVNASaver/Version.py
index 8ef790ce..4f75887c 100644
--- a/src/NanoVNASaver/Version.py
+++ b/src/NanoVNASaver/Version.py
@@ -22,13 +22,16 @@
logger = logging.getLogger(__name__)
-_RXP = re.compile(r"""^
+_RXP = re.compile(
+ r"""^
\D*
(?P\d+)\.
(?P\d+)\.?
(?P\d+)?
(?P.*)
- $""", re.VERBOSE)
+ $""",
+ re.VERBOSE,
+)
class _Version(typing.NamedTuple):
@@ -38,18 +41,17 @@ class _Version(typing.NamedTuple):
note: str
def __str__(self) -> str:
- return (
- f'{self.major}.{self.minor}'
- f'.{self.revision}{self.note}'
- )
+ return f"{self.major}.{self.minor}" f".{self.revision}{self.note}"
-def Version(vstring: str = "0.0.0") -> '_Version':
+def Version(vstring: str = "0.0.0") -> "_Version":
if (match := _RXP.search(vstring)) is None:
logger.error("Unable to parse version: %s", vstring)
- return _Version(0, 0, 0, '')
-
- return _Version(int(match.group('major')),
- int(match.group('minor')),
- int(match.group('revision') or '0'),
- match.group('note'))
+ return _Version(0, 0, 0, "")
+
+ return _Version(
+ int(match.group("major")),
+ int(match.group("minor")),
+ int(match.group("revision") or "0"),
+ match.group("note"),
+ )
diff --git a/src/NanoVNASaver/Windows/About.py b/src/NanoVNASaver/Windows/About.py
index 10b4729c..d7024d11 100644
--- a/src/NanoVNASaver/Windows/About.py
+++ b/src/NanoVNASaver/Windows/About.py
@@ -89,7 +89,9 @@ def __init__(self, app: QtWidgets.QWidget):
lower_layout = QtWidgets.QVBoxLayout()
top_layout.addLayout(lower_layout)
- btn_check_version = QtWidgets.QPushButton("Check for NanoVNASaver updates")
+ btn_check_version = QtWidgets.QPushButton(
+ "Check for NanoVNASaver updates"
+ )
btn_check_version.clicked.connect(self.findUpdates)
self.updateLabel = QtWidgets.QLabel()
@@ -129,14 +131,14 @@ def updateLabels(self):
"NanoVNA Firmware Version: Not connected."
)
-# attempt to scan the TAGS_URL web page for something that looks like
-# a version tag. assume the first match with a line containing the TAGS_KEY
-# will contain the latest version substring since it appears at the top
-# of the web page.
-#
-# this routine can also allow the application to automatically perform a
-# check-for-updates and display a pop-up if any are found when this
-# function is called with automatic=True.
+ # attempt to scan the TAGS_URL web page for something that looks like
+ # a version tag. assume the first match with a line containing the TAGS_KEY
+ # will contain the latest version substring since it appears at the top
+ # of the web page.
+ #
+ # this routine can also allow the application to automatically perform a
+ # check-for-updates and display a pop-up if any are found when this
+ # function is called with automatic=True.
def findUpdates(self, automatic=False):
try:
@@ -146,7 +148,9 @@ def findUpdates(self, automatic=False):
line = line.decode("utf-8")
found_latest_version = TAGS_KEY in line
if found_latest_version:
- latest_version = Version(re.search("(\d+\.\d+\.\d+)", line).group())
+ latest_version = Version(
+ re.search(r"(\d+\.\d+\.\d+)", line).group()
+ )
break
except error.HTTPError as e:
logger.exception(
@@ -205,7 +209,7 @@ def findUpdates(self, automatic=False):
# if we get here, something may have changed in the way github creates
# the .../latest web page.
self.updateLabel.setText(
- f"ERROR - Unable to determine what the latest version is! "
+ "ERROR - Unable to determine what the latest version is!"
)
logger.error(f"Can't find {TAGS_KEY} in {TAGS_URL} content.")
return
diff --git a/src/NanoVNASaver/Windows/Bands.py b/src/NanoVNASaver/Windows/Bands.py
index ee6cdceb..fcbdb00c 100644
--- a/src/NanoVNASaver/Windows/Bands.py
+++ b/src/NanoVNASaver/Windows/Bands.py
@@ -66,7 +66,8 @@ def resetBands(self):
QtWidgets.QMessageBox.Icon.Warning,
"Confirm reset",
"Are you sure you want to reset the bands to default?",
- QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Yes
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
).exec()
if confirm == QtWidgets.QMessageBox.StandardButton.Yes:
self.app.bands.resetBands()
diff --git a/src/NanoVNASaver/Windows/CalibrationSettings.py b/src/NanoVNASaver/Windows/CalibrationSettings.py
index 756bc1cb..dbe56ddc 100644
--- a/src/NanoVNASaver/Windows/CalibrationSettings.py
+++ b/src/NanoVNASaver/Windows/CalibrationSettings.py
@@ -282,8 +282,8 @@ def checkExpertUser(self):
"If you are certain you know what you are doing, click"
" Yes."
),
- QtWidgets.QMessageBox.StandardButton.Yes |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Yes
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
QtWidgets.QMessageBox.StandardButton.Cancel,
)
@@ -545,7 +545,7 @@ def calculate(self):
" Please complete SOL calibration and try again."
)
return
-
+
cal_element.short_is_ideal = True
cal_element.open_is_ideal = True
cal_element.load_is_ideal = True
@@ -649,8 +649,7 @@ def calculate(self):
)
self.calibration_source_label.setText(self.app.calibration.source)
self.app.showError(
- f"{e}"
- " Please complete SOL calibration and try again."
+ f"{e}" " Please complete SOL calibration and try again."
)
self.reset()
return
@@ -733,7 +732,8 @@ def automaticCalibration(self):
" in save slot 0 before starting.
"
"Once you are ready to proceed, press Ok."
),
- QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = introduction.exec()
if response != QtWidgets.QMessageBox.StandardButton.Ok:
@@ -772,7 +772,8 @@ def automaticCalibration(self):
" NanoVNA.\n\n"
"Press Ok when you are ready to continue."
),
- QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = short_step.exec()
@@ -808,8 +809,8 @@ def automaticCalibrationStep(self):
" cable unconnected if desired.\n\n"
"Press Ok when you are ready to continue."
),
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = open_step.exec()
@@ -835,8 +836,8 @@ def automaticCalibrationStep(self):
" NanoVNA.\n\n"
"Press Ok when you are ready to continue."
),
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = load_step.exec()
@@ -896,8 +897,8 @@ def automaticCalibrationStep(self):
" port 0.\n\n"
"Press Ok when you are ready to continue."
),
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = isolation_step.exec()
@@ -923,8 +924,8 @@ def automaticCalibrationStep(self):
" port 0 and port 1 of the NanoVNA.\n\n"
"Press Ok when you are ready to continue."
),
- QtWidgets.QMessageBox.StandardButton.Ok |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Ok
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = through_step.exec()
@@ -949,8 +950,8 @@ def automaticCalibrationStep(self):
"The calibration process is now complete. Press"
' "Apply" to apply the calibration parameters.'
),
- QtWidgets.QMessageBox.StandardButton.Apply |
- QtWidgets.QMessageBox.StandardButton.Cancel,
+ QtWidgets.QMessageBox.StandardButton.Apply
+ | QtWidgets.QMessageBox.StandardButton.Cancel,
)
response = apply_step.exec()
diff --git a/src/NanoVNASaver/Windows/DeviceSettings.py b/src/NanoVNASaver/Windows/DeviceSettings.py
index c45897ce..892813e3 100644
--- a/src/NanoVNASaver/Windows/DeviceSettings.py
+++ b/src/NanoVNASaver/Windows/DeviceSettings.py
@@ -101,8 +101,10 @@ def __init__(self, app: QtWidgets.QWidget):
self.custom_points_checkBox.stateChanged.connect(self.customPoint_check)
self.custom_points_Eidt = QtWidgets.QLineEdit("101")
self.custom_points_Eidt.setValidator(
- QIntValidator(self.app.vna.sweep_points_min,
- self.app.vna.sweep_points_max))
+ QIntValidator(
+ self.app.vna.sweep_points_min, self.app.vna.sweep_points_max
+ )
+ )
self.custom_points_Eidt.textEdited.connect(self.updatecustomPoint)
self.custom_points_Eidt.setDisabled(True)
@@ -156,8 +158,10 @@ def updateFields(self):
if "Customizable data points" in features:
self.datapoints.clear()
self.custom_points_Eidt.setValidator(
- QIntValidator(self.app.vna.sweep_points_min,
- self.app.vna.sweep_points_max))
+ QIntValidator(
+ self.app.vna.sweep_points_min, self.app.vna.sweep_points_max
+ )
+ )
cur_dps = self.app.vna.datapoints
for d in sorted(self.app.vna.valid_datapoints):
self.datapoints.addItem(str(d))
diff --git a/src/NanoVNASaver/Windows/DisplaySettings.py b/src/NanoVNASaver/Windows/DisplaySettings.py
index fa001334..a530bcd9 100644
--- a/src/NanoVNASaver/Windows/DisplaySettings.py
+++ b/src/NanoVNASaver/Windows/DisplaySettings.py
@@ -544,7 +544,8 @@ def setColor(self):
color = getattr(Chart.color, attr)
color = QtWidgets.QColorDialog.getColor(
- color, options=QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel
+ color,
+ options=QtWidgets.QColorDialog.ColorDialogOption.ShowAlphaChannel,
)
if not color.isValid():
diff --git a/src/NanoVNASaver/Windows/TDR.py b/src/NanoVNASaver/Windows/TDR.py
index a8c03049..237a4aca 100644
--- a/src/NanoVNASaver/Windows/TDR.py
+++ b/src/NanoVNASaver/Windows/TDR.py
@@ -89,6 +89,8 @@ def __init__(self, app: QtWidgets.QWidget):
layout = QtWidgets.QFormLayout()
make_scrollable(self, layout)
+ dropdown_layout = QtWidgets.QHBoxLayout()
+
self.tdr_velocity_dropdown = QtWidgets.QComboBox()
for cable_name, velocity in CABLE_PARAMETERS:
self.tdr_velocity_dropdown.addItem(cable_name, velocity)
@@ -98,7 +100,19 @@ def __init__(self, app: QtWidgets.QWidget):
self.tdr_velocity_dropdown.addItem("Custom", -1)
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
- layout.addRow(self.tdr_velocity_dropdown)
+
+ dropdown_layout.addWidget(self.tdr_velocity_dropdown)
+
+ self.format_dropdown = QtWidgets.QComboBox()
+ self.format_dropdown.addItem("|Z|")
+ self.format_dropdown.addItem("S11")
+ self.format_dropdown.addItem("VSWR")
+
+ self.format_dropdown.currentIndexChanged.connect(self.updateFormat)
+
+ dropdown_layout.addWidget(self.format_dropdown)
+
+ layout.addRow(dropdown_layout)
self.tdr_velocity_input = QtWidgets.QLineEdit()
self.tdr_velocity_input.setDisabled(True)
@@ -111,6 +125,10 @@ def __init__(self, app: QtWidgets.QWidget):
layout.addRow(self.app.tdr_chart)
+ def updateFormat(self):
+ self.app.tdr_chart.resetDisplayLimits()
+ self.updateTDR()
+
def updateTDR(self):
# TODO: Let the user select whether to use high or low resolution TDR?
FFT_POINTS = 2**14
@@ -138,15 +156,39 @@ def updateTDR(self):
return
s11 = [complex(d.re, d.im) for d in self.app.data.s11]
- window = np.blackman(len(self.app.data.s11))
- windowed_s11 = window * s11
- td = np.abs(np.fft.ifft(windowed_s11, FFT_POINTS))
- step = np.ones(FFT_POINTS)
- step_response = convolve(td, step)
+ s11 = np.array(s11)
+ s11 = np.concatenate(
+ [s11, np.conj(s11[-1:0:-1])]
+ ) # Include negative frequencies
+ s11 = np.fft.fftshift(s11)
+
+ window = np.blackman(len(s11))
+ windowed_s11 = (
+ window * s11
+ ) # Now windowing eliminates higher frequencies while leaving low frequencies untouched
- self.step_response_Z = 50 * (1 + step_response) / (1 - step_response)
+ pad_points = (FFT_POINTS - len(windowed_s11)) // 2
+ windowed_s11 = np.pad(
+ windowed_s11, [pad_points + 1, pad_points]
+ ) # Pad array to length FFT_POINTS
+ windowed_s11 = np.fft.ifftshift(windowed_s11)
+ td = np.fft.ifft(windowed_s11)
+ step = np.ones(FFT_POINTS)
+ step_response = convolve(td, step)
+ # calculate step response based on the format that the user selected
+ TDR_format = self.format_dropdown.currentText()
+ step_Z = 50 * (1 + step_response) / (1 - step_response)
+ step_refl_coefficient = np.abs((step_Z - 50) / (step_Z + 50))
+ if TDR_format == "|Z|":
+ self.step_response_Z = np.abs(step_Z)
+ elif TDR_format == "S11":
+ self.step_response_Z = 20 * np.log10(step_refl_coefficient)
+ elif TDR_format == "VSWR":
+ self.step_response_Z = np.abs(
+ (1 + step_refl_coefficient) / (1 - step_refl_coefficient)
+ )
time_axis = np.linspace(0, 1 / step_size, FFT_POINTS)
self.distance_axis = time_axis * v * speed_of_light
# peak = np.max(td)
diff --git a/src/NanoVNASaver/__main__.py b/src/NanoVNASaver/__main__.py
index 6eecaf55..0eb3b595 100644
--- a/src/NanoVNASaver/__main__.py
+++ b/src/NanoVNASaver/__main__.py
@@ -47,7 +47,10 @@ def main():
"-D", "--debug-file", help="File to write debug logging output to"
)
parser.add_argument(
- "-a", "--auto-connect", action="store_true", help="Auto connect if one device detected"
+ "-a",
+ "--auto-connect",
+ action="store_true",
+ help="Auto connect if one device detected",
)
parser.add_argument(
"-f",
diff --git a/tests/test_formatSweepFrequency.py b/tests/test_formatSweepFrequency.py
index 925fe547..8dc342b2 100644
--- a/tests/test_formatSweepFrequency.py
+++ b/tests/test_formatSweepFrequency.py
@@ -18,7 +18,6 @@
# along with this program. If not, see .
-import sys
import unittest
# Import targets to be tested