Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/refactoring #550

Merged
merged 14 commits into from
Sep 21, 2022
Merged
2 changes: 1 addition & 1 deletion .github/workflows/release_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
. build/bin/activate
python -m pip install pip==22.2.2 setuptools==65.3.0
pip install -r requirements.txt
pip install PyInstaller==5.3
pip install PyInstaller==5.4.1
- name: Build binary
run: |
. build/bin/activate
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: |
python -m pip install pip==22.2.2 setuptools==65.3.0
pip install -r requirements.txt
pip install PyInstaller==5.3
pip install PyInstaller==5.4.1
- name: Build binary
run: |
pyinstaller --onefile -n nanovna-saver nanovna-saver.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release_win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
run: |
python -m pip install pip==22.2.2 setuptools==65.3.0
pip install -r requirements.txt
pip install PyInstaller==5.3
pip install PyInstaller==5.4.1
- name: Build binary
run: |
pyinstaller --onefile -n nanovna-saver.exe nanovna-saver.py
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
Changelog
=========

0.5.4
-----
0.5.4-pre
---------

- Refectoring of Analysis modules

0.5.3
-----
Expand Down
2 changes: 1 addition & 1 deletion NanoVNASaver/About.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

VERSION = "0.5.3"
VERSION = "0.5.4-pre"
VERSION_URL = (
"https://raw.githubusercontent.com/"
"NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py")
Expand Down
25 changes: 8 additions & 17 deletions NanoVNASaver/Analysis/AntennaAnalysis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -16,14 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from PyQt5.Qt import QTimer

'''
Created on May 30th 2020

@author: mauro
'''
import logging
from time import sleep

from PyQt5 import QtWidgets

Expand Down Expand Up @@ -67,6 +63,7 @@ def runAnalysis(self):
self.layout.addRow("", QtWidgets.QLabel(
"Multiple minimums, not magloop or try to lower VSWR limit"))
return

if len(self.minimums) == 1:
m = self.minimums[0]
start, lowest, end = m
Expand Down Expand Up @@ -104,6 +101,7 @@ def runAnalysis(self):
logger.debug(
"no minimum found, looking for higher value %s",
self.vswr_limit_value)

new_start = max(self.min_freq, new_start)
new_end = min(self.max_freq, new_end)
logger.debug("next search will be %s - %s for vswr %s",
Expand All @@ -113,16 +111,9 @@ def runAnalysis(self):

self.app.sweep_control.set_start(new_start)
self.app.sweep_control.set_end(new_end)
# set timer to let finish all stuff before new sweep
QTimer.singleShot(2000, self._safe_sweep)

def _safe_sweep(self):
"""
sweep only if button enabled
to prevent multiple/concurrent sweep
"""

# TODO: get info if sweep is running instead of just sleeping
# a guessed time
sleep(2.0)
if self.app.sweep_control.btn_start.isEnabled():
self.app.sweep_start()
else:
logger.error("sweep alredy running")
103 changes: 50 additions & 53 deletions NanoVNASaver/Analysis/BandPassAnalysis.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -18,41 +18,34 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math
from typing import Dict
from typing import Dict, List

from PyQt5 import QtWidgets

import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis
from NanoVNASaver.Analysis.Base import Analysis, CUTOFF_VALS
from NanoVNASaver.Formatting import format_frequency

logger = logging.getLogger(__name__)

CUTOFF_VALS = (3.0, 6.0, 10.0, 20.0, 60.0)


class BandPassAnalysis(Analysis):
def __init__(self, app):
super().__init__(app)

self._widget = QtWidgets.QWidget()
for label in ('octave_l', 'octave_r', 'decade_l', 'decade_r',
'freq_center', 'span_3.0dB', 'span_6.0dB', 'q_factor'):
self.label[label] = QtWidgets.QLabel()
for attn in CUTOFF_VALS:
self.label[f"{attn:.1f}dB_l"] = QtWidgets.QLabel()
self.label[f"{attn:.1f}dB_r"] = QtWidgets.QLabel()

layout = QtWidgets.QFormLayout()
self._widget.setLayout(layout)
layout.addRow(QtWidgets.QLabel("Band pass filter analysis"))
layout = self.layout
layout.addRow(self.label['titel'])
layout.addRow(
QtWidgets.QLabel(
f"Please place {self.app.markers[0].name}"
f" in the filter passband."))
self.label = {
label: QtWidgets.QLabel() for label in
('result', 'octave_l', 'octave_r', 'decade_l', 'decade_r',
'freq_center', 'span_3.0dB', 'span_6.0dB', 'q_factor')
}
for attn in CUTOFF_VALS:
self.label[f"{attn:.1f}dB_l"] = QtWidgets.QLabel()
self.label[f"{attn:.1f}dB_r"] = QtWidgets.QLabel()

layout.addRow("Result:", self.label['result'])
layout.addRow(QtWidgets.QLabel(""))

Expand All @@ -77,42 +70,25 @@ def __init__(self, app):
layout.addRow("Roll-off:", self.label['octave_r'])
layout.addRow("Roll-off:", self.label['decade_r'])

def reset(self):
for label in self.label.values():
label.clear()
self.set_titel("Band pass filter analysis")

def runAnalysis(self):
if not self.app.data.s21:
logger.debug("No data to analyse")
self.label['result'].setText("No data to analyse.")
self.set_result("No data to analyse.")
return

self.reset()
s21 = self.app.data.s21
gains = [d.gain for d in s21]

marker = self.app.markers[0]
if marker.location <= 0 or marker.location >= len(s21) - 1:
logger.debug("No valid location for %s (%s)",
marker.name, marker.location)
self.label['result'].setText(
f"Please place {marker.name} in the passband.")
return

# find center of passband based on marker pos
if (peak := at.center_from_idx(gains, marker.location)) < 0:
self.label['result'].setText("Bandpass center not found")
if (peak := self.find_center(gains)) < 0:
return
peak_db = gains[peak]
logger.debug("Bandpass center pos: %d(%fdB)", peak, peak_db)
logger.debug("Filter center pos: %d(%fdB)", peak, peak_db)

# find passband bounderies
cutoff_pos = {}
for attn in CUTOFF_VALS:
cutoff_pos[f"{attn:.1f}dB_l"] = at.cut_off_left(
gains, peak, peak_db, attn)
cutoff_pos[f"{attn:.1f}dB_r"] = at.cut_off_right(
gains, peak, peak_db, attn)
cutoff_pos = self.find_bounderies(gains, peak, peak_db)
cutoff_freq = {
att: s21[val].freq if val >= 0 else math.nan
for att, val in cutoff_pos.items()
Expand All @@ -129,15 +105,15 @@ def runAnalysis(self):
result = {
'span_3.0dB': cutoff_freq['3.0dB_r'] - cutoff_freq['3.0dB_l'],
'span_6.0dB': cutoff_freq['6.0dB_r'] - cutoff_freq['6.0dB_l'],
'freq_center': int(
math.sqrt(cutoff_freq['3.0dB_l'] * cutoff_freq['3.0dB_r'])),
'freq_center':
math.sqrt(cutoff_freq['3.0dB_l'] * cutoff_freq['3.0dB_r']),
}
result['q_factor'] = result['freq_center'] / result['span_3.0dB']

result['octave_l'], result['decade_l'] = self.calculateRolloff(
cutoff_pos["10.0dB_l"], cutoff_pos["20.0dB_l"])
result['octave_r'], result['decade_r'] = self.calculateRolloff(
cutoff_pos["10.0dB_r"], cutoff_pos["20.0dB_r"])
result['octave_l'], result['decade_l'] = at.calculate_rolloff(
s21, cutoff_pos["10.0dB_l"], cutoff_pos["20.0dB_l"])
result['octave_r'], result['decade_r'] = at.calculate_rolloff(
s21, cutoff_pos["10.0dB_r"], cutoff_pos["20.0dB_r"])

for label, val in cutoff_freq.items():
self.label[label].setText(
Expand All @@ -151,22 +127,18 @@ def runAnalysis(self):
self.label[label].setText(f"{result[label]:.3f}dB/{label[:-2]}")

self.app.markers[0].setFrequency(f"{result['freq_center']}")
self.app.markers[0].frequencyInput.setText(f"{result['freq_center']}")
self.app.markers[1].setFrequency(f"{cutoff_freq['3.0dB_l']}")
self.app.markers[1].frequencyInput.setText(f"{cutoff_freq['3.0dB_l']}")
self.app.markers[2].setFrequency(f"{cutoff_freq['3.0dB_r']}")
self.app.markers[2].frequencyInput.setText(f"{cutoff_freq['3.0dB_r']}")

if cutoff_gain['3.0dB_l'] < -4 or cutoff_gain['3.0dB_r'] < -4:
logger.warning(
"Data points insufficient for true -3 dB points."
"Cutoff gains: %fdB, %fdB", cutoff_gain['3.0dB_l'], cutoff_gain['3.0dB_r'])
self.label['result'].setText(
self.set_result(
f"Analysis complete ({len(s21)} points)\n"
f"Insufficient data for analysis. Increase segment count.")
return
self.label['result'].setText(
f"Analysis complete ({len(s21)} points)")
self.set_result(f"Analysis complete ({len(s21)} points)")

def derive_60dB(self,
cutoff_pos: Dict[str, int],
Expand All @@ -190,3 +162,28 @@ def derive_60dB(self,
10 ** (5 * (math.log10(cutoff_pos['20.0dB_r']) -
math.log10(cutoff_pos['10.0dB_r'])
)))

def find_center(self, gains: List[float]) -> int:
marker = self.app.markers[0]
if marker.location <= 0 or marker.location >= len(gains) - 1:
logger.debug("No valid location for %s (%s)",
marker.name, marker.location)
self.set_result(f"Please place {marker.name} in the passband.")
return -1

# find center of passband based on marker pos
if (peak := at.center_from_idx(gains, marker.location)) < 0:
self.set_result("Bandpass center not found")
return -1
return peak

def find_bounderies(self,
gains: List[float],
peak: int, peak_db: float) -> Dict[str, int]:
cutoff_pos = {}
for attn in CUTOFF_VALS:
cutoff_pos[f"{attn:.1f}dB_l"] = at.cut_off_left(
gains, peak, peak_db, attn)
cutoff_pos[f"{attn:.1f}dB_r"] = at.cut_off_right(
gains, peak, peak_db, attn)
return cutoff_pos
Loading